3787 lines
155 KiB
Python
3787 lines
155 KiB
Python
# 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
|