Campbell Barton
e8da6131fd
Move copyright text to SPDX-FileCopyrightText or set to the Blender Foundation so "make check_licenses" now runs without warnings.
1459 lines
52 KiB
Python
1459 lines
52 KiB
Python
# SPDX-FileCopyrightText: 2019-2023 Blender Foundation
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
import bpy, bmesh
|
|
import threading
|
|
import numpy as np
|
|
import multiprocessing
|
|
from multiprocessing import Process, Pool
|
|
from mathutils import Vector, Matrix
|
|
from math import *
|
|
try: from .numba_functions import *
|
|
except: pass
|
|
|
|
from . import config
|
|
|
|
def use_numba_tess():
|
|
tissue_addon = bpy.context.preferences.addons[__package__]
|
|
if 'use_numba_tess' in tissue_addon.preferences.keys():
|
|
return tissue_addon.preferences['use_numba_tess']
|
|
else:
|
|
return True
|
|
|
|
def tissue_time(start_time, name, levels=0):
|
|
tissue_addon = bpy.context.preferences.addons[__package__]
|
|
end_time = time.time()
|
|
if 'print_stats' in tissue_addon.preferences.keys():
|
|
ps = tissue_addon.preferences['print_stats']
|
|
else:
|
|
ps = 1
|
|
if levels < ps:
|
|
if "Tissue: " in name: head = ""
|
|
else: head = " "
|
|
if start_time:
|
|
print('{}{}{} in {:.4f} sec'.format(head, "| "*levels, name, end_time - start_time))
|
|
else:
|
|
print('{}{}{}'.format(head, "| "*levels, name))
|
|
return end_time
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# MATH
|
|
# ------------------------------------------------------------------
|
|
|
|
def _np_broadcast(arrays):
|
|
shapes = [arr.shape for arr in arrays]
|
|
for i in range(len(shapes[0])):
|
|
ish = [sh[i] for sh in shapes]
|
|
max_len = max(ish)
|
|
for j in range(len(arrays)):
|
|
leng = ish[j]
|
|
if leng == 1: arrays[j] = np.repeat(arrays[j], max_len, axis=i)
|
|
for arr in arrays:
|
|
arr = arr.flatten()
|
|
#vt = v0 + (v1 - v0) * t
|
|
return arrays
|
|
|
|
def lerp(a, b, t):
|
|
return a + (b - a) * t
|
|
|
|
def _lerp2(v1, v2, v3, v4, v):
|
|
v12 = v1.lerp(v2,v.x) # + (v2 - v1) * v.x
|
|
v34 = v3.lerp(v4,v.x) # + (v4 - v3) * v.x
|
|
return v12.lerp(v34, v.y)# + (v34 - v12) * v.y
|
|
|
|
def lerp2(v1, v2, v3, v4, v):
|
|
v12 = v1 + (v2 - v1) * v.x
|
|
v34 = v3 + (v4 - v3) * v.x
|
|
v = v12 + (v34 - v12) * v.y
|
|
return v
|
|
|
|
def lerp3(v1, v2, v3, v4, v):
|
|
loc = lerp2(v1.co, v2.co, v3.co, v4.co, v)
|
|
nor = lerp2(v1.normal, v2.normal, v3.normal, v4.normal, v)
|
|
nor.normalize()
|
|
return loc + nor * v.z
|
|
|
|
import sys
|
|
def np_lerp2(v00, v10, v01, v11, vx, vy, mode=''):
|
|
if 'numba' in sys.modules and use_numba_tess():
|
|
if mode == 'verts':
|
|
co2 = numba_interp_points(v00, v10, v01, v11, vx, vy)
|
|
elif mode == 'shapekeys':
|
|
co2 = numba_interp_points_sk(v00, v10, v01, v11, vx, vy)
|
|
else:
|
|
co2 = numba_lerp2(v00, v10, v01, v11, vx, vy)
|
|
else:
|
|
co0 = v00 + (v10 - v00) * vx
|
|
co1 = v01 + (v11 - v01) * vx
|
|
co2 = co0 + (co1 - co0) * vy
|
|
return co2
|
|
|
|
def calc_thickness(co2,n2,vz,a,weight):
|
|
if 'numba' in sys.modules and use_numba_tess():
|
|
if len(co2.shape) == 3:
|
|
if type(a) != np.ndarray:
|
|
a = np.ones(len(co2)).reshape((-1,1,1))
|
|
if type(weight) != np.ndarray:
|
|
weight = np.ones(len(co2)).reshape((-1,1,1))
|
|
co3 = numba_calc_thickness_area_weight(co2,n2,vz,a,weight)
|
|
elif len(co2.shape) == 4:
|
|
n_patches = co2.shape[0]
|
|
n_sk = co2.shape[1]
|
|
n_verts = co2.shape[2]
|
|
if type(a) != np.ndarray:
|
|
a = np.ones(n_patches).reshape((n_patches,1,1,1))
|
|
if type(weight) != np.ndarray:
|
|
weight = np.ones(n_patches).reshape((n_patches,1,1,1))
|
|
na = a.shape[1]-1
|
|
nw = weight.shape[1]-1
|
|
co3 = np.empty((n_sk,n_patches,n_verts,3))
|
|
for i in range(n_sk):
|
|
co3[i] = numba_calc_thickness_area_weight(co2[:,i],n2[:,i],vz[:,i],a[:,min(i,na)],weight[:,min(i,nw)])
|
|
co3 = co3.swapaxes(0,1)
|
|
else:
|
|
use_area = type(a) == np.ndarray
|
|
use_weight = type(weight) == np.ndarray
|
|
if use_area:
|
|
if use_weight:
|
|
co3 = co2 + n2 * vz * a * weight
|
|
else:
|
|
co3 = co2 + n2 * vz * a
|
|
else:
|
|
if use_weight:
|
|
co3 = co2 + n2 * vz * weight
|
|
else:
|
|
co3 = co2 + n2 * vz
|
|
return co3
|
|
|
|
def combine_and_flatten(arrays):
|
|
if 'numba' in sys.modules:
|
|
new_list = numba_combine_and_flatten(arrays)
|
|
else:
|
|
new_list = np.concatenate(arrays, axis=0)
|
|
new_list = new_list.flatten().tolist()
|
|
return new_list
|
|
|
|
def np_interp2(grid, vx, vy):
|
|
grid_shape = grid.shape[-2:]
|
|
levels = len(grid.shape)-2
|
|
nu = grid_shape[0]
|
|
nv = grid_shape[1]
|
|
u = np.arange(nu)/(nu-1)
|
|
v = np.arange(nv)/(nv-1)
|
|
u_shape = [1]*levels + [nu]
|
|
v_shape = [1]*levels + [nv]
|
|
|
|
co0 = np.interp()
|
|
co1 = np.interp()
|
|
co2 = np.interp()
|
|
return co2
|
|
|
|
def flatten_vector(vec, x, y):
|
|
"""
|
|
Find planar vector according to two axis.
|
|
:arg vec: Input vector.
|
|
:type vec: :class:'mathutils.Vector'
|
|
:arg x: First axis.
|
|
:type x: :class:'mathutils.Vector'
|
|
:arg y: Second axis.
|
|
:type y: :class:'mathutils.Vector'
|
|
:return: Projected 2D Vector.
|
|
:rtype: :class:'mathutils.Vector'
|
|
"""
|
|
vx = vec.project(x)
|
|
vy = vec.project(y)
|
|
mult = 1 if vx.dot(x) > 0 else -1
|
|
vx = mult*vx.length
|
|
mult = 1 if vy.dot(y) > 0 else -1
|
|
vy = mult*vy.length
|
|
return Vector((vx, vy))
|
|
|
|
def vector_rotation(vec):
|
|
"""
|
|
Find vector rotation according to X axis.
|
|
:arg vec: Input vector.
|
|
:type vec: :class:'mathutils.Vector'
|
|
:return: Angle in radians.
|
|
:rtype: float
|
|
"""
|
|
v0 = Vector((1,0))
|
|
ang = Vector.angle_signed(vec, v0)
|
|
if ang < 0: ang = 2*pi + ang
|
|
return ang
|
|
|
|
# ------------------------------------------------------------------
|
|
# SCENE
|
|
# ------------------------------------------------------------------
|
|
|
|
def set_animatable_fix_handler(self, context):
|
|
'''
|
|
Prevent Blender Crashes with handlers
|
|
'''
|
|
old_handlers = []
|
|
blender_handlers = bpy.app.handlers.render_init
|
|
for h in blender_handlers:
|
|
if "turn_off_animatable" in str(h):
|
|
old_handlers.append(h)
|
|
for h in old_handlers: blender_handlers.remove(h)
|
|
blender_handlers.append(turn_off_animatable)
|
|
return
|
|
|
|
def turn_off_animatable(scene):
|
|
'''
|
|
Prevent Blender Crashes with handlers
|
|
'''
|
|
for o in [o for o in bpy.data.objects if o.type == 'MESH']:
|
|
o.tissue_tessellate.bool_run = False
|
|
#if not o.reaction_diffusion_settings.bool_cache:
|
|
# o.reaction_diffusion_settings.run = False
|
|
#except: pass
|
|
return
|
|
|
|
# ------------------------------------------------------------------
|
|
# OBJECTS
|
|
# ------------------------------------------------------------------
|
|
|
|
def convert_object_to_mesh(ob, apply_modifiers=True, preserve_status=True):
|
|
try: ob.name
|
|
except: return None
|
|
if ob.type != 'MESH':
|
|
if not apply_modifiers:
|
|
mod_visibility = [m.show_viewport for m in ob.modifiers]
|
|
for m in ob.modifiers: m.show_viewport = False
|
|
#ob.modifiers.update()
|
|
#dg = bpy.context.evaluated_depsgraph_get()
|
|
#ob_eval = ob.evaluated_get(dg)
|
|
#me = bpy.data.meshes.new_from_object(ob_eval, preserve_all_data_layers=True, depsgraph=dg)
|
|
me = simple_to_mesh(ob)
|
|
new_ob = bpy.data.objects.new(ob.data.name, me)
|
|
new_ob.location, new_ob.matrix_world = ob.location, ob.matrix_world
|
|
if not apply_modifiers:
|
|
for m,vis in zip(ob.modifiers,mod_visibility): m.show_viewport = vis
|
|
else:
|
|
if apply_modifiers:
|
|
new_ob = ob.copy()
|
|
new_me = simple_to_mesh(ob)
|
|
new_ob.modifiers.clear()
|
|
new_ob.data = new_me
|
|
else:
|
|
new_ob = ob.copy()
|
|
new_ob.data = ob.data.copy()
|
|
new_ob.modifiers.clear()
|
|
bpy.context.collection.objects.link(new_ob)
|
|
if preserve_status:
|
|
new_ob.select_set(False)
|
|
else:
|
|
for o in bpy.context.view_layer.objects: o.select_set(False)
|
|
new_ob.select_set(True)
|
|
bpy.context.view_layer.objects.active = new_ob
|
|
return new_ob
|
|
|
|
def simple_to_mesh(ob, depsgraph=None):
|
|
'''
|
|
Convert object to mesh applying Modifiers and Shape Keys
|
|
'''
|
|
#global evaluatedDepsgraph
|
|
if depsgraph == None:
|
|
if config.evaluatedDepsgraph == None:
|
|
dg = bpy.context.evaluated_depsgraph_get()
|
|
else: dg = config.evaluatedDepsgraph
|
|
else:
|
|
dg = depsgraph
|
|
ob_eval = ob.evaluated_get(dg)
|
|
me = bpy.data.meshes.new_from_object(ob_eval, preserve_all_data_layers=True, depsgraph=dg)
|
|
return me
|
|
|
|
def _join_objects(context, objects, link_to_scene=True, make_active=True):
|
|
C = context
|
|
bm = bmesh.new()
|
|
|
|
materials = {}
|
|
faces_materials = []
|
|
if config.evaluatedDepsgraph == None:
|
|
dg = C.evaluated_depsgraph_get()
|
|
else: dg = config.evaluatedDepsgraph
|
|
|
|
for o in objects:
|
|
bm.from_object(o, dg)
|
|
# add object's material to the dictionary
|
|
for m in o.data.materials:
|
|
if m not in materials: materials[m] = len(materials)
|
|
for f in o.data.polygons:
|
|
index = f.material_index
|
|
mat = o.material_slots[index].material
|
|
new_index = materials[mat]
|
|
faces_materials.append(new_index)
|
|
bm.verts.ensure_lookup_table()
|
|
bm.edges.ensure_lookup_table()
|
|
bm.faces.ensure_lookup_table()
|
|
# assign new indexes
|
|
for index, f in zip(faces_materials, bm.faces): f.material_index = index
|
|
# create object
|
|
me = bpy.data.meshes.new('joined')
|
|
bm.to_mesh(me)
|
|
me.update()
|
|
ob = bpy.data.objects.new('joined', me)
|
|
if link_to_scene: C.collection.objects.link(ob)
|
|
# make active
|
|
if make_active:
|
|
for o in C.view_layer.objects: o.select_set(False)
|
|
ob.select_set(True)
|
|
C.view_layer.objects.active = ob
|
|
# add materials
|
|
for m in materials.keys(): ob.data.materials.append(m)
|
|
|
|
return ob
|
|
|
|
def join_objects(context, objects):
|
|
generated_data = [o.data for o in objects]
|
|
context.view_layer.update()
|
|
for o in context.view_layer.objects:
|
|
o.select_set(o in objects)
|
|
bpy.ops.object.join()
|
|
new_ob = context.view_layer.objects.active
|
|
new_ob.select_set(True)
|
|
for me in generated_data:
|
|
if me != new_ob.data:
|
|
bpy.data.meshes.remove(me)
|
|
return new_ob
|
|
|
|
def join_objects(objects):
|
|
override = bpy.context.copy()
|
|
new_ob = objects[0]
|
|
override['active_object'] = new_ob
|
|
override['selected_editable_objects'] = objects
|
|
bpy.ops.object.join(override)
|
|
return new_ob
|
|
|
|
def repeat_mesh(me, n):
|
|
'''
|
|
Return Mesh data adding and applying an array without offset (Slower)
|
|
'''
|
|
bm = bmesh.new()
|
|
for i in range(n): bm.from_mesh(me)
|
|
new_me = me.copy()
|
|
bm.to_mesh(new_me)
|
|
bm.free()
|
|
return new_me
|
|
|
|
def array_mesh(ob, n):
|
|
'''
|
|
Return Mesh data adding and applying an array without offset
|
|
'''
|
|
arr = ob.modifiers.new('Repeat','ARRAY')
|
|
arr.relative_offset_displace[0] = 0
|
|
arr.count = n
|
|
# with bpy.context.temp_override(active_object=ob):
|
|
# bpy.ops.object.modifier_apply(modifier='Repeat')
|
|
# me = ob.data
|
|
ob.modifiers.update()
|
|
|
|
dg = bpy.context.evaluated_depsgraph_get()
|
|
me = simple_to_mesh(ob, depsgraph=dg)
|
|
ob.modifiers.remove(arr)
|
|
return me
|
|
|
|
def array_mesh_object(ob, n):
|
|
'''
|
|
Return Mesh data adding and applying an array without offset
|
|
'''
|
|
arr = ob.modifiers.new('Repeat','ARRAY')
|
|
arr.relative_offset_displace[0] = 0
|
|
arr.count = n
|
|
ob.modifiers.update()
|
|
override = bpy.context.copy()
|
|
override['active_object'] = ob
|
|
override = {'active_object': ob}
|
|
bpy.ops.object.modifier_apply(override, modifier=arr.name)
|
|
return ob
|
|
|
|
|
|
def get_mesh_before_subs(ob):
|
|
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')
|
|
subs = 0
|
|
hide_mods = []
|
|
mods_visibility = []
|
|
for m in ob.modifiers:
|
|
hide_mods.append(m)
|
|
mods_visibility.append(m.show_viewport)
|
|
if m.type in ('SUBSURF','MULTIRES'):
|
|
hide_mods = [m]
|
|
subs = m.levels
|
|
elif m.type in not_allowed:
|
|
subs = 0
|
|
hide_mods = []
|
|
mods_visibility = []
|
|
for m in hide_mods: m.show_viewport = False
|
|
me = simple_to_mesh(ob)
|
|
for m, vis in zip(hide_mods,mods_visibility): m.show_viewport = vis
|
|
return me, subs
|
|
|
|
# ------------------------------------------------------------------
|
|
# MESH FUNCTIONS
|
|
# ------------------------------------------------------------------
|
|
|
|
def calc_verts_area(me):
|
|
n_verts = len(me.vertices)
|
|
n_faces = len(me.polygons)
|
|
vareas = np.zeros(n_verts)
|
|
vcount = np.zeros(n_verts)
|
|
parea = [0]*n_faces
|
|
pverts = [0]*n_faces*4
|
|
me.polygons.foreach_get('area', parea)
|
|
me.polygons.foreach_get('vertices', pverts)
|
|
parea = np.array(parea)
|
|
pverts = np.array(pverts).reshape((n_faces, 4))
|
|
for a, verts in zip(parea,pverts):
|
|
vareas[verts] += a
|
|
vcount[verts] += 1
|
|
return vareas / vcount
|
|
|
|
def calc_verts_area_bmesh(me):
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me)
|
|
bm.verts.ensure_lookup_table()
|
|
verts_area = np.zeros(len(me.vertices))
|
|
for v in bm.verts:
|
|
area = 0
|
|
faces = v.link_faces
|
|
for f in faces:
|
|
area += f.calc_area()
|
|
verts_area[v.index] = area if area == 0 else area/len(faces)
|
|
bm.free()
|
|
return verts_area
|
|
|
|
import time
|
|
|
|
def get_patches____(me_low, me_high, sides, subs, bool_selection, bool_material_id, material_id):
|
|
nv = len(me_low.vertices) # number of vertices
|
|
ne = len(me_low.edges) # number of edges
|
|
nf = len(me_low.polygons) # number of polygons
|
|
n = 2**subs + 1 # number of vertices along each patch edge
|
|
nev = ne * n # number of vertices along the subdivided edges
|
|
nevi = nev - 2*ne # internal vertices along subdividede edges
|
|
|
|
n0 = 2**(subs-1) - 1
|
|
|
|
# filtered polygonal faces
|
|
poly_sides = np.array([len(p.vertices) for p in me_low.polygons])
|
|
mask = poly_sides == sides
|
|
if bool_material_id:
|
|
mask_material = [1]*nf
|
|
me_low.polygons.foreach_get('material_index',mask_material)
|
|
mask_material = np.array(mask_material) == material_id
|
|
mask = np.logical_and(mask,mask_material)
|
|
if bool_selection:
|
|
mask_selection = [True]*nf
|
|
me_low.polygons.foreach_get('select',mask_selection)
|
|
mask_selection = np.array(mask_selection)
|
|
mask = np.logical_and(mask,mask_selection)
|
|
polys = np.array(me_low.polygons)[mask]
|
|
mult = n0**2 + n0
|
|
ps = poly_sides * mult + 1
|
|
ps = np.insert(ps,0,nv + nevi, axis=0)[:-1]
|
|
ips = ps.cumsum()[mask] # incremental polygon sides
|
|
nf = len(polys)
|
|
|
|
# when subdivided quad faces follows a different pattern
|
|
if sides == 4:
|
|
n_patches = nf
|
|
else:
|
|
n_patches = nf*sides
|
|
|
|
if sides == 4:
|
|
patches = np.zeros((nf,n,n),dtype='int')
|
|
verts = [[vv for vv in p.vertices] for p in polys if len(p.vertices) == sides]
|
|
verts = np.array(verts).reshape((-1,sides))
|
|
|
|
# filling corners
|
|
|
|
patches[:,0,0] = verts[:,0]
|
|
patches[:,n-1,0] = verts[:,1]
|
|
patches[:,n-1,n-1] = verts[:,2]
|
|
patches[:,0,n-1] = verts[:,3]
|
|
|
|
if subs != 0:
|
|
shift_verts = np.roll(verts, -1, axis=1)[:,:,None]
|
|
edge_keys = np.concatenate((shift_verts, verts[:,:,None]), axis=2)
|
|
edge_keys.sort()
|
|
|
|
edge_verts = np.array(me_low.edge_keys) # edges keys
|
|
edges_index = np.zeros((ne,ne),dtype='int')
|
|
edges_index[edge_verts[:,0],edge_verts[:,1]] = np.arange(ne)
|
|
|
|
evi = np.arange(nevi) + nv
|
|
evi = evi.reshape(ne,n-2) # edges inner verts
|
|
straight = np.arange(n-2)+1
|
|
inverted = np.flip(straight)
|
|
inners = np.array([[j*(n-2)+i for j in range(n-2)] for i in range(n-2)])
|
|
|
|
ek1 = np.array(me_high.edge_keys) # edges keys
|
|
ids0 = np.arange(ne)*(n-1) # edge keys highres
|
|
keys0 = ek1[ids0] # first inner edge
|
|
keys1 = ek1[ids0 + n-2] # last inner edge
|
|
keys = np.concatenate((keys0,keys1))
|
|
pick_verts = np.array((inverted,straight))
|
|
|
|
patch_index = np.arange(nf)[:,None,None]
|
|
|
|
# edge 0
|
|
e0 = edge_keys[:,0] # get edge key (faces, 2)
|
|
edge_id = edges_index[e0[:,0],e0[:,1]] # edge index
|
|
edge_verts = evi[edge_id] # indexes of inner vertices
|
|
test = np.concatenate((verts[:,0,None], edge_verts[:,0,None]),axis=1)
|
|
dir = (test[:,None] == keys).all(2).any(1).astype('int8')
|
|
#dir = np.full(verts[:,0].shape, 0, dtype='int8')
|
|
ids = pick_verts[dir][:,None,:] # indexes order along the side
|
|
patches[patch_index,ids,0] = edge_verts[:,None,:] # assign indexes
|
|
#patches[:,msk] = inverted # np.flip(patches[msk])
|
|
|
|
# edge 1
|
|
e0 = edge_keys[:,1] # get edge key (faces, 2)
|
|
edge_id = edges_index[e0[:,0],e0[:,1]] # edge index
|
|
edge_verts = evi[edge_id] # indexes of inner vertices
|
|
test = np.concatenate((verts[:,1,None], edge_verts[:,0,None]),axis=1)
|
|
dir = (test[:,None] == keys).all(2).any(1).astype('int8')
|
|
ids = pick_verts[dir][:,:,None] # indexes order along the side
|
|
patches[patch_index,n-1,ids] = edge_verts[:,:,None] # assign indexes
|
|
|
|
# edge 2
|
|
e0 = edge_keys[:,2] # get edge key (faces, 2)
|
|
edge_id = edges_index[e0[:,0],e0[:,1]] # edge index
|
|
edge_verts = evi[edge_id] # indexes of inner vertices
|
|
test = np.concatenate((verts[:,3,None], edge_verts[:,0,None]),axis=1)
|
|
dir = (test[:,None] == keys).all(2).any(1).astype('int8')
|
|
ids = pick_verts[dir][:,None,:] # indexes order along the side
|
|
patches[patch_index,ids,n-1] = edge_verts[:,None,:] # assign indexes
|
|
|
|
# edge 3
|
|
e0 = edge_keys[:,3] # get edge key (faces, 2)
|
|
edge_id = edges_index[e0[:,0],e0[:,1]] # edge index
|
|
edge_verts = evi[edge_id] # indexes of inner vertices
|
|
test = np.concatenate((verts[:,0,None], edge_verts[:,0,None]),axis=1)
|
|
dir = (test[:,None] == keys).all(2).any(1).astype('int8')
|
|
ids = pick_verts[dir][:,:,None] # indexes order along the side
|
|
patches[patch_index,0,ids] = edge_verts[:,:,None] # assign indexes
|
|
|
|
# fill inners
|
|
patches[:,1:-1,1:-1] = inners[None,:,:] + ips[:,None,None]
|
|
|
|
#end_time = time.time()
|
|
#print('Tissue: Got Patches in {:.4f} sec'.format(end_time-start_time))
|
|
|
|
return patches, mask
|
|
|
|
def tessellate_prepare_component(ob1, props):
|
|
mode = props['mode']
|
|
bounds_x = props['bounds_x']
|
|
bounds_y = props['bounds_y']
|
|
scale_mode = props['scale_mode']
|
|
normals_mode = props['normals_mode']
|
|
zscale = props['zscale']
|
|
offset = props['offset']
|
|
use_origin_offset = props['use_origin_offset']
|
|
bool_shapekeys = props['bool_shapekeys']
|
|
|
|
thres = 0.005
|
|
|
|
me1 = ob1.data
|
|
|
|
# Component statistics
|
|
n_verts = len(me1.vertices)
|
|
|
|
# Component bounding box
|
|
min_c = Vector((0, 0, 0))
|
|
max_c = Vector((0, 0, 0))
|
|
first = True
|
|
for v in me1.vertices:
|
|
vert = v.co
|
|
if vert[0] < min_c[0] or first:
|
|
min_c[0] = vert[0]
|
|
if vert[1] < min_c[1] or first:
|
|
min_c[1] = vert[1]
|
|
if vert[2] < min_c[2] or first:
|
|
min_c[2] = vert[2]
|
|
if vert[0] > max_c[0] or first:
|
|
max_c[0] = vert[0]
|
|
if vert[1] > max_c[1] or first:
|
|
max_c[1] = vert[1]
|
|
if vert[2] > max_c[2] or first:
|
|
max_c[2] = vert[2]
|
|
first = False
|
|
bb = max_c - min_c
|
|
|
|
# adaptive XY
|
|
verts1 = []
|
|
for v in me1.vertices:
|
|
if mode == 'BOUNDS':
|
|
vert = v.co - min_c # (ob1.matrix_world * v.co) - min_c
|
|
if use_origin_offset: vert[2] = v.co[2]
|
|
vert[0] = vert[0] / bb[0] if bb[0] != 0 else 0.5
|
|
vert[1] = vert[1] / bb[1] if bb[1] != 0 else 0.5
|
|
if scale_mode == 'CONSTANT' or normals_mode in ('OBJECT', 'SHAPEKEYS'):
|
|
if not use_origin_offset:
|
|
vert[2] = vert[2] / bb[2] if bb[2] != 0 else 0
|
|
vert[2] = vert[2] - 0.5 + offset * 0.5
|
|
else:
|
|
if not use_origin_offset:
|
|
vert[2] = vert[2] + (-0.5 + offset * 0.5) * bb[2]
|
|
vert[2] *= zscale
|
|
elif mode == 'LOCAL':
|
|
vert = v.co.xyz
|
|
vert[2] *= zscale
|
|
#vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
|
|
elif mode == 'GLOBAL':
|
|
vert = ob1.matrix_world @ v.co
|
|
vert[2] *= zscale
|
|
try:
|
|
for sk in me1.shape_keys.key_blocks:
|
|
sk.data[v.index].co = ob1.matrix_world @ sk.data[v.index].co
|
|
except: pass
|
|
v.co = vert
|
|
|
|
# ShapeKeys
|
|
if bool_shapekeys and ob1.data.shape_keys:
|
|
for sk in ob1.data.shape_keys.key_blocks:
|
|
source = sk.data
|
|
_sk_uv_quads = [0]*len(verts1)
|
|
_sk_uv = [0]*len(verts1)
|
|
for i, sk_v in enumerate(source):
|
|
if mode == 'BOUNDS':
|
|
sk_vert = sk_v.co - min_c
|
|
if use_origin_offset: sk_vert[2] = sk_v.co[2]
|
|
sk_vert[0] = (sk_vert[0] / bb[0] if bb[0] != 0 else 0.5)
|
|
sk_vert[1] = (sk_vert[1] / bb[1] if bb[1] != 0 else 0.5)
|
|
if scale_mode == 'CONSTANT' or normals_mode in ('OBJECT', 'SHAPEKEYS'):
|
|
if not use_origin_offset:
|
|
sk_vert[2] = (sk_vert[2] / bb[2] if bb[2] != 0 else sk_vert[2])
|
|
sk_vert[2] = sk_vert[2] - 0.5 + offset * 0.5
|
|
else:
|
|
if not use_origin_offset:
|
|
sk_vert[2] = sk_vert[2] + (- 0.5 + offset * 0.5) * bb[2]
|
|
sk_vert[2] *= zscale
|
|
elif mode == 'LOCAL':
|
|
sk_vert = sk_v.co
|
|
sk_vert[2] *= zscale
|
|
elif mode == 'GLOBAL':
|
|
sk_vert = sk_v.co
|
|
sk_vert[2] *= zscale
|
|
sk_v.co = sk_vert
|
|
|
|
if mode != 'BOUNDS' and (bounds_x != 'EXTEND' or bounds_y != 'EXTEND'):
|
|
ob1.active_shape_key_index = 0
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me1)
|
|
# Bound X
|
|
planes_co = []
|
|
planes_no = []
|
|
bounds = []
|
|
if bounds_x != 'EXTEND':
|
|
planes_co += [(0,0,0), (1,0,0)]
|
|
planes_no += [(-1,0,0), (1,0,0)]
|
|
bounds += [bounds_x, bounds_x]
|
|
if bounds_y != 'EXTEND':
|
|
planes_co += [(0,0,0), (0,1,0)]
|
|
planes_no += [(0,-1,0), (0,1,0)]
|
|
bounds += [bounds_y, bounds_y]
|
|
for co, norm, bound in zip(planes_co, planes_no, bounds):
|
|
count = 0
|
|
while True:
|
|
moved = 0
|
|
original_edges = list(bm.edges)
|
|
geom = list(bm.verts) + list(bm.edges) + list(bm.faces)
|
|
bisect = bmesh.ops.bisect_plane(bm, geom=geom, dist=0,
|
|
plane_co=co, plane_no=norm, use_snap_center=False,
|
|
clear_outer=bound=='CLIP', clear_inner=False
|
|
)
|
|
geom = bisect['geom']
|
|
cut_edges = [g for g in bisect['geom_cut'] if type(g)==bmesh.types.BMEdge]
|
|
cut_verts = [g for g in bisect['geom_cut'] if type(g)==bmesh.types.BMVert]
|
|
|
|
if bound!='CLIP':
|
|
for e in cut_edges:
|
|
seam = True
|
|
# Prevent glitches
|
|
for e1 in original_edges:
|
|
match_00 = (e.verts[0].co-e1.verts[0].co).length < thres
|
|
match_11 = (e.verts[1].co-e1.verts[1].co).length < thres
|
|
match_01 = (e.verts[0].co-e1.verts[1].co).length < thres
|
|
match_10 = (e.verts[1].co-e1.verts[0].co).length < thres
|
|
if (match_00 and match_11) or (match_01 and match_10):
|
|
seam = False
|
|
break
|
|
e.seam = seam
|
|
|
|
if bound == 'CYCLIC':
|
|
geom_verts = []
|
|
if norm == (-1,0,0):
|
|
geom_verts = [v for v in bm.verts if v.co.x < 0]
|
|
if norm == (1,0,0):
|
|
geom_verts = [v for v in bm.verts if v.co.x > 1]
|
|
if norm == (0,-1,0):
|
|
geom_verts = [v for v in bm.verts if v.co.y < 0]
|
|
if norm == (0,1,0):
|
|
geom_verts = [v for v in bm.verts if v.co.y > 1]
|
|
if len(geom_verts) > 0:
|
|
geom = bmesh.ops.region_extend(bm, geom=geom_verts,
|
|
use_contract=False, use_faces=False, use_face_step=True
|
|
)
|
|
geom = bmesh.ops.split(bm, geom=geom['geom'], use_only_faces=False)
|
|
vec = Vector(norm)
|
|
move_verts = [g for g in geom['geom'] if type(g)==bmesh.types.BMVert]
|
|
bmesh.ops.translate(bm, vec=-vec, verts=move_verts)
|
|
for key in bm.verts.layers.shape.keys():
|
|
sk = bm.verts.layers.shape.get(key)
|
|
for v in move_verts:
|
|
v[sk] -= vec
|
|
moved += len(move_verts)
|
|
count += 1
|
|
if moved == 0 or count > 1000: break
|
|
bm.to_mesh(me1)
|
|
|
|
com_area = bb[0]*bb[1]
|
|
return ob1, com_area
|
|
|
|
def get_quads(me, bool_selection):
|
|
nf = len(me.polygons)
|
|
|
|
verts = []
|
|
materials = []
|
|
mask = []
|
|
for poly in me.polygons:
|
|
p = list(poly.vertices)
|
|
sides = len(p)
|
|
if sides == 3:
|
|
verts.append([[p[0], p[-1]], [p[1], p[2]]])
|
|
materials.append(poly.material_index)
|
|
mask.append(poly.select if bool_selection else True)
|
|
elif sides == 4:
|
|
verts.append([[p[0], p[3]], [p[1], p[2]]])
|
|
materials.append(poly.material_index)
|
|
mask.append(poly.select if bool_selection else True)
|
|
else:
|
|
while True:
|
|
new_poly = [[p[-2], p.pop(-1)], [p[1], p.pop(0)]]
|
|
verts.append(new_poly)
|
|
materials.append(poly.material_index)
|
|
mask.append(poly.select if bool_selection else True)
|
|
if len(p) < 3: break
|
|
mask = np.array(mask)
|
|
materials = np.array(materials)[mask]
|
|
verts = np.array(verts)[mask]
|
|
return verts, mask, materials
|
|
|
|
def get_patches(me_low, me_high, sides, subs, bool_selection): #, bool_material_id, material_id):
|
|
nv = len(me_low.vertices) # number of vertices
|
|
ne = len(me_low.edges) # number of edges
|
|
nf = len(me_low.polygons) # number of polygons
|
|
n = 2**subs + 1
|
|
nev = ne * n # number of vertices along the subdivided edges
|
|
nevi = nev - 2*ne # internal vertices along subdividede edges
|
|
|
|
n0 = 2**(subs-1) - 1
|
|
|
|
# filtered polygonal faces
|
|
poly_sides = [0]*nf
|
|
me_low.polygons.foreach_get('loop_total',poly_sides)
|
|
poly_sides = np.array(poly_sides)
|
|
mask = poly_sides == sides
|
|
|
|
if bool_selection:
|
|
mask_selection = [True]*nf
|
|
me_low.polygons.foreach_get('select',mask_selection)
|
|
mask = np.array(mask_selection)
|
|
|
|
materials = [1]*nf
|
|
me_low.polygons.foreach_get('material_index',materials)
|
|
materials = np.array(materials)[mask]
|
|
|
|
polys = np.array(me_low.polygons)[mask]
|
|
mult = n0**2 + n0
|
|
ps = poly_sides * mult + 1
|
|
ps = np.insert(ps,0,nv + nevi, axis=0)[:-1]
|
|
ips = ps.cumsum()[mask] # incremental polygon sides
|
|
nf = len(polys)
|
|
|
|
# when subdivided quad faces follows a different pattern
|
|
if sides == 4:
|
|
n_patches = nf
|
|
else:
|
|
n_patches = nf*sides
|
|
|
|
if sides == 4:
|
|
patches = np.empty((nf,n,n),dtype='int')
|
|
verts = [list(p.vertices) for p in polys if len(p.vertices) == sides]
|
|
verts = np.array(verts).reshape((-1,sides))
|
|
|
|
# filling corners
|
|
|
|
patches[:,0,0] = verts[:,0]
|
|
patches[:,n-1,0] = verts[:,1]
|
|
patches[:,n-1,n-1] = verts[:,2]
|
|
patches[:,0,n-1] = verts[:,3]
|
|
|
|
if subs != 0:
|
|
shift_verts = np.roll(verts, -1, axis=1)[:,:,None]
|
|
edge_keys = np.concatenate((shift_verts, verts[:,:,None]), axis=2)
|
|
edge_keys.sort()
|
|
|
|
edge_verts = np.array(me_low.edge_keys) # edges keys
|
|
edges_index = np.empty((ne,ne),dtype='int')
|
|
edges_index[edge_verts[:,0],edge_verts[:,1]] = np.arange(ne)
|
|
|
|
evi = np.arange(nevi) + nv
|
|
evi = evi.reshape(ne,n-2) # edges inner verts
|
|
straight = np.arange(n-2)+1
|
|
inverted = np.flip(straight)
|
|
inners = np.array([[j*(n-2)+i for j in range(n-2)] for i in range(n-2)])
|
|
|
|
ek1 = me_high.edge_keys # edges keys
|
|
ek1 = np.array(ek1) # edge keys highres
|
|
keys0 = ek1[np.arange(ne)*(n-1)] # first inner edge
|
|
keys1 = ek1[np.arange(ne)*(n-1)+n-2] # last inner edge
|
|
edges_dir = np.zeros((nev,nev),dtype='bool') # Better memory usage
|
|
#edges_dir = np.zeros((nev,nev),dtype='int8') ### Memory usage not efficient, dictionary as alternative?
|
|
edges_dir[keys0[:,0], keys0[:,1]] = 1
|
|
edges_dir[keys1[:,0], keys1[:,1]] = 1
|
|
pick_verts = np.array((inverted,straight))
|
|
|
|
patch_index = np.arange(nf)[:,None,None]
|
|
|
|
# edge 0
|
|
e0 = edge_keys[:,0] # get edge key (faces, 2)
|
|
edge_id = edges_index[e0[:,0],e0[:,1]] # edge index
|
|
edge_verts = evi[edge_id] # indexes of inner vertices
|
|
dir = edges_dir[verts[:,0], edge_verts[:,0]] # check correct direction
|
|
ids = pick_verts[dir.astype('int8')][:,None,:] # indexes order along the side
|
|
patches[patch_index,ids,0] = edge_verts[:,None,:] # assign indexes
|
|
|
|
# edge 1
|
|
e0 = edge_keys[:,1] # get edge key (faces, 2)
|
|
edge_id = edges_index[e0[:,0],e0[:,1]] # edge index
|
|
edge_verts = evi[edge_id] # indexes of inner vertices
|
|
dir = edges_dir[verts[:,1], edge_verts[:,0]] # check correct direction
|
|
ids = pick_verts[dir.astype('int8')][:,:,None] # indexes order along the side
|
|
patches[patch_index,n-1,ids] = edge_verts[:,:,None] # assign indexes
|
|
|
|
# edge 2
|
|
e0 = edge_keys[:,2] # get edge key (faces, 2)
|
|
edge_id = edges_index[e0[:,0],e0[:,1]] # edge index
|
|
edge_verts = evi[edge_id] # indexes of inner vertices
|
|
dir = edges_dir[verts[:,3], edge_verts[:,0]] # check correct direction
|
|
ids = pick_verts[dir.astype('int8')][:,None,:] # indexes order along the side
|
|
patches[patch_index,ids,n-1] = edge_verts[:,None,:] # assign indexes
|
|
|
|
# edge 3
|
|
e0 = edge_keys[:,3] # get edge key (faces, 2)
|
|
edge_id = edges_index[e0[:,0],e0[:,1]] # edge index
|
|
edge_verts = evi[edge_id] # indexes of inner vertices
|
|
dir = edges_dir[verts[:,0], edge_verts[:,0]] # check correct direction
|
|
ids = pick_verts[dir.astype('int8')][:,:,None] # indexes order along the side
|
|
patches[patch_index,0,ids] = edge_verts[:,:,None] # assign indexes
|
|
|
|
# fill inners
|
|
patches[:,1:-1,1:-1] = inners[None,:,:] + ips[:,None,None]
|
|
|
|
return patches, mask, materials
|
|
|
|
def get_vertices_numpy(mesh):
|
|
'''
|
|
Create a numpy array with the vertices of a given mesh
|
|
'''
|
|
n_verts = len(mesh.vertices)
|
|
verts = [0]*n_verts*3
|
|
mesh.vertices.foreach_get('co', verts)
|
|
verts = np.array(verts).reshape((n_verts,3))
|
|
return verts
|
|
|
|
def get_vertices_and_normals_numpy(mesh):
|
|
'''
|
|
Create two numpy arrays with the vertices and the normals of a given mesh
|
|
'''
|
|
n_verts = len(mesh.vertices)
|
|
verts = [0]*n_verts*3
|
|
normals = [0]*n_verts*3
|
|
mesh.vertices.foreach_get('co', verts)
|
|
mesh.vertices.foreach_get('normal', normals)
|
|
verts = np.array(verts).reshape((n_verts,3))
|
|
normals = np.array(normals).reshape((n_verts,3))
|
|
return verts, normals
|
|
|
|
def get_normals_numpy(mesh):
|
|
'''
|
|
Create a numpy array with the normals of a given mesh
|
|
'''
|
|
n_verts = len(mesh.vertices)
|
|
normals = [0]*n_verts*3
|
|
mesh.vertices.foreach_get('normal', normals)
|
|
normals = np.array(normals).reshape((n_verts,3))
|
|
return normals
|
|
|
|
def get_edges_numpy(mesh):
|
|
'''
|
|
Create a numpy array with the edges of a given mesh
|
|
'''
|
|
n_edges = len(mesh.edges)
|
|
edges = [0]*n_edges*2
|
|
mesh.edges.foreach_get('vertices', edges)
|
|
edges = np.array(edges).reshape((n_edges,2)).astype('int')
|
|
return edges
|
|
|
|
def get_edges_id_numpy(mesh):
|
|
n_edges = len(mesh.edges)
|
|
edges = [0]*n_edges*2
|
|
mesh.edges.foreach_get('vertices', edges)
|
|
edges = np.array(edges).reshape((n_edges,2))
|
|
indexes = np.arange(n_edges).reshape((n_edges,1))
|
|
edges = np.concatenate((edges,indexes), axis=1)
|
|
return edges
|
|
|
|
def get_polygons_select_numpy(mesh):
|
|
n_polys = len(mesh.polygons)
|
|
selections = [0]*n_polys*2
|
|
mesh.polygons.foreach_get('select', selections)
|
|
selections = np.array(selections)
|
|
return selections
|
|
|
|
def get_attribute_numpy(elements_list, attribute='select', mult=1):
|
|
'''
|
|
Generate a numpy array getting attribute from a list of element using
|
|
the foreach_get() function.
|
|
'''
|
|
n_elements = len(elements_list)
|
|
values = [0]*n_elements*mult
|
|
elements_list.foreach_get(attribute, values)
|
|
values = np.array(values)
|
|
if mult > 1: values = values.reshape((n_elements,mult))
|
|
return values
|
|
|
|
def get_vertices(mesh):
|
|
n_verts = len(mesh.vertices)
|
|
verts = [0]*n_verts*3
|
|
mesh.vertices.foreach_get('co', verts)
|
|
verts = np.array(verts).reshape((n_verts,3))
|
|
verts = [Vector(v) for v in verts]
|
|
return verts
|
|
|
|
def get_faces(mesh):
|
|
faces = [[v for v in f.vertices] for f in mesh.polygons]
|
|
return faces
|
|
|
|
def get_faces_numpy(mesh):
|
|
faces = [[v for v in f.vertices] for f in mesh.polygons]
|
|
return np.array(faces)
|
|
|
|
def get_faces_edges_numpy(mesh):
|
|
faces = [v.edge_keys for f in mesh.polygons]
|
|
return np.array(faces)
|
|
|
|
def find_curves(edges, n_verts):
|
|
verts_dict = {key:[] for key in range(n_verts)}
|
|
for e in edges:
|
|
verts_dict[e[0]].append(e[1])
|
|
verts_dict[e[1]].append(e[0])
|
|
curves = []
|
|
while True:
|
|
if len(verts_dict) == 0: break
|
|
# next starting point
|
|
v = list(verts_dict.keys())[0]
|
|
# neighbors
|
|
v01 = verts_dict[v]
|
|
if len(v01) == 0:
|
|
verts_dict.pop(v)
|
|
continue
|
|
curve = []
|
|
if len(v01) > 1: curve.append(v01[1]) # add neighbors
|
|
curve.append(v) # add starting point
|
|
curve.append(v01[0]) # add neighbors
|
|
verts_dict.pop(v)
|
|
# start building curve
|
|
while True:
|
|
#last_point = curve[-1]
|
|
#if last_point not in verts_dict: break
|
|
|
|
# try to change direction if needed
|
|
if curve[-1] in verts_dict: pass
|
|
elif curve[0] in verts_dict: curve.reverse()
|
|
else: break
|
|
|
|
# neighbors points
|
|
last_point = curve[-1]
|
|
v01 = verts_dict[last_point]
|
|
|
|
# curve end
|
|
if len(v01) == 1:
|
|
verts_dict.pop(last_point)
|
|
if curve[0] in verts_dict: continue
|
|
else: break
|
|
|
|
# chose next point
|
|
new_point = None
|
|
if v01[0] == curve[-2]: new_point = v01[1]
|
|
elif v01[1] == curve[-2]: new_point = v01[0]
|
|
#else: break
|
|
|
|
#if new_point != curve[1]:
|
|
curve.append(new_point)
|
|
verts_dict.pop(last_point)
|
|
if curve[0] == curve[-1]:
|
|
verts_dict.pop(new_point)
|
|
break
|
|
curves.append(curve)
|
|
return curves
|
|
|
|
def curve_from_points(points, name='Curve'):
|
|
curve = bpy.data.curves.new(name,'CURVE')
|
|
for c in points:
|
|
s = curve.splines.new('POLY')
|
|
s.points.add(len(c))
|
|
for i,p in enumerate(c): s.points[i].co = p.xyz + [1]
|
|
ob_curve = bpy.data.objects.new(name,curve)
|
|
return ob_curve
|
|
|
|
def curve_from_pydata(points, radii, indexes, name='Curve', skip_open=False, merge_distance=1, set_active=True, only_data=False):
|
|
curve = bpy.data.curves.new(name,'CURVE')
|
|
curve.dimensions = '3D'
|
|
use_rad = True
|
|
for c in indexes:
|
|
bool_cyclic = c[0] == c[-1]
|
|
if bool_cyclic: c.pop(-1)
|
|
# cleanup
|
|
pts = np.array([points[i] for i in c])
|
|
try:
|
|
rad = np.array([radii[i] for i in c])
|
|
except:
|
|
use_rad = False
|
|
rad = 1
|
|
if merge_distance > 0:
|
|
pts1 = np.roll(pts,1,axis=0)
|
|
dist = np.linalg.norm(pts1-pts, axis=1)
|
|
count = 0
|
|
n = len(dist)
|
|
mask = np.ones(n).astype('bool')
|
|
for i in range(n):
|
|
count += dist[i]
|
|
if count > merge_distance: count = 0
|
|
else: mask[i] = False
|
|
pts = pts[mask]
|
|
if use_rad: rad = rad[mask]
|
|
|
|
if skip_open and not bool_cyclic: continue
|
|
s = curve.splines.new('POLY')
|
|
n_pts = len(pts)
|
|
s.points.add(n_pts-1)
|
|
w = np.ones(n_pts).reshape((n_pts,1))
|
|
co = np.concatenate((pts,w),axis=1).reshape((n_pts*4))
|
|
s.points.foreach_set('co',co)
|
|
if use_rad: s.points.foreach_set('radius',rad)
|
|
s.use_cyclic_u = bool_cyclic
|
|
if only_data:
|
|
return curve
|
|
else:
|
|
ob_curve = bpy.data.objects.new(name,curve)
|
|
bpy.context.collection.objects.link(ob_curve)
|
|
if set_active:
|
|
bpy.context.view_layer.objects.active = ob_curve
|
|
return ob_curve
|
|
|
|
def update_curve_from_pydata(curve, points, normals, radii, indexes, merge_distance=1, pattern=[1,0], depth=0.1, offset=0):
|
|
curve.splines.clear()
|
|
use_rad = True
|
|
for ic, c in enumerate(indexes):
|
|
bool_cyclic = c[0] == c[-1]
|
|
if bool_cyclic: c.pop(-1)
|
|
|
|
# cleanup
|
|
pts = np.array([points[i] for i in c if i != None])
|
|
nor = np.array([normals[i] for i in c if i != None])
|
|
try:
|
|
rad = np.array([radii[i] for i in c if i != None])
|
|
except:
|
|
use_rad = False
|
|
rad = 1
|
|
if merge_distance > 0:
|
|
pts1 = np.roll(pts,1,axis=0)
|
|
dist = np.linalg.norm(pts1-pts, axis=1)
|
|
count = 0
|
|
n = len(dist)
|
|
mask = np.ones(n).astype('bool')
|
|
for i in range(n):
|
|
count += dist[i]
|
|
if count > merge_distance: count = 0
|
|
else: mask[i] = False
|
|
pts = pts[mask]
|
|
nor = nor[mask]
|
|
if use_rad: rad = rad[mask]
|
|
#if skip_open and not bool_cyclic: continue
|
|
n_pts = len(pts)
|
|
series = np.arange(n_pts)
|
|
patt1 = series + (series-series%pattern[1])/pattern[1]*pattern[0]+pattern[0]
|
|
patt1 = patt1[patt1<n_pts].astype('int')
|
|
patt0 = series + (series-series%pattern[0])/pattern[0]*pattern[1]
|
|
patt0 = patt0[patt0<n_pts].astype('int')
|
|
nor[patt0] *= 0.5*depth*(1 + offset)
|
|
nor[patt1] *= 0.5*depth*(-1 + offset)
|
|
if pattern[0]*pattern[1] != 0: pts += nor
|
|
s = curve.splines.new('POLY')
|
|
s.points.add(n_pts-1)
|
|
w = np.ones(n_pts).reshape((n_pts,1))
|
|
co = np.concatenate((pts,w),axis=1).reshape((n_pts*4))
|
|
s.points.foreach_set('co',co)
|
|
if use_rad: s.points.foreach_set('radius',rad)
|
|
s.use_cyclic_u = bool_cyclic
|
|
|
|
def loops_from_bmesh(edges):
|
|
"""
|
|
Return one or more loops given some starting edges.
|
|
:arg edges: Edges used as seeds.
|
|
:type edges: List of :class:'bmesh.types.BMEdge'
|
|
:return: Elements in each loop (Verts, Edges), where:
|
|
- Verts - List of Lists of :class:'bmesh.types.BMVert'
|
|
- Edges - List of Lists of :class:'bmesh.types.BMEdge'
|
|
:rtype: tuple
|
|
"""
|
|
todo_edges = list(edges)
|
|
#todo_edges = [e.index for e in bm.edges]
|
|
vert_loops = []
|
|
edge_loops = []
|
|
while len(todo_edges) > 0:
|
|
edge = todo_edges[0]
|
|
vert_loop, edge_loop = run_edge_loop(edge)
|
|
for e in edge_loop:
|
|
try: todo_edges.remove(e)
|
|
except: pass
|
|
edge_loops.append(edge_loop)
|
|
vert_loops.append(vert_loop)
|
|
#if len(todo_edges) == 0: break
|
|
return vert_loops, edge_loops
|
|
|
|
def run_edge_loop_direction(edge,vert):
|
|
"""
|
|
Return vertices and edges along a loop in a specific direction.
|
|
:arg edge: Edges used as seed.
|
|
:type edges: :class:'bmesh.types.BMEdge'
|
|
:arg edge: Vertex of the Edge used for the direction.
|
|
:type vert: :class:'bmesh.types.BMVert'
|
|
:return: Elements in the loop (Verts, Edges), where:
|
|
- Verts - List of :class:'bmesh.types.BMVert'
|
|
- Edges - List of :class:'bmesh.types.BMEdge'
|
|
:rtype: tuple
|
|
"""
|
|
edge0 = edge
|
|
edge_loop = [edge]
|
|
vert_loop = [vert]
|
|
while True:
|
|
link_edges = list(vert.link_edges)
|
|
link_edges.remove(edge)
|
|
n_edges = len(link_edges)
|
|
if n_edges == 1:
|
|
edge = link_edges[0]
|
|
elif n_edges < 4:
|
|
link_faces = edge.link_faces
|
|
if len(link_faces) == 0: break
|
|
edge = None
|
|
for e in link_edges:
|
|
link_faces1 = e.link_faces
|
|
if len(link_faces) == len(link_faces1):
|
|
common_faces = [f for f in link_faces1 if f in link_faces]
|
|
if len(common_faces) == 0:
|
|
edge = e
|
|
break
|
|
else: break
|
|
if edge == None: break
|
|
edge_loop.append(edge)
|
|
vert = edge.other_vert(vert)
|
|
vert_loop.append(vert)
|
|
if edge == edge0: break
|
|
return vert_loop, edge_loop
|
|
|
|
def run_edge_loop(edge):
|
|
"""
|
|
Return vertices and edges along a loop in both directions.
|
|
:arg edge: Edges used as seed.
|
|
:type edges: :class:'bmesh.types.BMEdge'
|
|
:return: Elements in the loop (Verts, Edges), where:
|
|
- Verts - List of :class:'bmesh.types.BMVert'
|
|
- Edges - List of :class:'bmesh.types.BMEdge'
|
|
:rtype: tuple
|
|
"""
|
|
vert0 = edge.verts[0]
|
|
vert_loop0, edge_loop0 = run_edge_loop_direction(edge, vert0)
|
|
if len(edge_loop0) == 1 or edge_loop0[0] != edge_loop0[-1]:
|
|
vert1 = edge.verts[1]
|
|
vert_loop1, edge_loop1 = run_edge_loop_direction(edge, vert1)
|
|
edge_loop0.reverse()
|
|
vert_loop0.reverse()
|
|
edge_loop = edge_loop0[:-1] + edge_loop1
|
|
vert_loop = vert_loop0 + vert_loop1
|
|
else:
|
|
edge_loop = edge_loop0[1:]
|
|
vert_loop = vert_loop0
|
|
return vert_loop, edge_loop
|
|
|
|
def curve_from_vertices(indexes, verts, name='Curve'):
|
|
"""
|
|
Curve data from given vertices.
|
|
:arg indexes: List of Lists of indexes of the vertices.
|
|
:type indexes: List of Lists of int
|
|
:arg verts: List of vertices.
|
|
:type verts: List of :class:'bpy.types.MeshVertex'
|
|
:arg name: Name of the Curve data.
|
|
:type name: str
|
|
:return: Generated Curve data
|
|
:rtype: :class:'bpy.types.Curve'
|
|
"""
|
|
curve = bpy.data.curves.new(name,'CURVE')
|
|
for c in indexes:
|
|
s = curve.splines.new('POLY')
|
|
s.points.add(len(c))
|
|
for i,p in enumerate(c):
|
|
s.points[i].co = verts[p].co.xyz + [1]
|
|
#s.points[i].tilt = degrees(asin(verts[p].co.z))
|
|
ob_curve = bpy.data.objects.new(name,curve)
|
|
return ob_curve
|
|
|
|
def nurbs_from_vertices(indexes, co, radii=[], name='Curve', set_active=True, interpolation='POLY'):
|
|
curve = bpy.data.curves.new(name,'CURVE')
|
|
curve.dimensions = '3D'
|
|
curve.resolution_u = 2
|
|
curve.bevel_depth = 0.01
|
|
curve.bevel_resolution = 0
|
|
for pts in indexes:
|
|
s = curve.splines.new(interpolation)
|
|
n_pts = len(pts)
|
|
s.points.add(n_pts-1)
|
|
w = np.ones(n_pts).reshape((n_pts,1))
|
|
curve_co = np.concatenate((co[pts],w),axis=1).reshape((n_pts*4))
|
|
s.points.foreach_set('co',curve_co)
|
|
try:
|
|
s.points.foreach_set('radius',radii[pts])
|
|
except: pass
|
|
s.use_endpoint_u = True
|
|
|
|
ob_curve = bpy.data.objects.new(name,curve)
|
|
bpy.context.collection.objects.link(ob_curve)
|
|
if set_active:
|
|
bpy.context.view_layer.objects.active = ob_curve
|
|
ob_curve.select_set(True)
|
|
return ob_curve
|
|
|
|
# ------------------------------------------------------------------
|
|
# VERTEX GROUPS AND WEIGHT
|
|
# ------------------------------------------------------------------
|
|
|
|
def get_weight(vertex_group, n_verts):
|
|
"""
|
|
Read weight values from given Vertex Group.
|
|
:arg vertex_group: Vertex Group.
|
|
:type vertex_group: :class:'bpy.types.VertexGroup'
|
|
:arg n_verts: Number of Vertices (output list size).
|
|
:type n_verts: int
|
|
:return: Read weight values.
|
|
:rtype: list
|
|
"""
|
|
weight = [0]*n_verts
|
|
for i in range(n_verts):
|
|
try: weight[i] = vertex_group.weight(i)
|
|
except: pass
|
|
return weight
|
|
|
|
def get_weight_numpy(vertex_group, n_verts):
|
|
"""
|
|
Read weight values from given Vertex Group.
|
|
:arg vertex_group: Vertex Group.
|
|
:type vertex_group: :class:'bpy.types.VertexGroup'
|
|
:arg n_verts: Number of Vertices (output list size).
|
|
:type n_verts: int
|
|
:return: Read weight values as numpy array.
|
|
:rtype: :class:'numpy.ndarray'
|
|
"""
|
|
weight = [0]*n_verts
|
|
for i in range(n_verts):
|
|
try: weight[i] = vertex_group.weight(i)
|
|
except: pass
|
|
return np.array(weight)
|
|
|
|
def bmesh_get_weight_numpy(group_index, layer, verts):
|
|
weight = np.zeros(len(verts))
|
|
for i, v in enumerate(verts):
|
|
dvert = v[layer]
|
|
if group_index in dvert:
|
|
weight[i] = dvert[group_index]
|
|
#dvert[group_index] = 0.5
|
|
return weight
|
|
|
|
def bmesh_set_weight_numpy(group_index, layer, verts, weight):
|
|
for i, v in enumerate(verts):
|
|
dvert = v[layer]
|
|
if group_index in dvert:
|
|
dvert[group_index] = weight[i]
|
|
return verts
|
|
|
|
def bmesh_set_weight_numpy(bm, group_index, weight):
|
|
layer = bm.verts.layers.deform.verify()
|
|
for i, v in enumerate(bm.verts):
|
|
dvert = v[layer]
|
|
#if group_index in dvert:
|
|
dvert[group_index] = weight[i]
|
|
return bm
|
|
|
|
def set_weight_numpy(vg, weight):
|
|
for i, w in enumerate(weight):
|
|
vg.add([i], w, 'REPLACE')
|
|
return vg
|
|
|
|
def uv_from_bmesh(bm, uv_index=None):
|
|
if uv_index:
|
|
uv_lay = bm.loops.layers.uv[uv_index]
|
|
else:
|
|
uv_lay = bm.loops.layers.uv.active
|
|
uv_co = [0]*len(bm.verts)
|
|
|
|
for face in bm.faces:
|
|
for vert,loop in zip(face.verts, face.loops):
|
|
uv_co[vert.index] = loop[uv_lay].uv
|
|
return uv_co
|
|
|
|
def get_uv_edge_vectors(me, uv_map = 0, only_positive=False):
|
|
count = 0
|
|
uv_vectors = {}
|
|
for i, f in enumerate(me.polygons):
|
|
f_verts = len(f.vertices)
|
|
for j0 in range(f_verts):
|
|
j1 = (j0+1)%f_verts
|
|
uv0 = me.uv_layers[uv_map].data[count+j0].uv
|
|
uv1 = me.uv_layers[uv_map].data[count+j1].uv
|
|
delta_uv = (uv1-uv0).normalized()
|
|
if only_positive:
|
|
delta_uv.x = abs(delta_uv.x)
|
|
delta_uv.y = abs(delta_uv.y)
|
|
edge_key = tuple(sorted([f.vertices[j0], f.vertices[j1]]))
|
|
uv_vectors[edge_key] = delta_uv
|
|
count += f_verts
|
|
uv_vectors = [uv_vectors[tuple(sorted(e.vertices))] for e in me.edges]
|
|
return uv_vectors
|
|
|
|
def mesh_diffusion(me, values, iter, diff=0.2, uv_dir=0):
|
|
values = np.array(values)
|
|
n_verts = len(me.vertices)
|
|
|
|
n_edges = len(me.edges)
|
|
edge_verts = [0]*n_edges*2
|
|
#me.edges.foreach_get("vertices", edge_verts)
|
|
|
|
count = 0
|
|
edge_verts = []
|
|
uv_factor = {}
|
|
uv_ang = (0.5 + uv_dir*0.5)*pi/2
|
|
uv_vec = Vector((cos(uv_ang), sin(uv_ang)))
|
|
for i, f in enumerate(me.polygons):
|
|
f_verts = len(f.vertices)
|
|
for j0 in range(f_verts):
|
|
j1 = (j0+1)%f_verts
|
|
if uv_dir != 0:
|
|
uv0 = me.uv_layers[0].data[count+j0].uv
|
|
uv1 = me.uv_layers[0].data[count+j1].uv
|
|
delta_uv = (uv1-uv0).normalized()
|
|
delta_uv.x = abs(delta_uv.x)
|
|
delta_uv.y = abs(delta_uv.y)
|
|
dir = uv_vec.dot(delta_uv)
|
|
else:
|
|
dir = 1
|
|
#dir = abs(dir)
|
|
#uv_factor.append(dir)
|
|
edge_key = [f.vertices[j0], f.vertices[j1]]
|
|
edge_key.sort()
|
|
uv_factor[tuple(edge_key)] = dir
|
|
count += f_verts
|
|
id0 = []
|
|
id1 = []
|
|
uv_mult = []
|
|
for ek, val in uv_factor.items():
|
|
id0.append(ek[0])
|
|
id1.append(ek[1])
|
|
uv_mult.append(val)
|
|
id0 = np.array(id0)
|
|
id1 = np.array(id1)
|
|
uv_mult = np.array(uv_mult)
|
|
|
|
#edge_verts = np.array(edge_verts)
|
|
#arr = np.arange(n_edges)*2
|
|
|
|
#id0 = edge_verts[arr] # first vertex indices for each edge
|
|
#id1 = edge_verts[arr+1] # second vertex indices for each edge
|
|
for ii in range(iter):
|
|
lap = np.zeros(n_verts)
|
|
if uv_dir != 0:
|
|
lap0 = (values[id1] - values[id0])*uv_mult # laplacian increment for first vertex of each edge
|
|
else:
|
|
lap0 = (values[id1] - values[id0])
|
|
np.add.at(lap, id0, lap0)
|
|
np.add.at(lap, id1, -lap0)
|
|
values += diff*lap
|
|
return values
|
|
|
|
def mesh_diffusion_vector(me, vectors, iter, diff, uv_dir=0):
|
|
vectors = np.array(vectors)
|
|
x = vectors[:,0]
|
|
y = vectors[:,1]
|
|
z = vectors[:,2]
|
|
x = mesh_diffusion(me, x, iter, diff, uv_dir)
|
|
y = mesh_diffusion(me, y, iter, diff, uv_dir)
|
|
z = mesh_diffusion(me, z, iter, diff, uv_dir)
|
|
vectors[:,0] = x
|
|
vectors[:,1] = y
|
|
vectors[:,2] = z
|
|
return vectors
|
|
|
|
# ------------------------------------------------------------------
|
|
# MODIFIERS
|
|
# ------------------------------------------------------------------
|
|
|
|
def mod_preserve_topology(mod):
|
|
same_topology_modifiers = ('DATA_TRANSFER','NORMAL_EDIT','WEIGHTED_NORMAL',
|
|
'UV_PROJECT','UV_WARP','VERTEX_WEIGHT_EDIT','VERTEX_WEIGHT_MIX',
|
|
'VERTEX_WEIGHT_PROXIMITY','ARMATURE','CAST','CURVE','DISPLACE','HOOK',
|
|
'LAPLACIANDEFORM','LATTICE','MESH_DEFORM','SHRINKWRAP','SIMPLE_DEFORM',
|
|
'SMOOTH','CORRECTIVE_SMOOTH','LAPLACIANSMOOTH','SURFACE_DEFORM','WARP',
|
|
'WAVE','CLOTH','COLLISION','DYNAMIC_PAINT','SOFT_BODY'
|
|
)
|
|
return mod.type in same_topology_modifiers
|
|
|
|
def mod_preserve_shape(mod):
|
|
same_shape_modifiers = ('DATA_TRANSFER','NORMAL_EDIT','WEIGHTED_NORMAL',
|
|
'UV_PROJECT','UV_WARP','VERTEX_WEIGHT_EDIT','VERTEX_WEIGHT_MIX',
|
|
'VERTEX_WEIGHT_PROXIMITY','DYNAMIC_PAINT'
|
|
)
|
|
return mod.type in same_shape_modifiers
|
|
|
|
|
|
def recurLayerCollection(layerColl, collName):
|
|
'''
|
|
Recursively transverse layer_collection for a particular name.
|
|
'''
|
|
found = None
|
|
if (layerColl.name == collName):
|
|
return layerColl
|
|
for layer in layerColl.children:
|
|
found = recurLayerCollection(layer, collName)
|
|
if found:
|
|
return found
|
|
|
|
def auto_layer_collection():
|
|
'''
|
|
Automatically change active layer collection.
|
|
'''
|
|
layer = bpy.context.view_layer.active_layer_collection
|
|
layer_collection = bpy.context.view_layer.layer_collection
|
|
if layer.hide_viewport or layer.collection.hide_viewport:
|
|
collections = bpy.context.object.users_collection
|
|
for c in collections:
|
|
lc = recurLayerCollection(layer_collection, c.name)
|
|
if not c.hide_viewport and not lc.hide_viewport:
|
|
bpy.context.view_layer.active_layer_collection = lc
|