# SPDX-License-Identifier: GPL-2.0-or-later # ---------------------------- ADAPTIVE DUPLIFACES --------------------------- # # ------------------------------- version 0.84 ------------------------------- # # # # Creates duplicates of selected mesh to active morphing the shape according # # to target faces. # # # # (c) Alessandro Zomparelli # # (2017) # # # # http://www.co-de-it.com/ # # # # ############################################################################ # import bpy from bpy.types import ( Operator, Panel, PropertyGroup, ) from bpy.props import ( BoolProperty, EnumProperty, FloatProperty, IntProperty, StringProperty, PointerProperty ) from mathutils import Vector, Quaternion, Matrix import numpy as np from math import * import random, time, copy import bmesh from .utils import * from .weight_tools import * from .numba_functions import * from .tissue_properties import * import os, mathutils from pathlib import Path from . import config def allowed_objects(): return ('MESH', 'CURVE', 'SURFACE', 'FONT', 'META') def remove_temp_objects(): # clean objects for o in bpy.data.objects: if "_tissue_tmp" in o.name: bpy.data.objects.remove(o) return def tessellated(ob): tess_props = ob.tissue_tessellate if tess_props.generator not in list(bpy.data.objects): return False elif tess_props.component_mode == 'OBJECT': return tess_props.component in list(bpy.data.objects) elif tess_props.component_mode == 'COLLECTION': if tess_props.component_coll in list(bpy.data.collections): for o in list(tess_props.component_coll.objects): if o.type in allowed_objects(): return True else: for mat in tess_props.generator.material_slots.keys(): if mat in bpy.data.objects.keys(): if bpy.data.objects[mat].type in allowed_objects(): return True return False def tessellate_patch(props): tt = time.time() ob = props['self'] _ob0 = props['generator'] components = props['component'] offset = props['offset'] zscale = props['zscale'] gen_modifiers = props['gen_modifiers'] com_modifiers = props['com_modifiers'] mode = props['mode'] fill_mode = props['fill_mode'] scale_mode = props['scale_mode'] rotation_mode = props['rotation_mode'] rotation_shift = props['rotation_shift'] rand_seed = props['rand_seed'] rand_step = props['rand_step'] bool_vertex_group = props['bool_vertex_group'] bool_selection = props['bool_selection'] bool_shapekeys = props['bool_shapekeys'] bool_material_id = props['bool_material_id'] material_id = props['material_id'] normals_mode = props['normals_mode'] bounds_x = props['bounds_x'] bounds_y = props['bounds_y'] use_origin_offset = props['use_origin_offset'] vertex_group_thickness = props['vertex_group_thickness'] invert_vertex_group_thickness = props['invert_vertex_group_thickness'] vertex_group_thickness_factor = props['vertex_group_thickness_factor'] vertex_group_distribution = props['vertex_group_distribution'] invert_vertex_group_distribution = props['invert_vertex_group_distribution'] vertex_group_distribution_factor = props['vertex_group_distribution_factor'] vertex_group_cap_owner = props['vertex_group_cap_owner'] vertex_group_cap = props['vertex_group_cap'] invert_vertex_group_cap = props['invert_vertex_group_cap'] vertex_group_bridge_owner = props['vertex_group_bridge_owner'] vertex_group_bridge = props['vertex_group_bridge'] invert_vertex_group_bridge = props['invert_vertex_group_bridge'] vertex_group_rotation = props['vertex_group_rotation'] invert_vertex_group_rotation = props['invert_vertex_group_rotation'] rotation_direction = props['rotation_direction'] target = props['target'] even_thickness = props['even_thickness'] even_thickness_iter = props['even_thickness_iter'] smooth_normals = props['smooth_normals'] smooth_normals_iter = props['smooth_normals_iter'] smooth_normals_uv = props['smooth_normals_uv'] vertex_group_smooth_normals = props['vertex_group_smooth_normals'] invert_vertex_group_smooth_normals = props['invert_vertex_group_smooth_normals'] #bool_multi_components = props['bool_multi_components'] component_mode = props['component_mode'] coll_rand_seed = props['coll_rand_seed'] consistent_wedges = props['consistent_wedges'] vertex_group_scale_normals = props['vertex_group_scale_normals'] invert_vertex_group_scale_normals = props['invert_vertex_group_scale_normals'] boundary_mat_offset = props['boundary_mat_offset'] _props = props.copy() # reset messages ob.tissue_tessellate.warning_message_thickness = '' if normals_mode == 'SHAPEKEYS': if _ob0.data.shape_keys != None: target = _ob0 else: normals_mode = 'VERTS' message = "Base mesh doesn't have Shape Keys" ob.tissue_tessellate.warning_message_thickness = message print("Tissue: " + message) if normals_mode == 'OBJECT' and target == None: normals_mode = 'VERTS' message = "Please select a target object" ob.tissue_tessellate.warning_message_thickness = message print("Tissue: " + message) random.seed(rand_seed) if len(_ob0.modifiers) == 0: gen_modifiers = False # Target mesh used for normals if normals_mode in ('SHAPEKEYS', 'OBJECT'): if fill_mode == 'PATCH': ob0_sk = convert_object_to_mesh(target, True, True) else: use_modifiers = gen_modifiers if normals_mode == 'SHAPEKEYS' and not gen_modifiers: target = _ob0 for m in target.modifiers: m.show_viewport = False use_modifiers = True _props['use_modifiers'] = use_modifiers if fill_mode == 'FAN': ob0_sk = convert_to_fan(target, _props, add_id_layer=id_layer) elif fill_mode == 'FRAME': ob0_sk = convert_to_frame(target, _props) elif fill_mode == 'TRI': ob0_sk = convert_to_triangles(target, _props) elif fill_mode == 'QUAD': ob0_sk = reduce_to_quads(target, _props) me0_sk = ob0_sk.data normals_target = get_vertices_numpy(me0_sk) bpy.data.objects.remove(ob0_sk) if normals_mode == 'SHAPEKEYS': key_values0 = [sk.value for sk in _ob0.data.shape_keys.key_blocks] for sk in _ob0.data.shape_keys.key_blocks: sk.value = 0 # Base mesh if fill_mode == 'PATCH': ob0 = convert_object_to_mesh(_ob0) if boundary_mat_offset != 0: bm=bmesh.new() bm.from_mesh(ob0.data) bm = offset_boundary_materials( bm, boundary_mat_offset = _props['boundary_mat_offset'], boundary_variable_offset = _props['boundary_variable_offset'], auto_rotate_boundary = _props['auto_rotate_boundary']) bm.to_mesh(ob0.data) bm.free() ob0.data.update() else: if fill_mode == 'FAN': id_layer = component_mode == 'COLLECTION' and consistent_wedges ob0 = convert_to_fan(_ob0, _props, add_id_layer=id_layer) elif fill_mode == 'FRAME': ob0 = convert_to_frame(_ob0, _props) elif fill_mode == 'TRI': ob0 = convert_to_triangles(_ob0, _props) elif fill_mode == 'QUAD': ob0 = reduce_to_quads(_ob0, _props) ob0.name = "_tissue_tmp_ob0" me0 = ob0.data n_verts0 = len(me0.vertices) # read vertices coordinates verts0_co = get_vertices_numpy(me0) # base normals if normals_mode in ('SHAPEKEYS','OBJECT'): if len(normals_target) != len(me0.vertices): normals_mode = 'VERTS' message = "Base mesh and Target mesh don't match" ob.tissue_tessellate.warning_message_thickness = message print("Tissue: " + message) else: if normals_mode == 'SHAPEKEYS': for sk, val in zip(_ob0.data.shape_keys.key_blocks, key_values0): sk.value = val verts0_normal = normals_target - verts0_co ''' While in Relative thickness method the components are built between the two surfaces, in Constant mode the thickness is uniform. ''' if scale_mode == 'CONSTANT': # Normalize vectors verts0_normal /= np.linalg.norm(verts0_normal, axis=1).reshape((-1,1)) if not even_thickness: pass #original_normals = get_normals_numpy(me0) #verts0_normal /= np.multiply(verts0_normal, original_normals).sum(1)[:,None] else: # Evaluate maximum components thickness first_component = True for com in components: if com: com = convert_object_to_mesh(com, com_modifiers, False) com, com_area = tessellate_prepare_component(com, props) com_verts = get_vertices_numpy(com.data) bpy.data.objects.remove(com) if first_component: all_com_verts = com_verts first_component = False else: all_com_verts = np.concatenate((all_com_verts, com_verts), axis=0) pos_step_dist = abs(np.max(all_com_verts[:,2])) neg_step_dist = abs(np.min(all_com_verts[:,2])) # Rescale normalized vectors according to the angle with the normals original_normals = get_normals_numpy(me0) kd = mathutils.kdtree.KDTree(len(verts0_co)) for i, v in enumerate(verts0_co): kd.insert(v, i) kd.balance() step_dist = [neg_step_dist, pos_step_dist] mult = 1 sign = [-1,1] for sgn, stp in zip(sign, step_dist): if stp == 0: if sgn == 1: verts0_normal_pos = verts0_normal if sgn == -1: verts0_normal_neg = verts0_normal continue for i in range(even_thickness_iter): test_dist = stp * mult test_pts = verts0_co + verts0_normal * test_dist * sgn # Find the closest point to the sample point closest_dist = [] closest_co = [] closest_nor = [] closest_index = [] for find in test_pts: co, index, dist = kd.find(find) closest_co.append(co) # co, index, dist closest_index.append(index) # co, index, dist closest_co = np.array(closest_co)#[:,3,None] closest_index = np.array(closest_index) closest_nor = original_normals[closest_index] closest_vec = test_pts - closest_co projected_vectors = np.multiply(closest_vec, closest_nor).sum(1)[:,None] closest_dist = np.linalg.norm(projected_vectors, axis=1)[:,None] mult = mult*0.2 + test_dist/closest_dist*0.8 # Reduces bouncing effect if sgn == 1: verts0_normal_pos = verts0_normal * mult if sgn == -1: verts0_normal_neg = verts0_normal * mult if normals_mode in ('VERTS','FACES'): verts0_normal = get_normals_numpy(me0) levels = 0 not_allowed = ['FLUID_SIMULATION', 'ARRAY', 'BEVEL', 'BOOLEAN', 'BUILD', 'DECIMATE', 'EDGE_SPLIT', 'MASK', 'MIRROR', 'REMESH', 'SCREW', 'SOLIDIFY', 'TRIANGULATE', 'WIREFRAME', 'SKIN', 'EXPLODE', 'PARTICLE_INSTANCE', 'PARTICLE_SYSTEM', 'SMOKE'] modifiers0 = list(_ob0.modifiers) if len(modifiers0) == 0 or fill_mode != 'PATCH': before_subsurf = me0 if fill_mode == 'PATCH': fill_mode = 'QUAD' else: show_modifiers = [m.show_viewport for m in _ob0.modifiers] show_modifiers.reverse() modifiers0.reverse() for m in modifiers0: visible = m.show_viewport if not visible: continue #m.show_viewport = False if m.type in ('SUBSURF', 'MULTIRES') and visible: levels = m.levels break elif m.type in not_allowed: bpy.data.meshes.remove(ob0.data) #bpy.data.meshes.remove(me0) return "modifiers_error" before = _ob0.copy() before.name = _ob0.name + "_before_subs" bpy.context.collection.objects.link(before) #if ob0.type == 'MESH': before.data = me0 before_mod = list(before.modifiers) before_mod.reverse() for m in before_mod: if m.type in ('SUBSURF', 'MULTIRES') and m.show_viewport: before.modifiers.remove(m) break else: before.modifiers.remove(m) before_subsurf = simple_to_mesh(before) if boundary_mat_offset != 0: bm=bmesh.new() bm.from_mesh(before_subsurf) bm = offset_boundary_materials( bm, boundary_mat_offset = _props['boundary_mat_offset'], boundary_variable_offset = _props['boundary_variable_offset'], auto_rotate_boundary = _props['auto_rotate_boundary']) bm.to_mesh(before_subsurf) bm.free() before_subsurf.update() bpy.data.objects.remove(before) tt = tissue_time(tt, "Meshes preparation", levels=2) ### PATCHES ### patch_faces = 4**levels sides = int(sqrt(patch_faces)) step = 1/sides sides0 = sides-2 patch_faces0 = int((sides-2)**2) if fill_mode == 'PATCH': all_verts, mask, materials = get_patches(before_subsurf, me0, 4, levels, bool_selection) else: all_verts, mask, materials = get_quads(me0, bool_selection) n_patches = len(all_verts) tt = tissue_time(tt, "Indexing", levels=2) ### WEIGHT ### # Check if possible to use Weight Rotation if rotation_mode == 'WEIGHT': if not vertex_group_rotation in ob0.vertex_groups.keys(): rotation_mode = 'DEFAULT' bool_weight_smooth_normals = vertex_group_smooth_normals in ob0.vertex_groups.keys() bool_weight_thickness = vertex_group_thickness in ob0.vertex_groups.keys() bool_weight_distribution = vertex_group_distribution in ob0.vertex_groups.keys() bool_weight_cap = vertex_group_cap_owner == 'BASE' and vertex_group_cap in ob0.vertex_groups.keys() bool_weight_bridge = vertex_group_bridge_owner == 'BASE' and vertex_group_bridge in ob0.vertex_groups.keys() bool_weight_normals = vertex_group_scale_normals in ob0.vertex_groups.keys() 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 weight = weight_thickness = weight_rotation = None if read_vertex_groups: if bool_vertex_group: weight = [get_weight(vg, n_verts0) for vg in ob0.vertex_groups] weight = np.array(weight) n_vg = len(ob0.vertex_groups) if rotation_mode == 'WEIGHT': vg_id = ob0.vertex_groups[vertex_group_rotation].index weight_rotation = weight[vg_id] if bool_weight_smooth_normals: vg_id = ob0.vertex_groups[bool_weight_smooth_normals].index weight_rotation = weight[vg_id] if bool_weight_distribution: vg_id = ob0.vertex_groups[vertex_group_distribution].index weight_distribution = weight[vg_id] if bool_weight_normals: vg_id = ob0.vertex_groups[vertex_group_scale_normals].index weight_normals = weight[vg_id] else: if rotation_mode == 'WEIGHT': vg = ob0.vertex_groups[vertex_group_rotation] weight_rotation = get_weight_numpy(vg, n_verts0) if bool_weight_smooth_normals: vg = ob0.vertex_groups[vertex_group_smooth_normals] weight_smooth_normals = get_weight_numpy(vg, n_verts0) if bool_weight_distribution: vg = ob0.vertex_groups[vertex_group_distribution] weight_distribution = get_weight_numpy(vg, n_verts0) if bool_weight_normals: vg = ob0.vertex_groups[vertex_group_scale_normals] weight_normals = get_weight_numpy(vg, n_verts0) if component_mode == 'COLLECTION': np.random.seed(coll_rand_seed) if fill_mode == 'FAN' and consistent_wedges: bm0 = bmesh.new() bm0.from_mesh(me0) bm0.faces.ensure_lookup_table() lay_id = bm0.faces.layers.int["id"] faces_id = np.array([f[lay_id] for f in bm0.faces]) bm0.clear() n_original_faces = faces_id[-1]+1 coll_materials = np.random.randint(len(components),size=n_original_faces) coll_materials = coll_materials[faces_id] else: coll_materials = np.random.randint(len(components),size=n_patches) gradient_distribution = [] if bool_weight_distribution: if invert_vertex_group_distribution: weight_distribution = 1-weight_distribution v00 = all_verts[:,0,0] v01 = all_verts[:,0,-1] v10 = all_verts[:,-1,0] v11 = all_verts[:,-1,-1] face_weight = (weight_distribution[v00] + weight_distribution[v01] + weight_distribution[v10] + weight_distribution[v11])/4 * len(components) if fill_mode == 'FAN' and consistent_wedges: for i in range(n_original_faces): face_mask = faces_id == i face_weight[face_mask] = np.average(face_weight[face_mask]) face_weight = face_weight.clip(max=len(components)-1) coll_materials = coll_materials.astype('float') coll_materials = face_weight + (coll_materials - face_weight)*vertex_group_distribution_factor coll_materials = coll_materials.astype('int') random.seed(rand_seed) bool_correct = False tt = tissue_time(tt, "Reading Vertex Groups", levels=2) ### SMOOTH NORMALS if smooth_normals: weight_smooth_normals = 0.2 weight_smooth_normals0 = 0.2 if vertex_group_smooth_normals in ob0.vertex_groups.keys(): vg = ob0.vertex_groups[vertex_group_smooth_normals] weight_smooth_normals0 = get_weight_numpy(vg, n_verts0) if invert_vertex_group_smooth_normals: weight_smooth_normals0 = 1-weight_smooth_normals0 weight_smooth_normals0 *= 0.2 verts0_normal = mesh_diffusion_vector(me0, verts0_normal, smooth_normals_iter, weight_smooth_normals0, smooth_normals_uv) ''' While in Relative thickness method the components are built between the two surfaces, in Constant mode the thickness is uniform. ''' if scale_mode == 'CONSTANT': # Normalize vectors verts0_normal /= np.linalg.norm(verts0_normal, axis=1).reshape((-1,1)) # Compare to the original normals direction original_normals = get_normals_numpy(me0) verts0_normal /= np.multiply(verts0_normal, original_normals).sum(1)[:,None] tt = tissue_time(tt, "Smooth Normals", levels=2) if normals_mode in ('FACES', 'VERTS'): normals_x = props['normals_x'] normals_y = props['normals_y'] normals_z = props['normals_z'] if bool_weight_normals: if invert_vertex_group_scale_normals: weight_normals = 1-weight_normals w_normals_x = 1 - weight_normals * (1 - normals_x) w_normals_y = 1 - weight_normals * (1 - normals_y) w_normals_z = 1 - weight_normals * (1 - normals_z) else: w_normals_x = normals_x w_normals_y = normals_y w_normals_z = normals_z if normals_x < 1: verts0_normal[:,0] *= w_normals_x if normals_y < 1: verts0_normal[:,1] *= w_normals_y if normals_z < 1: verts0_normal[:,2] *= w_normals_z div_value = np.linalg.norm(verts0_normal, axis=1).reshape((-1,1)) div_value[div_value == 0] = 0.00001 verts0_normal /= div_value ### ROTATE PATCHES ### if rotation_mode != 'DEFAULT' or rotation_shift != 0: # Weight rotation weight_shift = 0 if rotation_mode == 'WEIGHT': corners_id = np.array(((0,0,-1,-1),(0,-1,-1,0))) corners = all_verts[:,corners_id[0],corners_id[1]] corners_weight = weight_rotation[corners] if invert_vertex_group_rotation: corners_weight = 1-corners_weight ids4 = np.arange(4) if rotation_direction == 'DIAG': c0 = corners_weight[:,ids4] c3 = corners_weight[:,(ids4+2)%4] differential = c3 - c0 else: c0 = corners_weight[:,ids4] c1 = corners_weight[:,(ids4+1)%4] c2 = corners_weight[:,(ids4+2)%4] c3 = corners_weight[:,(ids4+3)%4] differential = - c0 + c1 + c2 - c3 weight_shift = np.argmax(differential, axis=1) # Random rotation random_shift = 0 if rotation_mode == 'RANDOM': np.random.seed(rand_seed) random_shift = np.random.randint(0,4,size=n_patches)*rand_step # UV rotation UV_shift = 0 if rotation_mode == 'UV' and ob0.type == 'MESH': bm = bmesh.new() bm.from_mesh(before_subsurf) uv_lay = bm.loops.layers.uv.active UV_shift = [0]*len(mask) for f in bm.faces: ll = f.loops if len(ll) == 4: uv0 = ll[0][uv_lay].uv uv1 = ll[3][uv_lay].uv uv2 = ll[2][uv_lay].uv uv3 = ll[1][uv_lay].uv v01 = (uv0 + uv1) # not necessary to divide by 2 v32 = (uv3 + uv2) v0132 = v32 - v01 # axis vector 1 v0132.normalize() # based on the rotation not on the size v12 = (uv1 + uv2) v03 = (uv0 + uv3) v1203 = v03 - v12 # axis vector 2 v1203.normalize() # based on the rotation not on the size dot1203 = v1203.x dot0132 = v0132.x if(abs(dot1203) < abs(dot0132)): # already vertical if (dot0132 > 0): shift = 0 else: shift = 2 # rotate 180° else: # horizontal if(dot1203 < 0): shift = 3 else: shift = 1 #UV_shift.append(shift) UV_shift[f.index] = shift UV_shift = np.array(UV_shift)[mask] bm.free() # Rotate Patch rotation_shift = np.zeros((n_patches))+rotation_shift rot = weight_shift + random_shift + UV_shift + rotation_shift rot = rot%4 flip_u = np.logical_or(rot==2,rot==3) flip_v = np.logical_or(rot==1,rot==2) flip_uv = np.logical_or(rot==1,rot==3) all_verts[flip_u] = all_verts[flip_u,::-1,:] all_verts[flip_v] = all_verts[flip_v,:,::-1] all_verts[flip_uv] = np.transpose(all_verts[flip_uv],(0,2,1)) tt = tissue_time(tt, "Rotations", levels=2) #for o in bpy.context.view_layer.objects: o.select_set(False) new_patch = None ### COMPONENT ### new_objects = [] # Store original values _com_modifiers = com_modifiers _bool_shapekeys = bool_shapekeys for mat_id, _ob1 in enumerate(components): if _ob1 == None: continue # Set original values (for next components) com_modifiers = _com_modifiers bool_shapekeys = _bool_shapekeys if component_mode != 'OBJECT': if component_mode == 'COLLECTION': mat_mask = coll_materials == mat_id else: mat_mask = materials == mat_id if bool_material_id: mat_mask = np.logical_and(mat_mask, materials == material_id) masked_verts = all_verts[mat_mask] masked_faces = mat_mask elif bool_material_id: masked_verts = all_verts[materials == material_id] masked_faces = np.logical_and(mask, materials == material_id) else: masked_verts = all_verts masked_faces = mask n_patches = len(masked_verts) if n_patches == 0: continue if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False # set Shape Keys to zero original_key_values = None if (bool_shapekeys or not com_modifiers) and _ob1.type == 'MESH': if _ob1.data.shape_keys: original_key_values = [] for sk in _ob1.data.shape_keys.key_blocks: original_key_values.append(sk.value) sk.value = 0 else: bool_shapekeys = False else: bool_shapekeys = False if not com_modifiers and not bool_shapekeys: mod_visibility = [] for m in _ob1.modifiers: mod_visibility.append(m.show_viewport) m.show_viewport = False com_modifiers = True ob1 = convert_object_to_mesh(_ob1, com_modifiers, False) ob1, com_area = tessellate_prepare_component(ob1, props) ob1.name = "_tissue_tmp_ob1" # restore original modifiers visibility for component object try: for m, vis in zip(_ob1.modifiers, mod_visibility): m.show_viewport = vis except: pass me1 = ob1.data verts1 = [v.co for v in me1.vertices] n_verts1 = len(verts1) if n_verts1 == 0: bpy.data.objects.remove(ob1) continue ### COMPONENT GRID COORDINATES ### # find relative UV component's vertices if fill_mode == 'PATCH': verts1_uv_quads = [0]*n_verts1 verts1_uv = [0]*n_verts1 for i, vert in enumerate(verts1): # grid coordinates u = int(vert[0]//step) v = int(vert[1]//step) u1 = min(u+1, sides) v1 = min(v+1, sides) if mode != 'BOUNDS': if u > sides-1: u = sides-1 u1 = sides if u < 0: u = 0 u1 = 1 if v > sides-1: v = sides-1 v1 = sides if v < 0: v = 0 v1 = 1 verts1_uv_quads[i] = (u,v,u1,v1) # factor coordinates fu = (vert[0]-u*step)/step fv = (vert[1]-v*step)/step fw = vert.z # interpolate Z scaling factor verts1_uv[i] = Vector((fu,fv,fw)) else: verts1_uv = verts1 if bool_shapekeys: sk_uv_quads = [] sk_uv = [] for sk in ob1.data.shape_keys.key_blocks[1:]: source = sk.data _sk_uv_quads = [0]*n_verts1 _sk_uv = [0]*n_verts1 for i, sk_v in enumerate(source): sk_vert = sk_v.co # grid coordinates u = int(sk_vert[0]//step) v = int(sk_vert[1]//step) u1 = min(u+1, sides) v1 = min(v+1, sides) if mode != 'BOUNDS': if u > sides-1: u = sides-1 u1 = sides if u < 0: u = 0 u1 = 1 if v > sides-1: v = sides-1 v1 = sides if v < 0: v = 0 v1 = 1 _sk_uv_quads[i] = (u,v,u1,v1) # factor coordinates fu = (sk_vert[0]-u*step)/step fv = (sk_vert[1]-v*step)/step fw = sk_vert.z _sk_uv[i] = Vector((fu,fv,fw)) sk_uv_quads.append(_sk_uv_quads) sk_uv.append(_sk_uv) store_sk_coordinates = [[] for t in ob1.data.shape_keys.key_blocks[1:]] sk_uv_quads = np.array(sk_uv_quads) sk_uv = np.array(sk_uv) np_verts1_uv = np.array(verts1_uv) if fill_mode == 'PATCH': verts1_uv_quads = np.array(verts1_uv_quads) np_u = verts1_uv_quads[:,0] np_v = verts1_uv_quads[:,1] np_u1 = verts1_uv_quads[:,2] np_v1 = verts1_uv_quads[:,3] else: np_u = 0 np_v = 0 np_u1 = 1 np_v1 = 1 tt = tissue_time(tt, "Component preparation", levels=2) ### DEFORM PATCHES ### verts_xyz = verts0_co[masked_verts] v00 = verts_xyz[:, np_u, np_v].reshape((n_patches,-1,3)) v10 = verts_xyz[:, np_u1, np_v].reshape((n_patches,-1,3)) v01 = verts_xyz[:, np_u, np_v1].reshape((n_patches,-1,3)) v11 = verts_xyz[:, np_u1, np_v1].reshape((n_patches,-1,3)) vx = np_verts1_uv[:,0].reshape((1,n_verts1,1)) vy = np_verts1_uv[:,1].reshape((1,n_verts1,1)) vz = np_verts1_uv[:,2].reshape((1,n_verts1,1)) co2 = np_lerp2(v00, v10, v01, v11, vx, vy, 'verts') ### PATCHES WEIGHT ### weight_thickness = None if bool_vertex_group: n_vg = len(weight) patches_weight = weight[:, masked_verts] w00 = patches_weight[:, :, np_u, np_v].reshape((n_vg, n_patches,-1,1)) w10 = patches_weight[:, :, np_u1, np_v].reshape((n_vg, n_patches,-1,1)) w01 = patches_weight[:, :, np_u, np_v1].reshape((n_vg, n_patches,-1,1)) w11 = patches_weight[:, :, np_u1, np_v1].reshape((n_vg, n_patches,-1,1)) store_weight = np_lerp2(w00,w10,w01,w11,vx[None,:,:,:],vy[None,:,:,:],'weight') if vertex_group_thickness in ob0.vertex_groups.keys(): vg_id = ob0.vertex_groups[vertex_group_thickness].index weight_thickness = store_weight[vg_id,:,:] if vertex_group_smooth_normals in ob0.vertex_groups.keys(): vg_id = ob0.vertex_groups[vertex_group_smooth_normals].index weight_smooth_normals = store_weight[vg_id,:,:] else: # Read vertex group Thickness if vertex_group_thickness in ob0.vertex_groups.keys(): vg = ob0.vertex_groups[vertex_group_thickness] weight_thickness = get_weight_numpy(vg, n_verts0) wt = weight_thickness[masked_verts] wt = wt[:,:,:,np.newaxis] w00 = wt[:, np_u, np_v].reshape((n_patches, -1, 1)) w10 = wt[:, np_u1, np_v].reshape((n_patches, -1, 1)) w01 = wt[:, np_u, np_v1].reshape((n_patches, -1, 1)) w11 = wt[:, np_u1, np_v1].reshape((n_patches, -1, 1)) weight_thickness = np_lerp2(w00,w10,w01,w11,vx,vy,'verts') try: weight_thickness.shape if invert_vertex_group_thickness: weight_thickness = 1-weight_thickness fact = vertex_group_thickness_factor if fact > 0: weight_thickness = weight_thickness*(1-fact) + fact except: pass # Read vertex group smooth normals if vertex_group_smooth_normals in ob0.vertex_groups.keys(): vg = ob0.vertex_groups[vertex_group_smooth_normals] weight_smooth_normals = get_weight_numpy(vg, n_verts0) wt = weight_smooth_normals[masked_verts] wt = wt[:,:,:,None] w00 = wt[:, np_u, np_v].reshape((n_patches, -1, 1)) w10 = wt[:, np_u1, np_v].reshape((n_patches, -1, 1)) w01 = wt[:, np_u, np_v1].reshape((n_patches, -1, 1)) w11 = wt[:, np_u1, np_v1].reshape((n_patches, -1, 1)) weight_smooth_normals = np_lerp2(w00,w10,w01,w11,vx,vy,'verts') try: weight_smooth_normals.shape if invert_vertex_group_smooth_normals: weight_smooth_normals = 1-weight_smooth_normals #fact = vertex_group_thickness_factor #if fact > 0: # weight_thickness = weight_thickness*(1-fact) + fact except: pass if normals_mode == 'FACES': n2 = get_attribute_numpy(before_subsurf.polygons,'normal',3) n2 = n2[masked_faces][:,None,:] else: if normals_mode == 'CUSTOM': me0.calc_normals_split() normals_split = [0]*len(me0.loops)*3 vertex_indexes = [0]*len(me0.loops) me0.loops.foreach_get('normal', normals_split) me0.loops.foreach_get('vertex_index', vertex_indexes) normals_split = np.array(normals_split).reshape(-1,3) vertex_indexes = np.array(vertex_indexes) verts0_normal = np.zeros((len(me0.vertices),3)) np.add.at(verts0_normal, vertex_indexes, normals_split) indexes, counts = np.unique(vertex_indexes,return_counts=True) verts0_normal[indexes] /= counts[:,np.newaxis] if 'Eval_Normals' in me1.uv_layers.keys(): bm1 = bmesh.new() bm1.from_mesh(me1) uv_co = np.array(uv_from_bmesh(bm1, 'Eval_Normals')) vx_nor = uv_co[:,0]#.reshape((1,n_verts1,1)) #vy_nor = uv_co[:,1]#.reshape((1,n_verts1,1)) # grid coordinates np_u = np.clip(vx_nor//step, 0, sides).astype('int') #np_v = np.maximum(vy_nor//step, 0).astype('int') np_u1 = np.clip(np_u+1, 0, sides).astype('int') #np_v1 = np.minimum(np_v+1, sides).astype('int') vx_nor = (vx_nor - np_u * step)/step #vy_nor = (vy_nor - np_v * step)/step vx_nor = vx_nor.reshape((1,n_verts1,1)) #vy_nor = vy_nor.reshape((1,n_verts1,1)) vy_nor = vy bm1.free() else: vx_nor = vx vy_nor = vy if normals_mode in ('SHAPEKEYS','OBJECT') and scale_mode == 'CONSTANT' and even_thickness: verts_norm_pos = verts0_normal_pos[masked_verts] verts_norm_neg = verts0_normal_neg[masked_verts] nor_mask = (vz<0).reshape((-1)) n00 = verts_norm_pos[:, np_u, np_v].reshape((n_patches,-1,3)) n10 = verts_norm_pos[:, np_u1, np_v].reshape((n_patches,-1,3)) n01 = verts_norm_pos[:, np_u, np_v1].reshape((n_patches,-1,3)) n11 = verts_norm_pos[:, np_u1, np_v1].reshape((n_patches,-1,3)) n00_neg = verts_norm_neg[:, np_u, np_v].reshape((n_patches,-1,3)) n10_neg = verts_norm_neg[:, np_u1, np_v].reshape((n_patches,-1,3)) n01_neg = verts_norm_neg[:, np_u, np_v1].reshape((n_patches,-1,3)) n11_neg = verts_norm_neg[:, np_u1, np_v1].reshape((n_patches,-1,3)) n00[:,nor_mask] = n00_neg[:,nor_mask] n10[:,nor_mask] = n10_neg[:,nor_mask] n01[:,nor_mask] = n01_neg[:,nor_mask] n11[:,nor_mask] = n11_neg[:,nor_mask] else: verts_norm = verts0_normal[masked_verts] n00 = verts_norm[:, np_u, np_v].reshape((n_patches,-1,3)) n10 = verts_norm[:, np_u1, np_v].reshape((n_patches,-1,3)) n01 = verts_norm[:, np_u, np_v1].reshape((n_patches,-1,3)) n11 = verts_norm[:, np_u1, np_v1].reshape((n_patches,-1,3)) n2 = np_lerp2(n00, n10, n01, n11, vx_nor, vy_nor, 'verts') # thickness variation mean_area = [] a2 = None if scale_mode == 'ADAPTIVE' and normals_mode not in ('SHAPEKEYS','OBJECT'): #com_area = bb[0]*bb[1] if mode != 'BOUNDS' or com_area == 0: com_area = 1 if normals_mode == 'FACES': if levels == 0 and True: areas = [0]*len(mask) before_subsurf.polygons.foreach_get('area',areas) areas = np.sqrt(np.array(areas)/com_area)[masked_faces] a2 = areas[:,None,None] else: areas = calc_verts_area_bmesh(me0) verts_area = np.sqrt(areas*patch_faces/com_area) verts_area = verts_area[masked_verts] verts_area = verts_area.mean(axis=(1,2)).reshape((n_patches,1,1)) a2 = verts_area else: areas = calc_verts_area_bmesh(me0) verts_area = np.sqrt(areas*patch_faces/com_area) verts_area = verts_area[masked_verts] a00 = verts_area[:, np_u, np_v].reshape((n_patches,-1,1)) a10 = verts_area[:, np_u1, np_v].reshape((n_patches,-1,1)) a01 = verts_area[:, np_u, np_v1].reshape((n_patches,-1,1)) a11 = verts_area[:, np_u1, np_v1].reshape((n_patches,-1,1)) # remapped z scale a2 = np_lerp2(a00,a10,a01,a11,vx,vy,'verts') store_coordinates = calc_thickness(co2,n2,vz,a2,weight_thickness) co2 = n2 = vz = a2 = None if bool_shapekeys: tt_sk = time.time() n_sk = len(sk_uv_quads) # ids of face corners for each vertex (n_sk, n_verts1, 4) np_u = np.clip(sk_uv_quads[:,:,0], 0, sides).astype('int')[:,None,:] np_v = np.clip(sk_uv_quads[:,:,1], 0, sides).astype('int')[:,None,:] np_u1 = np.clip(sk_uv_quads[:,:,2], 0, sides).astype('int')[:,None,:] np_v1 = np.clip(sk_uv_quads[:,:,3], 0, sides).astype('int')[:,None,:] print(np_v1) # face corners for each vertex (n_patches, n_sk, n_verts1, 4) v00 = verts_xyz[:,np_u,np_v].reshape((n_patches,n_sk,n_verts1,3))#.swapaxes(0,1) v10 = verts_xyz[:,np_u1,np_v].reshape((n_patches,n_sk,n_verts1,3))#.swapaxes(0,1) v01 = verts_xyz[:,np_u,np_v1].reshape((n_patches,n_sk,n_verts1,3))#.swapaxes(0,1) v11 = verts_xyz[:,np_u1,np_v1].reshape((n_patches,n_sk,n_verts1,3))#.swapaxes(0,1) vx = sk_uv[:,:,0].reshape((1,n_sk,n_verts1,1)) vy = sk_uv[:,:,1].reshape((1,n_sk,n_verts1,1)) vz = sk_uv[:,:,2].reshape((1,n_sk,n_verts1,1)) co2 = np_lerp2(v00,v10,v01,v11,vx,vy,mode='shapekeys') if normals_mode == 'FACES': n2 = n2[None,:,:,:] else: if normals_mode in ('SHAPEKEYS','OBJECT') and scale_mode == 'CONSTANT' and even_thickness: verts_norm_pos = verts0_normal_pos[masked_verts] verts_norm_neg = verts0_normal_neg[masked_verts] nor_mask = (vz<0).reshape((-1)) n00 = verts_norm_pos[:, np_u, np_v].reshape((n_patches,n_sk,n_verts1,3)) n10 = verts_norm_pos[:, np_u1, np_v].reshape((n_patches,n_sk,n_verts1,3)) n01 = verts_norm_pos[:, np_u, np_v1].reshape((n_patches,n_sk,n_verts1,3)) n11 = verts_norm_pos[:, np_u1, np_v1].reshape((n_patches,n_sk,n_verts1,3)) n00_neg = verts_norm_neg[:, np_u, np_v].reshape((n_patches,n_sk,n_verts1,3)) n10_neg = verts_norm_neg[:, np_u1, np_v].reshape((n_patches,n_sk,n_verts1,3)) n01_neg = verts_norm_neg[:, np_u, np_v1].reshape((n_patches,n_sk,n_verts1,3)) n11_neg = verts_norm_neg[:, np_u1, np_v1].reshape((n_patches,n_sk,n_verts1,3)) n00[:,:,nor_mask] = n00_neg[:,:,nor_mask] n10[:,:,nor_mask] = n10_neg[:,:,nor_mask] n01[:,:,nor_mask] = n01_neg[:,:,nor_mask] n11[:,:,nor_mask] = n11_neg[:,:,nor_mask] else: n00 = verts_norm[:, np_u, np_v].reshape((n_patches,n_sk,n_verts1,3)) n10 = verts_norm[:, np_u1, np_v].reshape((n_patches,n_sk,n_verts1,3)) n01 = verts_norm[:, np_u, np_v1].reshape((n_patches,n_sk,n_verts1,3)) n11 = verts_norm[:, np_u1, np_v1].reshape((n_patches,n_sk,n_verts1,3)) n2 = np_lerp2(n00,n10,n01,n11,vx,vy,'shapekeys') # NOTE: weight thickness is based on the base position of the # vertices, not on the coordinates of the shape keys if scale_mode == 'ADAPTIVE':# and normals_mode not in ('OBJECT', 'SHAPEKEYS'): ### not sure if normals_mode == 'FACES': a2 = mean_area else: a00 = verts_area[:, np_u, np_v].reshape((n_patches,n_sk,n_verts1,1)) a10 = verts_area[:, np_u1, np_v].reshape((n_patches,n_sk,n_verts1,1)) a01 = verts_area[:, np_u, np_v1].reshape((n_patches,n_sk,n_verts1,1)) a11 = verts_area[:, np_u1, np_v1].reshape((n_patches,n_sk,n_verts1,1)) # remapped z scale a2 = np_lerp2(a00,a10,a01,a11,vx,vy,'shapekeys') store_sk_coordinates = calc_thickness(co2,n2,vz,a2,weight_thickness) co2 = n2 = vz = a2 = weight_thickness = None tissue_time(tt_sk, "Compute ShapeKeys", levels=3) tt = tissue_time(tt, "Compute Coordinates", levels=2) new_me = array_mesh(ob1, len(masked_verts)) tt = tissue_time(tt, "Repeat component", levels=2) new_patch = bpy.data.objects.new("_tissue_tmp_patch", new_me) bpy.context.collection.objects.link(new_patch) store_coordinates = np.concatenate(store_coordinates, axis=0).reshape((-1)).tolist() new_me.vertices.foreach_set('co',store_coordinates) for area in bpy.context.screen.areas: for space in area.spaces: try: new_patch.local_view_set(space, True) except: pass tt = tissue_time(tt, "Inject coordinates", levels=2) # Vertex Group for vg in ob1.vertex_groups: vg_name = vg.name if vg_name in ob0.vertex_groups.keys(): vg_name = '_{}_'.format(vg_name) new_patch.vertex_groups.new(name=vg_name) if bool_vertex_group: new_groups = [] for vg in ob0.vertex_groups: new_groups.append(new_patch.vertex_groups.new(name=vg.name)) for vg, w in zip(new_groups, store_weight): set_weight_numpy(vg, w.reshape(-1)) tt = tissue_time(tt, "Write Vertex Groups", levels=2) if bool_shapekeys: for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values): sk.value = val new_patch.shape_key_add(name=sk.name, from_mix=False) new_patch.data.shape_keys.key_blocks[sk.name].value = val for i in range(n_sk): coordinates = np.concatenate(store_sk_coordinates[:,i], axis=0) coordinates = coordinates.flatten().tolist() new_patch.data.shape_keys.key_blocks[i+1].data.foreach_set('co', coordinates) # set original values and combine Shape Keys and Vertex Groups for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values): sk.value = val new_patch.data.shape_keys.key_blocks[sk.name].value = val if bool_vertex_group: vg_keys = new_patch.vertex_groups.keys() for sk in new_patch.data.shape_keys.key_blocks: if sk.name in vg_keys: sk.vertex_group = sk.name tt = tissue_time(tt, "Shape Keys", levels=2) elif original_key_values: for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values): sk.value = val new_name = ob0.name + "_" + ob1.name new_patch.name = "_tissue_tmp_patch" new_patch.data.update() # needed for updating the normals new_objects.append(new_patch) bpy.data.objects.remove(ob1) bpy.data.objects.remove(ob0) tt = tissue_time(tt, "Closing Tessellate Iteration", levels=2) return new_objects class tissue_tessellate(Operator): bl_idname = "object.tissue_tessellate" bl_label = "Tissue Tessellate" bl_description = ("Create a copy of selected object on the active object's " "faces, adapting the shape to the different faces") bl_options = {'REGISTER', 'UNDO'} bool_hold : BoolProperty( name="Hold", description="Wait...", default=False ) object_name : StringProperty( name="", description="Name of the generated object" ) zscale : FloatProperty( name="Scale", default=1, soft_min=0, soft_max=10, description="Scale factor for the component thickness" ) scale_mode : EnumProperty( items=( ('CONSTANT', "Constant", "Uniform thickness"), ('ADAPTIVE', "Relative", "Preserve component's proportions") ), default='ADAPTIVE', name="Z-Scale according to faces size" ) offset : FloatProperty( name="Surface Offset", default=1, min=-1, max=1, soft_min=-1, soft_max=1, description="Surface offset" ) component_mode : EnumProperty( items=( ('OBJECT', "Object", "Use the same component object for all the faces"), ('COLLECTION', "Collection", "Use multiple components from Collection"), ('MATERIALS', "Materials", "Use multiple components by materials name") ), default='OBJECT', name="Component Mode" ) mode : EnumProperty( items=( ('BOUNDS', "Bounds", "The component fits automatically the size of the target face"), ('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"), ('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")), default='BOUNDS', name="Component Mode" ) rotation_mode : EnumProperty( items=(('RANDOM', "Random", "Random faces rotation"), ('UV', "Active UV", "Face rotation is based on UV coordinates"), ('WEIGHT', "Weight Gradient", "Rotate according to Vertex Group gradient"), ('DEFAULT', "Default", "Default rotation")), default='DEFAULT', name="Component Rotation" ) rotation_direction : EnumProperty( items=(('ORTHO', "Orthogonal", "Component main directions in XY"), ('DIAG', "Diagonal", "Component main direction aligned with diagonal")), default='ORTHO', name="Direction" ) rotation_shift : IntProperty( name="Shift", default=0, soft_min=0, soft_max=3, description="Shift components rotation" ) fill_mode : EnumProperty( items=( ('TRI', 'Tri', 'Triangulate the base mesh'), ('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'), ('FAN', 'Fan', 'Radial tessellation for polygonal faces'), ('PATCH', 'Patch', 'Curved tessellation according to the last ' + 'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' + 'patches.\nAfter the last Subsurf (or Multires) only ' + 'deformation\nmodifiers can be used'), ('FRAME', 'Frame', 'Tessellation along the edges of each face')), default='QUAD', name="Fill Mode" ) combine_mode : EnumProperty( items=( ('LAST', 'Last', 'Show only the last iteration'), ('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'), ('ALL', 'All', 'Combine the result of all iterations')), default='LAST', name="Combine Mode", ) gen_modifiers : BoolProperty( name="Generator Modifiers", default=True, description="Apply Modifiers and Shape Keys to the base object" ) com_modifiers : BoolProperty( name="Component Modifiers", default=True, description="Apply Modifiers and Shape Keys to the component object" ) merge : BoolProperty( name="Merge", default=False, description="Merge vertices in adjacent duplicates" ) merge_open_edges_only : BoolProperty( name="Open edges only", default=True, description="Merge only open edges" ) merge_thres : FloatProperty( name="Distance", default=0.0001, soft_min=0, soft_max=10, description="Limit below which to merge vertices" ) bool_random : BoolProperty( name="Randomize", default=False, description="Randomize component rotation" ) rand_seed : IntProperty( name="Seed", default=0, soft_min=0, soft_max=10, description="Random seed" ) coll_rand_seed : IntProperty( name="Seed", default=0, soft_min=0, soft_max=10, description="Random seed" ) rand_step : IntProperty( name="Step", default=1, min=1, soft_max=2, description="Random step" ) bool_vertex_group : BoolProperty( name="Map Vertex Groups", default=False, description="Transfer all Vertex Groups from Base object" ) bool_selection : BoolProperty( name="On selected Faces", default=False, description="Create Tessellation only on selected faces" ) bool_shapekeys : BoolProperty( name="Use Shape Keys", default=False, description="Transfer Component's Shape Keys. If the name of Vertex " "Groups and Shape Keys are the same, they will be " "automatically combined" ) bool_smooth : BoolProperty( name="Smooth Shading", default=False, description="Output faces with smooth shading rather than flat shaded" ) bool_materials : BoolProperty( name="Transfer Materials", default=True, description="Preserve component's materials" ) generator : StringProperty( name="", description="Base object for the tessellation", default = "" ) component : StringProperty( name="", description="Component object for the tessellation", default = "" ) component_coll : StringProperty( name="", description="Components collection for the tessellation", default = "" ) target : StringProperty( name="", description="Target object for custom direction", default = "" ) even_thickness : BoolProperty( name="Even Thickness", default=False, description="Iterative sampling method for determine the correct length of the vectors (Experimental)" ) even_thickness_iter : IntProperty( name="Even Thickness Iterations", default=3, min = 1, soft_max = 20, description="More iterations produces more accurate results but make the tessellation slower" ) bool_material_id : BoolProperty( name="Tessellation on Material ID", default=False, description="Apply the component only on the selected Material" ) bool_dissolve_seams : BoolProperty( name="Dissolve Seams", default=False, description="Dissolve all seam edges" ) material_id : IntProperty( name="Material ID", default=0, min=0, description="Material ID" ) iterations : IntProperty( name="Iterations", default=1, min=1, soft_max=5, description="Automatically repeat the Tessellation using the " + "generated geometry as new base object.\nUseful for " + "for branching systems. Dangerous!" ) bool_combine : BoolProperty( name="Combine unused", default=False, description="Combine the generated geometry with unused faces" ) bool_advanced : BoolProperty( name="Advanced Settings", default=False, description="Show more settings" ) normals_mode : EnumProperty( items=( ('VERTS', 'Normals', 'Consistent direction based on vertices normal'), ('FACES', 'Faces', 'Based on individual faces normal'), ('SHAPEKEYS', 'Keys', "According to base object's shape keys"), ('OBJECT', 'Object', "According to a target object")), default='VERTS', name="Direction" ) bounds_x : EnumProperty( items=( ('EXTEND', 'Extend', 'Default X coordinates'), ('CLIP', 'Clip', 'Trim out of bounds in X direction'), ('CYCLIC', 'Cyclic', 'Cyclic components in X direction')), default='EXTEND', name="Bounds X", ) bounds_y : EnumProperty( items=( ('EXTEND', 'Extend', 'Default Y coordinates'), ('CLIP', 'Clip', 'Trim out of bounds in Y direction'), ('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')), default='EXTEND', name="Bounds Y", ) close_mesh : EnumProperty( items=( ('NONE', 'None', 'Keep the mesh open'), ('CAP', 'Cap Holes', 'Automatically cap open loops'), ('BRIDGE', 'Bridge Open Loops', 'Automatically bridge loop pairs'), ('BRIDGE_CAP', 'Custom', 'Bridge loop pairs and cap holes according to vertex groups')), default='NONE', name="Close Mesh" ) cap_faces : BoolProperty( name="Cap Holes", default=False, description="Cap open edges loops" ) frame_boundary : BoolProperty( name="Frame Boundary", default=False, description="Support face boundaries" ) fill_frame : BoolProperty( name="Fill Frame", default=False, description="Fill inner faces with Fan tessellation" ) boundary_mat_offset : IntProperty( name="Material Offset", default=0, description="Material Offset for boundaries (with Multi Components or Material ID)" ) fill_frame_mat : IntProperty( name="Material Offset", default=0, description="Material Offset for inner faces (with Multi Components or Material ID)" ) open_edges_crease : FloatProperty( name="Open Edges Crease", default=0, min=0, max=1, description="Automatically set crease for open edges" ) bridge_edges_crease : FloatProperty( name="Bridge Edges Crease", default=0, min=0, max=1, description="Automatically set crease for bridge edges" ) bridge_smoothness : FloatProperty( name="Smoothness", default=1, min=0, max=1, description="Bridge Smoothness" ) frame_thickness : FloatProperty( name="Frame Thickness", default=0.2, min=0, soft_max=2, description="Frame Thickness" ) frame_mode : EnumProperty( items=( ('CONSTANT', 'Constant', 'Even thickness'), ('RELATIVE', 'Relative', 'Frame offset depends on face areas')), default='CONSTANT', name="Offset" ) bridge_cuts : IntProperty( name="Cuts", default=0, min=0, max=20, description="Bridge Cuts" ) cap_material_offset : IntProperty( name="Material Offset", default=0, min=0, description="Material index offset for the cap faces" ) bridge_material_offset : IntProperty( name="Material Offset", default=0, min=0, description="Material index offset for the bridge faces" ) patch_subs : IntProperty( name="Patch Subdivisions", default=1, min=0, description="Subdivisions levels for Patch tessellation after the first iteration" ) use_origin_offset : BoolProperty( name="Align to Origins", default=False, description="Define offset according to components origin and local Z coordinate" ) vertex_group_thickness : StringProperty( name="Thickness weight", default='', description="Vertex Group used for thickness" ) invert_vertex_group_thickness : BoolProperty( name="Invert", default=False, description="Invert the vertex group influence" ) vertex_group_thickness_factor : FloatProperty( name="Factor", default=0, min=0, max=1, description="Thickness factor to use for zero vertex group influence" ) vertex_group_distribution : StringProperty( name="Distribution weight", default='', description="Vertex Group used for gradient distribution" ) invert_vertex_group_distribution : BoolProperty( name="Invert", default=False, description="Invert the vertex group influence" ) vertex_group_distribution_factor : FloatProperty( name="Factor", default=0, min=0, max=1, description="Randomness factor to use for zero vertex group influence" ) vertex_group_cap_owner : EnumProperty( items=( ('BASE', 'Base', 'Use base vertex group'), ('COMP', 'Component', 'Use component vertex group')), default='COMP', name="Source" ) vertex_group_cap : StringProperty( name="Cap Vertex Group", default='', description="Vertex Group used for cap open edges" ) invert_vertex_group_cap : BoolProperty( name="Invert", default=False, description="Invert the vertex group influence" ) vertex_group_bridge_owner : EnumProperty( items=( ('BASE', 'Base', 'Use base vertex group'), ('COMP', 'Component', 'Use component vertex group')), default='COMP', name="Source" ) vertex_group_bridge : StringProperty( name="Thickness weight", default='', description="Vertex Group used for bridge open edges" ) invert_vertex_group_bridge : BoolProperty( name="Invert", default=False, description="Invert the vertex group influence" ) vertex_group_rotation : StringProperty( name="Rotation weight", default='', description="Vertex Group used for rotation" ) invert_vertex_group_rotation : BoolProperty( name="Invert", default=False, description="Invert the vertex group influence" ) normals_x : FloatProperty( name="X", default=1, min=0, max=1, description="Scale X component of the normals" ) normals_y : FloatProperty( name="Y", default=1, min=0, max=1, description="Scale Y component of the normals" ) normals_z : FloatProperty( name="Z", default=1, min=0, max=1, description="Scale Z component of the normals" ) vertex_group_scale_normals : StringProperty( name="Scale normals weight", default='', description="Vertex Group used for editing the normals directions" ) invert_vertex_group_scale_normals : BoolProperty( name="Invert", default=False, description="Invert the vertex group influence" ) smooth_normals : BoolProperty( name="Smooth Normals", default=False, description="Smooth normals of the surface in order to reduce intersections" ) smooth_normals_iter : IntProperty( name="Iterations", default=5, min=0, description="Smooth iterations" ) smooth_normals_uv : FloatProperty( name="UV Anisotropy", default=0, min=-1, max=1, description="0 means no anisotropy, -1 represent the U direction, while 1 represent the V direction" ) vertex_group_smooth_normals : StringProperty( name="Smooth Normals weight", default='', description="Vertex Group used for smoothing normals" ) invert_vertex_group_smooth_normals : BoolProperty( name="Invert", default=False, description="Invert the vertex group influence" ) consistent_wedges : BoolProperty( name="Consistent Wedges", default=True, description="Use same component for the wedges generated by the Fan tessellation" ) boundary_variable_offset : BoolProperty( name="Boundary Variable Offset", default=False, description="Additional material offset based on the number of boundary vertices" ) auto_rotate_boundary : BoolProperty( name="Automatic Rotation", default=False, description="Automatically rotate the boundary faces" ) working_on = "" def draw(self, context): ''' try: bool_working = self.working_on == self.object_name and \ self.working_on != "" except: bool_working = False ''' bool_working = False bool_allowed = False ob0 = None ob1 = None ob = context.object sel = context.selected_objects if len(sel) == 2: bool_allowed = True for o in sel: if o.type not in allowed_objects(): bool_allowed = False if self.component_mode == 'OBJECT': if len(sel) != 2 and not bool_working: layout = self.layout layout.label(icon='OBJECT_DATA', text='Single Object Component') layout.label(icon='INFO', text="Please, select two different objects. Select first the") layout.label(text="Component object, then select the Base object.") return elif not bool_allowed and not bool_working: layout = self.layout layout.label(icon='OBJECT_DATA', text='Single Object Component') layout.label(icon='ERROR', text="Please, select Mesh, Curve, Surface, Meta or Text") return elif self.component_mode == 'COLLECTION': no_components = True for o in bpy.data.collections[self.component_coll].objects: if o.type in ('MESH', 'CURVE', 'META', 'SURFACE', 'FONT') and o is not ob0: no_components = False break if no_components: layout = self.layout layout.label(icon='OUTLINER_COLLECTION', text='Components from Active Collection') layout.label(icon='INFO', text="The Active Collection does not contain any Mesh,") layout.label(text="Curve, Surface, Meta or Text object.") return elif self.component_mode == 'MATERIALS': no_components = True for mat in ob.material_slots.keys(): if mat in bpy.data.objects.keys(): if bpy.data.objects[mat].type in allowed_objects(): no_components = False break if no_components: layout = self.layout layout.label(icon='INFO', text='Components from Materials') layout.label(text="Can't find any object according to the materials name.") return if ob0 == ob1 == None: ob0 = context.object self.generator = ob0.name if self.component_mode == 'OBJECT': for o in sel: if o != ob0: ob1 = o self.component = o.name self.no_component = False break # new object name if self.object_name == "": if self.generator == "": self.object_name = "Tessellation" else: #self.object_name = self.generator + "_Tessellation" self.object_name = "Tessellation" layout = self.layout # Base and Component col = layout.column(align=True) #col.prop(self, "copy_settings") row = col.row(align=True) row.label(text="Base : " + self.generator, icon='OBJECT_DATA') if self.component_mode == 'OBJECT': row.label(text="Component : " + self.component, icon='OBJECT_DATA') elif self.component_mode == 'COLLECTION': row.label(text="Collection : " + self.component_coll, icon='OUTLINER_COLLECTION') elif self.component_mode == 'MATERIALS': row.label(text="Multiple Components", icon='MATERIAL') # Base Modifiers row = col.row(align=True) col2 = row.column(align=True) col2.prop(self, "gen_modifiers", text="Use Modifiers", icon='MODIFIER') base = bpy.data.objects[self.generator] # Component Modifiers row.separator() col3 = row.column(align=True) col3.prop(self, "com_modifiers", text="Use Modifiers", icon='MODIFIER') if self.component_mode == 'OBJECT': component = bpy.data.objects[self.component] col.separator() # Fill and Rotation row = col.row(align=True) row.label(text="Fill Mode:") row = col.row(align=True) row.prop( self, "fill_mode", icon='NONE', expand=True, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) row = col.row(align=True) # merge settings row.prop(self, "merge") row.prop(self, "bool_smooth") # frame settings if self.fill_mode == 'FRAME': col.separator() col.label(text="Frame Settings:") row = col.row(align=True) row.prop(self, "frame_mode", expand=True) col.prop(self, "frame_thickness", text='Thickness', icon='NONE') col.separator() row = col.row(align=True) row.prop(self, "fill_frame", icon='NONE') show_frame_mat = self.component_mode == 'MATERIALS' or self.bool_material_id col2 = row.column(align=True) col2.prop(self, "fill_frame_mat", icon='NONE') col2.enabled = self.fill_frame and show_frame_mat row = col.row(align=True) row.prop(self, "frame_boundary", text='Boundary', icon='NONE') col2 = row.column(align=True) col2.prop(self, "boundary_mat_offset", icon='NONE') col2.enabled = self.frame_boundary and show_frame_mat if self.rotation_mode == 'UV': uv_error = False if ob0.type != 'MESH': row = col.row(align=True) row.label( text="UV rotation supported only for Mesh objects", icon='ERROR') uv_error = True else: if len(ob0.data.uv_layers) == 0: row = col.row(align=True) check_name = self.generator row.label(text="'" + check_name + "' doesn't have UV Maps", icon='ERROR') uv_error = True if uv_error: row = col.row(align=True) row.label(text="Default rotation will be used instead", icon='INFO') # Component Z col.separator() col.label(text="Thickness:") row = col.row(align=True) row.prop( self, "scale_mode", text="Scale Mode", icon='NONE', expand=True, slider=False, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) col.prop( self, "zscale", text="Scale", icon='NONE', expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) if self.mode == 'BOUNDS': row = col.row(align=True) row.prop( self, "offset", text="Offset", icon='NONE', expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) row.enabled = not self.use_origin_offset col.separator() col.label(text="More settings in the Object Data Properties panel...", icon='PROPERTIES') def execute(self, context): try: ob0 = bpy.data.objects[self.generator] if self.component_mode == 'OBJECT': ob1 = bpy.data.objects[self.component] except: return {'CANCELLED'} self.object_name = "Tessellation" # Check if existing object with same name names = [o.name for o in bpy.data.objects] if self.object_name in names: count_name = 1 while True: test_name = self.object_name + '.{:03d}'.format(count_name) if not (test_name in names): self.object_name = test_name break count_name += 1 if self.component_mode == 'OBJECT': if ob1.type not in allowed_objects(): message = "Component must be Mesh, Curve, Surface, Text or Meta object!" self.report({'ERROR'}, message) self.component = None if ob0.type not in allowed_objects(): message = "Generator must be Mesh, Curve, Surface, Text or Meta object!" self.report({'ERROR'}, message) self.generator = "" if bpy.ops.object.select_all.poll(): bpy.ops.object.select_all(action='TOGGLE') bpy.ops.object.mode_set(mode='OBJECT') bool_update = False if context.object == ob0: auto_layer_collection() new_ob = convert_object_to_mesh(ob0,False,False) new_ob.data.name = self.object_name new_ob.name = self.object_name else: new_ob = context.object bool_update = True new_ob = store_parameters(self, new_ob) new_ob.tissue.tissue_type = 'TESSELLATE' try: bpy.ops.object.tissue_update_tessellate() except RuntimeError as e: bpy.data.objects.remove(new_ob) remove_temp_objects() self.report({'ERROR'}, str(e)) return {'CANCELLED'} if not bool_update: self.object_name = new_ob.name #self.working_on = self.object_name new_ob.location = ob0.location new_ob.matrix_world = ob0.matrix_world # Assign collection of the base object old_coll = new_ob.users_collection if old_coll != ob0.users_collection: for c in old_coll: c.objects.unlink(new_ob) for c in ob0.users_collection: c.objects.link(new_ob) context.view_layer.objects.active = new_ob return {'FINISHED'} def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) class tissue_update_tessellate_deps(Operator): bl_idname = "object.tissue_update_tessellate_deps" bl_label = "Tissue Refresh" bl_description = ("Fast update the tessellated mesh according to base and " "component changes.") bl_options = {'REGISTER', 'UNDO'} go = False @classmethod def poll(cls, context): try: return context.object.tissue.tissue_type != 'NONE' except: return False #@staticmethod #def check_gen_comp(checking): # note pass the stored name key in here to check it out # return checking in bpy.data.objects.keys() def execute(self, context): active_ob = context.object selected_objects = context.selected_objects ### TO-DO: sorting according to dependencies update_objects = [o for o in selected_objects if o.tissue.tissue_type != 'NONE'] for ob in selected_objects: update_objects = list(reversed(update_dependencies(ob, update_objects))) #update_objects = list(reversed(update_dependencies(ob, [ob]))) for o in update_objects: override = { 'object': o, 'selected_objects' : [o] } if o.type == 'MESH': try: bpy.ops.object.tissue_update_tessellate(override) except: self.report({'ERROR'}, "Can't Tessellate :-(") else: try: bpy.ops.object.tissue_convert_to_curve_update(override) except: self.report({'ERROR'}, "Can't compute Curve :-(") context.view_layer.objects.active = active_ob for o in context.view_layer.objects: o.select_set(o in selected_objects) return {'FINISHED'} class tissue_update_tessellate(Operator): bl_idname = "object.tissue_update_tessellate" bl_label = "Tissue Refresh Simple" bl_description = ("Fast update the tessellated mesh according to base and " "component changes. Does not update dependencies") bl_options = {'REGISTER', 'UNDO'} go = False @classmethod def poll(cls, context): try: ob = context.object return ob.tissue.tissue_type == 'TESSELLATE' except: return False def execute(self, context): tissue_time(None,'Tissue: Tessellating...', levels=0) start_time = time.time() ob = context.object tess_props = props_to_dict(ob) if not self.go: generator = ob.tissue_tessellate.generator component = ob.tissue_tessellate.component zscale = ob.tissue_tessellate.zscale scale_mode = ob.tissue_tessellate.scale_mode rotation_mode = ob.tissue_tessellate.rotation_mode rotation_shift = ob.tissue_tessellate.rotation_shift rotation_direction = ob.tissue_tessellate.rotation_direction offset = ob.tissue_tessellate.offset merge = ob.tissue_tessellate.merge merge_open_edges_only = ob.tissue_tessellate.merge_open_edges_only merge_thres = ob.tissue_tessellate.merge_thres mode = ob.tissue_tessellate.mode gen_modifiers = ob.tissue_tessellate.gen_modifiers com_modifiers = ob.tissue_tessellate.com_modifiers bool_random = ob.tissue_tessellate.bool_random rand_seed = ob.tissue_tessellate.rand_seed rand_step = ob.tissue_tessellate.rand_step fill_mode = ob.tissue_tessellate.fill_mode bool_vertex_group = ob.tissue_tessellate.bool_vertex_group bool_selection = ob.tissue_tessellate.bool_selection bool_shapekeys = ob.tissue_tessellate.bool_shapekeys bool_smooth = ob.tissue_tessellate.bool_smooth bool_materials = ob.tissue_tessellate.bool_materials bool_dissolve_seams = ob.tissue_tessellate.bool_dissolve_seams bool_material_id = ob.tissue_tessellate.bool_material_id material_id = ob.tissue_tessellate.material_id iterations = ob.tissue_tessellate.iterations bool_combine = ob.tissue_tessellate.bool_combine normals_mode = ob.tissue_tessellate.normals_mode bool_advanced = ob.tissue_tessellate.bool_advanced #bool_multi_components = ob.tissue_tessellate.bool_multi_components combine_mode = ob.tissue_tessellate.combine_mode bounds_x = ob.tissue_tessellate.bounds_x bounds_y = ob.tissue_tessellate.bounds_y cap_faces = ob.tissue_tessellate.cap_faces close_mesh = ob.tissue_tessellate.close_mesh open_edges_crease = ob.tissue_tessellate.open_edges_crease bridge_edges_crease = ob.tissue_tessellate.bridge_edges_crease bridge_smoothness = ob.tissue_tessellate.bridge_smoothness frame_thickness = ob.tissue_tessellate.frame_thickness frame_mode = ob.tissue_tessellate.frame_mode frame_boundary = ob.tissue_tessellate.frame_boundary fill_frame = ob.tissue_tessellate.fill_frame boundary_mat_offset = ob.tissue_tessellate.boundary_mat_offset fill_frame_mat = ob.tissue_tessellate.fill_frame_mat bridge_cuts = ob.tissue_tessellate.bridge_cuts cap_material_offset = ob.tissue_tessellate.cap_material_offset bridge_material_offset = ob.tissue_tessellate.bridge_material_offset patch_subs = ob.tissue_tessellate.patch_subs use_origin_offset = ob.tissue_tessellate.use_origin_offset vertex_group_thickness = ob.tissue_tessellate.vertex_group_thickness invert_vertex_group_thickness = ob.tissue_tessellate.invert_vertex_group_thickness vertex_group_thickness_factor = ob.tissue_tessellate.vertex_group_thickness_factor vertex_group_distribution = ob.tissue_tessellate.vertex_group_distribution invert_vertex_group_distribution = ob.tissue_tessellate.invert_vertex_group_distribution vertex_group_distribution_factor = ob.tissue_tessellate.vertex_group_distribution_factor vertex_group_cap_owner = ob.tissue_tessellate.vertex_group_cap_owner vertex_group_cap = ob.tissue_tessellate.vertex_group_cap invert_vertex_group_cap = ob.tissue_tessellate.invert_vertex_group_cap vertex_group_bridge_owner = ob.tissue_tessellate.vertex_group_bridge_owner vertex_group_bridge = ob.tissue_tessellate.vertex_group_bridge invert_vertex_group_bridge = ob.tissue_tessellate.invert_vertex_group_bridge vertex_group_rotation = ob.tissue_tessellate.vertex_group_rotation invert_vertex_group_rotation = ob.tissue_tessellate.invert_vertex_group_rotation vertex_group_smooth_normals = ob.tissue_tessellate.vertex_group_smooth_normals invert_vertex_group_smooth_normals = ob.tissue_tessellate.invert_vertex_group_smooth_normals target = ob.tissue_tessellate.target even_thickness = ob.tissue_tessellate.even_thickness even_thickness_iter = ob.tissue_tessellate.even_thickness_iter component_mode = ob.tissue_tessellate.component_mode component_coll = ob.tissue_tessellate.component_coll coll_rand_seed = ob.tissue_tessellate.coll_rand_seed try: generator.name if component_mode == 'OBJECT': component.name except: self.report({'ERROR'}, "Active object must be Tessellated before Update") return {'CANCELLED'} # reset messages ob.tissue_tessellate.warning_message_merge = '' tess_props = props_to_dict(ob) # Solve Local View issues local_spaces = [] local_ob0 = [] local_ob1 = [] for area in context.screen.areas: for space in area.spaces: try: if ob.local_view_get(space): local_spaces.append(space) local_ob0 = ob0.local_view_get(space) ob0.local_view_set(space, True) local_ob1 = ob1.local_view_get(space) ob1.local_view_set(space, True) except: pass starting_mode = context.object.mode #if starting_mode == 'PAINT_WEIGHT': starting_mode = 'WEIGHT_PAINT' if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode='OBJECT') ob0 = generator ob1 = component ##### auto_layer_collection() ob0_hide = ob0.hide_get() ob0_hidev = ob0.hide_viewport ob0_hider = ob0.hide_render ob0.hide_set(False) ob0.hide_viewport = False ob0.hide_render = False if component_mode == 'OBJECT': ob1_hide = ob1.hide_get() ob1_hidev = ob1.hide_viewport ob1_hider = ob1.hide_render ob1.hide_set(False) ob1.hide_viewport = False ob1.hide_render = False components = [] if component_mode == 'COLLECTION': dict_components = {} meta_object = True for _ob1 in component_coll.objects: if _ob1 == ob: continue if _ob1.type in ('MESH', 'CURVE','SURFACE','FONT','META'): if _ob1.type == 'META': if meta_object: meta_object = False else: continue dict_components[_ob1.name] = _ob1 for k in sorted(dict_components): components.append(dict_components[k]) elif component_mode == 'OBJECT': components.append(ob1) if ob0.type == 'META': base_ob = convert_object_to_mesh(ob0, False, True) else: base_ob = ob0.copy() base_ob.data = ob0.data context.collection.objects.link(base_ob) base_ob.name = '_tissue_tmp_base' # In Blender 2.80 cache of copied objects is lost, must be re-baked bool_update_cloth = False for m in base_ob.modifiers: if m.type == 'CLOTH': m.point_cache.frame_end = context.scene.frame_current bool_update_cloth = True if bool_update_cloth: scene = context.scene for mod in base_ob.modifiers: if mod.type == 'CLOTH': override = {'scene': scene, 'active_object': base_ob, 'point_cache': mod.point_cache} bpy.ops.ptcache.bake(override, bake=True) break base_ob.modifiers.update() # clear vertex groups before creating new ones if ob not in components: ob.vertex_groups.clear() if bool_selection: faces = base_ob.data.polygons selections = [False]*len(faces) faces.foreach_get('select',selections) selections = np.array(selections) if not selections.any(): message = "There are no faces selected." context.view_layer.objects.active = ob ob.select_set(True) bpy.ops.object.mode_set(mode=starting_mode) remove_temp_objects() self.report({'ERROR'}, message) return {'CANCELLED'} iter_objects = [base_ob] ob_location = ob.location ob_matrix_world = ob.matrix_world #if ob not in components: ob.data.clear_geometry() # Faster with heavy geometries (from previous tessellations) for iter in range(iterations): tess_props['generator'] = base_ob if iter > 0 and len(iter_objects) == 0: break if iter > 0 and normals_mode in ('SHAPEKEYS','OBJECT'): tess_props['normals_mode'] = 'VERTS' same_iteration = [] matched_materials = [] if component_mode == 'MATERIALS': components = [] objects_keys = bpy.data.objects.keys() for mat_slot in base_ob.material_slots: mat_name = mat_slot.material.name if mat_name in objects_keys: ob1 = bpy.data.objects[mat_name] if ob1.type in ('MESH', 'CURVE','SURFACE','FONT','META'): components.append(bpy.data.objects[mat_name]) matched_materials.append(mat_name) else: components.append(None) else: components.append(None) tess_props['component'] = components # patch subdivisions for additional iterations if iter > 0 and fill_mode == 'PATCH': temp_mod = base_ob.modifiers.new('Tissue_Subsurf', type='SUBSURF') temp_mod.levels = patch_subs # patch tessellation tissue_time(None,"Tessellate iteration...",levels=1) tt = time.time() same_iteration = tessellate_patch(tess_props) tissue_time(tt, "Tessellate iteration",levels=1) tt = time.time() # if empty or error, continue #if type(same_iteration) != list:#is not bpy.types.Object and : # return {'CANCELLED'} for id, new_ob in enumerate(same_iteration): # rename, make active and change transformations new_ob.name = '_tissue_tmp_{}_{}'.format(iter,id) new_ob.select_set(True) context.view_layer.objects.active = new_ob new_ob.location = ob_location new_ob.matrix_world = ob_matrix_world base_ob.location = ob_location base_ob.matrix_world = ob_matrix_world # join together multiple components iterations if type(same_iteration) == list: if len(same_iteration) == 0: remove_temp_objects() tissue_time(None,"Can't Tessellate :-(",levels=0) return {'CANCELLED'} if len(same_iteration) > 1: #join_objects(context, same_iteration) new_ob = join_objects(same_iteration) if type(same_iteration) in (int,str): new_ob = same_iteration if iter == 0: try: bpy.data.objects.remove(iter_objects[0]) iter_objects = [] except: continue continue # Clean last iteration, needed for combine object if (bool_selection or bool_material_id) and combine_mode == 'UNUSED': # remove faces from last mesh bm = bmesh.new() if (fill_mode == 'PATCH' or gen_modifiers) and iter == 0: last_mesh = simple_to_mesh(base_ob)#(ob0) else: last_mesh = iter_objects[-1].data.copy() bm.from_mesh(last_mesh) bm.faces.ensure_lookup_table() if component_mode == 'MATERIALS': remove_materials = matched_materials elif bool_material_id: remove_materials = [material_id] else: remove_materials = [] if bool_selection: if component_mode == 'MATERIALS' or bool_material_id: remove_faces = [f for f in bm.faces if f.material_index in remove_materials and f.select] else: remove_faces = [f for f in bm.faces if f.select] else: remove_faces = [f for f in bm.faces if f.material_index in remove_materials] bmesh.ops.delete(bm, geom=remove_faces, context='FACES') bm.to_mesh(last_mesh) bm.free() last_mesh.update() last_mesh.name = '_tissue_tmp_previous_unused' # delete previous iteration if empty or update it if len(last_mesh.vertices) > 0: iter_objects[-1].data = last_mesh.copy() iter_objects[-1].data.update() else: bpy.data.objects.remove(iter_objects[-1]) iter_objects = iter_objects[:-1] # set new base object for next iteration base_ob = convert_object_to_mesh(new_ob,True,True) if iter < iterations-1: new_ob.data = base_ob.data # store new iteration and set transformations iter_objects.append(new_ob) base_ob.name = '_tissue_tmp_base' elif combine_mode == 'ALL': base_ob = new_ob.copy() iter_objects = [new_ob] + iter_objects else: if base_ob != new_ob: bpy.data.objects.remove(base_ob) base_ob = new_ob iter_objects = [new_ob] if iter > 0:# and fill_mode == 'PATCH': base_ob.modifiers.clear()#remove(temp_mod) # Combine if combine_mode != 'LAST' and len(iter_objects) > 1: if base_ob not in iter_objects and type(base_ob) == bpy.types.Object: bpy.data.objects.remove(base_ob) new_ob = join_objects(iter_objects) new_ob.modifiers.clear() iter_objects = [new_ob] tissue_time(tt, "Combine tessellations", levels=1) if merge: new_ob.active_shape_key_index = 0 use_bmesh = not (bool_shapekeys and fill_mode == 'PATCH' and component_mode != 'OBJECT') merged = merge_components(new_ob, ob.tissue_tessellate, use_bmesh) if merged == 'bridge_error': message = "Can't make the bridge!" ob.tissue_tessellate.warning_message_merge = message base_ob = new_ob #context.view_layer.objects.active tt = time.time() if new_ob == 0: #bpy.data.objects.remove(base_ob.data) try: bpy.data.objects.remove(base_ob) except: pass message = "The generated object is an empty geometry!" context.view_layer.objects.active = ob ob.select_set(True) bpy.ops.object.mode_set(mode=starting_mode) self.report({'ERROR'}, message) return {'CANCELLED'} errors = {} errors["modifiers_error"] = "Modifiers that change the topology of the mesh \n" \ "after the last Subsurf (or Multires) are not allowed." if new_ob in errors: for o in iter_objects: try: bpy.data.objects.remove(o) except: pass try: bpy.data.meshes.remove(data1) except: pass context.view_layer.objects.active = ob ob.select_set(True) message = errors[new_ob] ob.tissue_tessellate.error_message = message bpy.ops.object.mode_set(mode=starting_mode) self.report({'ERROR'}, message) return {'CANCELLED'} # update data and preserve name if ob.type != 'MESH': loc, matr = ob.location, ob.matrix_world ob = convert_object_to_mesh(ob,False,True) ob.location, ob.matrix_world = loc, matr data_name = ob.data.name old_data = ob.data old_data.name = '_tissue_tmp_old_data' #ob.data = bpy.data.meshes.new_from_object(new_ob)# linked_objects = [o for o in bpy.data.objects if o.data == old_data] for o in linked_objects: o.data = new_ob.data if len(linked_objects) > 1: copy_tessellate_props(ob, o) #ob.data = new_ob.data ob.data.name = data_name bpy.data.meshes.remove(old_data) # copy vertex group for vg in new_ob.vertex_groups: if not vg.name in ob.vertex_groups.keys(): ob.vertex_groups.new(name=vg.name) selected_objects = [o for o in context.selected_objects] for o in selected_objects: o.select_set(False) ob.select_set(True) context.view_layer.objects.active = ob is_multiple = iterations > 1 or combine_mode != 'LAST'# or bool_multi_components if merge and is_multiple: use_bmesh = not (bool_shapekeys and fill_mode == 'PATCH' and component_mode != 'OBJECT') merge_components(new_ob, ob.tissue_tessellate, use_bmesh) if bool_smooth: bpy.ops.object.shade_smooth() for mesh in bpy.data.meshes: if not mesh.users: bpy.data.meshes.remove(mesh) for o in selected_objects: try: o.select_set(True) except: pass ob.tissue_tessellate.error_message = "" # Restore Base visibility ob0.hide_set(ob0_hide) ob0.hide_viewport = ob0_hidev ob0.hide_render = ob0_hider # Restore Component visibility if component_mode == 'OBJECT': ob1.hide_set(ob1_hide) ob1.hide_viewport = ob1_hidev ob1.hide_render = ob1_hider # Restore Local visibility for space, local0, local1 in zip(local_spaces, local_ob0, local_ob1): ob0.local_view_set(space, local0) ob1.local_view_set(space, local1) bpy.data.objects.remove(new_ob) remove_temp_objects() tissue_time(tt, "Closing tessellation", levels=1) tissue_time(start_time,'Tessellation of "{}"'.format(ob.name),levels=0) return {'FINISHED'} def check(self, context): return True class TISSUE_PT_tessellate(Panel): bl_label = "Tissue Tools" bl_category = "Tissue" bl_space_type = "VIEW_3D" bl_region_type = "UI" #bl_options = {'DEFAULT_OPEN'} @classmethod def poll(cls, context): return context.mode in {'OBJECT', 'EDIT_MESH'} def draw(self, context): layout = self.layout col = layout.column(align=True) col.label(text="Generate:") row = col.row(align=True) row.operator("object.tissue_tessellate", text='Tessellate', icon='OBJECT_DATA').component_mode = 'OBJECT' tss = row.operator("object.tissue_tessellate", text='', icon='OUTLINER_COLLECTION') tss.component_mode = 'COLLECTION' tss.component_coll = context.collection.name row.operator("object.tissue_tessellate", text='', icon='MATERIAL').component_mode = 'MATERIALS' #col.operator("object.tissue_tessellate_multi", text='Tessellate Multi') col.operator("object.dual_mesh_tessellated", text='Dual Mesh', icon='SEQ_CHROMA_SCOPE') col.separator() #col.label(text="Curves:") col.operator("object.tissue_convert_to_curve", icon='OUTLINER_OB_CURVE', text="Convert to Curve") #row.operator("object.tissue_convert_to_curve_update", icon='FILE_REFRESH', text='') col.separator() col.operator("object.tissue_update_tessellate_deps", icon='FILE_REFRESH', text='Refresh') ##### col.separator() col.label(text="Rotate Faces:") row = col.row(align=True) row.operator("mesh.tissue_rotate_face_left", text='Left', icon='LOOP_BACK') row.operator("mesh.tissue_rotate_face_flip", text='Flip', icon='UV_SYNC_SELECT') row.operator("mesh.tissue_rotate_face_right", text='Right', icon='LOOP_FORWARDS') col.separator() col.label(text="Other:") col.operator("object.dual_mesh", icon='SEQ_CHROMA_SCOPE') col.operator("object.polyhedra_wireframe", icon='MOD_WIREFRAME', text='Polyhedra Wireframe') col.operator("object.lattice_along_surface", icon="OUTLINER_OB_LATTICE") act = context.object if act and act.type == 'MESH': col.operator("object.uv_to_mesh", icon="UV") if act.mode == 'EDIT': col.separator() col.label(text="Weight:") col.operator("object.tissue_weight_distance", icon="TRACKING") col.operator("object.tissue_weight_streamlines", icon="ANIM") col.separator() col.label(text="Materials:") col.operator("object.random_materials", icon='COLOR') col.operator("object.weight_to_materials", icon='GROUP_VERTEX') col.separator() col.label(text="Utils:") col.operator("render.tissue_render_animation", icon='RENDER_ANIMATION') class TISSUE_PT_tessellate_object(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "data" bl_label = "Tissue Tessellate" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): try: return context.object.type == 'MESH' except: return False def draw(self, context): ob = context.object props = ob.tissue_tessellate tissue_props = ob.tissue bool_tessellated = tissue_props.tissue_type == 'TESSELLATE' layout = self.layout if not bool_tessellated: layout.label(text="The selected object is not a Tessellated object", icon='INFO') else: if props.error_message != "": layout.label(text=props.error_message, icon='ERROR') col = layout.column(align=True) row = col.row(align=True) set_tessellate_handler(self,context) ###### set_animatable_fix_handler(self,context) row.operator("object.tissue_update_tessellate_deps", icon='FILE_REFRESH', text='Refresh') #### lock_icon = 'LOCKED' if tissue_props.bool_lock else 'UNLOCKED' #lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED' deps_icon = 'LINKED' if tissue_props.bool_dependencies else 'UNLINKED' row.prop(tissue_props, "bool_dependencies", text="", icon=deps_icon) row.prop(tissue_props, "bool_lock", text="", icon=lock_icon) col2 = row.column(align=True) col2.prop(tissue_props, "bool_run", text="",icon='TIME') col2.enabled = not tissue_props.bool_lock #layout.use_property_split = True #layout.use_property_decorate = False # No animation. col = layout.column(align=True) col.label(text='Base object:') row = col.row(align=True) row.prop_search(props, "generator", context.scene, "objects") col2 = row.column(align=True) col2.prop(props, "gen_modifiers", text='Use Modifiers',icon='MODIFIER') ''' try: if not (props.generator.modifiers or props.generator.data.shape_keys): col2.enabled = False except: col2.enabled = False ''' #col.separator() layout.use_property_split = False # Fill col = layout.column(align=True) col.label(text="Fill Mode:") # fill row = col.row(align=True) row.prop(props, "fill_mode", icon='NONE', expand=True, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) #layout.use_property_split = True col = layout.column(align=True) col.prop(props, "bool_smooth") class TISSUE_PT_tessellate_frame(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "data" bl_parent_id = "TISSUE_PT_tessellate_object" bl_label = "Frame Settings" #bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): try: bool_frame = context.object.tissue_tessellate.fill_mode == 'FRAME' bool_tessellated = context.object.tissue_tessellate.generator != None return context.object.type == 'MESH' and bool_frame and bool_tessellated except: return False def draw(self, context): ob = context.object props = ob.tissue_tessellate layout = self.layout col = layout.column(align=True) row = col.row(align=True) row.prop(props, "frame_mode", expand=True) row = col.row(align=True) row.prop(props, "frame_thickness", icon='NONE', expand=True) col.separator() row = col.row(align=True) row.prop(props, "fill_frame", icon='NONE') show_frame_mat = props.component_mode == 'MATERIALS' or props.bool_material_id col2 = row.column(align=True) col2.prop(props, "fill_frame_mat", icon='NONE') col2.enabled = props.fill_frame and show_frame_mat row = col.row(align=True) row.prop(props, "frame_boundary", text='Boundary', icon='NONE') col2 = row.column(align=True) col2.prop(props, "boundary_mat_offset", icon='NONE') col2.enabled = props.frame_boundary and show_frame_mat class TISSUE_PT_tessellate_component(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "data" bl_parent_id = "TISSUE_PT_tessellate_object" bl_label = "Components" #bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): try: bool_tessellated = context.object.tissue.tissue_type == 'TESSELLATE' return context.object.type == 'MESH' and bool_tessellated except: return False def draw(self, context): ob = context.object props = ob.tissue_tessellate layout = self.layout col = layout.column(align=True) col.label(text='Component Mode:') row = col.row(align=True) row.prop(props, "component_mode", icon='NONE', expand=True, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) if props.component_mode == 'OBJECT': col.separator() row = col.row(align=True) row.prop_search(props, "component", context.scene, "objects") col2 = row.column(align=True) col2.prop(props, "com_modifiers", text='Use Modifiers',icon='MODIFIER') ''' try: if not (props.component.modifiers or props.component.data.shape_keys): col2.enabled = False except: col2.enabled = False ''' elif props.component_mode == 'COLLECTION': col.separator() if props.component_coll in list(bpy.data.collections): components = [] for o in props.component_coll.objects: if o.type in allowed_objects() and o is not ob: components.append(o.name) n_comp = len(components) if n_comp == 0: col.label(text="Can't find components in the Collection.", icon='ERROR') else: text = "{} Component{}".format(n_comp,"s" if n_comp>1 else "") row = col.row(align=True) row.label(text=text, icon='OBJECT_DATA') row.prop(props, "com_modifiers", text='Use Modifiers',icon='MODIFIER') else: col.label(text="Please, chose one Collection.", icon='ERROR') col.separator() row = col.row(align=True) row.prop_search(props,'component_coll',bpy.data,'collections') col2 = row.column(align=True) col2.prop(props, "coll_rand_seed") col = layout.column(align=True) row = col.row(align=True) ob0 = props.generator row.prop_search(props, 'vertex_group_distribution', ob0, "vertex_groups", text='') col2 = row.column(align=True) row2 = col2.row(align=True) row2.prop(props, "invert_vertex_group_distribution", text="", toggle=True, icon='ARROW_LEFTRIGHT') row2.prop(props, "vertex_group_distribution_factor") row2.enabled = props.vertex_group_distribution in ob0.vertex_groups.keys() if props.fill_mode == 'FAN': col.prop(props, "consistent_wedges") else: components = [] for mat in props.generator.material_slots.keys(): if mat in bpy.data.objects.keys(): if bpy.data.objects[mat].type in allowed_objects(): components.append(mat) n_comp = len(components) if n_comp == 0: col.label(text="Can't find components from the materials.", icon='ERROR') else: col.separator() text = "{} Component{}".format(n_comp,"s" if n_comp>1 else "") row = col.row(align=True) row.label(text=text, icon='OBJECT_DATA') row.prop(props, "com_modifiers", text='Use Modifiers',icon='MODIFIER') if props.fill_mode != 'FRAME': col.separator() col.separator() row = col.row(align=True) row.label(text="Boundary Faces:") row.prop(props, "boundary_mat_offset", icon='NONE') row = col.row(align=True) row.prop(props, "boundary_variable_offset", text='Variable Offset', icon='NONE') row.prop(props, "auto_rotate_boundary", icon='NONE') col.separator() class TISSUE_PT_tessellate_coordinates(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "data" bl_parent_id = "TISSUE_PT_tessellate_object" bl_label = "Components Coordinates" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): try: bool_tessellated = context.object.tissue.tissue_type == 'TESSELLATE' return context.object.type == 'MESH' and bool_tessellated except: return False def draw(self, context): ob = context.object props = ob.tissue_tessellate layout = self.layout col = layout.column(align=True) # component XY row = col.row(align=True) row.prop(props, "mode", expand=True) if props.mode != 'BOUNDS': col.separator() row = col.row(align=True) row.label(text="X:") row.prop( props, "bounds_x", text="Bounds X", icon='NONE', expand=True, slider=False, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) row = col.row(align=True) row.label(text="Y:") row.prop( props, "bounds_y", text="Bounds X", icon='NONE', expand=True, slider=False, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) class TISSUE_PT_tessellate_rotation(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "data" bl_parent_id = "TISSUE_PT_tessellate_object" bl_label = "Rotation" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): try: bool_tessellated = context.object.tissue.tissue_type == 'TESSELLATE' return context.object.type == 'MESH' and bool_tessellated except: return False def draw(self, context): ob = context.object props = ob.tissue_tessellate layout = self.layout # rotation layout.use_property_split = True layout.use_property_decorate = False # No animation. col = layout.column(align=True) col.prop(props, "rotation_mode", text='Rotation', icon='NONE', expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) if props.rotation_mode == 'WEIGHT': col.separator() row = col.row(align=True) row.separator() row.separator() row.separator() row.prop_search(props, 'vertex_group_rotation', ob0, "vertex_groups", text='Vertex Group') col2 = row.column(align=True) col2.prop(props, "invert_vertex_group_rotation", text="", toggle=True, icon='ARROW_LEFTRIGHT') col2.enabled = props.vertex_group_rotation in ob0.vertex_groups.keys() col.separator() col.prop(props, "rotation_direction", expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) if props.rotation_mode == 'RANDOM': col.prop(props, "rand_seed") col.prop(props, "rand_step") else: col.prop(props, "rotation_shift") if props.rotation_mode == 'UV': uv_error = False if props.generator.type != 'MESH': row = col.row(align=True) row.label( text="UV rotation supported only for Mesh objects", icon='ERROR') uv_error = True else: if len(props.generator.data.uv_layers) == 0: row = col.row(align=True) row.label(text="'" + props.generator.name + " doesn't have UV Maps", icon='ERROR') uv_error = True if uv_error: row = col.row(align=True) row.label(text="Default rotation will be used instead", icon='INFO') class TISSUE_PT_tessellate_thickness(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "data" bl_parent_id = "TISSUE_PT_tessellate_object" bl_label = "Thickness" #bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): try: return context.object.tissue.tissue_type == 'TESSELLATE' except: return False def draw(self, context): ob = context.object props = ob.tissue_tessellate layout = self.layout #layout.use_property_split = True col = layout.column(align=True) # component Z row = col.row(align=True) row.prop(props, "scale_mode", expand=True) col.prop(props, "zscale", text="Scale", icon='NONE', expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) if props.mode == 'BOUNDS': row = col.row(align=True) row.prop(props, "offset", text="Offset", icon='NONE', expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) row.enabled = not props.use_origin_offset col.prop(props, 'use_origin_offset') col.separator() row = col.row(align=True) ob0 = props.generator row.prop_search(props, 'vertex_group_thickness', ob0, "vertex_groups", text='') col2 = row.column(align=True) row2 = col2.row(align=True) row2.prop(props, "invert_vertex_group_thickness", text="", toggle=True, icon='ARROW_LEFTRIGHT') row2.prop(props, "vertex_group_thickness_factor") row2.enabled = props.vertex_group_thickness in ob0.vertex_groups.keys() class TISSUE_PT_tessellate_direction(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "data" bl_parent_id = "TISSUE_PT_tessellate_object" bl_label = "Thickness Direction" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): try: return context.object.tissue.tissue_type == 'TESSELLATE' except: return False def draw(self, context): ob = context.object props = ob.tissue_tessellate layout = self.layout ob0 = props.generator #layout.use_property_split = True col = layout.column(align=True) row = col.row(align=True) row.prop( props, "normals_mode", text="Direction", icon='NONE', expand=True, slider=False, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) if props.normals_mode == 'OBJECT': col.separator() row = col.row(align=True) row.prop_search(props, "target", context.scene, "objects", text='Target') if props.warning_message_thickness != '': col.separator() col.label(text=props.warning_message_thickness, icon='ERROR') if props.normals_mode != 'FACES': col.separator() col.prop(props, "smooth_normals") if props.smooth_normals: row = col.row(align=True) row.prop(props, "smooth_normals_iter") row.separator() row.prop_search(props, 'vertex_group_smooth_normals', ob0, "vertex_groups", text='') col2 = row.column(align=True) col2.prop(props, "invert_vertex_group_smooth_normals", text="", toggle=True, icon='ARROW_LEFTRIGHT') col2.enabled = props.vertex_group_smooth_normals in ob0.vertex_groups.keys() if props.normals_mode == 'VERTS': col.separator() row = col.row(align=True) row.prop(props, "normals_x") row.prop(props, "normals_y") row.prop(props, "normals_z") row = col.row(align=True) row.prop_search(props, 'vertex_group_scale_normals', ob0, "vertex_groups", text='') col2 = row.column(align=True) col2.prop(props, "invert_vertex_group_scale_normals", text="", toggle=True, icon='ARROW_LEFTRIGHT') col2.enabled = props.vertex_group_scale_normals in ob0.vertex_groups.keys() if props.normals_mode in ('OBJECT', 'SHAPEKEYS'): col.separator() row = col.row(align=True) row.prop(props, "even_thickness") if props.even_thickness: row.prop(props, "even_thickness_iter") class TISSUE_PT_tessellate_options(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "data" bl_parent_id = "TISSUE_PT_tessellate_object" bl_label = " " bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): try: return context.object.tissue.tissue_type == 'TESSELLATE' except: return False def draw_header(self, context): ob = context.object props = ob.tissue_tessellate self.layout.prop(props, "merge") def draw(self, context): ob = context.object props = ob.tissue_tessellate layout = self.layout layout.use_property_split = True layout.use_property_decorate = False # No animation. col = layout.column(align=True) if props.merge: col.prop(props, "merge_thres") col.prop(props, "merge_open_edges_only") col.prop(props, "bool_dissolve_seams") col.prop(props, "close_mesh") if props.close_mesh in ('BRIDGE', 'BRIDGE_CAP'): col.separator() if props.close_mesh == 'BRIDGE_CAP': if props.vertex_group_bridge_owner == 'BASE': ob_bridge = ob0 else: ob_bridge = ob1 row = col.row(align=True) row.prop_search(props, 'vertex_group_bridge', ob_bridge, "vertex_groups") row.prop(props, "invert_vertex_group_bridge", text="", toggle=True, icon='ARROW_LEFTRIGHT') row = col.row(align=True) row.prop(props, "vertex_group_bridge_owner", expand=True, slider=False, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) col2 = row.column(align=True) row2 = col2.row(align=True) col.prop(props, "bridge_edges_crease", text="Crease") col.prop(props, "bridge_material_offset", text='Material Offset') ''' if props.close_mesh == 'BRIDGE' and False: col.separator() col.prop(props, "bridge_cuts") col.prop(props, "bridge_smoothness") ''' if props.close_mesh in ('CAP', 'BRIDGE_CAP'): #row = col.row(align=True) col.separator() if props.close_mesh == 'BRIDGE_CAP': if props.vertex_group_cap_owner == 'BASE': ob_cap = ob0 else: ob_cap = ob1 row = col.row(align=True) row.prop_search(props, 'vertex_group_cap', ob_cap, "vertex_groups") row.prop(props, "invert_vertex_group_cap", text="", toggle=True, icon='ARROW_LEFTRIGHT') row = col.row(align=True) row.prop(props, "vertex_group_cap_owner", expand=True, slider=False, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) col.prop(props, "open_edges_crease", text="Crease") col.prop(props, "cap_material_offset", text='Material Offset') if props.warning_message_merge: col.separator() col.label(text=props.warning_message_merge, icon='ERROR') class TISSUE_PT_tessellate_morphing(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "data" bl_parent_id = "TISSUE_PT_tessellate_object" bl_label = "Weight and Morphing" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): try: return context.object.tissue.tissue_type == 'TESSELLATE' except: return False def draw(self, context): ob = context.object props = ob.tissue_tessellate layout = self.layout allow_shapekeys = not props.com_modifiers if tessellated(ob): ob0 = props.generator for m in ob0.data.materials: try: o = bpy.data.objects[m.name] allow_multi = True try: if o.data.shape_keys is None: continue elif len(o.data.shape_keys.key_blocks) < 2: continue else: allow_shapekeys = not props.com_modifiers except: pass except: pass col = layout.column(align=True) #col.label(text="Morphing:") row = col.row(align=True) col2 = row.column(align=True) col2.prop(props, "bool_vertex_group", icon='GROUP_VERTEX') #col2.prop_search(props, "vertex_group", props.generator, "vertex_groups") try: if len(props.generator.vertex_groups) == 0: col2.enabled = False except: col2.enabled = False row.separator() col2 = row.column(align=True) row2 = col2.row(align=True) row2.prop(props, "bool_shapekeys", text="Use Shape Keys", icon='SHAPEKEY_DATA') row2.enabled = allow_shapekeys if not allow_shapekeys: col2 = layout.column(align=True) row2 = col2.row(align=True) row2.label(text="Component's Shape Keys cannot be used together with Component's Modifiers", icon='INFO') class TISSUE_PT_tessellate_selective(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "data" bl_parent_id = "TISSUE_PT_tessellate_object" bl_label = "Selective" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): try: return context.object.tissue.tissue_type == 'TESSELLATE' except: return False def draw(self, context): ob = context.object props = ob.tissue_tessellate layout = self.layout #layout.use_property_split = True #layout.use_property_decorate = False # No animation. allow_multi = False allow_shapekeys = not props.com_modifiers ob0 = props.generator for m in ob0.data.materials: try: o = bpy.data.objects[m.name] allow_multi = True try: if o.data.shape_keys is None: continue elif len(o.data.shape_keys.key_blocks) < 2: continue else: allow_shapekeys = not props.com_modifiers except: pass except: pass # LIMITED TESSELLATION col = layout.column(align=True) #col.label(text="Limited Tessellation:") row = col.row(align=True) col2 = row.column(align=True) col2.prop(props, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF') row.separator() if props.generator.type != 'MESH': col2.enabled = False col2 = row.column(align=True) col2.prop(props, "bool_material_id", icon='MATERIAL_DATA', text="Material Index") #if props.bool_material_id and not props.component_mode == 'MATERIALS': #col2 = row.column(align=True) col2.prop(props, "material_id") #if props.component_mode == 'MATERIALS': # col2.enabled = False #col.separator() #row = col.row(align=True) #col2 = row.column(align=True) #col2.prop(props, "bool_multi_components", icon='MOD_TINT') #if not allow_multi: # col2.enabled = False class TISSUE_PT_tessellate_iterations(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "data" bl_parent_id = "TISSUE_PT_tessellate_object" bl_label = "Iterations" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): try: return context.object.tissue.tissue_type == 'TESSELLATE' except: return False def draw(self, context): ob = context.object props = ob.tissue_tessellate layout = self.layout layout.use_property_split = True layout.use_property_decorate = False # No animation. col = layout.column(align=True) row = col.row(align=True) #row.label(text='', icon='FILE_REFRESH') col.prop(props, 'iterations', text='Repeat')#, icon='FILE_REFRESH') if props.iterations > 1 and props.fill_mode == 'PATCH': col.separator() #row = col.row(align=True) col.prop(props, 'patch_subs') layout.use_property_split = False col = layout.column(align=True) #row = col.row(align=True) col.label(text='Combine Iterations:') row = col.row(align=True) row.prop( props, "combine_mode", text="Combine:",icon='NONE', expand=True, slider=False, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) class tissue_rotate_face_right(Operator): bl_idname = "mesh.tissue_rotate_face_right" bl_label = "Tissue Rotate Faces Right" bl_description = "Rotate clockwise selected faces and update tessellated meshes" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): try: #bool_tessellated = context.object.tissue_tessellate.generator != None ob = context.object return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated except: return False def execute(self, context): ob = context.active_object me = ob.data bm = bmesh.from_edit_mesh(me) mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode] for face in bm.faces: if (face.select): vs = face.verts[:] vs2 = vs[-1:]+vs[:-1] material_index = face.material_index bm.faces.remove(face) f2 = bm.faces.new(vs2) f2.select = True f2.material_index = material_index bm.normal_update() # trigger UI update bmesh.update_edit_mesh(me) bm.free() ob.select_set(False) # update tessellated meshes bpy.ops.object.mode_set(mode='OBJECT') for o in [obj for obj in bpy.data.objects if obj.tissue_tessellate.generator == ob and obj.visible_get()]: context.view_layer.objects.active = o #override = {'object': o, 'mode': 'OBJECT', 'selected_objects': [o]} if not o.tissue.bool_lock: bpy.ops.object.tissue_update_tessellate() o.select_set(False) ob.select_set(True) context.view_layer.objects.active = ob bpy.ops.object.mode_set(mode='EDIT') context.tool_settings.mesh_select_mode = mesh_select_mode return {'FINISHED'} class tissue_rotate_face_flip(Operator): bl_idname = "mesh.tissue_rotate_face_flip" bl_label = "Tissue Rotate Faces Flip" bl_description = "Fully rotate selected faces and update tessellated meshes" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): try: #bool_tessellated = context.object.tissue_tessellate.generator != None ob = context.object return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated except: return False def execute(self, context): ob = context.active_object me = ob.data bm = bmesh.from_edit_mesh(me) mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode] for face in bm.faces: if (face.select): vs = face.verts[:] nrot = int(len(vs)/2) vs2 = vs[-nrot:]+vs[:-nrot] material_index = face.material_index bm.faces.remove(face) f2 = bm.faces.new(vs2) f2.select = True f2.material_index = material_index bm.normal_update() # trigger UI update bmesh.update_edit_mesh(me) bm.free() ob.select_set(False) # update tessellated meshes bpy.ops.object.mode_set(mode='OBJECT') for o in [obj for obj in bpy.data.objects if obj.tissue_tessellate.generator == ob and obj.visible_get()]: context.view_layer.objects.active = o #override = {'object': o, 'mode': 'OBJECT', 'selected_objects': [o]} if not o.tissue.bool_lock: bpy.ops.object.tissue_update_tessellate() o.select_set(False) ob.select_set(True) context.view_layer.objects.active = ob bpy.ops.object.mode_set(mode='EDIT') context.tool_settings.mesh_select_mode = mesh_select_mode return {'FINISHED'} class tissue_rotate_face_left(Operator): bl_idname = "mesh.tissue_rotate_face_left" bl_label = "Tissue Rotate Faces Left" bl_description = "Rotate counterclockwise selected faces and update tessellated meshes" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): try: #bool_tessellated = context.object.tissue_tessellate.generator != None ob = context.object return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated except: return False def execute(self, context): ob = context.active_object me = ob.data bm = bmesh.from_edit_mesh(me) mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode] for face in bm.faces: if (face.select): vs = face.verts[:] vs2 = vs[1:]+vs[:1] material_index = face.material_index bm.faces.remove(face) f2 = bm.faces.new(vs2) f2.select = True f2.material_index = material_index bm.normal_update() # trigger UI update bmesh.update_edit_mesh(me) bm.free() ob.select_set(False) # update tessellated meshes bpy.ops.object.mode_set(mode='OBJECT') for o in [obj for obj in bpy.data.objects if obj.tissue_tessellate.generator == ob and obj.visible_get()]: context.view_layer.objects.active = o if not o.tissue.bool_lock: bpy.ops.object.tissue_update_tessellate() o.select_set(False) ob.select_set(True) context.view_layer.objects.active = ob bpy.ops.object.mode_set(mode='EDIT') context.tool_settings.mesh_select_mode = mesh_select_mode return {'FINISHED'} def convert_to_frame(ob, props, use_modifiers): new_ob = convert_object_to_mesh(ob, use_modifiers, True) # create bmesh bm = bmesh.new() bm.from_mesh(new_ob.data) bm.verts.ensure_lookup_table() bm.edges.ensure_lookup_table() bm.faces.ensure_lookup_table() if props['bool_selection']: original_faces = [f for f in bm.faces if f.select] else: original_faces = list(bm.faces) # detect edge loops loops = [] boundaries_mat = [] neigh_face_center = [] face_normals = [] # append boundary loops if props['frame_boundary']: #selected_edges = [e for e in bm.edges if e.select] selected_edges = [e for e in bm.edges if e.is_boundary] if len(selected_edges) > 0: loop = [] count = 0 e0 = selected_edges[0] face = e0.link_faces[0] boundary_mat = [face.material_index + props['boundary_mat_offset']] face_center = [face.calc_center_median()] loop_normals = [face.normal] selected_edges = selected_edges[1:] if props['bool_vertex_group']: n_verts = len(new_ob.data.vertices) base_vg = [get_weight(vg,n_verts) for vg in new_ob.vertex_groups] while True: new_vert = None face = None for e1 in selected_edges: if e1.verts[0] in e0.verts: new_vert = e1.verts[1] elif e1.verts[1] in e0.verts: new_vert = e1.verts[0] if new_vert != None: if len(loop)==0: loop = [v for v in e1.verts if v != new_vert] loop.append(new_vert) e0 = e1 face = e0.link_faces[0] boundary_mat.append(face.material_index + props['boundary_mat_offset']) face_center.append(face.calc_center_median()) loop_normals.append(face.normal) selected_edges.remove(e0) break if new_vert == None: try: loops.append(loop) loop = [] e0 = selected_edges[0] selected_edges = selected_edges[1:] boundaries_mat.append(boundary_mat) neigh_face_center.append(face_center) face_normals.append(loop_normals) face = e0.link_faces[0] boundary_mat = [face.material_index + props['boundary_mat_offset']] face_center = [face.calc_center_median()] loop_normals = [face.normal] except: break boundaries_mat.append(boundary_mat) neigh_face_center.append(face_center) face_normals.append(loop_normals) # compute boundary frames new_faces = [] vert_ids = [] # append regular faces for f in original_faces:#bm.faces: loop = list(f.verts) loops.append(loop) boundaries_mat.append([f.material_index for v in loop]) face_normals.append([f.normal for v in loop]) # calc areas for relative frame mode if props['frame_mode'] == 'RELATIVE': verts_area = [] for v in bm.verts: linked_faces = v.link_faces if len(linked_faces) > 0: area = sum([sqrt(f.calc_area())/len(f.verts) for f in v.link_faces])*2 area /= len(linked_faces) else: area = 0 verts_area.append(area) for loop_index, loop in enumerate(loops): is_boundary = loop_index < len(neigh_face_center) materials = boundaries_mat[loop_index] new_loop = [] loop_ext = [loop[-1]] + loop + [loop[0]] # calc tangents tangents = [] for i in range(len(loop)): # vertices vert0 = loop_ext[i] vert = loop_ext[i+1] vert1 = loop_ext[i+2] # edge vectors vec0 = (vert0.co - vert.co).normalized() vec1 = (vert.co - vert1.co).normalized() # tangent _vec1 = -vec1 _vec0 = -vec0 ang = (pi - vec0.angle(vec1))/2 normal = face_normals[loop_index][i] tan0 = normal.cross(vec0) tan1 = normal.cross(vec1) tangent = (tan0 + tan1).normalized()/sin(ang)*props['frame_thickness'] tangents.append(tangent) # calc correct direction for boundaries mult = -1 if is_boundary: dir_val = 0 for i in range(len(loop)): surf_point = neigh_face_center[loop_index][i] tangent = tangents[i] vert = loop_ext[i+1] dir_val += tangent.dot(vert.co - surf_point) if dir_val > 0: mult = 1 # add vertices for i in range(len(loop)): vert = loop_ext[i+1] if props['frame_mode'] == 'RELATIVE': area = verts_area[vert.index] else: area = 1 new_co = vert.co + tangents[i] * mult * area # add vertex new_vert = bm.verts.new(new_co) new_loop.append(new_vert) vert_ids.append(vert.index) new_loop.append(new_loop[0]) # add faces materials += [materials[0]] for i in range(len(loop)): v0 = loop_ext[i+1] v1 = loop_ext[i+2] v2 = new_loop[i+1] v3 = new_loop[i] face_verts = [v1,v0,v3,v2] if mult == -1: face_verts = [v0,v1,v2,v3] new_face = bm.faces.new(face_verts) new_face.material_index = materials[i+1] new_face.select = True new_faces.append(new_face) # fill frame if props['fill_frame'] and not is_boundary: n_verts = len(new_loop)-1 loop_center = Vector((0,0,0)) for v in new_loop[1:]: loop_center += v.co loop_center /= n_verts center = bm.verts.new(loop_center) for i in range(n_verts): v0 = new_loop[i+1] v1 = new_loop[i] face_verts = [v1,v0,center] new_face = bm.faces.new(face_verts) new_face.material_index = materials[i] + props['fill_frame_mat'] new_face.select = True new_faces.append(new_face) #bpy.ops.object.mode_set(mode='OBJECT') #for f in bm.faces: f.select_set(f not in new_faces) for f in original_faces: bm.faces.remove(f) bm.to_mesh(new_ob.data) # propagate vertex groups if props['bool_vertex_group']: base_vg = [] for vg in new_ob.vertex_groups: vertex_group = [] for v in bm.verts: try: vertex_group.append(vg.weight(v.index)) except: vertex_group.append(0) base_vg.append(vertex_group) new_vert_ids = range(len(bm.verts)-len(vert_ids),len(bm.verts)) for vg_id, vg in enumerate(new_ob.vertex_groups): for ii, jj in zip(vert_ids, new_vert_ids): vg.add([jj], base_vg[vg_id][ii], 'REPLACE') new_ob.data.update() bm.free() return new_ob def reduce_to_quads(ob, props): ''' Convert an input object to a mesh with polygons that have maximum 4 vertices ''' new_ob = convert_object_to_mesh(ob, props['gen_modifiers'], True) me = new_ob.data # Check if there are polygons with more than 4 sides np_sides = get_attribute_numpy(me.polygons, 'loop_total') mask = np_sides > 4 if not np.any(mask): if props['boundary_mat_offset'] != 0 or props['boundary_variable_offset']: bm=bmesh.new() bm.from_mesh(me) bm = offset_boundary_materials( bm, boundary_mat_offset = props['boundary_mat_offset'], boundary_variable_offset = props['boundary_variable_offset'], auto_rotate_boundary = props['auto_rotate_boundary']) bm.to_mesh(me) bm.free() me.update() return new_ob # create bmesh bm = bmesh.new() bm.from_mesh(me) bm.verts.ensure_lookup_table() bm.edges.ensure_lookup_table() bm.faces.ensure_lookup_table() np_faces = np.array(bm.faces) np_faces = np_faces[mask] new_faces = [] for f in np_faces: verts = list(f.verts) while True: n_verts = len(verts) if n_verts < 3: break elif n_verts == 3: face_verts = [verts[-2], verts.pop(-1), verts.pop(0)] else: face_verts = [verts[-2], verts.pop(-1), verts.pop(0), verts[0]] new_face = bm.faces.new(face_verts) new_face.material_index = f.material_index new_face.select = f.select new_faces.append(new_face) for f in np_faces: bm.faces.remove(f) bm = offset_boundary_materials( bm, boundary_mat_offset = props['boundary_mat_offset'], boundary_variable_offset = props['boundary_variable_offset'], auto_rotate_boundary = props['auto_rotate_boundary']) bm.to_mesh(me) bm.free() me.update() return new_ob def convert_to_fan(ob, props, add_id_layer=False): new_ob = convert_object_to_mesh(ob, props['gen_modifiers'], True) bm = bmesh.new() bm.from_mesh(new_ob.data) if add_id_layer: bm.faces.ensure_lookup_table() lay = bm.faces.layers.int.new("id") for i,f in enumerate(bm.faces): f[lay] = i bmesh.ops.poke(bm, faces=bm.faces)#, quad_method, ngon_method) bm = offset_boundary_materials( bm, boundary_mat_offset = props['boundary_mat_offset'], boundary_variable_offset = props['boundary_variable_offset'], auto_rotate_boundary = props['auto_rotate_boundary']) bm.to_mesh(new_ob.data) new_ob.data.update() bm.free() return new_ob def convert_to_triangles(ob, props): new_ob = convert_object_to_mesh(ob, props['gen_modifiers'], True) bm = bmesh.new() bm.from_mesh(new_ob.data) bmesh.ops.triangulate(bm, faces=bm.faces, quad_method='FIXED', ngon_method='BEAUTY') bm = offset_boundary_materials( bm, boundary_mat_offset = props['boundary_mat_offset'], boundary_variable_offset = props['boundary_variable_offset'], auto_rotate_boundary = props['auto_rotate_boundary']) bm.to_mesh(new_ob.data) new_ob.data.update() bm.free() return new_ob def merge_components(ob, props, use_bmesh): if not use_bmesh and False: skip = True ob.active_shape_key_index = 1 if ob.data.shape_keys != None: for sk in ob.data.shape_keys.key_blocks: if skip: skip = False continue sk.mute = True ob.data.update() bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='OBJECT') if ob.data.shape_keys != None: for sk in ob.data.shape_keys.key_blocks: sk.mute = False ob.data.update() bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_mode( use_extend=False, use_expand=False, type='VERT') bpy.ops.mesh.select_non_manifold( extend=False, use_wire=True, use_boundary=True, use_multi_face=False, use_non_contiguous=False, use_verts=False) bpy.ops.mesh.remove_doubles( threshold=props.merge_thres, use_unselected=False) if props.bool_dissolve_seams: bpy.ops.mesh.select_mode(type='EDGE') bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.object.mode_set(mode='OBJECT') for e in new_ob.data.edges: e.select = e.use_seam bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.dissolve_edges() bpy.ops.object.mode_set(mode='OBJECT') if props.close_mesh != 'NONE': bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_mode( use_extend=False, use_expand=False, type='EDGE') bpy.ops.mesh.select_non_manifold( extend=False, use_wire=False, use_boundary=True, use_multi_face=False, use_non_contiguous=False, use_verts=False) if props.close_mesh == 'CAP': if props.open_edges_crease != 0: bpy.ops.transform.edge_crease(value=props.open_edges_crease) bpy.ops.mesh.edge_face_add() bpy.ops.object.mode_set(mode='OBJECT') for f in ob.data.polygons: if f.select: f.material_index += props.cap_material_offset elif props.close_mesh == 'BRIDGE': try: if props.bridge_edges_crease != 0: bpy.ops.transform.edge_crease(value=props.bridge_edges_crease) bpy.ops.mesh.bridge_edge_loops( type='PAIRS', number_cuts=props.bridge_cuts, interpolation='SURFACE', smoothness=props.bridge_smoothness) bpy.ops.object.mode_set(mode='OBJECT') for f in ob.data.polygons: if f.select: f.material_index += props.bridge_material_offset except: pass elif props.close_mesh == 'BRIDGE_CAP': # BRIDGE try: bpy.ops.object.mode_set(mode='OBJECT') vg = ob.vertex_groups[props.vertex_group_bridge] weight = get_weight_numpy(vg, len(ob.data.vertices)) for e in ob.data.edges: if weight[e.vertices[0]]*weight[e.vertices[1]] < 1: e.select = False bpy.ops.object.mode_set(mode='EDIT') if props.bridge_edges_crease != 0: bpy.ops.transform.edge_crease(value=props.bridge_edges_crease) bpy.ops.mesh.bridge_edge_loops( type='PAIRS', number_cuts=props.bridge_cuts, interpolation='SURFACE', smoothness=props.bridge_smoothness) for f in ob.data.polygons: if f.select: f.material_index += props.bridge_material_offset bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.mesh.select_non_manifold( extend=False, use_wire=False, use_boundary=True, use_multi_face=False, use_non_contiguous=False, use_verts=False) bpy.ops.object.mode_set(mode='OBJECT') except: pass # CAP try: bpy.ops.object.mode_set(mode='OBJECT') vg = ob.vertex_groups[props.vertex_group_cap] weight = get_weight_numpy(vg, len(ob.data.vertices)) for e in ob.data.edges: if weight[e.vertices[0]]*weight[e.vertices[1]] < 1: e.select = False bpy.ops.object.mode_set(mode='EDIT') if props.open_edges_crease != 0: bpy.ops.transform.edge_crease(value=props.open_edges_crease) bpy.ops.mesh.edge_face_add() for f in ob.data.polygons: if f.select: f.material_index += props.cap_material_offset bpy.ops.object.mode_set(mode='OBJECT') except: pass else: bm = bmesh.new() bm.from_mesh(ob.data.copy()) if props.merge_open_edges_only: boundary_verts = [v for v in bm.verts if v.is_boundary or v.is_wire] else: boundary_verts = bm.verts bmesh.ops.remove_doubles(bm, verts=boundary_verts, dist=props.merge_thres) if props.bool_dissolve_seams: seam_edges = [e for e in bm.edges if e.seam] bmesh.ops.dissolve_edges(bm, edges=seam_edges, use_verts=True, use_face_split=False) if props.close_mesh != 'NONE': bm.edges.ensure_lookup_table() # set crease crease_layer = bm.edges.layers.crease.verify() boundary_edges = [e for e in bm.edges if e.is_boundary or e.is_wire] if props.close_mesh == 'BRIDGE': try: for e in boundary_edges: e[crease_layer] = props.bridge_edges_crease closed = bmesh.ops.bridge_loops(bm, edges=boundary_edges, use_pairs=True) for f in closed['faces']: f.material_index += props.bridge_material_offset except: bm.to_mesh(ob.data) return 'bridge_error' elif props.close_mesh == 'CAP': for e in boundary_edges: e[crease_layer] = props.open_edges_crease closed = bmesh.ops.holes_fill(bm, edges=boundary_edges) for f in closed['faces']: f.material_index += props.cap_material_offset elif props.close_mesh == 'BRIDGE_CAP': # BRIDGE dvert_lay = bm.verts.layers.deform.active try: dvert_lay = bm.verts.layers.deform.active group_index = ob.vertex_groups[props.vertex_group_bridge].index bw = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts) if props.invert_vertex_group_bridge: bw = 1-bw bridge_edges = [e for e in boundary_edges if bw[e.verts[0].index]*bw[e.verts[1].index] >= 1] for e in bridge_edges: e[crease_layer] = props.bridge_edges_crease closed = bmesh.ops.bridge_loops(bm, edges=bridge_edges, use_pairs=True) for f in closed['faces']: f.material_index += props.bridge_material_offset boundary_edges = [e for e in bm.edges if e.is_boundary] except: pass # CAP try: dvert_lay = bm.verts.layers.deform.active group_index = ob.vertex_groups[props.vertex_group_cap].index bw = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts) if props.invert_vertex_group_cap: bw = 1-bw cap_edges = [e for e in boundary_edges if bw[e.verts[0].index]*bw[e.verts[1].index] >= 1] for e in cap_edges: e[crease_layer] = props.open_edges_crease closed = bmesh.ops.holes_fill(bm, edges=cap_edges) for f in closed['faces']: f.material_index += props.cap_material_offset except: pass bm.to_mesh(ob.data) class tissue_render_animation(Operator): bl_idname = "render.tissue_render_animation" bl_label = "Tissue Render Animation" bl_description = "Turnaround for issues related to animatable tessellation" bl_options = {'REGISTER', 'UNDO'} start = True path = "" timer = None def invoke(self, context, event): self.start = True return context.window_manager.invoke_props_dialog(self) def draw(self, context): layout = self.layout col = layout.column(align=True) col.label(text="All frames will be rendered in the background.") col.label(text="Press ESC to abort.") def modal(self, context, event): ''' # check render format format = context.scene.render.image_settings.file_format if format in ('FFMPEG', 'AVI_RAW', 'AVI_JPEG'): message = "Please use an image format as render output" self.report({'ERROR'}, message) return {'CANCELLED'} ''' remove_tessellate_handler() scene = context.scene if event.type == 'ESC' or scene.frame_current >= scene.frame_end: scene.render.filepath = self.path # set again the handler blender_handlers = bpy.app.handlers.frame_change_post blender_handlers.append(anim_tessellate) blender_handlers.append(reaction_diffusion_scene) context.window_manager.event_timer_remove(self.timer) if event.type == 'ESC': print("Tissue: Render Animation aborted.") return {'CANCELLED'} else: print("Tissue: Render Animation completed!") return {'FINISHED'} else: self.execute(context) return {'RUNNING_MODAL'} def execute(self, context): # check output format format = context.scene.render.image_settings.file_format if format in ('FFMPEG', 'AVI_RAW', 'AVI_JPEG'): message = "Please use an image format as render output" self.report({'ERROR'}, message) return {'CANCELLED'} scene = context.scene if self.start: remove_tessellate_handler() reaction_diffusion_remove_handler(self, context) scene = context.scene scene.frame_current = scene.frame_start self.path = scene.render.filepath context.window_manager.modal_handler_add(self) self.timer = context.window_manager.event_timer_add(0.1, window = context.window) self.start = False else: scene.frame_current += scene.frame_step anim_tessellate(scene) reaction_diffusion_scene(scene) scene.render.filepath = "{}{:04d}".format(self.path,scene.frame_current) bpy.ops.render.render(write_still=True) return {'RUNNING_MODAL'} def offset_boundary_materials(bm, boundary_mat_offset=0, boundary_variable_offset=False, auto_rotate_boundary=False): if boundary_mat_offset != 0 or boundary_variable_offset: bm.edges.ensure_lookup_table() bm.faces.ensure_lookup_table() bound_faces = [] bound_verts_value = [0]*len(bm.faces) bound_edges_value = [0]*len(bm.faces) shift_faces = [0]*len(bm.faces) # store boundaries information for v in bm.verts: if v.is_boundary: for f in v.link_faces: bound_faces.append(f) bound_verts_value[f.index] += 1 for e in bm.edges: if e.is_boundary: for f in e.link_faces: bound_edges_value[f.index] += 1 # Set material index offset if boundary_variable_offset: for f in bm.faces: if bound_verts_value[f.index] > 0: f.material_index += boundary_mat_offset if bound_verts_value[f.index] == bound_edges_value[f.index]+1: f.material_index += bound_verts_value[f.index] else: for f in bm.faces: if bound_edges_value[f.index] > 0: f.material_index += boundary_mat_offset if auto_rotate_boundary: rotate_faces = [] new_verts_all = [] for f in bm.faces: val = bound_verts_value[f.index] val2 = bound_edges_value[f.index] if val > 0 and val2 == val-1 and val < len(f.verts): pattern = [v.is_boundary for v in f.verts] new_verts = [v for v in f.verts] while True: mult = 1 _pattern = pattern[val//2+1:] + pattern[:val//2+1] for p in _pattern[-val:]: mult*=p if mult == 1: break pattern = pattern[-1:] + pattern[:-1] new_verts = new_verts[-1:] + new_verts[:-1] new_verts_all.append(new_verts) rotate_faces.append(f) if val == 4 and val2 == 3: pattern = [e.is_boundary for e in f.edges] new_verts = [v for v in f.verts] while True: mult = 1 _pattern = pattern[val2//2+1:] + pattern[:val2//2+1] for p in _pattern[-val2:]: mult*=p if mult == 1: break pattern = pattern[-1:] + pattern[:-1] new_verts = new_verts[-1:] + new_verts[:-1] new_verts_all.append(new_verts) rotate_faces.append(f) for f, new_verts in zip(rotate_faces, new_verts_all): material_index = f.material_index bm.faces.remove(f) f2 = bm.faces.new(new_verts) f2.select = True f2.material_index = material_index bm.normal_update() return bm