expre-gen/blender/3.4/scripts/addons/mesh_tissue/tessellate_numpy.py

3787 lines
155 KiB
Python
Raw Permalink Normal View History

2025-10-17 09:47:23 +08:00
# 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