4666 lines
170 KiB
Python
4666 lines
170 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#-------------------------- COLORS / GROUPS EXCHANGER -------------------------#
|
|
# #
|
|
# Vertex Color to Vertex Group allow you to convert colors channels to weight #
|
|
# maps. #
|
|
# The main purpose is to use vertex colors to store information when importing #
|
|
# files from other software. The script works with the active vertex color #
|
|
# slot. #
|
|
# For use the command "Vertex Clors to Vertex Groups" use the search bar #
|
|
# (space bar). #
|
|
# #
|
|
# (c) Alessandro Zomparelli #
|
|
# (2017) #
|
|
# #
|
|
# http://www.co-de-it.com/ #
|
|
# #
|
|
################################################################################
|
|
|
|
import bpy, bmesh, os
|
|
import numpy as np
|
|
import math, timeit, time
|
|
from math import pi
|
|
from statistics import mean, stdev
|
|
from mathutils import Vector
|
|
from mathutils.kdtree import KDTree
|
|
from numpy import *
|
|
try: from .numba_functions import numba_reaction_diffusion, numba_reaction_diffusion_anisotropic, integrate_field
|
|
except: pass
|
|
#from .numba_functions import integrate_field
|
|
#from .numba_functions import numba_reaction_diffusion
|
|
try: import numexpr as ne
|
|
except: pass
|
|
|
|
# Reaction-Diffusion cache
|
|
from pathlib import Path
|
|
import random as rnd
|
|
import string
|
|
|
|
from bpy.types import (
|
|
Operator,
|
|
Panel,
|
|
PropertyGroup,
|
|
)
|
|
|
|
from bpy.props import (
|
|
BoolProperty,
|
|
EnumProperty,
|
|
FloatProperty,
|
|
IntProperty,
|
|
StringProperty,
|
|
FloatVectorProperty,
|
|
IntVectorProperty
|
|
)
|
|
|
|
from .utils import *
|
|
|
|
def reaction_diffusion_add_handler(self, context):
|
|
# remove existing handlers
|
|
reaction_diffusion_remove_handler(self, context)
|
|
# add new handler
|
|
bpy.app.handlers.frame_change_post.append(reaction_diffusion_scene)
|
|
|
|
def reaction_diffusion_remove_handler(self, context):
|
|
# remove existing handlers
|
|
old_handlers = []
|
|
for h in bpy.app.handlers.frame_change_post:
|
|
if "reaction_diffusion" in str(h):
|
|
old_handlers.append(h)
|
|
for h in old_handlers: bpy.app.handlers.frame_change_post.remove(h)
|
|
|
|
class formula_prop(PropertyGroup):
|
|
name : StringProperty()
|
|
formula : StringProperty()
|
|
float_var : FloatVectorProperty(name="", description="", default=(0, 0, 0, 0, 0), size=5)
|
|
int_var : IntVectorProperty(name="", description="", default=(0, 0, 0, 0, 0), size=5)
|
|
|
|
class reaction_diffusion_prop(PropertyGroup):
|
|
run : BoolProperty(default=False, update = reaction_diffusion_add_handler,
|
|
description='Compute a new iteration on frame changes. Currently is not working during Render Animation')
|
|
|
|
time_steps : IntProperty(
|
|
name="Steps", default=10, min=0, soft_max=50,
|
|
description="Number of Steps")
|
|
|
|
dt : FloatProperty(
|
|
name="dt", default=1, min=0, soft_max=0.2,
|
|
description="Time Step")
|
|
|
|
diff_a : FloatProperty(
|
|
name="Diff A", default=0.1, min=0, soft_max=2, precision=3,
|
|
description="Diffusion A")
|
|
|
|
diff_b : FloatProperty(
|
|
name="Diff B", default=0.05, min=0, soft_max=2, precision=3,
|
|
description="Diffusion B")
|
|
|
|
f : FloatProperty(
|
|
name="f", default=0.055, soft_min=0.01, soft_max=0.06, precision=4, step=0.05,
|
|
description="Feed Rate")
|
|
|
|
k : FloatProperty(
|
|
name="k", default=0.062, soft_min=0.035, soft_max=0.065, precision=4, step=0.05,
|
|
description="Kill Rate")
|
|
|
|
diff_mult : FloatProperty(
|
|
name="Scale", default=1, min=0, soft_max=1, max=10, precision=2,
|
|
description="Multiplier for the diffusion of both substances")
|
|
|
|
vertex_group_diff_a : StringProperty(
|
|
name="Diff A", default='',
|
|
description="Vertex Group used for A diffusion")
|
|
|
|
vertex_group_diff_b : StringProperty(
|
|
name="Diff B", default='',
|
|
description="Vertex Group used for B diffusion")
|
|
|
|
vertex_group_scale : StringProperty(
|
|
name="Scale", default='',
|
|
description="Vertex Group used for Scale value")
|
|
|
|
vertex_group_f : StringProperty(
|
|
name="f", default='',
|
|
description="Vertex Group used for Feed value (f)")
|
|
|
|
vertex_group_k : StringProperty(
|
|
name="k", default='',
|
|
description="Vertex Group used for Kill value (k)")
|
|
|
|
vertex_group_brush : StringProperty(
|
|
name="Brush", default='',
|
|
description="Vertex Group used for adding/removing B")
|
|
|
|
invert_vertex_group_diff_a : BoolProperty(default=False,
|
|
description='Inverte the value of the Vertex Group Diff A')
|
|
|
|
invert_vertex_group_diff_b : BoolProperty(default=False,
|
|
description='Inverte the value of the Vertex Group Diff B')
|
|
|
|
invert_vertex_group_scale : BoolProperty(default=False,
|
|
description='Inverte the value of the Vertex Group Scale')
|
|
|
|
invert_vertex_group_f : BoolProperty(default=False,
|
|
description='Inverte the value of the Vertex Group f')
|
|
|
|
invert_vertex_group_k : BoolProperty(default=False,
|
|
description='Inverte the value of the Vertex Group k')
|
|
|
|
min_diff_a : FloatProperty(
|
|
name="Min Diff A", default=0.1, min=0, soft_max=2, precision=3,
|
|
description="Min Diff A")
|
|
|
|
max_diff_a : FloatProperty(
|
|
name="Max Diff A", default=0.1, min=0, soft_max=2, precision=3,
|
|
description="Max Diff A")
|
|
|
|
min_diff_b : FloatProperty(
|
|
name="Min Diff B", default=0.1, min=0, soft_max=2, precision=3,
|
|
description="Min Diff B")
|
|
|
|
max_diff_b : FloatProperty(
|
|
name="Max Diff B", default=0.1, min=0, soft_max=2, precision=3,
|
|
description="Max Diff B")
|
|
|
|
min_scale : FloatProperty(
|
|
name="Scale", default=0.35, min=0, soft_max=1, max=10, precision=2,
|
|
description="Min Scale Value")
|
|
|
|
max_scale : FloatProperty(
|
|
name="Scale", default=1, min=0, soft_max=1, max=10, precision=2,
|
|
description="Max Scale value")
|
|
|
|
min_f : FloatProperty(
|
|
name="Min f", default=0.02, min=0, soft_min=0.01, soft_max=0.06, max=0.1, precision=4, step=0.05,
|
|
description="Min Feed Rate")
|
|
|
|
max_f : FloatProperty(
|
|
name="Max f", default=0.055, min=0, soft_min=0.01, soft_max=0.06, max=0.1, precision=4, step=0.05,
|
|
description="Max Feed Rate")
|
|
|
|
min_k : FloatProperty(
|
|
name="Min k", default=0.035, min=0, soft_min=0.035, soft_max=0.065, max=0.1, precision=4, step=0.05,
|
|
description="Min Kill Rate")
|
|
|
|
max_k : FloatProperty(
|
|
name="Max k", default=0.062, min=0, soft_min=0.035, soft_max=0.065, max=0.1, precision=4, step=0.05,
|
|
description="Max Kill Rate")
|
|
|
|
brush_mult : FloatProperty(
|
|
name="Mult", default=0.5, min=-1, max=1, precision=3, step=0.05,
|
|
description="Multiplier for brush value")
|
|
|
|
bool_mod : BoolProperty(
|
|
name="Use Modifiers", default=False,
|
|
description="Read modifiers affect the vertex groups")
|
|
|
|
bool_cache : BoolProperty(
|
|
name="Use Cache", default=False,
|
|
description="Read modifiers affect the vertex groups")
|
|
|
|
cache_frame_start : IntProperty(
|
|
name="Start", default=1,
|
|
description="Frame on which the simulation starts")
|
|
|
|
cache_frame_end : IntProperty(
|
|
name="End", default=250,
|
|
description="Frame on which the simulation ends")
|
|
|
|
cache_dir : StringProperty(
|
|
name="Cache directory", default="", subtype='FILE_PATH',
|
|
description = 'Directory that contains Reaction-Diffusion cache files'
|
|
)
|
|
|
|
update_weight_a : BoolProperty(
|
|
name="Update Vertex Group A", default=True,
|
|
description="Transfer Cache to the Vertex Groups named A")
|
|
|
|
update_weight_b : BoolProperty(
|
|
name="Update Vertex Group B", default=True,
|
|
description="Transfer Cache to the Vertex Groups named B")
|
|
|
|
update_colors_a : BoolProperty(
|
|
name="Update Vertex Color A", default=False,
|
|
description="Transfer Cache to the Vertex Color named A")
|
|
|
|
update_colors_b : BoolProperty(
|
|
name="Update Vertex Color B", default=False,
|
|
description="Transfer Cache to the Vertex Color named B")
|
|
|
|
update_colors : BoolProperty(
|
|
name="Update Vertex Color AB", default=False,
|
|
description="Transfer Cache to the Vertex Color named AB")
|
|
|
|
update_uv : BoolProperty(
|
|
name="Update UV", default=False,
|
|
description="Transfer Cache to the UV Map Layer named AB")
|
|
|
|
normalize : BoolProperty(
|
|
name="Normalize values", default=False,
|
|
description="Normalize values from 0 to 1")
|
|
|
|
fast_bake : BoolProperty(
|
|
name="Fast Bake", default=True,
|
|
description="Do not update modifiers or vertex groups while baking. Much faster!")
|
|
|
|
|
|
from numpy import *
|
|
def compute_formula(ob=None, formula="rx", float_var=(0,0,0,0,0), int_var=(0,0,0,0,0)):
|
|
verts = ob.data.vertices
|
|
n_verts = len(verts)
|
|
|
|
f1,f2,f3,f4,f5 = float_var
|
|
i1,i2,i3,i4,i5 = int_var
|
|
|
|
do_groups = "w[" in formula
|
|
do_local = "lx" in formula or "ly" in formula or "lz" in formula
|
|
do_global = "gx" in formula or "gy" in formula or "gz" in formula
|
|
do_relative = "rx" in formula or "ry" in formula or "rz" in formula
|
|
do_normal = "nx" in formula or "ny" in formula or "nz" in formula
|
|
mat = ob.matrix_world
|
|
|
|
for i in range(1000):
|
|
if "w["+str(i)+"]" in formula and i > len(ob.vertex_groups)-1:
|
|
return "w["+str(i)+"] not found"
|
|
|
|
w = []
|
|
for i in range(len(ob.vertex_groups)):
|
|
w.append([])
|
|
if "w["+str(i)+"]" in formula:
|
|
vg = ob.vertex_groups[i]
|
|
for v in verts:
|
|
try:
|
|
w[i].append(vg.weight(v.index))
|
|
except:
|
|
w[i].append(0)
|
|
w[i] = array(w[i])
|
|
|
|
start_time = timeit.default_timer()
|
|
# compute vertex coordinates
|
|
if do_local or do_relative or do_global:
|
|
co = [0]*n_verts*3
|
|
verts.foreach_get('co', co)
|
|
np_co = array(co).reshape((n_verts, 3))
|
|
lx, ly, lz = array(np_co).transpose()
|
|
if do_relative:
|
|
rx = np.interp(lx, (lx.min(), lx.max()), (0, +1))
|
|
ry = np.interp(ly, (ly.min(), ly.max()), (0, +1))
|
|
rz = np.interp(lz, (lz.min(), lz.max()), (0, +1))
|
|
if do_global:
|
|
co = [v.co for v in verts]
|
|
global_co = []
|
|
for v in co:
|
|
global_co.append(mat @ v)
|
|
global_co = array(global_co).reshape((n_verts, 3))
|
|
gx, gy, gz = array(global_co).transpose()
|
|
# compute vertex normals
|
|
if do_normal:
|
|
normal = [0]*n_verts*3
|
|
verts.foreach_get('normal', normal)
|
|
normal = array(normal).reshape((n_verts, 3))
|
|
nx, ny, nz = array(normal).transpose()
|
|
|
|
try:
|
|
weight = eval(formula)
|
|
return weight
|
|
except:
|
|
return "There is something wrong"
|
|
print("Weight Formula: " + str(timeit.default_timer() - start_time))
|
|
|
|
class weight_formula_wiki(Operator):
|
|
bl_idname = "scene.weight_formula_wiki"
|
|
bl_label = "Online Documentation"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
bpy.ops.wm.url_open(url="https://github.com/alessandro-zomparelli/tissue/wiki/Weight-Tools#weight-formula")
|
|
return {'FINISHED'}
|
|
|
|
class weight_formula(Operator):
|
|
bl_idname = "object.weight_formula"
|
|
bl_label = "Weight Formula"
|
|
bl_description = "Generate a Vertex Group according to a mathematical formula"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
ex_items = [
|
|
('cos(arctan(nx/ny)*i1*2 + sin(rz*i3))/i2 + cos(arctan(nx/ny)*i1*2 - sin(rz*i3))/i2 + 0.5','Vertical Spots'),
|
|
('cos(arctan(nx/ny)*i1*2 + sin(rz*i2))/2 + cos(arctan(nx/ny)*i1*2 - sin(rz*i2))/2','Vertical Spots'),
|
|
('(sin(arctan(nx/ny)*i1*2)*sin(nz*i1*2)+1)/2','Grid Spots'),
|
|
('cos(arctan(nx/ny)*f1)','Vertical Stripes'),
|
|
('cos(arctan(lx/ly)*f1 + sin(rz*f2)*f3)','Curly Stripes'),
|
|
('sin(rz*pi*i1+arctan2(nx,ny))/2+0.5', 'Vertical Spiral'),
|
|
('sin(nx*15)<sin(ny*15)','Chess'),
|
|
('cos(ny*rz**2*i1)','Hyperbolic'),
|
|
('sin(rx*30) > 0','Step Stripes'),
|
|
('sin(nz*i1)','Normal Stripes'),
|
|
('w[0]**2','Vertex Group square'),
|
|
('abs(0.5-rz)*2','Double vertical gradient'),
|
|
('rz', 'Vertical Gradient')
|
|
]
|
|
_ex_items = list((str(i),'{} ( {} )'.format(s[0],s[1]),s[1]) for i,s in enumerate(ex_items))
|
|
_ex_items.append(('CUSTOM', "User Formula", ""))
|
|
|
|
examples : EnumProperty(
|
|
items = _ex_items, default='CUSTOM', name="Examples")
|
|
|
|
old_ex = ""
|
|
|
|
formula : StringProperty(
|
|
name="Formula", default="", description="Formula to Evaluate")
|
|
|
|
slider_f01 : FloatProperty(
|
|
name="f1", default=1, description="Slider Float 1")
|
|
slider_f02 : FloatProperty(
|
|
name="f2", default=1, description="Slider Float 2")
|
|
slider_f03 : FloatProperty(
|
|
name="f3", default=1, description="Slider Float 3")
|
|
slider_f04 : FloatProperty(
|
|
name="f4", default=1, description="Slider Float 4")
|
|
slider_f05 : FloatProperty(
|
|
name="f5", default=1, description="Slider Float 5")
|
|
slider_i01 : IntProperty(
|
|
name="i1", default=1, description="Slider Integer 1")
|
|
slider_i02 : IntProperty(
|
|
name="i2", default=1, description="Slider Integer 2")
|
|
slider_i03 : IntProperty(
|
|
name="i3", default=1, description="Slider Integer 3")
|
|
slider_i04 : IntProperty(
|
|
name="i4", default=1, description="Slider Integer 4")
|
|
slider_i05 : IntProperty(
|
|
name="i5", default=1, description="Slider Integer 5")
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_props_dialog(self, width=350)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
#layout.label(text="Examples")
|
|
layout.prop(self, "examples", text="Examples")
|
|
#if self.examples == 'CUSTOM':
|
|
layout.label(text="Formula")
|
|
layout.prop(self, "formula", text="")
|
|
#try: self.examples = self.formula
|
|
#except: pass
|
|
|
|
if self.examples != 'CUSTOM':
|
|
example = self.ex_items[int(self.examples)][0]
|
|
if example != self.old_ex:
|
|
self.formula = example
|
|
self.old_ex = example
|
|
elif self.formula != example:
|
|
self.examples = 'CUSTOM'
|
|
formula = self.formula
|
|
|
|
layout.separator()
|
|
if "f1" in formula: layout.prop(self, "slider_f01")
|
|
if "f2" in formula: layout.prop(self, "slider_f02")
|
|
if "f3" in formula: layout.prop(self, "slider_f03")
|
|
if "f4" in formula: layout.prop(self, "slider_f04")
|
|
if "f5" in formula: layout.prop(self, "slider_f05")
|
|
if "i1" in formula: layout.prop(self, "slider_i01")
|
|
if "i2" in formula: layout.prop(self, "slider_i02")
|
|
if "i3" in formula: layout.prop(self, "slider_i03")
|
|
if "i4" in formula: layout.prop(self, "slider_i04")
|
|
if "i5" in formula: layout.prop(self, "slider_i05")
|
|
|
|
layout.label(text="Variables (for each vertex):")
|
|
layout.label(text="lx, ly, lz: Local Coordinates", icon='ORIENTATION_LOCAL')
|
|
layout.label(text="gx, gy, gz: Global Coordinates", icon='WORLD')
|
|
layout.label(text="rx, ry, rz: Local Coordinates (0 to 1)", icon='NORMALIZE_FCURVES')
|
|
layout.label(text="nx, ny, nz: Normal Coordinates", icon='SNAP_NORMAL')
|
|
layout.label(text="w[0], w[1], w[2], ... : Vertex Groups", icon="GROUP_VERTEX")
|
|
layout.separator()
|
|
layout.label(text="f1, f2, f3, f4, f5: Float Sliders", icon='MOD_HUE_SATURATION')#PROPERTIES
|
|
layout.label(text="i1, i2, i3, i4, i5: Integer Sliders", icon='MOD_HUE_SATURATION')
|
|
layout.separator()
|
|
#layout.label(text="All mathematical functions are based on Numpy", icon='INFO')
|
|
#layout.label(text="https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.math.html", icon='INFO')
|
|
layout.operator("scene.weight_formula_wiki", icon="HELP")
|
|
#layout.label(text="(where 'i' is the index of the Vertex Group)")
|
|
|
|
def execute(self, context):
|
|
ob = context.active_object
|
|
n_verts = len(ob.data.vertices)
|
|
#if self.examples == 'CUSTOM':
|
|
# formula = self.formula
|
|
#else:
|
|
#self.formula = self.examples
|
|
# formula = self.examples
|
|
|
|
#f1, f2, f3, f4, f5 = self.slider_f01, self.slider_f02, self.slider_f03, self.slider_f04, self.slider_f05
|
|
#i1, i2, i3, i4, i5 = self.slider_i01, self.slider_i02, self.slider_i03, self.slider_i04, self.slider_i05
|
|
f_sliders = self.slider_f01, self.slider_f02, self.slider_f03, self.slider_f04, self.slider_f05
|
|
i_sliders = self.slider_i01, self.slider_i02, self.slider_i03, self.slider_i04, self.slider_i05
|
|
|
|
if self.examples != 'CUSTOM':
|
|
example = self.ex_items[int(self.examples)][0]
|
|
if example != self.old_ex:
|
|
self.formula = example
|
|
self.old_ex = example
|
|
elif self.formula != example:
|
|
self.examples = 'CUSTOM'
|
|
formula = self.formula
|
|
|
|
if formula == "": return {'FINISHED'}
|
|
# replace numeric sliders value
|
|
for i, slider in enumerate(f_sliders):
|
|
formula = formula.replace('f'+str(i+1),"{0:.2f}".format(slider))
|
|
for i, slider in enumerate(i_sliders):
|
|
formula =formula.replace('i'+str(i+1),str(slider))
|
|
vertex_group_name = "" + formula
|
|
ob.vertex_groups.new(name=vertex_group_name)
|
|
|
|
weight = compute_formula(ob, formula=formula, float_var=f_sliders, int_var=i_sliders)
|
|
if type(weight) == str:
|
|
self.report({'ERROR'}, weight)
|
|
return {'CANCELLED'}
|
|
|
|
#start_time = timeit.default_timer()
|
|
weight = nan_to_num(weight)
|
|
vg = ob.vertex_groups[-1]
|
|
if type(weight) == int or type(weight) == float:
|
|
for i in range(n_verts):
|
|
vg.add([i], weight, 'REPLACE')
|
|
elif type(weight) == ndarray:
|
|
for i in range(n_verts):
|
|
vg.add([i], weight[i], 'REPLACE')
|
|
ob.data.update()
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
|
|
# Store formula settings
|
|
new_formula = ob.formula_settings.add()
|
|
new_formula.name = ob.vertex_groups[-1].name
|
|
new_formula.formula = formula
|
|
new_formula.int_var = i_sliders
|
|
new_formula.float_var = f_sliders
|
|
|
|
#for f in ob.formula_settings:
|
|
# print(f.name, f.formula, f.int_var, f.float_var)
|
|
return {'FINISHED'}
|
|
|
|
|
|
class update_weight_formula(Operator):
|
|
bl_idname = "object.update_weight_formula"
|
|
bl_label = "Update Weight Formula"
|
|
bl_description = "Update an existing Vertex Group. Make sure that the name\nof the active Vertex Group is a valid formula"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.object.vertex_groups) > 0
|
|
|
|
def execute(self, context):
|
|
ob = context.active_object
|
|
n_verts = len(ob.data.vertices)
|
|
|
|
vg = ob.vertex_groups.active
|
|
formula = vg.name
|
|
weight = compute_formula(ob, formula=formula)
|
|
if type(weight) == str:
|
|
self.report({'ERROR'}, "The name of the active Vertex Group\nis not a valid Formula")
|
|
return {'CANCELLED'}
|
|
|
|
#start_time = timeit.default_timer()
|
|
weight = nan_to_num(weight)
|
|
if type(weight) == int or type(weight) == float:
|
|
for i in range(n_verts):
|
|
vg.add([i], weight, 'REPLACE')
|
|
elif type(weight) == ndarray:
|
|
for i in range(n_verts):
|
|
vg.add([i], weight[i], 'REPLACE')
|
|
ob.data.update()
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
return {'FINISHED'}
|
|
|
|
|
|
class _weight_laplacian(Operator):
|
|
bl_idname = "object._weight_laplacian"
|
|
bl_label = "Weight Laplacian"
|
|
bl_description = ("Compute the Vertex Group Laplacian")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
bounds : EnumProperty(
|
|
items=(('MANUAL', "Manual Bounds", ""),
|
|
('POSITIVE', "Positive Only", ""),
|
|
('NEGATIVE', "Negative Only", ""),
|
|
('AUTOMATIC', "Automatic Bounds", "")),
|
|
default='AUTOMATIC', name="Bounds")
|
|
|
|
mode : EnumProperty(
|
|
items=(('LENGTH', "Length Weight", ""),
|
|
('SIMPLE', "Simple", "")),
|
|
default='SIMPLE', name="Evaluation Mode")
|
|
|
|
min_def : FloatProperty(
|
|
name="Min", default=0, soft_min=-1, soft_max=0,
|
|
description="Laplacian value with 0 weight")
|
|
|
|
max_def : FloatProperty(
|
|
name="Max", default=0.5, soft_min=0, soft_max=5,
|
|
description="Laplacian value with 1 weight")
|
|
|
|
bounds_string = ""
|
|
|
|
frame = None
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.object.vertex_groups) > 0
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
col = layout.column(align=True)
|
|
col.label(text="Evaluation Mode")
|
|
col.prop(self, "mode", text="")
|
|
col.label(text="Bounds")
|
|
col.prop(self, "bounds", text="")
|
|
if self.bounds == 'MANUAL':
|
|
col.label(text="Strain Rate \u03B5:")
|
|
col.prop(self, "min_def")
|
|
col.prop(self, "max_def")
|
|
col.label(text="\u03B5" + ": from " + self.bounds_string)
|
|
|
|
|
|
def execute(self, context):
|
|
try: ob = context.object
|
|
except:
|
|
self.report({'ERROR'}, "Please select an Object")
|
|
return {'CANCELLED'}
|
|
|
|
group_id = ob.vertex_groups.active_index
|
|
input_group = ob.vertex_groups[group_id].name
|
|
|
|
group_name = "Laplacian"
|
|
ob.vertex_groups.new(name=group_name)
|
|
me = ob.data
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me)
|
|
bm.edges.ensure_lookup_table()
|
|
|
|
# store weight values
|
|
weight = []
|
|
for v in me.vertices:
|
|
try:
|
|
weight.append(ob.vertex_groups[input_group].weight(v.index))
|
|
except:
|
|
weight.append(0)
|
|
|
|
n_verts = len(bm.verts)
|
|
lap = [0]*n_verts
|
|
for e in bm.edges:
|
|
if self.mode == 'LENGTH':
|
|
length = e.calc_length()
|
|
if length == 0: continue
|
|
id0 = e.verts[0].index
|
|
id1 = e.verts[1].index
|
|
lap[id0] += weight[id1]/length - weight[id0]/length
|
|
lap[id1] += weight[id0]/length - weight[id1]/length
|
|
else:
|
|
id0 = e.verts[0].index
|
|
id1 = e.verts[1].index
|
|
lap[id0] += weight[id1] - weight[id0]
|
|
lap[id1] += weight[id0] - weight[id1]
|
|
|
|
mean_lap = mean(lap)
|
|
stdev_lap = stdev(lap)
|
|
filter_lap = [i for i in lap if mean_lap-2*stdev_lap < i < mean_lap+2*stdev_lap]
|
|
if self.bounds == 'MANUAL':
|
|
min_def = self.min_def
|
|
max_def = self.max_def
|
|
elif self.bounds == 'AUTOMATIC':
|
|
min_def = min(filter_lap)
|
|
max_def = max(filter_lap)
|
|
self.min_def = min_def
|
|
self.max_def = max_def
|
|
elif self.bounds == 'NEGATIVE':
|
|
min_def = 0
|
|
max_def = min(filter_lap)
|
|
self.min_def = min_def
|
|
self.max_def = max_def
|
|
elif self.bounds == 'POSITIVE':
|
|
min_def = 0
|
|
max_def = max(filter_lap)
|
|
self.min_def = min_def
|
|
self.max_def = max_def
|
|
delta_def = max_def - min_def
|
|
|
|
# check undeformed errors
|
|
if delta_def == 0: delta_def = 0.0001
|
|
|
|
for i in range(len(lap)):
|
|
val = (lap[i]-min_def)/delta_def
|
|
if val > 0.7: print(str(val) + " " + str(lap[i]))
|
|
#val = weight[i] + 0.2*lap[i]
|
|
ob.vertex_groups[-1].add([i], val, 'REPLACE')
|
|
self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
|
|
ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
|
|
ob.vertex_groups.update()
|
|
ob.data.update()
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
bm.free()
|
|
return {'FINISHED'}
|
|
|
|
class ok_weight_laplacian(Operator):
|
|
bl_idname = "object.weight_laplacian"
|
|
bl_label = "Weight Laplacian"
|
|
bl_description = ("Compute the Vertex Group Laplacian")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
bounds_string = ""
|
|
|
|
frame = None
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.object.vertex_groups) > 0
|
|
|
|
|
|
def execute(self, context):
|
|
try: ob = context.object
|
|
except:
|
|
self.report({'ERROR'}, "Please select an Object")
|
|
return {'CANCELLED'}
|
|
|
|
me = ob.data
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me)
|
|
bm.edges.ensure_lookup_table()
|
|
|
|
group_id = ob.vertex_groups.active_index
|
|
input_group = ob.vertex_groups[group_id].name
|
|
|
|
group_name = "Laplacian"
|
|
ob.vertex_groups.new(name=group_name)
|
|
|
|
# store weight values
|
|
a = []
|
|
for v in me.vertices:
|
|
try:
|
|
a.append(ob.vertex_groups[input_group].weight(v.index))
|
|
except:
|
|
a.append(0)
|
|
|
|
a = array(a)
|
|
|
|
|
|
# initialize
|
|
n_verts = len(bm.verts)
|
|
# find max number of edges for vertex
|
|
max_edges = 0
|
|
n_neighbors = []
|
|
id_neighbors = []
|
|
for v in bm.verts:
|
|
n_edges = len(v.link_edges)
|
|
max_edges = max(max_edges, n_edges)
|
|
n_neighbors.append(n_edges)
|
|
neighbors = []
|
|
for e in v.link_edges:
|
|
for v1 in e.verts:
|
|
if v != v1: neighbors.append(v1.index)
|
|
id_neighbors.append(neighbors)
|
|
n_neighbors = array(n_neighbors)
|
|
|
|
|
|
lap_map = [[] for i in range(n_verts)]
|
|
#lap_map = []
|
|
'''
|
|
for e in bm.edges:
|
|
id0 = e.verts[0].index
|
|
id1 = e.verts[1].index
|
|
lap_map[id0].append(id1)
|
|
lap_map[id1].append(id0)
|
|
'''
|
|
lap = zeros((n_verts))#[0]*n_verts
|
|
n_records = zeros((n_verts))
|
|
for e in bm.edges:
|
|
id0 = e.verts[0].index
|
|
id1 = e.verts[1].index
|
|
length = e.calc_length()
|
|
if length == 0: continue
|
|
#lap[id0] += abs(a[id1] - a[id0])/length
|
|
#lap[id1] += abs(a[id0] - a[id1])/length
|
|
lap[id0] += (a[id1] - a[id0])/length
|
|
lap[id1] += (a[id0] - a[id1])/length
|
|
n_records[id0]+=1
|
|
n_records[id1]+=1
|
|
lap /= n_records
|
|
lap /= max(lap)
|
|
|
|
for i in range(n_verts):
|
|
ob.vertex_groups['Laplacian'].add([i], lap[i], 'REPLACE')
|
|
ob.vertex_groups.update()
|
|
ob.data.update()
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
bm.free()
|
|
return {'FINISHED'}
|
|
|
|
class weight_laplacian(Operator):
|
|
bl_idname = "object.weight_laplacian"
|
|
bl_label = "Weight Laplacian"
|
|
bl_description = ("Compute the Vertex Group Laplacian")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
bounds_string = ""
|
|
|
|
frame = None
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.object.vertex_groups) > 0
|
|
|
|
|
|
def execute(self, context):
|
|
try: ob = context.object
|
|
except:
|
|
self.report({'ERROR'}, "Please select an Object")
|
|
return {'CANCELLED'}
|
|
|
|
me = ob.data
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me)
|
|
bm.edges.ensure_lookup_table()
|
|
n_verts = len(me.vertices)
|
|
|
|
group_id = ob.vertex_groups.active_index
|
|
input_group = ob.vertex_groups[group_id].name
|
|
|
|
group_name = "Laplacian"
|
|
vg = ob.vertex_groups.new(name=group_name)
|
|
|
|
# store weight values
|
|
dvert_lay = bm.verts.layers.deform.active
|
|
weight = bmesh_get_weight_numpy(group_id, dvert_lay, bm.verts)
|
|
|
|
#verts, normals = get_vertices_and_normals_numpy(me)
|
|
|
|
#lap = zeros((n_verts))#[0]*n_verts
|
|
lap = [Vector((0,0,0)) for i in range(n_verts)]
|
|
n_records = zeros((n_verts))
|
|
for e in bm.edges:
|
|
vert0 = e.verts[0]
|
|
vert1 = e.verts[1]
|
|
id0 = vert0.index
|
|
id1 = vert1.index
|
|
v0 = vert0.co
|
|
v1 = vert1.co
|
|
v01 = v1-v0
|
|
v10 = -v01
|
|
v01 -= v01.project(vert0.normal)
|
|
v10 -= v10.project(vert1.normal)
|
|
length = e.calc_length()
|
|
if length == 0: continue
|
|
dw = (weight[id1] - weight[id0])/length
|
|
lap[id0] += v01.normalized() * dw
|
|
lap[id1] -= v10.normalized() * dw
|
|
n_records[id0]+=1
|
|
n_records[id1]+=1
|
|
#lap /= n_records[:,np.newaxis]
|
|
lap = [l.length/r for r,l in zip(n_records,lap)]
|
|
|
|
lap = np.array(lap)
|
|
lap /= np.max(lap)
|
|
lap = list(lap)
|
|
print(lap)
|
|
|
|
for i in range(n_verts):
|
|
vg.add([i], lap[i], 'REPLACE')
|
|
ob.vertex_groups.update()
|
|
ob.data.update()
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
bm.free()
|
|
return {'FINISHED'}
|
|
|
|
|
|
class reaction_diffusion(Operator):
|
|
bl_idname = "object.reaction_diffusion"
|
|
bl_label = "Reaction Diffusion"
|
|
bl_description = ("Run a Reaction-Diffusion based on existing Vertex Groups: A and B")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
steps : IntProperty(
|
|
name="Steps", default=10, min=0, soft_max=50,
|
|
description="Number of Steps")
|
|
|
|
dt : FloatProperty(
|
|
name="dt", default=0.2, min=0, soft_max=0.2,
|
|
description="Time Step")
|
|
|
|
diff_a : FloatProperty(
|
|
name="Diff A", default=1, min=0, soft_max=2,
|
|
description="Diffusion A")
|
|
|
|
diff_b : FloatProperty(
|
|
name="Diff B", default=0.5, min=0, soft_max=2,
|
|
description="Diffusion B")
|
|
|
|
f : FloatProperty(
|
|
name="f", default=0.055, min=0, soft_min=0.01, soft_max=0.06, max=0.1, precision=4,
|
|
description="Feed Rate")
|
|
|
|
k : FloatProperty(
|
|
name="k", default=0.062, min=0, soft_min=0.035, soft_max=0.065, max=0.1, precision=4,
|
|
description="Kill Rate")
|
|
|
|
bounds_string = ""
|
|
|
|
frame = None
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.object.vertex_groups) > 0
|
|
|
|
|
|
def execute(self, context):
|
|
#bpy.app.handlers.frame_change_post.remove(reaction_diffusion_def)
|
|
reaction_diffusion_add_handler(self, context)
|
|
set_animatable_fix_handler(self, context)
|
|
try: ob = context.object
|
|
except:
|
|
self.report({'ERROR'}, "Please select an Object")
|
|
return {'CANCELLED'}
|
|
|
|
me = ob.data
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me)
|
|
bm.edges.ensure_lookup_table()
|
|
|
|
# store weight values
|
|
a = []
|
|
b = []
|
|
for v in me.vertices:
|
|
try:
|
|
a.append(ob.vertex_groups["A"].weight(v.index))
|
|
except:
|
|
a.append(0)
|
|
try:
|
|
b.append(ob.vertex_groups["B"].weight(v.index))
|
|
except:
|
|
b.append(0)
|
|
|
|
a = array(a)
|
|
b = array(b)
|
|
f = self.f
|
|
k = self.k
|
|
diff_a = self.diff_a
|
|
diff_b = self.diff_b
|
|
dt = self.dt
|
|
n_verts = len(bm.verts)
|
|
|
|
for i in range(self.steps):
|
|
|
|
lap_a = zeros((n_verts))#[0]*n_verts
|
|
lap_b = zeros((n_verts))#[0]*n_verts
|
|
for e in bm.edges:
|
|
id0 = e.verts[0].index
|
|
id1 = e.verts[1].index
|
|
lap_a[id0] += a[id1] - a[id0]
|
|
lap_a[id1] += a[id0] - a[id1]
|
|
lap_b[id0] += b[id1] - b[id0]
|
|
lap_b[id1] += b[id0] - b[id1]
|
|
ab2 = a*b**2
|
|
a += (diff_a*lap_a - ab2 + f*(1-a))*dt
|
|
b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
|
|
|
|
for i in range(n_verts):
|
|
ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
|
|
ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
|
|
ob.vertex_groups.update()
|
|
ob.data.update()
|
|
|
|
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
|
|
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
bm.free()
|
|
return {'FINISHED'}
|
|
|
|
|
|
class edges_deformation(Operator):
|
|
bl_idname = "object.edges_deformation"
|
|
bl_label = "Edges Deformation"
|
|
bl_description = ("Compute Weight based on the deformation of edges"+
|
|
"according to visible modifiers.")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
bounds : EnumProperty(
|
|
items=(('MANUAL', "Manual Bounds", ""),
|
|
('COMPRESSION', "Compressed Only", ""),
|
|
('TENSION', "Extended Only", ""),
|
|
('AUTOMATIC', "Automatic Bounds", "")),
|
|
default='AUTOMATIC', name="Bounds")
|
|
|
|
mode : EnumProperty(
|
|
items=(('MAX', "Max Deformation", ""),
|
|
('MEAN', "Average Deformation", "")),
|
|
default='MEAN', name="Evaluation Mode")
|
|
|
|
min_def : FloatProperty(
|
|
name="Min", default=0, soft_min=-1, soft_max=0,
|
|
description="Deformations with 0 weight")
|
|
|
|
max_def : FloatProperty(
|
|
name="Max", default=0.5, soft_min=0, soft_max=5,
|
|
description="Deformations with 1 weight")
|
|
|
|
bounds_string = ""
|
|
|
|
frame = None
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.object.modifiers) > 0
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
col = layout.column(align=True)
|
|
col.label(text="Evaluation Mode")
|
|
col.prop(self, "mode", text="")
|
|
col.label(text="Bounds")
|
|
col.prop(self, "bounds", text="")
|
|
if self.bounds == 'MANUAL':
|
|
col.label(text="Strain Rate \u03B5:")
|
|
col.prop(self, "min_def")
|
|
col.prop(self, "max_def")
|
|
col.label(text="\u03B5" + ": from " + self.bounds_string)
|
|
|
|
def execute(self, context):
|
|
try: ob = context.object
|
|
except:
|
|
self.report({'ERROR'}, "Please select an Object")
|
|
return {'CANCELLED'}
|
|
|
|
# check if the object is Cloth or Softbody
|
|
physics = False
|
|
for m in ob.modifiers:
|
|
if m.type == 'CLOTH' or m.type == 'SOFT_BODY':
|
|
physics = True
|
|
if context.scene.frame_current == 1 and self.frame != None:
|
|
context.scene.frame_current = self.frame
|
|
break
|
|
if not physics: self.frame = None
|
|
|
|
if self.mode == 'MEAN': group_name = "Average Deformation"
|
|
elif self.mode == 'MAX': group_name = "Max Deformation"
|
|
ob.vertex_groups.new(name=group_name)
|
|
me0 = ob.data
|
|
|
|
me = simple_to_mesh(ob) #ob.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
|
|
if len(me.vertices) != len(me0.vertices) or len(me.edges) != len(me0.edges):
|
|
self.report({'ERROR'}, "The topology of the object should be" +
|
|
"unaltered")
|
|
return {'CANCELLED'}
|
|
|
|
bm0 = bmesh.new()
|
|
bm0.from_mesh(me0)
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me)
|
|
deformations = []
|
|
for e0, e in zip(bm0.edges, bm.edges):
|
|
try:
|
|
l0 = e0.calc_length()
|
|
l1 = e.calc_length()
|
|
epsilon = (l1 - l0)/l0
|
|
deformations.append(epsilon)
|
|
except: deformations.append(1)
|
|
v_deformations = []
|
|
for v in bm.verts:
|
|
vdef = []
|
|
for e in v.link_edges:
|
|
vdef.append(deformations[e.index])
|
|
if self.mode == 'MEAN': v_deformations.append(mean(vdef))
|
|
elif self.mode == 'MAX': v_deformations.append(max(vdef, key=abs))
|
|
#elif self.mode == 'MIN': v_deformations.append(min(vdef, key=abs))
|
|
|
|
if self.bounds == 'MANUAL':
|
|
min_def = self.min_def
|
|
max_def = self.max_def
|
|
elif self.bounds == 'AUTOMATIC':
|
|
min_def = min(v_deformations)
|
|
max_def = max(v_deformations)
|
|
self.min_def = min_def
|
|
self.max_def = max_def
|
|
elif self.bounds == 'COMPRESSION':
|
|
min_def = 0
|
|
max_def = min(v_deformations)
|
|
self.min_def = min_def
|
|
self.max_def = max_def
|
|
elif self.bounds == 'TENSION':
|
|
min_def = 0
|
|
max_def = max(v_deformations)
|
|
self.min_def = min_def
|
|
self.max_def = max_def
|
|
delta_def = max_def - min_def
|
|
|
|
# check undeformed errors
|
|
if delta_def == 0:
|
|
if self.bounds == 'MANUAL':
|
|
delta_def = 0.0001
|
|
else:
|
|
message = "The object doesn't have deformations."
|
|
if physics:
|
|
message = message + ("\nIf you are using Physics try to " +
|
|
"save it in the cache before.")
|
|
self.report({'ERROR'}, message)
|
|
return {'CANCELLED'}
|
|
else:
|
|
if physics:
|
|
self.frame = context.scene.frame_current
|
|
|
|
for i in range(len(v_deformations)):
|
|
weight = (v_deformations[i] - min_def)/delta_def
|
|
ob.vertex_groups[-1].add([i], weight, 'REPLACE')
|
|
self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
|
|
ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
|
|
ob.vertex_groups.update()
|
|
ob.data.update()
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
bpy.data.meshes.remove(me)
|
|
bm.free()
|
|
bm0.free()
|
|
return {'FINISHED'}
|
|
|
|
class edges_bending(Operator):
|
|
bl_idname = "object.edges_bending"
|
|
bl_label = "Edges Bending"
|
|
bl_description = ("Compute Weight based on the bending of edges"+
|
|
"according to visible modifiers.")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
bounds : EnumProperty(
|
|
items=(('MANUAL', "Manual Bounds", ""),
|
|
('POSITIVE', "Positive Only", ""),
|
|
('NEGATIVE', "Negative Only", ""),
|
|
('UNSIGNED', "Absolute Bending", ""),
|
|
('AUTOMATIC', "Signed Bending", "")),
|
|
default='AUTOMATIC', name="Bounds")
|
|
|
|
min_def : FloatProperty(
|
|
name="Min", default=-10, soft_min=-45, soft_max=45,
|
|
description="Deformations with 0 weight")
|
|
|
|
max_def : FloatProperty(
|
|
name="Max", default=10, soft_min=-45, soft_max=45,
|
|
description="Deformations with 1 weight")
|
|
|
|
bounds_string = ""
|
|
frame = None
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.object.modifiers) > 0
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.label(text="Bounds")
|
|
layout.prop(self, "bounds", text="")
|
|
if self.bounds == 'MANUAL':
|
|
layout.prop(self, "min_def")
|
|
layout.prop(self, "max_def")
|
|
|
|
def execute(self, context):
|
|
try: ob = context.object
|
|
except:
|
|
self.report({'ERROR'}, "Please select an Object")
|
|
return {'CANCELLED'}
|
|
|
|
group_name = "Edges Bending"
|
|
ob.vertex_groups.new(name=group_name)
|
|
|
|
# check if the object is Cloth or Softbody
|
|
physics = False
|
|
for m in ob.modifiers:
|
|
if m.type == 'CLOTH' or m.type == 'SOFT_BODY':
|
|
physics = True
|
|
if context.scene.frame_current == 1 and self.frame != None:
|
|
context.scene.frame_current = self.frame
|
|
break
|
|
if not physics: self.frame = None
|
|
|
|
#ob.data.update()
|
|
#context.scene.update()
|
|
me0 = ob.data
|
|
me = simple_to_mesh(ob) #ob.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
|
|
if len(me.vertices) != len(me0.vertices) or len(me.edges) != len(me0.edges):
|
|
self.report({'ERROR'}, "The topology of the object should be" +
|
|
"unaltered")
|
|
bm0 = bmesh.new()
|
|
bm0.from_mesh(me0)
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me)
|
|
deformations = []
|
|
for e0, e in zip(bm0.edges, bm.edges):
|
|
try:
|
|
ang = e.calc_face_angle_signed()
|
|
ang0 = e0.calc_face_angle_signed()
|
|
if self.bounds == 'UNSIGNED':
|
|
deformations.append(abs(ang-ang0))
|
|
else:
|
|
deformations.append(ang-ang0)
|
|
except: deformations.append(0)
|
|
v_deformations = []
|
|
for v in bm.verts:
|
|
vdef = []
|
|
for e in v.link_edges:
|
|
vdef.append(deformations[e.index])
|
|
v_deformations.append(mean(vdef))
|
|
if self.bounds == 'MANUAL':
|
|
min_def = radians(self.min_def)
|
|
max_def = radians(self.max_def)
|
|
elif self.bounds == 'AUTOMATIC':
|
|
min_def = min(v_deformations)
|
|
max_def = max(v_deformations)
|
|
elif self.bounds == 'POSITIVE':
|
|
min_def = 0
|
|
max_def = min(v_deformations)
|
|
elif self.bounds == 'NEGATIVE':
|
|
min_def = 0
|
|
max_def = max(v_deformations)
|
|
elif self.bounds == 'UNSIGNED':
|
|
min_def = 0
|
|
max_def = max(v_deformations)
|
|
delta_def = max_def - min_def
|
|
|
|
# check undeformed errors
|
|
if delta_def == 0:
|
|
if self.bounds == 'MANUAL':
|
|
delta_def = 0.0001
|
|
else:
|
|
message = "The object doesn't have deformations."
|
|
if physics:
|
|
message = message + ("\nIf you are using Physics try to " +
|
|
"save it in the cache before.")
|
|
self.report({'ERROR'}, message)
|
|
return {'CANCELLED'}
|
|
else:
|
|
if physics:
|
|
self.frame = context.scene.frame_current
|
|
|
|
for i in range(len(v_deformations)):
|
|
weight = (v_deformations[i] - min_def)/delta_def
|
|
ob.vertex_groups[-1].add([i], weight, 'REPLACE')
|
|
self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
|
|
ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
|
|
ob.vertex_groups.update()
|
|
ob.data.update()
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
bpy.data.meshes.remove(me)
|
|
bm0.free()
|
|
bm.free()
|
|
return {'FINISHED'}
|
|
|
|
class weight_contour_displace(Operator):
|
|
bl_idname = "object.weight_contour_displace"
|
|
bl_label = "Contour Displace"
|
|
bl_description = ("")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
use_modifiers : BoolProperty(
|
|
name="Use Modifiers", default=True,
|
|
description="Apply all the modifiers")
|
|
min_iso : FloatProperty(
|
|
name="Min Iso Value", default=0.49, min=0, max=1,
|
|
description="Threshold value")
|
|
max_iso : FloatProperty(
|
|
name="Max Iso Value", default=0.51, min=0, max=1,
|
|
description="Threshold value")
|
|
n_cuts : IntProperty(
|
|
name="Cuts", default=2, min=1, soft_max=10,
|
|
description="Number of cuts in the selected range of values")
|
|
bool_displace : BoolProperty(
|
|
name="Add Displace", default=True, description="Add Displace Modifier")
|
|
bool_flip : BoolProperty(
|
|
name="Flip", default=False, description="Flip Output Weight")
|
|
|
|
weight_mode : EnumProperty(
|
|
items=[('Remapped', 'Remapped', 'Remap values'),
|
|
('Alternate', 'Alternate', 'Alternate 0 and 1'),
|
|
('Original', 'Original', 'Keep original Vertex Group')],
|
|
name="Weight", description="Choose how to convert vertex group",
|
|
default="Remapped", options={'LIBRARY_EDITABLE'})
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.object.vertex_groups) > 0
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_props_dialog(self, width=350)
|
|
|
|
def execute(self, context):
|
|
start_time = timeit.default_timer()
|
|
try:
|
|
check = context.object.vertex_groups[0]
|
|
except:
|
|
self.report({'ERROR'}, "The object doesn't have Vertex Groups")
|
|
return {'CANCELLED'}
|
|
|
|
ob0 = context.object
|
|
|
|
group_id = ob0.vertex_groups.active_index
|
|
vertex_group_name = ob0.vertex_groups[group_id].name
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
bpy.ops.mesh.select_all(action='SELECT')
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
if self.use_modifiers:
|
|
#me0 = ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
|
|
me0 = simple_to_mesh(ob0)
|
|
else:
|
|
me0 = ob0.data.copy()
|
|
|
|
# generate new bmesh
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me0)
|
|
bm.verts.ensure_lookup_table()
|
|
bm.edges.ensure_lookup_table()
|
|
bm.faces.ensure_lookup_table()
|
|
|
|
# store weight values
|
|
weight = []
|
|
ob = bpy.data.objects.new("temp", me0)
|
|
for g in ob0.vertex_groups:
|
|
ob.vertex_groups.new(name=g.name)
|
|
for v in me0.vertices:
|
|
try:
|
|
weight.append(ob.vertex_groups[vertex_group_name].weight(v.index))
|
|
except:
|
|
weight.append(0)
|
|
|
|
# define iso values
|
|
iso_values = []
|
|
for i_cut in range(self.n_cuts):
|
|
delta_iso = abs(self.max_iso - self.min_iso)
|
|
min_iso = min(self.min_iso, self.max_iso)
|
|
max_iso = max(self.min_iso, self.max_iso)
|
|
if delta_iso == 0: iso_val = min_iso
|
|
elif self.n_cuts > 1: iso_val = i_cut/(self.n_cuts-1)*delta_iso + min_iso
|
|
else: iso_val = (self.max_iso + self.min_iso)/2
|
|
iso_values.append(iso_val)
|
|
|
|
# Start Cuts Iterations
|
|
filtered_edges = bm.edges
|
|
for iso_val in iso_values:
|
|
delete_edges = []
|
|
|
|
faces_mask = []
|
|
for f in bm.faces:
|
|
w_min = 2
|
|
w_max = 2
|
|
for v in f.verts:
|
|
w = weight[v.index]
|
|
if w_min == 2:
|
|
w_max = w_min = w
|
|
if w > w_max: w_max = w
|
|
if w < w_min: w_min = w
|
|
if w_min < iso_val and w_max > iso_val:
|
|
faces_mask.append(f)
|
|
break
|
|
|
|
#link_faces = [[f for f in e.link_faces] for e in bm.edges]
|
|
|
|
#faces_todo = [f.select for f in bm.faces]
|
|
#faces_todo = [True for f in bm.faces]
|
|
verts = []
|
|
edges = []
|
|
edges_id = {}
|
|
_filtered_edges = []
|
|
n_verts = len(bm.verts)
|
|
count = n_verts
|
|
for e in filtered_edges:
|
|
#id0 = e.vertices[0]
|
|
#id1 = e.vertices[1]
|
|
id0 = e.verts[0].index
|
|
id1 = e.verts[1].index
|
|
w0 = weight[id0]
|
|
w1 = weight[id1]
|
|
|
|
if w0 == w1: continue
|
|
elif w0 > iso_val and w1 > iso_val:
|
|
_filtered_edges.append(e)
|
|
continue
|
|
elif w0 < iso_val and w1 < iso_val: continue
|
|
elif w0 == iso_val or w1 == iso_val:
|
|
_filtered_edges.append(e)
|
|
continue
|
|
else:
|
|
v0 = bm.verts[id0].co
|
|
v1 = bm.verts[id1].co
|
|
v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
|
|
if e not in delete_edges:
|
|
delete_edges.append(e)
|
|
verts.append(v)
|
|
edges_id[str(id0)+"_"+str(id1)] = count
|
|
edges_id[str(id1)+"_"+str(id0)] = count
|
|
count += 1
|
|
_filtered_edges.append(e)
|
|
filtered_edges = _filtered_edges
|
|
splitted_faces = []
|
|
|
|
switch = False
|
|
# splitting faces
|
|
for f in faces_mask:
|
|
# create sub-faces slots. Once a new vertex is reached it will
|
|
# change slot, storing the next vertices for a new face.
|
|
build_faces = [[],[]]
|
|
#switch = False
|
|
verts0 = [v.index for v in f.verts]
|
|
verts1 = list(verts0)
|
|
verts1.append(verts1.pop(0)) # shift list
|
|
for id0, id1 in zip(verts0, verts1):
|
|
|
|
# add first vertex to active slot
|
|
build_faces[switch].append(id0)
|
|
|
|
# try to split edge
|
|
try:
|
|
# check if the edge must be splitted
|
|
new_vert = edges_id[str(id0)+"_"+str(id1)]
|
|
# add new vertex
|
|
build_faces[switch].append(new_vert)
|
|
# if there is an open face on the other slot
|
|
if len(build_faces[not switch]) > 0:
|
|
# store actual face
|
|
splitted_faces.append(build_faces[switch])
|
|
# reset actual faces and switch
|
|
build_faces[switch] = []
|
|
# change face slot
|
|
switch = not switch
|
|
# continue previous face
|
|
build_faces[switch].append(new_vert)
|
|
except: pass
|
|
if len(build_faces[not switch]) == 2:
|
|
build_faces[not switch].append(id0)
|
|
if len(build_faces[not switch]) > 2:
|
|
splitted_faces.append(build_faces[not switch])
|
|
# add last face
|
|
splitted_faces.append(build_faces[switch])
|
|
#del_faces.append(f.index)
|
|
|
|
# adding new vertices
|
|
_new_vert = bm.verts.new
|
|
for v in verts: new_vert = _new_vert(v)
|
|
bm.verts.index_update()
|
|
bm.verts.ensure_lookup_table()
|
|
# adding new faces
|
|
_new_face = bm.faces.new
|
|
missed_faces = []
|
|
added_faces = []
|
|
for f in splitted_faces:
|
|
try:
|
|
face_verts = [bm.verts[i] for i in f]
|
|
new_face = _new_face(face_verts)
|
|
for e in new_face.edges:
|
|
filtered_edges.append(e)
|
|
except:
|
|
missed_faces.append(f)
|
|
|
|
bm.faces.ensure_lookup_table()
|
|
# updating weight values
|
|
weight = weight + [iso_val]*len(verts)
|
|
|
|
# deleting old edges/faces
|
|
_remove_edge = bm.edges.remove
|
|
bm.edges.ensure_lookup_table()
|
|
for e in delete_edges:
|
|
_remove_edge(e)
|
|
_filtered_edges = []
|
|
for e in filtered_edges:
|
|
if e not in delete_edges: _filtered_edges.append(e)
|
|
filtered_edges = _filtered_edges
|
|
|
|
name = ob0.name + '_ContourDisp'
|
|
me = bpy.data.meshes.new(name)
|
|
bm.to_mesh(me)
|
|
bm.free()
|
|
ob = bpy.data.objects.new(name, me)
|
|
|
|
# Link object to scene and make active
|
|
scn = context.scene
|
|
context.collection.objects.link(ob)
|
|
context.view_layer.objects.active = ob
|
|
ob.select_set(True)
|
|
ob0.select_set(False)
|
|
|
|
# generate new vertex group
|
|
for g in ob0.vertex_groups:
|
|
ob.vertex_groups.new(name=g.name)
|
|
#ob.vertex_groups.new(name=vertex_group_name)
|
|
|
|
all_weight = weight + [iso_val]*len(verts)
|
|
#mult = 1/(1-iso_val)
|
|
for id in range(len(all_weight)):
|
|
#if False: w = (all_weight[id]-iso_val)*mult
|
|
w = all_weight[id]
|
|
if self.weight_mode == 'Alternate':
|
|
direction = self.bool_flip
|
|
for i in range(len(iso_values)-1):
|
|
val0, val1 = iso_values[i], iso_values[i+1]
|
|
if val0 < w <= val1:
|
|
if direction: w1 = (w-val0)/(val1-val0)
|
|
else: w1 = (val1-w)/(val1-val0)
|
|
direction = not direction
|
|
if w < iso_values[0]: w1 = not self.bool_flip
|
|
if w > iso_values[-1]: w1 = not direction
|
|
elif self.weight_mode == 'Remapped':
|
|
if w < min_iso: w1 = 0
|
|
elif w > max_iso: w1 = 1
|
|
else: w1 = (w - min_iso)/delta_iso
|
|
else:
|
|
if self.bool_flip: w1 = 1-w
|
|
else: w1 = w
|
|
ob.vertex_groups[vertex_group_name].add([id], w1, 'REPLACE')
|
|
|
|
ob.vertex_groups.active_index = group_id
|
|
|
|
# align new object
|
|
ob.matrix_world = ob0.matrix_world
|
|
|
|
# Displace Modifier
|
|
if self.bool_displace:
|
|
ob.modifiers.new(type='DISPLACE', name='Displace')
|
|
ob.modifiers["Displace"].mid_level = 0
|
|
ob.modifiers["Displace"].strength = 0.1
|
|
ob.modifiers['Displace'].vertex_group = vertex_group_name
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
print("Contour Displace time: " + str(timeit.default_timer() - start_time) + " sec")
|
|
|
|
bpy.data.meshes.remove(me0)
|
|
|
|
return {'FINISHED'}
|
|
|
|
class weight_contour_mask(Operator):
|
|
bl_idname = "object.weight_contour_mask"
|
|
bl_label = "Contour Mask"
|
|
bl_description = ("")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
use_modifiers : BoolProperty(
|
|
name="Use Modifiers", default=True,
|
|
description="Apply all the modifiers")
|
|
iso : FloatProperty(
|
|
name="Iso Value", default=0.5, soft_min=0, soft_max=1,
|
|
description="Threshold value")
|
|
bool_solidify : BoolProperty(
|
|
name="Solidify", default=True, description="Add Solidify Modifier")
|
|
offset : FloatProperty(
|
|
name="Offset", default=1, min=0, max=1,
|
|
description="Offset")
|
|
thickness : FloatProperty(
|
|
name="Thickness", default=0.5, soft_min=0, soft_max=1,
|
|
description="Thickness")
|
|
normalize_weight : BoolProperty(
|
|
name="Normalize Weight", default=True,
|
|
description="Normalize weight of remaining vertices")
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.object.vertex_groups) > 0
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_props_dialog(self, width=350)
|
|
|
|
def execute(self, context):
|
|
start_time = timeit.default_timer()
|
|
try:
|
|
check = context.object.vertex_groups[0]
|
|
except:
|
|
self.report({'ERROR'}, "The object doesn't have Vertex Groups")
|
|
return {'CANCELLED'}
|
|
|
|
ob0 = bpy.context.object
|
|
|
|
iso_val = self.iso
|
|
group_id = ob0.vertex_groups.active_index
|
|
vertex_group_name = ob0.vertex_groups[group_id].name
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
bpy.ops.mesh.select_all(action='SELECT')
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
if self.use_modifiers:
|
|
me0 = simple_to_mesh(ob0)#ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
|
|
else:
|
|
me0 = ob0.data.copy()
|
|
|
|
# generate new bmesh
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me0)
|
|
bm.verts.ensure_lookup_table()
|
|
bm.edges.ensure_lookup_table()
|
|
bm.faces.ensure_lookup_table()
|
|
|
|
# store weight values
|
|
weight = []
|
|
ob = bpy.data.objects.new("temp", me0)
|
|
for g in ob0.vertex_groups:
|
|
ob.vertex_groups.new(name=g.name)
|
|
for v in me0.vertices:
|
|
try:
|
|
#weight.append(v.groups[vertex_group_name].weight)
|
|
weight.append(ob.vertex_groups[vertex_group_name].weight(v.index))
|
|
except:
|
|
weight.append(0)
|
|
|
|
faces_mask = []
|
|
for f in bm.faces:
|
|
w_min = 2
|
|
w_max = 2
|
|
for v in f.verts:
|
|
w = weight[v.index]
|
|
if w_min == 2:
|
|
w_max = w_min = w
|
|
if w > w_max: w_max = w
|
|
if w < w_min: w_min = w
|
|
if w_min < iso_val and w_max > iso_val:
|
|
faces_mask.append(f)
|
|
break
|
|
|
|
filtered_edges = bm.edges# me0.edges
|
|
faces_todo = [f.select for f in bm.faces]
|
|
verts = []
|
|
edges = []
|
|
delete_edges = []
|
|
edges_id = {}
|
|
_filtered_edges = []
|
|
n_verts = len(bm.verts)
|
|
count = n_verts
|
|
for e in filtered_edges:
|
|
id0 = e.verts[0].index
|
|
id1 = e.verts[1].index
|
|
w0 = weight[id0]
|
|
w1 = weight[id1]
|
|
|
|
if w0 == w1: continue
|
|
elif w0 > iso_val and w1 > iso_val:
|
|
continue
|
|
elif w0 < iso_val and w1 < iso_val: continue
|
|
elif w0 == iso_val or w1 == iso_val: continue
|
|
else:
|
|
v0 = me0.vertices[id0].co
|
|
v1 = me0.vertices[id1].co
|
|
v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
|
|
delete_edges.append(e)
|
|
verts.append(v)
|
|
edges_id[str(id0)+"_"+str(id1)] = count
|
|
edges_id[str(id1)+"_"+str(id0)] = count
|
|
count += 1
|
|
|
|
splitted_faces = []
|
|
|
|
switch = False
|
|
# splitting faces
|
|
for f in faces_mask:
|
|
# create sub-faces slots. Once a new vertex is reached it will
|
|
# change slot, storing the next vertices for a new face.
|
|
build_faces = [[],[]]
|
|
#switch = False
|
|
verts0 = list(me0.polygons[f.index].vertices)
|
|
verts1 = list(verts0)
|
|
verts1.append(verts1.pop(0)) # shift list
|
|
for id0, id1 in zip(verts0, verts1):
|
|
|
|
# add first vertex to active slot
|
|
build_faces[switch].append(id0)
|
|
|
|
# try to split edge
|
|
try:
|
|
# check if the edge must be splitted
|
|
new_vert = edges_id[str(id0)+"_"+str(id1)]
|
|
# add new vertex
|
|
build_faces[switch].append(new_vert)
|
|
# if there is an open face on the other slot
|
|
if len(build_faces[not switch]) > 0:
|
|
# store actual face
|
|
splitted_faces.append(build_faces[switch])
|
|
# reset actual faces and switch
|
|
build_faces[switch] = []
|
|
# change face slot
|
|
switch = not switch
|
|
# continue previous face
|
|
build_faces[switch].append(new_vert)
|
|
except: pass
|
|
if len(build_faces[not switch]) == 2:
|
|
build_faces[not switch].append(id0)
|
|
if len(build_faces[not switch]) > 2:
|
|
splitted_faces.append(build_faces[not switch])
|
|
# add last face
|
|
splitted_faces.append(build_faces[switch])
|
|
|
|
# adding new vertices
|
|
_new_vert = bm.verts.new
|
|
for v in verts: _new_vert(v)
|
|
bm.verts.ensure_lookup_table()
|
|
|
|
# deleting old edges/faces
|
|
_remove_edge = bm.edges.remove
|
|
bm.edges.ensure_lookup_table()
|
|
remove_edges = []
|
|
for e in delete_edges: _remove_edge(e)
|
|
|
|
bm.verts.ensure_lookup_table()
|
|
# adding new faces
|
|
_new_face = bm.faces.new
|
|
missed_faces = []
|
|
for f in splitted_faces:
|
|
try:
|
|
face_verts = [bm.verts[i] for i in f]
|
|
_new_face(face_verts)
|
|
except:
|
|
missed_faces.append(f)
|
|
|
|
# Mask geometry
|
|
if(True):
|
|
_remove_vert = bm.verts.remove
|
|
all_weight = weight + [iso_val+0.0001]*len(verts)
|
|
weight = []
|
|
for w, v in zip(all_weight, bm.verts):
|
|
if w < iso_val: _remove_vert(v)
|
|
else: weight.append(w)
|
|
|
|
# Create mesh and object
|
|
name = ob0.name + '_ContourMask_{:.3f}'.format(iso_val)
|
|
me = bpy.data.meshes.new(name)
|
|
bm.to_mesh(me)
|
|
bm.free()
|
|
ob = bpy.data.objects.new(name, me)
|
|
|
|
# Link object to scene and make active
|
|
scn = context.scene
|
|
context.collection.objects.link(ob)
|
|
context.view_layer.objects.active = ob
|
|
ob.select_set(True)
|
|
ob0.select_set(False)
|
|
|
|
# generate new vertex group
|
|
for g in ob0.vertex_groups:
|
|
ob.vertex_groups.new(name=g.name)
|
|
|
|
if iso_val != 1: mult = 1/(1-iso_val)
|
|
else: mult = 1
|
|
for id in range(len(weight)):
|
|
if self.normalize_weight: w = (weight[id]-iso_val)*mult
|
|
else: w = weight[id]
|
|
ob.vertex_groups[vertex_group_name].add([id], w, 'REPLACE')
|
|
ob.vertex_groups.active_index = group_id
|
|
|
|
# align new object
|
|
ob.matrix_world = ob0.matrix_world
|
|
|
|
# Add Solidify
|
|
if self.bool_solidify and True:
|
|
ob.modifiers.new(type='SOLIDIFY', name='Solidify')
|
|
ob.modifiers['Solidify'].thickness = self.thickness
|
|
ob.modifiers['Solidify'].offset = self.offset
|
|
ob.modifiers['Solidify'].vertex_group = vertex_group_name
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
print("Contour Mask time: " + str(timeit.default_timer() - start_time) + " sec")
|
|
|
|
bpy.data.meshes.remove(me0)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class weight_contour_mask_wip(Operator):
|
|
bl_idname = "object.weight_contour_mask"
|
|
bl_label = "Contour Mask"
|
|
bl_description = ("")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
use_modifiers : BoolProperty(
|
|
name="Use Modifiers", default=True,
|
|
description="Apply all the modifiers")
|
|
iso : FloatProperty(
|
|
name="Iso Value", default=0.5, soft_min=0, soft_max=1,
|
|
description="Threshold value")
|
|
bool_solidify : BoolProperty(
|
|
name="Solidify", default=True, description="Add Solidify Modifier")
|
|
normalize_weight : BoolProperty(
|
|
name="Normalize Weight", default=True,
|
|
description="Normalize weight of remaining vertices")
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.object.vertex_groups) > 0
|
|
|
|
def execute(self, context):
|
|
start_time = timeit.default_timer()
|
|
try:
|
|
check = context.object.vertex_groups[0]
|
|
except:
|
|
self.report({'ERROR'}, "The object doesn't have Vertex Groups")
|
|
return {'CANCELLED'}
|
|
|
|
ob0 = bpy.context.object
|
|
|
|
iso_val = self.iso
|
|
group_id = ob0.vertex_groups.active_index
|
|
vertex_group_name = ob0.vertex_groups[group_id].name
|
|
|
|
#bpy.ops.object.mode_set(mode='EDIT')
|
|
#bpy.ops.mesh.select_all(action='SELECT')
|
|
#bpy.ops.object.mode_set(mode='OBJECT')
|
|
if self.use_modifiers:
|
|
me0 = simple_to_mesh(ob0)#ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
|
|
else:
|
|
me0 = ob0.data.copy()
|
|
|
|
# generate new bmesh
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me0)
|
|
|
|
# store weight values
|
|
weight = []
|
|
ob = bpy.data.objects.new("temp", me0)
|
|
for g in ob0.vertex_groups:
|
|
ob.vertex_groups.new(name=g.name)
|
|
weight = get_weight_numpy(ob.vertex_groups[vertex_group_name], len(me0.vertices))
|
|
|
|
me0, bm, weight = contour_bmesh(me0, bm, weight, iso_val)
|
|
|
|
# Mask geometry
|
|
mask = weight >= iso_val
|
|
weight = weight[mask]
|
|
mask = np.logical_not(mask)
|
|
delete_verts = np.array(bm.verts)[mask]
|
|
#for v in delete_verts: bm.verts.remove(v)
|
|
|
|
# Create mesh and object
|
|
name = ob0.name + '_ContourMask_{:.3f}'.format(iso_val)
|
|
me = bpy.data.meshes.new(name)
|
|
bm.to_mesh(me)
|
|
bm.free()
|
|
ob = bpy.data.objects.new(name, me)
|
|
|
|
# Link object to scene and make active
|
|
scn = context.scene
|
|
context.collection.objects.link(ob)
|
|
context.view_layer.objects.active = ob
|
|
ob.select_set(True)
|
|
ob0.select_set(False)
|
|
|
|
# generate new vertex group
|
|
for g in ob0.vertex_groups:
|
|
ob.vertex_groups.new(name=g.name)
|
|
|
|
if iso_val != 1: mult = 1/(1-iso_val)
|
|
else: mult = 1
|
|
for id in range(len(weight)):
|
|
if self.normalize_weight: w = (weight[id]-iso_val)*mult
|
|
else: w = weight[id]
|
|
ob.vertex_groups[vertex_group_name].add([id], w, 'REPLACE')
|
|
ob.vertex_groups.active_index = group_id
|
|
|
|
# align new object
|
|
ob.matrix_world = ob0.matrix_world
|
|
|
|
# Add Solidify
|
|
if self.bool_solidify and True:
|
|
ob.modifiers.new(type='SOLIDIFY', name='Solidify')
|
|
ob.modifiers['Solidify'].thickness = 0.05
|
|
ob.modifiers['Solidify'].offset = 0
|
|
ob.modifiers['Solidify'].vertex_group = vertex_group_name
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
print("Contour Mask time: " + str(timeit.default_timer() - start_time) + " sec")
|
|
|
|
bpy.data.meshes.remove(me0)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class weight_contour_curves(Operator):
|
|
bl_idname = "object.weight_contour_curves"
|
|
bl_label = "Contour Curves"
|
|
bl_description = ("")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
use_modifiers : BoolProperty(
|
|
name="Use Modifiers", default=True,
|
|
description="Apply all the modifiers")
|
|
|
|
min_iso : FloatProperty(
|
|
name="Min Value", default=0., soft_min=0, soft_max=1,
|
|
description="Minimum weight value")
|
|
max_iso : FloatProperty(
|
|
name="Max Value", default=1, soft_min=0, soft_max=1,
|
|
description="Maximum weight value")
|
|
n_curves : IntProperty(
|
|
name="Curves", default=3, soft_min=1, soft_max=10,
|
|
description="Number of Contour Curves")
|
|
|
|
min_rad : FloatProperty(
|
|
name="Min Radius", default=1, soft_min=0, soft_max=1,
|
|
description="Change radius according to Iso Value")
|
|
max_rad : FloatProperty(
|
|
name="Max Radius", default=1, soft_min=0, soft_max=1,
|
|
description="Change radius according to Iso Value")
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
ob = context.object
|
|
return len(ob.vertex_groups) > 0 or ob.type == 'CURVE'
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_props_dialog(self, width=350)
|
|
|
|
def execute(self, context):
|
|
start_time = timeit.default_timer()
|
|
try:
|
|
check = context.object.vertex_groups[0]
|
|
except:
|
|
self.report({'ERROR'}, "The object doesn't have Vertex Groups")
|
|
return {'CANCELLED'}
|
|
ob0 = context.object
|
|
|
|
group_id = ob0.vertex_groups.active_index
|
|
vertex_group_name = ob0.vertex_groups[group_id].name
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
bpy.ops.mesh.select_all(action='SELECT')
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
if self.use_modifiers:
|
|
me0 = simple_to_mesh(ob0) #ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
|
|
else:
|
|
me0 = ob0.data.copy()
|
|
|
|
# generate new bmesh
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me0)
|
|
bm.verts.ensure_lookup_table()
|
|
bm.edges.ensure_lookup_table()
|
|
bm.faces.ensure_lookup_table()
|
|
|
|
# store weight values
|
|
weight = []
|
|
ob = bpy.data.objects.new("temp", me0)
|
|
for g in ob0.vertex_groups:
|
|
ob.vertex_groups.new(name=g.name)
|
|
weight = get_weight_numpy(ob.vertex_groups[vertex_group_name], len(bm.verts))
|
|
|
|
#filtered_edges = bm.edges
|
|
total_verts = np.zeros((0,3))
|
|
total_segments = []
|
|
radius = []
|
|
|
|
# start iterate contours levels
|
|
vertices = get_vertices_numpy(me0)
|
|
filtered_edges = get_edges_id_numpy(me0)
|
|
|
|
faces_weight = [np.array([weight[v] for v in p.vertices]) for p in me0.polygons]
|
|
fw_min = np.array([np.min(fw) for fw in faces_weight])
|
|
fw_max = np.array([np.max(fw) for fw in faces_weight])
|
|
|
|
bm_faces = np.array(bm.faces)
|
|
|
|
### Spiral
|
|
normals = np.array([v.normal for v in me0.vertices])
|
|
|
|
for c in range(self.n_curves):
|
|
min_iso = min(self.min_iso, self.max_iso)
|
|
max_iso = max(self.min_iso, self.max_iso)
|
|
try:
|
|
delta_iso = (max_iso-min_iso)/(self.n_curves-1)
|
|
iso_val = c*delta_iso + min_iso
|
|
if iso_val < 0: iso_val = (min_iso + max_iso)/2
|
|
except:
|
|
iso_val = (min_iso + max_iso)/2
|
|
|
|
# remove passed faces
|
|
bool_mask = iso_val < fw_max
|
|
bm_faces = bm_faces[bool_mask]
|
|
fw_min = fw_min[bool_mask]
|
|
fw_max = fw_max[bool_mask]
|
|
|
|
# mask faces
|
|
bool_mask = fw_min < iso_val
|
|
faces_mask = bm_faces[bool_mask]
|
|
|
|
n_verts = len(bm.verts)
|
|
count = len(total_verts)
|
|
|
|
# vertices indexes
|
|
id0 = filtered_edges[:,0]
|
|
id1 = filtered_edges[:,1]
|
|
# vertices weight
|
|
w0 = weight[id0]
|
|
w1 = weight[id1]
|
|
# weight condition
|
|
bool_w0 = w0 < iso_val
|
|
bool_w1 = w1 < iso_val
|
|
|
|
# mask all edges that have one weight value below the iso value
|
|
mask_new_verts = np.logical_xor(bool_w0, bool_w1)
|
|
|
|
id0 = id0[mask_new_verts]
|
|
id1 = id1[mask_new_verts]
|
|
# filter arrays
|
|
v0 = vertices[id0]
|
|
v1 = vertices[id1]
|
|
w0 = w0[mask_new_verts]
|
|
w1 = w1[mask_new_verts]
|
|
div = (w1-w0)
|
|
if div == 0: div = 0.000001
|
|
|
|
param = np.expand_dims((iso_val-w0)/div,axis=1)
|
|
verts = v0 + (v1-v0)*param
|
|
|
|
# indexes of edges with new vertices
|
|
edges_index = filtered_edges[mask_new_verts][:,2]
|
|
edges_id = {}
|
|
for i, id in enumerate(edges_index): edges_id[id] = i+len(total_verts)
|
|
|
|
# remove all edges completely below the iso value
|
|
mask_edges = np.logical_not(np.logical_and(bool_w0, bool_w1))
|
|
filtered_edges = filtered_edges[mask_edges]
|
|
if len(verts) == 0: continue
|
|
|
|
# finding segments
|
|
segments = []
|
|
for f in faces_mask:
|
|
seg = []
|
|
for e in f.edges:
|
|
try:
|
|
seg.append(edges_id[e.index])
|
|
if len(seg) == 2:
|
|
segments.append(seg)
|
|
seg = []
|
|
except: pass
|
|
|
|
|
|
#curves_points_indexes = find_curves(segments)
|
|
total_segments = total_segments + segments
|
|
total_verts = np.concatenate((total_verts,verts))
|
|
|
|
if self.min_rad != self.max_rad:
|
|
try:
|
|
iso_rad = c*(self.max_rad-self.min_rad)/(self.n_curves-1)+self.min_rad
|
|
if iso_rad < 0: iso_rad = (self.min_rad + self.max_rad)/2
|
|
except:
|
|
iso_rad = (self.min_rad + self.max_rad)/2
|
|
radius = radius + [iso_rad]*len(verts)
|
|
print("Contour Curves, computing time: " + str(timeit.default_timer() - start_time) + " sec")
|
|
bm.free()
|
|
bm = bmesh.new()
|
|
# adding new vertices _local for fast access
|
|
_new_vert = bm.verts.new
|
|
for v in total_verts: _new_vert(v)
|
|
bm.verts.ensure_lookup_table()
|
|
|
|
# adding new edges
|
|
_new_edge = bm.edges.new
|
|
for s in total_segments:
|
|
try:
|
|
pts = [bm.verts[i] for i in s]
|
|
_new_edge(pts)
|
|
except: pass
|
|
|
|
|
|
try:
|
|
name = ob0.name + '_ContourCurves'
|
|
me = bpy.data.meshes.new(name)
|
|
bm.to_mesh(me)
|
|
bm.free()
|
|
ob = bpy.data.objects.new(name, me)
|
|
# Link object to scene and make active
|
|
scn = context.scene
|
|
context.collection.objects.link(ob)
|
|
context.view_layer.objects.active = ob
|
|
ob.select_set(True)
|
|
ob0.select_set(False)
|
|
|
|
print("Contour Curves, bmesh time: " + str(timeit.default_timer() - start_time) + " sec")
|
|
bpy.ops.object.convert(target='CURVE')
|
|
ob = context.object
|
|
if not (self.min_rad == 0 and self.max_rad == 0):
|
|
if self.min_rad != self.max_rad:
|
|
count = 0
|
|
for s in ob.data.splines:
|
|
for p in s.points:
|
|
p.radius = radius[count]
|
|
count += 1
|
|
else:
|
|
for s in ob.data.splines:
|
|
for p in s.points:
|
|
p.radius = self.min_rad
|
|
ob.data.bevel_depth = 0.01
|
|
ob.data.fill_mode = 'FULL'
|
|
ob.data.bevel_resolution = 3
|
|
except:
|
|
self.report({'ERROR'}, "There are no values in the chosen range")
|
|
return {'CANCELLED'}
|
|
|
|
# align new object
|
|
ob.matrix_world = ob0.matrix_world
|
|
print("Contour Curves time: " + str(timeit.default_timer() - start_time) + " sec")
|
|
|
|
bpy.data.meshes.remove(me0)
|
|
bpy.data.meshes.remove(me)
|
|
|
|
return {'FINISHED'}
|
|
|
|
class tissue_weight_contour_curves_pattern(Operator):
|
|
bl_idname = "object.tissue_weight_contour_curves_pattern"
|
|
bl_label = "Contour Curves"
|
|
bl_description = ("")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
use_modifiers : BoolProperty(
|
|
name="Use Modifiers", default=True,
|
|
description="Apply all the modifiers")
|
|
|
|
auto_bevel : BoolProperty(
|
|
name="Automatic Bevel", default=False,
|
|
description="Bevel depends on weight density")
|
|
|
|
min_iso : FloatProperty(
|
|
name="Min Value", default=0., soft_min=0, soft_max=1,
|
|
description="Minimum weight value")
|
|
max_iso : FloatProperty(
|
|
name="Max Value", default=1, soft_min=0, soft_max=1,
|
|
description="Maximum weight value")
|
|
n_curves : IntProperty(
|
|
name="Curves", default=10, soft_min=1, soft_max=100,
|
|
description="Number of Contour Curves")
|
|
min_rad = 1
|
|
max_rad = 1
|
|
|
|
in_displace : FloatProperty(
|
|
name="Displace A", default=0, soft_min=-10, soft_max=10,
|
|
description="Pattern displace strength")
|
|
out_displace : FloatProperty(
|
|
name="Displace B", default=2, soft_min=-10, soft_max=10,
|
|
description="Pattern displace strength")
|
|
|
|
in_steps : IntProperty(
|
|
name="Steps A", default=1, min=0, soft_max=10,
|
|
description="Number of layers to move inwards")
|
|
out_steps : IntProperty(
|
|
name="Steps B", default=1, min=0, soft_max=10,
|
|
description="Number of layers to move outwards")
|
|
limit_z : BoolProperty(
|
|
name="Limit Z", default=False,
|
|
description="Limit Pattern in Z")
|
|
|
|
merge : BoolProperty(
|
|
name="Merge Vertices", default=True,
|
|
description="Merge points")
|
|
merge_thres : FloatProperty(
|
|
name="Merge Threshold", default=0.01, min=0, soft_max=1,
|
|
description="Minimum Curve Radius")
|
|
|
|
bevel_depth : FloatProperty(
|
|
name="Bevel Depth", default=0, min=0, soft_max=1,
|
|
description="")
|
|
min_bevel_depth : FloatProperty(
|
|
name="Min Bevel Depth", default=0.1, min=0, soft_max=1,
|
|
description="")
|
|
max_bevel_depth : FloatProperty(
|
|
name="Max Bevel Depth", default=1, min=0, soft_max=1,
|
|
description="")
|
|
remove_open_curves : BoolProperty(
|
|
name="Remove Open Curves", default=False,
|
|
description="Remove Open Curves")
|
|
|
|
vertex_group_pattern : StringProperty(
|
|
name="Displace", default='',
|
|
description="Vertex Group used for pattern displace")
|
|
|
|
vertex_group_bevel : StringProperty(
|
|
name="Bevel", default='',
|
|
description="Variable Bevel depth")
|
|
|
|
object_name : StringProperty(
|
|
name="Active Object", default='',
|
|
description="")
|
|
|
|
try: vg_name = bpy.context.object.vertex_groups.active.name
|
|
except: vg_name = ''
|
|
|
|
vertex_group_contour : StringProperty(
|
|
name="Contour", default=vg_name,
|
|
description="Vertex Group used for contouring")
|
|
clean_distance : FloatProperty(
|
|
name="Clean Distance", default=0, min=0, soft_max=10,
|
|
description="Remove short segments")
|
|
|
|
|
|
spiralized: BoolProperty(
|
|
name='Spiralized', default=False,
|
|
description='Create a Spiral Contour. Works better with dense meshes.'
|
|
)
|
|
spiral_axis: FloatVectorProperty(
|
|
name="Spiral Axis", default=(0,0,1),
|
|
description="Axis of the Spiral (in local coordinates)"
|
|
)
|
|
spiral_rotation : FloatProperty(
|
|
name="Spiral Rotation", default=0, min=0, max=2*pi,
|
|
description=""
|
|
)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
ob = context.object
|
|
return ob and len(ob.vertex_groups) > 0 or ob.type == 'CURVE'
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_props_dialog(self, width=250)
|
|
|
|
def draw(self, context):
|
|
if not context.object.type == 'CURVE':
|
|
self.object_name = context.object.name
|
|
ob = bpy.data.objects[self.object_name]
|
|
if self.vertex_group_contour not in [vg.name for vg in ob.vertex_groups]:
|
|
self.vertex_group_contour = ob.vertex_groups.active.name
|
|
layout = self.layout
|
|
col = layout.column(align=True)
|
|
col.prop(self, "use_modifiers")
|
|
col.label(text="Contour Curves:")
|
|
col.prop_search(self, 'vertex_group_contour', ob, "vertex_groups", text='')
|
|
row = col.row(align=True)
|
|
row.prop(self,'min_iso')
|
|
row.prop(self,'max_iso')
|
|
col.prop(self,'n_curves')
|
|
col.separator()
|
|
col.label(text='Curves Bevel:')
|
|
col.prop(self,'auto_bevel')
|
|
if not self.auto_bevel:
|
|
col.prop_search(self, 'vertex_group_bevel', ob, "vertex_groups", text='')
|
|
if self.vertex_group_bevel != '' or self.auto_bevel:
|
|
row = col.row(align=True)
|
|
row.prop(self,'min_bevel_depth')
|
|
row.prop(self,'max_bevel_depth')
|
|
else:
|
|
col.prop(self,'bevel_depth')
|
|
col.separator()
|
|
|
|
col.label(text="Displace Pattern:")
|
|
col.prop_search(self, 'vertex_group_pattern', ob, "vertex_groups", text='')
|
|
if self.vertex_group_pattern != '':
|
|
row = col.row(align=True)
|
|
row.prop(self,'in_steps')
|
|
row.prop(self,'out_steps')
|
|
row = col.row(align=True)
|
|
row.prop(self,'in_displace')
|
|
row.prop(self,'out_displace')
|
|
col.prop(self,'limit_z')
|
|
col.separator()
|
|
row=col.row(align=True)
|
|
row.prop(self,'spiralized')
|
|
row.label(icon='MOD_SCREW')
|
|
if self.spiralized:
|
|
#row=col.row(align=True)
|
|
#row.prop(self,'spiral_axis')
|
|
#col.separator()
|
|
col.prop(self,'spiral_rotation')
|
|
col.separator()
|
|
|
|
col.label(text='Clean Curves:')
|
|
col.prop(self,'clean_distance')
|
|
col.prop(self,'remove_open_curves')
|
|
|
|
def execute(self, context):
|
|
n_curves = self.n_curves
|
|
start_time = timeit.default_timer()
|
|
try:
|
|
check = context.object.vertex_groups[0]
|
|
except:
|
|
self.report({'ERROR'}, "The object doesn't have Vertex Groups")
|
|
return {'CANCELLED'}
|
|
ob0 = bpy.data.objects[self.object_name]
|
|
|
|
dg = context.evaluated_depsgraph_get()
|
|
ob = ob0.evaluated_get(dg)
|
|
me0 = ob.data
|
|
|
|
# generate new bmesh
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me0)
|
|
n_verts = len(bm.verts)
|
|
|
|
# store weight values
|
|
try:
|
|
weight = get_weight_numpy(ob.vertex_groups[self.vertex_group_contour], len(me0.vertices))
|
|
except:
|
|
bm.free()
|
|
self.report({'ERROR'}, "Please select a Vertex Group for contouring")
|
|
return {'CANCELLED'}
|
|
|
|
try:
|
|
pattern_weight = get_weight_numpy(ob.vertex_groups[self.vertex_group_pattern], len(me0.vertices))
|
|
except:
|
|
#self.report({'WARNING'}, "There is no Vertex Group assigned to the pattern displace")
|
|
pattern_weight = np.zeros(len(me0.vertices))
|
|
|
|
variable_bevel = False
|
|
try:
|
|
bevel_weight = get_weight_numpy(ob.vertex_groups[self.vertex_group_bevel], len(me0.vertices))
|
|
variable_bevel = True
|
|
except:
|
|
bevel_weight = np.ones(len(me0.vertices))
|
|
|
|
if self.auto_bevel:
|
|
# calc weight density
|
|
bevel_weight = np.ones(len(me0.vertices))*10000
|
|
bevel_weight = np.zeros(len(me0.vertices))
|
|
edges_length = np.array([e.calc_length() for e in bm.edges])
|
|
edges_dw = np.array([max(abs(weight[e.verts[0].index]-weight[e.verts[1].index]),0.000001) for e in bm.edges])
|
|
dens = edges_length/edges_dw
|
|
n_records = np.zeros(len(me0.vertices))
|
|
for i, e in enumerate(bm.edges):
|
|
for v in e.verts:
|
|
id = v.index
|
|
#bevel_weight[id] = min(bevel_weight[id], dens[i])
|
|
bevel_weight[id] += dens[i]
|
|
n_records[id] += 1
|
|
bevel_weight = bevel_weight/n_records
|
|
bevel_weight = (bevel_weight - min(bevel_weight))/(max(bevel_weight) - min(bevel_weight))
|
|
#bevel_weight = 1-bevel_weight
|
|
variable_bevel = True
|
|
|
|
#filtered_edges = bm.edges
|
|
total_verts = np.zeros((0,3))
|
|
total_radii = np.zeros((0,1))
|
|
total_segments = []# np.array([])
|
|
radius = []
|
|
|
|
# start iterate contours levels
|
|
vertices, normals = get_vertices_and_normals_numpy(me0)
|
|
filtered_edges = get_edges_id_numpy(me0)
|
|
|
|
|
|
min_iso = min(self.min_iso, self.max_iso)
|
|
max_iso = max(self.min_iso, self.max_iso)
|
|
|
|
# Spiral
|
|
if self.spiralized:
|
|
nx = normals[:,0]
|
|
ny = normals[:,1]
|
|
ang = self.spiral_rotation + weight*pi*n_curves+arctan2(nx,ny)
|
|
weight = sin(ang)/2+0.5
|
|
n_curves = 1
|
|
|
|
if n_curves > 1:
|
|
delta_iso = (max_iso-min_iso)/(n_curves-1)
|
|
|
|
else:
|
|
delta_iso = None
|
|
|
|
faces_weight = [np.array([weight[v] for v in p.vertices]) for p in me0.polygons]
|
|
fw_min = np.array([np.min(fw) for fw in faces_weight])
|
|
fw_max = np.array([np.max(fw) for fw in faces_weight])
|
|
|
|
bm_faces = np.array(bm.faces)
|
|
|
|
#print("Contour Curves, data loaded: " + str(timeit.default_timer() - start_time) + " sec")
|
|
step_time = timeit.default_timer()
|
|
for c in range(n_curves):
|
|
if delta_iso:
|
|
iso_val = c*delta_iso + min_iso
|
|
if iso_val < 0: iso_val = (min_iso + max_iso)/2
|
|
else:
|
|
iso_val = (min_iso + max_iso)/2
|
|
|
|
#if c == 0 and self.auto_bevel:
|
|
|
|
|
|
# remove passed faces
|
|
bool_mask = iso_val < fw_max
|
|
bm_faces = bm_faces[bool_mask]
|
|
fw_min = fw_min[bool_mask]
|
|
fw_max = fw_max[bool_mask]
|
|
|
|
# mask faces
|
|
bool_mask = fw_min < iso_val
|
|
faces_mask = bm_faces[bool_mask]
|
|
|
|
count = len(total_verts)
|
|
|
|
new_filtered_edges, edges_index, verts, bevel = contour_edges_pattern(self, c, len(total_verts), iso_val, vertices, normals, filtered_edges, weight, pattern_weight, bevel_weight)
|
|
|
|
if len(edges_index) > 0:
|
|
if self.auto_bevel and False:
|
|
bevel = 1-dens[edges_index]
|
|
bevel = bevel[:,np.newaxis]
|
|
if self.max_bevel_depth != self.min_bevel_depth:
|
|
min_radius = self.min_bevel_depth / max(0.0001,self.max_bevel_depth)
|
|
radii = min_radius + bevel*(1 - min_radius)
|
|
else:
|
|
radii = bevel
|
|
else:
|
|
continue
|
|
|
|
if verts[0,0] == None: continue
|
|
else: filtered_edges = new_filtered_edges
|
|
edges_id = {}
|
|
for i, id in enumerate(edges_index): edges_id[id] = i + count
|
|
|
|
if len(verts) == 0: continue
|
|
|
|
# finding segments
|
|
segments = []
|
|
for f in faces_mask:
|
|
seg = []
|
|
for e in f.edges:
|
|
try:
|
|
#seg.append(new_ids[np.where(edges_index == e.index)[0][0]])
|
|
seg.append(edges_id[e.index])
|
|
if len(seg) == 2:
|
|
segments.append(seg)
|
|
seg = []
|
|
except: pass
|
|
|
|
total_segments = total_segments + segments
|
|
total_verts = np.concatenate((total_verts, verts))
|
|
total_radii = np.concatenate((total_radii, radii))
|
|
|
|
if self.min_rad != self.max_rad:
|
|
try:
|
|
iso_rad = c*(self.max_rad-self.min_rad)/(self.n_curves-1)+self.min_rad
|
|
if iso_rad < 0: iso_rad = (self.min_rad + self.max_rad)/2
|
|
except:
|
|
iso_rad = (self.min_rad + self.max_rad)/2
|
|
radius = radius + [iso_rad]*len(verts)
|
|
#print("Contour Curves, points computing: " + str(timeit.default_timer() - step_time) + " sec")
|
|
step_time = timeit.default_timer()
|
|
|
|
if len(total_segments) > 0:
|
|
step_time = timeit.default_timer()
|
|
ordered_points = find_curves(total_segments, len(total_verts))
|
|
|
|
#print("Contour Curves, point ordered in: " + str(timeit.default_timer() - step_time) + " sec")
|
|
step_time = timeit.default_timer()
|
|
crv = curve_from_pydata(total_verts, total_radii, ordered_points, ob0.name + '_ContourCurves', self.remove_open_curves, merge_distance=self.clean_distance)
|
|
context.view_layer.objects.active = crv
|
|
if variable_bevel: crv.data.bevel_depth = self.max_bevel_depth
|
|
else: crv.data.bevel_depth = self.bevel_depth
|
|
|
|
crv.select_set(True)
|
|
ob0.select_set(False)
|
|
crv.matrix_world = ob0.matrix_world
|
|
#print("Contour Curves, curves created in: " + str(timeit.default_timer() - step_time) + " sec")
|
|
else:
|
|
bm.free()
|
|
self.report({'ERROR'}, "There are no values in the chosen range")
|
|
return {'CANCELLED'}
|
|
bm.free()
|
|
print("Contour Curves, total time: " + str(timeit.default_timer() - start_time) + " sec")
|
|
return {'FINISHED'}
|
|
|
|
class vertex_colors_to_vertex_groups(Operator):
|
|
bl_idname = "object.vertex_colors_to_vertex_groups"
|
|
bl_label = "Vertex Color"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
bl_description = ("Convert the active Vertex Color into a Vertex Group.")
|
|
|
|
red : BoolProperty(
|
|
name="red channel", default=False, description="convert red channel")
|
|
green : BoolProperty(
|
|
name="green channel", default=False,
|
|
description="convert green channel")
|
|
blue : BoolProperty(
|
|
name="blue channel", default=False, description="convert blue channel")
|
|
value : BoolProperty(
|
|
name="value channel", default=True, description="convert value channel")
|
|
invert : BoolProperty(
|
|
name="invert", default=False, description="invert all color channels")
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
try:
|
|
return len(context.object.data.vertex_colors) > 0
|
|
except: return False
|
|
|
|
def execute(self, context):
|
|
obj = context.active_object
|
|
id = len(obj.vertex_groups)
|
|
id_red = id
|
|
id_green = id
|
|
id_blue = id
|
|
id_value = id
|
|
|
|
boolCol = len(obj.data.vertex_colors)
|
|
if(boolCol): col_name = obj.data.vertex_colors.active.name
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
bpy.ops.mesh.select_all(action='SELECT')
|
|
|
|
if(self.red and boolCol):
|
|
bpy.ops.object.vertex_group_add()
|
|
bpy.ops.object.vertex_group_assign()
|
|
id_red = id
|
|
obj.vertex_groups[id_red].name = col_name + '_red'
|
|
id+=1
|
|
if(self.green and boolCol):
|
|
bpy.ops.object.vertex_group_add()
|
|
bpy.ops.object.vertex_group_assign()
|
|
id_green = id
|
|
obj.vertex_groups[id_green].name = col_name + '_green'
|
|
id+=1
|
|
if(self.blue and boolCol):
|
|
bpy.ops.object.vertex_group_add()
|
|
bpy.ops.object.vertex_group_assign()
|
|
id_blue = id
|
|
obj.vertex_groups[id_blue].name = col_name + '_blue'
|
|
id+=1
|
|
if(self.value and boolCol):
|
|
bpy.ops.object.vertex_group_add()
|
|
bpy.ops.object.vertex_group_assign()
|
|
id_value = id
|
|
obj.vertex_groups[id_value].name = col_name + '_value'
|
|
id+=1
|
|
|
|
mult = 1
|
|
if(self.invert): mult = -1
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
sub_red = 1 + self.value + self.blue + self.green
|
|
sub_green = 1 + self.value + self.blue
|
|
sub_blue = 1 + self.value
|
|
sub_value = 1
|
|
|
|
id = len(obj.vertex_groups)
|
|
if(id_red <= id and id_green <= id and id_blue <= id and id_value <= \
|
|
id and boolCol):
|
|
v_colors = obj.data.vertex_colors.active.data
|
|
i = 0
|
|
for f in obj.data.polygons:
|
|
for v in f.vertices:
|
|
gr = obj.data.vertices[v].groups
|
|
if(self.red): gr[min(len(gr)-sub_red, id_red)].weight = \
|
|
self.invert + mult * v_colors[i].color[0]
|
|
if(self.green): gr[min(len(gr)-sub_green, id_green)].weight\
|
|
= self.invert + mult * v_colors[i].color[1]
|
|
if(self.blue): gr[min(len(gr)-sub_blue, id_blue)].weight = \
|
|
self.invert + mult * v_colors[i].color[2]
|
|
if(self.value):
|
|
r = v_colors[i].color[0]
|
|
g = v_colors[i].color[1]
|
|
b = v_colors[i].color[2]
|
|
gr[min(len(gr)-sub_value, id_value)].weight\
|
|
= self.invert + mult * (0.2126*r + 0.7152*g + 0.0722*b)
|
|
i+=1
|
|
bpy.ops.paint.weight_paint_toggle()
|
|
return {'FINISHED'}
|
|
|
|
class vertex_group_to_vertex_colors(Operator):
|
|
bl_idname = "object.vertex_group_to_vertex_colors"
|
|
bl_label = "Vertex Group"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
bl_description = ("Convert the active Vertex Group into a Vertex Color.")
|
|
|
|
channel : EnumProperty(
|
|
items=[('BLUE', 'Blue Channel', 'Convert to Blue Channel'),
|
|
('GREEN', 'Green Channel', 'Convert to Green Channel'),
|
|
('RED', 'Red Channel', 'Convert to Red Channel'),
|
|
('VALUE', 'Value Channel', 'Convert to Grayscale'),
|
|
('FALSE_COLORS', 'False Colors', 'Convert to False Colors')],
|
|
name="Convert to", description="Choose how to convert vertex group",
|
|
default="VALUE", options={'LIBRARY_EDITABLE'})
|
|
|
|
invert : BoolProperty(
|
|
name="invert", default=False, description="invert color channel")
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.object.vertex_groups) > 0
|
|
|
|
def execute(self, context):
|
|
obj = context.active_object
|
|
me = obj.data
|
|
group_id = obj.vertex_groups.active_index
|
|
if (group_id == -1):
|
|
return {'FINISHED'}
|
|
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
group_name = obj.vertex_groups[group_id].name
|
|
me.vertex_colors.new()
|
|
colors_id = obj.data.vertex_colors.active_index
|
|
|
|
colors_name = group_name
|
|
if(self.channel == 'FALSE_COLORS'): colors_name += "_false_colors"
|
|
elif(self.channel == 'VALUE'): colors_name += "_value"
|
|
elif(self.channel == 'RED'): colors_name += "_red"
|
|
elif(self.channel == 'GREEN'): colors_name += "_green"
|
|
elif(self.channel == 'BLUE'): colors_name += "_blue"
|
|
context.object.data.vertex_colors[colors_id].name = colors_name
|
|
|
|
v_colors = obj.data.vertex_colors.active.data
|
|
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me)
|
|
dvert_lay = bm.verts.layers.deform.active
|
|
weight = bmesh_get_weight_numpy(group_id,dvert_lay,bm.verts)
|
|
if self.invert: weight = 1-weight
|
|
loops_size = get_attribute_numpy(me.polygons, attribute='loop_total', mult=1)
|
|
n_colors = np.sum(loops_size)
|
|
verts = np.ones(n_colors)
|
|
me.polygons.foreach_get('vertices',verts)
|
|
splitted_weight = weight[verts.astype(int)][:,None]
|
|
r = np.zeros(splitted_weight.shape)
|
|
g = np.zeros(splitted_weight.shape)
|
|
b = np.zeros(splitted_weight.shape)
|
|
a = np.ones(splitted_weight.shape)
|
|
if(self.channel == 'FALSE_COLORS'):
|
|
mult = 0.6+0.4*splitted_weight
|
|
mask = splitted_weight < 0.25
|
|
g[mask] = splitted_weight[mask]*4
|
|
b[mask] = np.ones(splitted_weight.shape)[mask]
|
|
|
|
mask = np.where(np.logical_and(splitted_weight>=0.25, splitted_weight<0.5))
|
|
g[mask] = np.ones(splitted_weight.shape)[mask]
|
|
b[mask] = (1-(splitted_weight[mask]-0.25)*4)
|
|
|
|
mask = np.where(np.logical_and(splitted_weight>=0.5, splitted_weight<0.75))
|
|
r[mask] = (splitted_weight[mask]-0.5)*4
|
|
g[mask] = np.ones(splitted_weight.shape)[mask]
|
|
|
|
mask = 0.75 <= splitted_weight
|
|
r[mask] = np.ones(splitted_weight.shape)[mask]
|
|
g[mask] = (1-(splitted_weight[mask]-0.75)*4)
|
|
elif(self.channel == 'VALUE'):
|
|
r = splitted_weight
|
|
g = splitted_weight
|
|
b = splitted_weight
|
|
elif(self.channel == 'RED'):
|
|
r = splitted_weight
|
|
elif(self.channel == 'GREEN'):
|
|
g = splitted_weight
|
|
elif(self.channel == 'BLUE'):
|
|
b = splitted_weight
|
|
|
|
colors = np.concatenate((r,g,b,a),axis=1).flatten()
|
|
v_colors.foreach_set('color',colors)
|
|
|
|
bpy.ops.paint.vertex_paint_toggle()
|
|
context.object.data.vertex_colors[colors_id].active_render = True
|
|
return {'FINISHED'}
|
|
|
|
class vertex_group_to_uv(Operator):
|
|
bl_idname = "object.vertex_group_to_uv"
|
|
bl_label = "Vertex Group"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
bl_description = ("Combine two Vertex Groups as UV Map Layer.")
|
|
|
|
vertex_group_u : StringProperty(
|
|
name="U", default='',
|
|
description="Vertex Group used for the U coordinate")
|
|
vertex_group_v : StringProperty(
|
|
name="V", default='',
|
|
description="Vertex Group used for the V coordinate")
|
|
normalize_weight : BoolProperty(
|
|
name="Normalize Weight", default=True,
|
|
description="Normalize weight values")
|
|
invert_u : BoolProperty(
|
|
name="Invert U", default=False, description="Invert U")
|
|
invert_v : BoolProperty(
|
|
name="Invert V", default=False, description="Invert V")
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.object.vertex_groups) > 0
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_props_dialog(self, width=250)
|
|
|
|
def draw(self, context):
|
|
ob = context.object
|
|
layout = self.layout
|
|
col = layout.column(align=True)
|
|
row = col.row(align=True)
|
|
row.prop_search(self, 'vertex_group_u', ob, "vertex_groups", text='')
|
|
row.separator()
|
|
row.prop_search(self, 'vertex_group_v', ob, "vertex_groups", text='')
|
|
row = col.row(align=True)
|
|
row.prop(self, "invert_u")
|
|
row.separator()
|
|
row.prop(self, "invert_v")
|
|
row = col.row(align=True)
|
|
row.prop(self, "normalize_weight")
|
|
|
|
def execute(self, context):
|
|
ob = context.active_object
|
|
me = ob.data
|
|
n_verts = len(me.vertices)
|
|
vg_keys = ob.vertex_groups.keys()
|
|
bool_u = self.vertex_group_u in vg_keys
|
|
bool_v = self.vertex_group_v in vg_keys
|
|
if bool_u or bool_v:
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me)
|
|
dvert_lay = bm.verts.layers.deform.active
|
|
if bool_u:
|
|
u_index = ob.vertex_groups[self.vertex_group_u].index
|
|
u = bmesh_get_weight_numpy(u_index, dvert_lay, bm.verts)
|
|
if self.invert_u:
|
|
u = 1-u
|
|
if self.normalize_weight:
|
|
u = np.interp(u, (u.min(), u.max()), (0, 1))
|
|
else:
|
|
u = np.zeros(n_verts)
|
|
if bool_v:
|
|
v_index = ob.vertex_groups[self.vertex_group_v].index
|
|
v = bmesh_get_weight_numpy(v_index, dvert_lay, bm.verts)
|
|
if self.invert_v:
|
|
v = 1-v
|
|
if self.normalize_weight:
|
|
v = np.interp(v, (v.min(), v.max()), (0, 1))
|
|
else:
|
|
v = np.zeros(n_verts)
|
|
else:
|
|
u = v = np.zeros(n_verts)
|
|
|
|
uv_layer = me.uv_layers.new(name='Weight_to_UV')
|
|
loops_size = get_attribute_numpy(me.polygons, attribute='loop_total', mult=1)
|
|
n_data = np.sum(loops_size)
|
|
v_id = np.ones(n_data)
|
|
me.polygons.foreach_get('vertices',v_id)
|
|
v_id = v_id.astype(int)
|
|
split_u = u[v_id,None]
|
|
split_v = v[v_id,None]
|
|
uv = np.concatenate((split_u,split_v),axis=1).flatten()
|
|
uv_layer.data.foreach_set('uv',uv)
|
|
me.uv_layers.update()
|
|
return {'FINISHED'}
|
|
|
|
class curvature_to_vertex_groups(Operator):
|
|
bl_idname = "object.curvature_to_vertex_groups"
|
|
bl_label = "Curvature"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
bl_description = ("Generate a Vertex Group based on the curvature of the"
|
|
"mesh. Is based on Dirty Vertex Color.")
|
|
|
|
invert : BoolProperty(
|
|
name="invert", default=False, description="invert values")
|
|
|
|
blur_strength : FloatProperty(
|
|
name="Blur Strength", default=1, min=0.001,
|
|
max=1, description="Blur strength per iteration")
|
|
|
|
blur_iterations : IntProperty(
|
|
name="Blur Iterations", default=1, min=0,
|
|
max=40, description="Number of times to blur the values")
|
|
|
|
min_angle : FloatProperty(
|
|
name="Min Angle", default=0, min=0,
|
|
max=pi/2, subtype='ANGLE', description="Minimum angle")
|
|
|
|
max_angle : FloatProperty(
|
|
name="Max Angle", default=pi, min=pi/2,
|
|
max=pi, subtype='ANGLE', description="Maximum angle")
|
|
|
|
invert : BoolProperty(
|
|
name="Invert", default=False,
|
|
description="Invert the curvature map")
|
|
|
|
def execute(self, context):
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
vertex_colors = context.active_object.data.vertex_colors
|
|
vertex_colors.new()
|
|
vertex_colors[-1].active = True
|
|
vertex_colors[-1].active_render = True
|
|
vertex_colors[-1].name = "Curvature"
|
|
for c in vertex_colors[-1].data: c.color = (1,1,1,1)
|
|
bpy.ops.object.mode_set(mode='VERTEX_PAINT')
|
|
bpy.ops.paint.vertex_color_dirt(
|
|
blur_strength=self.blur_strength,
|
|
blur_iterations=self.blur_iterations, clean_angle=self.max_angle,
|
|
dirt_angle=self.min_angle)
|
|
bpy.ops.object.vertex_colors_to_vertex_groups(invert=self.invert)
|
|
vertex_colors.remove(vertex_colors.active)
|
|
return {'FINISHED'}
|
|
|
|
class face_area_to_vertex_groups(Operator):
|
|
bl_idname = "object.face_area_to_vertex_groups"
|
|
bl_label = "Area"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
bl_description = ("Generate a Vertex Group based on the area of individual"
|
|
"faces.")
|
|
|
|
invert : BoolProperty(
|
|
name="invert", default=False, description="invert values")
|
|
bounds : EnumProperty(
|
|
items=(('MANUAL', "Manual Bounds", ""),
|
|
('AUTOMATIC', "Automatic Bounds", "")),
|
|
default='AUTOMATIC', name="Bounds")
|
|
|
|
min_area : FloatProperty(
|
|
name="Min", default=0.01, soft_min=0, soft_max=1,
|
|
description="Faces with 0 weight")
|
|
|
|
max_area : FloatProperty(
|
|
name="Max", default=0.1, soft_min=0, soft_max=1,
|
|
description="Faces with 1 weight")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.label(text="Bounds")
|
|
layout.prop(self, "bounds", text="")
|
|
if self.bounds == 'MANUAL':
|
|
layout.prop(self, "min_area")
|
|
layout.prop(self, "max_area")
|
|
|
|
def execute(self, context):
|
|
try: ob = context.object
|
|
except:
|
|
self.report({'ERROR'}, "Please select an Object")
|
|
return {'CANCELLED'}
|
|
ob.vertex_groups.new(name="Faces Area")
|
|
|
|
areas = [[] for v in ob.data.vertices]
|
|
|
|
for p in ob.data.polygons:
|
|
for v in p.vertices:
|
|
areas[v].append(p.area)
|
|
|
|
for i in range(len(areas)):
|
|
areas[i] = mean(areas[i])
|
|
if self.bounds == 'MANUAL':
|
|
min_area = self.min_area
|
|
max_area = self.max_area
|
|
elif self.bounds == 'AUTOMATIC':
|
|
min_area = min(areas)
|
|
max_area = max(areas)
|
|
elif self.bounds == 'COMPRESSION':
|
|
min_area = 1
|
|
max_area = min(areas)
|
|
elif self.bounds == 'TENSION':
|
|
min_area = 1
|
|
max_area = max(areas)
|
|
delta_area = max_area - min_area
|
|
if delta_area == 0:
|
|
delta_area = 0.0001
|
|
if self.bounds == 'MANUAL':
|
|
delta_area = 0.0001
|
|
else:
|
|
self.report({'ERROR'}, "The faces have the same areas")
|
|
#return {'CANCELLED'}
|
|
for i in range(len(areas)):
|
|
weight = (areas[i] - min_area)/delta_area
|
|
ob.vertex_groups[-1].add([i], weight, 'REPLACE')
|
|
ob.vertex_groups.update()
|
|
ob.data.update()
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
return {'FINISHED'}
|
|
|
|
class random_weight(Operator):
|
|
bl_idname = "object.random_weight"
|
|
bl_label = "Random"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
bl_description = ("Generate a random Vertex Group")
|
|
|
|
min_val : FloatProperty(
|
|
name="Min", default=0, soft_min=0, soft_max=1,
|
|
description="Minimum Value")
|
|
|
|
max_val : FloatProperty(
|
|
name="Max", default=1, soft_min=0, soft_max=1,
|
|
description="Maximum Value")
|
|
|
|
#def draw(self, context):
|
|
# layout = self.layout
|
|
# layout.prop(self, "min_area")
|
|
# layout.prop(self, "max_area")
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.object.vertex_groups) > 0
|
|
|
|
def execute(self, context):
|
|
try: ob = context.object
|
|
except:
|
|
self.report({'ERROR'}, "Please select an Object")
|
|
return {'CANCELLED'}
|
|
#ob.vertex_groups.new(name="Random")
|
|
n_verts = len(ob.data.vertices)
|
|
weight = np.random.uniform(low=self.min_val, high=self.max_val, size=(n_verts,))
|
|
np.clip(weight, 0, 1, out=weight)
|
|
|
|
group_id = ob.vertex_groups.active_index
|
|
for i in range(n_verts):
|
|
ob.vertex_groups[group_id].add([i], weight[i], 'REPLACE')
|
|
ob.vertex_groups.update()
|
|
ob.data.update()
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
return {'FINISHED'}
|
|
|
|
|
|
class harmonic_weight(Operator):
|
|
bl_idname = "object.harmonic_weight"
|
|
bl_label = "Harmonic"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
bl_description = ("Create an harmonic variation of the active Vertex Group")
|
|
|
|
freq : FloatProperty(
|
|
name="Frequency", default=20, soft_min=0,
|
|
soft_max=100, description="Wave frequency")
|
|
|
|
amp : FloatProperty(
|
|
name="Amplitude", default=1, soft_min=0,
|
|
soft_max=10, description="Wave amplitude")
|
|
|
|
midlevel : FloatProperty(
|
|
name="Midlevel", default=0, min=-1,
|
|
max=1, description="Midlevel")
|
|
|
|
add : FloatProperty(
|
|
name="Add", default=0, min=-1,
|
|
max=1, description="Add to the Weight")
|
|
|
|
mult : FloatProperty(
|
|
name="Multiply", default=0, min=0,
|
|
max=1, description="Multiply for he Weight")
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.object.vertex_groups) > 0
|
|
|
|
def execute(self, context):
|
|
ob = context.active_object
|
|
if len(ob.vertex_groups) > 0:
|
|
group_id = ob.vertex_groups.active_index
|
|
ob.vertex_groups.new(name="Harmonic")
|
|
for i in range(len(ob.data.vertices)):
|
|
try: val = ob.vertex_groups[group_id].weight(i)
|
|
except: val = 0
|
|
weight = self.amp*(math.sin(val*self.freq) - self.midlevel)/2 + 0.5 + self.add*val*(1-(1-val)*self.mult)
|
|
ob.vertex_groups[-1].add([i], weight, 'REPLACE')
|
|
ob.data.update()
|
|
else:
|
|
self.report({'ERROR'}, "Active object doesn't have vertex groups")
|
|
return {'CANCELLED'}
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
return {'FINISHED'}
|
|
|
|
|
|
class tissue_weight_distance(Operator):
|
|
bl_idname = "object.tissue_weight_distance"
|
|
bl_label = "Weight Distance"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
bl_description = ("Create a weight map according to the distance from the "
|
|
"selected vertices along the mesh surface")
|
|
|
|
mode : EnumProperty(
|
|
items=(('GEOD', "Geodesic Distance", ""),
|
|
('EUCL', "Euclidean Distance", ""),
|
|
('TOPO', "Topology Distance", "")),
|
|
default='GEOD', name="Distance Method")
|
|
|
|
normalize : BoolProperty(
|
|
name="Normalize", default=True,
|
|
description="Automatically remap the distance values from 0 to 1")
|
|
|
|
min_value : FloatProperty(
|
|
name="Min", default=0, min=0,
|
|
soft_max=100, description="Minimum Distance")
|
|
|
|
max_value : FloatProperty(
|
|
name="Max", default=10, min=0,
|
|
soft_max=100, description="Max Distance")
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_props_dialog(self, width=250)
|
|
|
|
def fill_neighbors(self,verts,weight):
|
|
neigh = {}
|
|
for v0 in verts:
|
|
for f in v0.link_faces:
|
|
for v1 in f.verts:
|
|
if self.mode == 'GEOD':
|
|
dist = weight[v0.index] + (v0.co-v1.co).length
|
|
elif self.mode == 'TOPO':
|
|
dist = weight[v0.index] + 1.0
|
|
w1 = weight[v1.index]
|
|
if w1 == None or w1 > dist:
|
|
weight[v1.index] = dist
|
|
neigh[v1] = 0
|
|
if len(neigh) == 0: return weight
|
|
else: return self.fill_neighbors(neigh.keys(), weight)
|
|
|
|
def execute(self, context):
|
|
ob = context.object
|
|
old_mode = ob.mode
|
|
if old_mode != 'OBJECT':
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
me = ob.data
|
|
|
|
# store weight values
|
|
weight = [None]*len(me.vertices)
|
|
|
|
if self.mode != 'EUCL':
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me)
|
|
bm.verts.ensure_lookup_table()
|
|
bm.edges.ensure_lookup_table()
|
|
bm.faces.ensure_lookup_table()
|
|
selected = [v for v in bm.verts if v.select]
|
|
if len(selected) == 0:
|
|
bpy.ops.object.mode_set(mode=old_mode)
|
|
message = "Please, select one or more vertices"
|
|
self.report({'ERROR'}, message)
|
|
return {'CANCELLED'}
|
|
for v in selected: weight[v.index] = 0
|
|
weight = self.fill_neighbors(selected, weight)
|
|
bm.free()
|
|
else:
|
|
selected = [v for v in me.vertices if v.select]
|
|
kd = KDTree(len(selected))
|
|
for i, v in enumerate(selected):
|
|
kd.insert(v.co, i)
|
|
kd.balance()
|
|
for i,v in enumerate(me.vertices):
|
|
co, index, dist = kd.find(v.co)
|
|
weight[i] = dist
|
|
|
|
|
|
for i in range(len(weight)):
|
|
if weight[i] == None: weight[i] = 0
|
|
weight = np.array(weight)
|
|
max_dist = np.max(weight)
|
|
if self.normalize:
|
|
if max_dist > 0:
|
|
weight /= max_dist
|
|
else:
|
|
delta_value = self.max_value - self.min_value
|
|
if delta_value == 0: delta_value = 0.0000001
|
|
weight = (weight-self.min_value)/delta_value
|
|
|
|
if self.mode == 'TOPO':
|
|
vg = ob.vertex_groups.new(name='Distance: {:d}'.format(int(max_dist)))
|
|
else:
|
|
vg = ob.vertex_groups.new(name='Distance: {:.4f}'.format(max_dist))
|
|
for i, w in enumerate(weight):
|
|
vg.add([i], w, 'REPLACE')
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
return {'FINISHED'}
|
|
|
|
class TISSUE_PT_color(Panel):
|
|
bl_label = "Tissue Tools"
|
|
bl_category = "Tissue"
|
|
bl_space_type = "VIEW_3D"
|
|
bl_region_type = "UI"
|
|
#bl_options = {'DEFAULT_CLOSED'}
|
|
bl_context = "vertexpaint"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
col = layout.column(align=True)
|
|
col.operator("object.vertex_colors_to_vertex_groups",
|
|
icon="GROUP_VERTEX", text="Convert to Weight")
|
|
|
|
class TISSUE_PT_weight(Panel):
|
|
bl_label = "Tissue Tools"
|
|
bl_category = "Tissue"
|
|
bl_space_type = "VIEW_3D"
|
|
bl_region_type = "UI"
|
|
#bl_options = {'DEFAULT_CLOSED'}
|
|
bl_context = "weightpaint"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
col = layout.column(align=True)
|
|
#if context.object.type == 'MESH' and context.mode == 'OBJECT':
|
|
#col.label(text="Transform:")
|
|
#col.separator()
|
|
#elif bpy.context.mode == 'PAINT_WEIGHT':
|
|
col.label(text="Weight Generate:")
|
|
#col.operator(
|
|
# "object.vertex_colors_to_vertex_groups", icon="GROUP_VCOL")
|
|
col.operator("object.face_area_to_vertex_groups", icon="FACESEL")
|
|
col.operator("object.curvature_to_vertex_groups", icon="SMOOTHCURVE")
|
|
col.operator("object.tissue_weight_distance", icon="TRACKING")
|
|
row = col.row(align=True)
|
|
try: row.operator("object.weight_formula", icon="CON_TRANSFORM")
|
|
except: row.operator("object.weight_formula")#, icon="CON_TRANSFORM")
|
|
row.operator("object.update_weight_formula", icon="FILE_REFRESH", text='')#, icon="CON_TRANSFORM")
|
|
#col.label(text="Weight Processing:")
|
|
col.separator()
|
|
|
|
# TO BE FIXED
|
|
col.operator("object.weight_laplacian", icon="SMOOTHCURVE")
|
|
|
|
col.label(text="Weight Edit:")
|
|
col.operator("object.harmonic_weight", icon="IPO_ELASTIC")
|
|
col.operator("object.random_weight", icon="RNDCURVE")
|
|
col.separator()
|
|
col.label(text="Deformation Analysis:")
|
|
col.operator("object.edges_deformation", icon="DRIVER_DISTANCE")#FULLSCREEN_ENTER")
|
|
col.operator("object.edges_bending", icon="DRIVER_ROTATIONAL_DIFFERENCE")#"MOD_SIMPLEDEFORM")
|
|
col.separator()
|
|
col.label(text="Weight Curves:")
|
|
#col.operator("object.weight_contour_curves", icon="MOD_CURVE")
|
|
col.operator("object.tissue_weight_streamlines", icon="ANIM")
|
|
col.operator("object.tissue_weight_contour_curves_pattern", icon="FORCE_TURBULENCE")
|
|
col.separator()
|
|
col.operator("object.weight_contour_displace", icon="MOD_DISPLACE")
|
|
col.operator("object.weight_contour_mask", icon="MOD_MASK")
|
|
col.separator()
|
|
col.label(text="Simulations:")
|
|
#col.operator("object.reaction_diffusion", icon="MOD_OCEAN")
|
|
col.operator("object.start_reaction_diffusion",
|
|
icon="EXPERIMENTAL",
|
|
text="Reaction-Diffusion")
|
|
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="Weight Convert:")
|
|
col.operator("object.vertex_group_to_vertex_colors", icon="GROUP_VCOL",
|
|
text="Convert to Colors")
|
|
col.operator("object.vertex_group_to_uv", icon="UV",
|
|
text="Convert to UV")
|
|
|
|
#col.prop(context.object, "reaction_diffusion_run", icon="PLAY", text="Run Simulation")
|
|
####col.prop(context.object, "reaction_diffusion_run")
|
|
#col.separator()
|
|
#col.label(text="Vertex Color from:")
|
|
#col.operator("object.vertex_group_to_vertex_colors", icon="GROUP_VERTEX")
|
|
|
|
|
|
|
|
|
|
class start_reaction_diffusion(Operator):
|
|
bl_idname = "object.start_reaction_diffusion"
|
|
bl_label = "Start Reaction Diffusion"
|
|
bl_description = ("Run a Reaction-Diffusion based on existing Vertex Groups: A and B")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
run : BoolProperty(
|
|
name="Run Reaction-Diffusion", default=True, description="Compute a new iteration on frame changes")
|
|
|
|
time_steps : IntProperty(
|
|
name="Steps", default=10, min=0, soft_max=50,
|
|
description="Number of Steps")
|
|
|
|
dt : FloatProperty(
|
|
name="dt", default=1, min=0, soft_max=0.2,
|
|
description="Time Step")
|
|
|
|
diff_a : FloatProperty(
|
|
name="Diff A", default=0.18, min=0, soft_max=2,
|
|
description="Diffusion A")
|
|
|
|
diff_b : FloatProperty(
|
|
name="Diff B", default=0.09, min=0, soft_max=2,
|
|
description="Diffusion B")
|
|
|
|
f : FloatProperty(
|
|
name="f", default=0.055, min=0, soft_min=0.01, soft_max=0.06, max=0.1, precision=4,
|
|
description="Feed Rate")
|
|
|
|
k : FloatProperty(
|
|
name="k", default=0.062, min=0, soft_min=0.035, soft_max=0.065, max=0.1, precision=4,
|
|
description="Kill Rate")
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object.type == 'MESH' and context.mode != 'EDIT_MESH'
|
|
|
|
def execute(self, context):
|
|
reaction_diffusion_add_handler(self, context)
|
|
set_animatable_fix_handler(self, context)
|
|
|
|
ob = context.object
|
|
|
|
ob.reaction_diffusion_settings.run = self.run
|
|
ob.reaction_diffusion_settings.dt = self.dt
|
|
ob.reaction_diffusion_settings.time_steps = self.time_steps
|
|
ob.reaction_diffusion_settings.f = self.f
|
|
ob.reaction_diffusion_settings.k = self.k
|
|
ob.reaction_diffusion_settings.diff_a = self.diff_a
|
|
ob.reaction_diffusion_settings.diff_b = self.diff_b
|
|
|
|
|
|
# check vertex group A
|
|
try:
|
|
vg = ob.vertex_groups['A']
|
|
except:
|
|
ob.vertex_groups.new(name='A')
|
|
# check vertex group B
|
|
try:
|
|
vg = ob.vertex_groups['B']
|
|
except:
|
|
ob.vertex_groups.new(name='B')
|
|
|
|
for v in ob.data.vertices:
|
|
ob.vertex_groups['A'].add([v.index], 1, 'REPLACE')
|
|
ob.vertex_groups['B'].add([v.index], 0, 'REPLACE')
|
|
|
|
ob.vertex_groups.update()
|
|
ob.data.update()
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
|
|
return {'FINISHED'}
|
|
|
|
class reset_reaction_diffusion_weight(Operator):
|
|
bl_idname = "object.reset_reaction_diffusion_weight"
|
|
bl_label = "Reset Reaction Diffusion Weight"
|
|
bl_description = ("Set A and B weight to default values")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object.type == 'MESH' and context.mode != 'EDIT_MESH'
|
|
|
|
def execute(self, context):
|
|
reaction_diffusion_add_handler(self, context)
|
|
set_animatable_fix_handler(self, context)
|
|
|
|
ob = context.object
|
|
|
|
# check vertex group A
|
|
try:
|
|
vg = ob.vertex_groups['A']
|
|
except:
|
|
ob.vertex_groups.new(name='A')
|
|
# check vertex group B
|
|
try:
|
|
vg = ob.vertex_groups['B']
|
|
except:
|
|
ob.vertex_groups.new(name='B')
|
|
|
|
for v in ob.data.vertices:
|
|
ob.vertex_groups['A'].add([v.index], 1, 'REPLACE')
|
|
ob.vertex_groups['B'].add([v.index], 0, 'REPLACE')
|
|
|
|
ob.vertex_groups.update()
|
|
ob.data.update()
|
|
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
|
|
|
|
return {'FINISHED'}
|
|
|
|
class bake_reaction_diffusion(Operator):
|
|
bl_idname = "object.bake_reaction_diffusion"
|
|
bl_label = "Bake Data"
|
|
bl_description = ("Bake the Reaction-Diffusion to the cache directory")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object.type == 'MESH' and context.mode != 'EDIT_MESH'
|
|
|
|
def execute(self, context):
|
|
ob = context.object
|
|
props = ob.reaction_diffusion_settings
|
|
if props.fast_bake:
|
|
bool_run = props.run
|
|
props.run = False
|
|
context.scene.frame_current = props.cache_frame_start
|
|
fast_bake_def(ob, frame_start=props.cache_frame_start, frame_end=props.cache_frame_end)
|
|
#create_fast_bake_def(ob, frame_start=props.cache_frame_start, frame_end=props.cache_frame_end)
|
|
context.scene.frame_current = props.cache_frame_end
|
|
props.run = bool_run
|
|
else:
|
|
for i in range(props.cache_frame_start, props.cache_frame_end):
|
|
context.scene.frame_current = i
|
|
reaction_diffusion_def(ob, bake=True)
|
|
props.bool_cache = True
|
|
|
|
return {'FINISHED'}
|
|
|
|
class reaction_diffusion_free_data(Operator):
|
|
bl_idname = "object.reaction_diffusion_free_data"
|
|
bl_label = "Free Data"
|
|
bl_description = ("Free Reaction-Diffusion data")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object.type == 'MESH'
|
|
|
|
def execute(self, context):
|
|
ob = context.object
|
|
props = ob.reaction_diffusion_settings
|
|
props.bool_cache = False
|
|
|
|
folder = Path(props.cache_dir)
|
|
for i in range(props.cache_frame_start, props.cache_frame_end):
|
|
data_a = folder / "a_{:04d}".format(i)
|
|
if os.path.exists(data_a):
|
|
os.remove(data_a)
|
|
data_a = folder / "b_{:04d}".format(i)
|
|
if os.path.exists(data_a):
|
|
os.remove(data_a)
|
|
return {'FINISHED'}
|
|
|
|
from bpy.app.handlers import persistent
|
|
|
|
def reaction_diffusion_scene(scene, bake=False):
|
|
for ob in scene.objects:
|
|
if ob.reaction_diffusion_settings.run:
|
|
reaction_diffusion_def(ob)
|
|
|
|
def reaction_diffusion_def(ob, bake=False):
|
|
|
|
scene = bpy.context.scene
|
|
start = time.time()
|
|
if type(ob) == bpy.types.Scene: return None
|
|
props = ob.reaction_diffusion_settings
|
|
|
|
if bake or props.bool_cache:
|
|
if props.cache_dir == '':
|
|
letters = string.ascii_letters
|
|
random_name = ''.join(rnd.choice(letters) for i in range(6))
|
|
if bpy.context.blend_data.filepath == '':
|
|
folder = Path(bpy.context.preferences.filepaths.temporary_directory)
|
|
folder = folder / 'reaction_diffusion_cache' / random_name
|
|
else:
|
|
folder = '//' + Path(bpy.context.blend_data.filepath).stem
|
|
folder = Path(bpy.path.abspath(folder)) / 'reaction_diffusion_cache' / random_name
|
|
folder.mkdir(parents=True, exist_ok=True)
|
|
props.cache_dir = str(folder)
|
|
else:
|
|
folder = Path(props.cache_dir)
|
|
|
|
me = ob.data
|
|
n_edges = len(me.edges)
|
|
n_verts = len(me.vertices)
|
|
a = np.zeros(n_verts)
|
|
b = np.zeros(n_verts)
|
|
|
|
print("{:6d} Reaction-Diffusion: {}".format(scene.frame_current, ob.name))
|
|
|
|
if not props.bool_cache:
|
|
|
|
if props.bool_mod:
|
|
# hide deforming modifiers
|
|
mod_visibility = []
|
|
for m in ob.modifiers:
|
|
mod_visibility.append(m.show_viewport)
|
|
if not mod_preserve_shape(m): m.show_viewport = False
|
|
|
|
# evaluated mesh
|
|
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)
|
|
|
|
# set original visibility
|
|
for v, m in zip(mod_visibility, ob.modifiers):
|
|
m.show_viewport = v
|
|
ob.modifiers.update()
|
|
|
|
bm = bmesh.new() # create an empty BMesh
|
|
bm.from_mesh(me) # fill it in from a Mesh
|
|
dvert_lay = bm.verts.layers.deform.active
|
|
|
|
dt = props.dt
|
|
time_steps = props.time_steps
|
|
f = props.f
|
|
k = props.k
|
|
diff_a = props.diff_a
|
|
diff_b = props.diff_b
|
|
scale = props.diff_mult
|
|
|
|
brush_mult = props.brush_mult
|
|
|
|
# store weight values
|
|
if 'dB' in ob.vertex_groups: db = np.zeros(n_verts)
|
|
if 'grad' in ob.vertex_groups: grad = np.zeros(n_verts)
|
|
|
|
if props.vertex_group_diff_a != '': diff_a = np.zeros(n_verts)
|
|
if props.vertex_group_diff_b != '': diff_b = np.zeros(n_verts)
|
|
if props.vertex_group_scale != '': scale = np.zeros(n_verts)
|
|
if props.vertex_group_f != '': f = np.zeros(n_verts)
|
|
if props.vertex_group_k != '': k = np.zeros(n_verts)
|
|
if props.vertex_group_brush != '': brush = np.zeros(n_verts)
|
|
else: brush = 0
|
|
|
|
group_index_a = ob.vertex_groups["A"].index
|
|
group_index_b = ob.vertex_groups["B"].index
|
|
a = bmesh_get_weight_numpy(group_index_a, dvert_lay, bm.verts)
|
|
b = bmesh_get_weight_numpy(group_index_b, dvert_lay, bm.verts)
|
|
|
|
if props.vertex_group_diff_a != '':
|
|
group_index = ob.vertex_groups[props.vertex_group_diff_a].index
|
|
diff_a = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
|
|
if props.invert_vertex_group_diff_a:
|
|
vg_bounds = (props.min_diff_a, props.max_diff_a)
|
|
else:
|
|
vg_bounds = (props.max_diff_a, props.min_diff_a)
|
|
diff_a = np.interp(diff_a, (0,1), vg_bounds)
|
|
|
|
if props.vertex_group_diff_b != '':
|
|
group_index = ob.vertex_groups[props.vertex_group_diff_b].index
|
|
diff_b = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
|
|
if props.invert_vertex_group_diff_b:
|
|
vg_bounds = (props.max_diff_b, props.min_diff_b)
|
|
else:
|
|
vg_bounds = (props.min_diff_b, props.max_diff_b)
|
|
diff_b = np.interp(diff_b, (0,1), vg_bounds)
|
|
|
|
if props.vertex_group_scale != '':
|
|
group_index = ob.vertex_groups[props.vertex_group_scale].index
|
|
scale = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
|
|
if props.invert_vertex_group_scale:
|
|
vg_bounds = (props.max_scale, props.min_scale)
|
|
else:
|
|
vg_bounds = (props.min_scale, props.max_scale)
|
|
scale = np.interp(scale, (0,1), vg_bounds)
|
|
|
|
if props.vertex_group_f != '':
|
|
group_index = ob.vertex_groups[props.vertex_group_f].index
|
|
f = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
|
|
if props.invert_vertex_group_f:
|
|
vg_bounds = (props.max_f, props.min_f)
|
|
else:
|
|
vg_bounds = (props.min_f, props.max_f)
|
|
f = np.interp(f, (0,1), vg_bounds, )
|
|
|
|
if props.vertex_group_k != '':
|
|
group_index = ob.vertex_groups[props.vertex_group_k].index
|
|
k = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
|
|
if props.invert_vertex_group_k:
|
|
vg_bounds = (props.max_k, props.min_k)
|
|
else:
|
|
vg_bounds = (props.min_k, props.max_k)
|
|
k = np.interp(k, (0,1), vg_bounds)
|
|
|
|
if props.vertex_group_brush != '':
|
|
group_index = ob.vertex_groups[props.vertex_group_brush].index
|
|
brush = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
|
|
brush *= brush_mult
|
|
|
|
|
|
#timeElapsed = time.time() - start
|
|
#print('RD - Read Vertex Groups:',timeElapsed)
|
|
#start = time.time()
|
|
|
|
diff_a *= scale
|
|
diff_b *= scale
|
|
|
|
edge_verts = [0]*n_edges*2
|
|
me.edges.foreach_get("vertices", edge_verts)
|
|
edge_verts = np.array(edge_verts)
|
|
|
|
if 'gradient' in ob.vertex_groups.keys() and False:
|
|
group_index = ob.vertex_groups['gradient'].index
|
|
gradient = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
|
|
|
|
arr = (np.arange(n_edges)*2).astype(int)
|
|
id0 = edge_verts[arr]
|
|
id1 = edge_verts[arr+1]
|
|
|
|
#gradient = np.abs(gradient[id0] - gradient[id1])
|
|
gradient = gradient[id1] - gradient[id0]
|
|
gradient /= np.max(gradient)
|
|
sign = np.sign(gradient)
|
|
sign[sign==0] = 1
|
|
gradient = (0.05*abs(gradient) + 0.95)*sign
|
|
#gradient *= (1-abs(gradient)
|
|
#gradient = 0.2*(1-gradient) + 0.95
|
|
|
|
#gradient = get_uv_edge_vectors(me)
|
|
#uv_dir = Vector((0.5,0.5,0)).normalized()
|
|
#gradient = np.array([abs(g.dot(uv_dir.normalized())) for g in gradient])
|
|
#gradient = (gradient + 0.5)/2
|
|
#gradient = np.array([max(0,g.dot(uv_dir.normalized())) for g in gradient])
|
|
|
|
timeElapsed = time.time() - start
|
|
print(' Preparation Time:',timeElapsed)
|
|
start = time.time()
|
|
|
|
try:
|
|
_f = f if type(f) is np.ndarray else np.array((f,))
|
|
_k = k if type(k) is np.ndarray else np.array((k,))
|
|
_diff_a = diff_a if type(diff_a) is np.ndarray else np.array((diff_a,))
|
|
_diff_b = diff_b if type(diff_b) is np.ndarray else np.array((diff_b,))
|
|
_brush = brush if type(brush) is np.ndarray else np.array((brush,))
|
|
|
|
#a, b = numba_reaction_diffusion_anisotropic(n_verts, n_edges, edge_verts, a, b, _brush, _diff_a, _diff_b, _f, _k, dt, time_steps, gradient)
|
|
a, b = numba_reaction_diffusion(n_verts, n_edges, edge_verts, a, b, _brush, _diff_a, _diff_b, _f, _k, dt, time_steps)
|
|
except:
|
|
print('Not using Numba! The simulation could be slow.')
|
|
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 i in range(time_steps):
|
|
b += brush
|
|
lap_a = np.zeros(n_verts)
|
|
lap_b = np.zeros(n_verts)
|
|
lap_a0 = a[id1] - a[id0] # laplacian increment for first vertex of each edge
|
|
lap_b0 = b[id1] - b[id0] # laplacian increment for first vertex of each edge
|
|
|
|
np.add.at(lap_a, id0, lap_a0)
|
|
np.add.at(lap_b, id0, lap_b0)
|
|
np.add.at(lap_a, id1, -lap_a0)
|
|
np.add.at(lap_b, id1, -lap_b0)
|
|
|
|
ab2 = a*b**2
|
|
a += eval("(diff_a*lap_a - ab2 + f*(1-a))*dt")
|
|
b += eval("(diff_b*lap_b + ab2 - (k+f)*b)*dt")
|
|
#a += (diff_a*lap_a - ab2 + f*(1-a))*dt
|
|
#b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
|
|
|
|
a = nan_to_num(a)
|
|
b = nan_to_num(b)
|
|
|
|
timeElapsed = time.time() - start
|
|
print(' Simulation Time:',timeElapsed)
|
|
|
|
if bake:
|
|
if not(os.path.exists(folder)):
|
|
os.mkdir(folder)
|
|
file_name = folder / "a_{:04d}".format(scene.frame_current)
|
|
a.tofile(file_name)
|
|
file_name = folder / "b_{:04d}".format(scene.frame_current)
|
|
b.tofile(file_name)
|
|
elif props.bool_cache:
|
|
try:
|
|
file_name = folder / "a_{:04d}".format(scene.frame_current)
|
|
a = np.fromfile(file_name)
|
|
file_name = folder / "b_{:04d}".format(scene.frame_current)
|
|
b = np.fromfile(file_name)
|
|
except:
|
|
print(' Cannot read cache.')
|
|
return
|
|
|
|
if props.update_weight_a or props.update_weight_b:
|
|
start = time.time()
|
|
if props.update_weight_a:
|
|
if 'A' in ob.vertex_groups.keys():
|
|
vg_a = ob.vertex_groups['A']
|
|
else:
|
|
vg_a = ob.vertex_groups.new(name='A')
|
|
else:
|
|
vg_a = None
|
|
if props.update_weight_b:
|
|
if 'B' in ob.vertex_groups.keys():
|
|
vg_b = ob.vertex_groups['B']
|
|
else:
|
|
vg_b = ob.vertex_groups.new(name='B')
|
|
else:
|
|
vg_b = None
|
|
if vg_a == vg_b == None:
|
|
pass
|
|
else:
|
|
if ob.mode == 'WEIGHT_PAINT':# or props.bool_cache:
|
|
# slower, but prevent crashes
|
|
for i in range(n_verts):
|
|
if vg_a: vg_a.add([i], a[i], 'REPLACE')
|
|
if vg_b: vg_b.add([i], b[i], 'REPLACE')
|
|
else:
|
|
if props.bool_mod or props.bool_cache:
|
|
#bm.free() # release old bmesh
|
|
bm = bmesh.new() # create an empty BMesh
|
|
bm.from_mesh(ob.data) # fill it in from a Mesh
|
|
dvert_lay = bm.verts.layers.deform.active
|
|
# faster, but can cause crashes while painting weight
|
|
if vg_a: index_a = vg_a.index
|
|
if vg_b: index_b = vg_b.index
|
|
for i, v in enumerate(bm.verts):
|
|
dvert = v[dvert_lay]
|
|
if vg_a: dvert[index_a] = a[i]
|
|
if vg_b: dvert[index_b] = b[i]
|
|
bm.to_mesh(ob.data)
|
|
bm.free()
|
|
print(' Writing Vertex Groups Time:',time.time() - start)
|
|
if props.normalize:
|
|
min_a = np.min(a)
|
|
max_a = np.max(a)
|
|
min_b = np.min(b)
|
|
max_b = np.max(b)
|
|
a = (a - min_a)/(max_a - min_a)
|
|
b = (b - min_b)/(max_b - min_b)
|
|
split_a = None
|
|
split_b = None
|
|
splitted = False
|
|
if props.update_colors:#_a or props.update_colors_b:
|
|
start = time.time()
|
|
loops_size = get_attribute_numpy(me.polygons, attribute='loop_total', mult=1)
|
|
n_colors = np.sum(loops_size)
|
|
v_id = np.ones(n_colors)
|
|
me.polygons.foreach_get('vertices',v_id)
|
|
v_id = v_id.astype(int)
|
|
#v_id = np.array([v for p in ob.data.polygons for v in p.vertices])
|
|
'''
|
|
if props.update_colors_b:
|
|
if 'B' in ob.data.vertex_colors.keys():
|
|
vc = ob.data.vertex_colors['B']
|
|
else:
|
|
vc = ob.data.vertex_colors.new(name='B')
|
|
c_val = b[v_id]
|
|
c_val = np.repeat(c_val, 4, axis=0)
|
|
vc.data.foreach_set('color',c_val)
|
|
|
|
if props.update_colors_a:
|
|
if 'A' in ob.data.vertex_colors.keys():
|
|
vc = ob.data.vertex_colors['A']
|
|
else:
|
|
vc = ob.data.vertex_colors.new(name='A')
|
|
c_val = a[v_id]
|
|
c_val = np.repeat(c_val, 4, axis=0)
|
|
vc.data.foreach_set('color',c_val)
|
|
'''
|
|
split_a = a[v_id,None]
|
|
split_b = b[v_id,None]
|
|
splitted = True
|
|
ones = np.ones((n_colors,1))
|
|
#rgba = np.concatenate((split_a,split_b,-split_b+split_a,ones),axis=1).flatten()
|
|
rgba = np.concatenate((split_a,split_b,ones,ones),axis=1).flatten()
|
|
if 'AB' in ob.data.vertex_colors.keys():
|
|
vc = ob.data.vertex_colors['AB']
|
|
else:
|
|
vc = ob.data.vertex_colors.new(name='AB')
|
|
vc.data.foreach_set('color',rgba)
|
|
ob.data.vertex_colors.update()
|
|
|
|
print(' Writing Vertex Colors Time:',time.time() - start)
|
|
if props.update_uv:
|
|
start = time.time()
|
|
if 'AB' in me.uv_layers.keys():
|
|
uv_layer = me.uv_layers['AB']
|
|
else:
|
|
uv_layer = me.uv_layers.new(name='AB')
|
|
if not splitted:
|
|
loops_size = get_attribute_numpy(me.polygons, attribute='loop_total', mult=1)
|
|
n_data = np.sum(loops_size)
|
|
v_id = np.ones(n_data)
|
|
me.polygons.foreach_get('vertices',v_id)
|
|
v_id = v_id.astype(int)
|
|
split_a = a[v_id,None]
|
|
split_b = b[v_id,None]
|
|
uv = np.concatenate((split_a,split_b),axis=1).flatten()
|
|
uv_layer.data.foreach_set('uv',uv)
|
|
me.uv_layers.update()
|
|
print(' Writing UV Map Time:',time.time() - start)
|
|
|
|
for ps in ob.particle_systems:
|
|
if ps.vertex_group_density == 'B' or ps.vertex_group_density == 'A':
|
|
ps.invert_vertex_group_density = not ps.invert_vertex_group_density
|
|
ps.invert_vertex_group_density = not ps.invert_vertex_group_density
|
|
|
|
if props.bool_mod and not props.bool_cache: bpy.data.meshes.remove(me)
|
|
|
|
def fast_bake_def(ob, frame_start=1, frame_end=250):
|
|
scene = bpy.context.scene
|
|
start = time.time()
|
|
if type(ob) == bpy.types.Scene: return None
|
|
props = ob.reaction_diffusion_settings
|
|
|
|
# Define cache folder
|
|
if props.cache_dir == '':
|
|
letters = string.ascii_letters
|
|
random_name = ''.join(rnd.choice(letters) for i in range(6))
|
|
if bpy.context.blend_data.filepath == '':
|
|
folder = Path(bpy.context.preferences.filepaths.temporary_directory)
|
|
folder = folder / 'reaction_diffusion_cache' / random_name
|
|
else:
|
|
folder = '//' + Path(bpy.context.blend_data.filepath).stem
|
|
folder = Path(bpy.path.abspath(folder)) / 'reaction_diffusion_cache' / random_name
|
|
folder.mkdir(parents=True, exist_ok=True)
|
|
props.cache_dir = str(folder)
|
|
else:
|
|
folder = Path(props.cache_dir)
|
|
|
|
if props.bool_mod:
|
|
# hide deforming modifiers
|
|
mod_visibility = []
|
|
for m in ob.modifiers:
|
|
mod_visibility.append(m.show_viewport)
|
|
if not mod_preserve_shape(m): m.show_viewport = False
|
|
|
|
# evaluated mesh
|
|
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)
|
|
|
|
# set original visibility
|
|
for v, m in zip(mod_visibility, ob.modifiers):
|
|
m.show_viewport = v
|
|
ob.modifiers.update()
|
|
else:
|
|
me = ob.data
|
|
|
|
bm = bmesh.new() # create an empty BMesh
|
|
bm.from_mesh(me) # fill it in from a Mesh
|
|
dvert_lay = bm.verts.layers.deform.active
|
|
n_edges = len(me.edges)
|
|
n_verts = len(me.vertices)
|
|
a = np.zeros(n_verts)
|
|
b = np.zeros(n_verts)
|
|
group_index_a = ob.vertex_groups["A"].index
|
|
group_index_b = ob.vertex_groups["B"].index
|
|
|
|
dt = props.dt
|
|
time_steps = props.time_steps
|
|
f = props.f
|
|
k = props.k
|
|
diff_a = props.diff_a
|
|
diff_b = props.diff_b
|
|
scale = props.diff_mult
|
|
|
|
brush_mult = props.brush_mult
|
|
|
|
# store weight values
|
|
if 'dB' in ob.vertex_groups: db = np.zeros(n_verts)
|
|
if 'grad' in ob.vertex_groups: grad = np.zeros(n_verts)
|
|
|
|
if props.vertex_group_diff_a != '': diff_a = np.zeros(n_verts)
|
|
if props.vertex_group_diff_b != '': diff_b = np.zeros(n_verts)
|
|
if props.vertex_group_scale != '': scale = np.zeros(n_verts)
|
|
if props.vertex_group_f != '': f = np.zeros(n_verts)
|
|
if props.vertex_group_k != '': k = np.zeros(n_verts)
|
|
if props.vertex_group_brush != '': brush = np.zeros(n_verts)
|
|
else: brush = 0
|
|
|
|
a = bmesh_get_weight_numpy(group_index_a, dvert_lay, bm.verts)
|
|
b = bmesh_get_weight_numpy(group_index_b, dvert_lay, bm.verts)
|
|
|
|
if props.vertex_group_diff_a != '':
|
|
group_index = ob.vertex_groups[props.vertex_group_diff_a].index
|
|
diff_a = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
|
|
if props.invert_vertex_group_diff_a:
|
|
vg_bounds = (props.min_diff_a, props.max_diff_a)
|
|
else:
|
|
vg_bounds = (props.max_diff_a, props.min_diff_a)
|
|
diff_a = np.interp(diff_a, (0,1), vg_bounds)
|
|
|
|
if props.vertex_group_diff_b != '':
|
|
group_index = ob.vertex_groups[props.vertex_group_diff_b].index
|
|
diff_b = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
|
|
if props.invert_vertex_group_diff_b:
|
|
vg_bounds = (props.max_diff_b, props.min_diff_b)
|
|
else:
|
|
vg_bounds = (props.min_diff_b, props.max_diff_b)
|
|
diff_b = np.interp(diff_b, (0,1), vg_bounds)
|
|
|
|
if props.vertex_group_scale != '':
|
|
group_index = ob.vertex_groups[props.vertex_group_scale].index
|
|
scale = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
|
|
if props.invert_vertex_group_scale:
|
|
vg_bounds = (props.max_scale, props.min_scale)
|
|
else:
|
|
vg_bounds = (props.min_scale, props.max_scale)
|
|
scale = np.interp(scale, (0,1), vg_bounds)
|
|
|
|
if props.vertex_group_f != '':
|
|
group_index = ob.vertex_groups[props.vertex_group_f].index
|
|
f = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
|
|
if props.invert_vertex_group_f:
|
|
vg_bounds = (props.max_f, props.min_f)
|
|
else:
|
|
vg_bounds = (props.min_f, props.max_f)
|
|
f = np.interp(f, (0,1), vg_bounds, )
|
|
|
|
if props.vertex_group_k != '':
|
|
group_index = ob.vertex_groups[props.vertex_group_k].index
|
|
k = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
|
|
if props.invert_vertex_group_k:
|
|
vg_bounds = (props.max_k, props.min_k)
|
|
else:
|
|
vg_bounds = (props.min_k, props.max_k)
|
|
k = np.interp(k, (0,1), vg_bounds)
|
|
|
|
if props.vertex_group_brush != '':
|
|
group_index = ob.vertex_groups[props.vertex_group_brush].index
|
|
brush = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
|
|
brush *= brush_mult
|
|
|
|
diff_a *= scale
|
|
diff_b *= scale
|
|
|
|
edge_verts = [0]*n_edges*2
|
|
me.edges.foreach_get("vertices", edge_verts)
|
|
|
|
gradient = get_uv_edge_vectors(me)
|
|
uv_dir = Vector((0.5,0.5,0))
|
|
#gradient = [abs(g.dot(uv_dir)) for g in gradient]
|
|
gradient = [max(0,g.dot(uv_dir)) for g in gradient]
|
|
|
|
timeElapsed = time.time() - start
|
|
print(' Preparation Time:',timeElapsed)
|
|
start = time.time()
|
|
|
|
try:
|
|
edge_verts = np.array(edge_verts)
|
|
_f = f if type(f) is np.ndarray else np.array((f,))
|
|
_k = k if type(k) is np.ndarray else np.array((k,))
|
|
_diff_a = diff_a if type(diff_a) is np.ndarray else np.array((diff_a,))
|
|
_diff_b = diff_b if type(diff_b) is np.ndarray else np.array((diff_b,))
|
|
_brush = brush if type(brush) is np.ndarray else np.array((brush,))
|
|
|
|
run_rd = False
|
|
for j in range(props.cache_frame_start, props.cache_frame_end+1):
|
|
start2 = time.time()
|
|
print("{:6d} Reaction-Diffusion: {}".format(j, ob.name))
|
|
if run_rd:
|
|
b += _brush
|
|
a, b = numba_reaction_diffusion(n_verts, n_edges, edge_verts, a, b, _brush, _diff_a, _diff_b, _f, _k, dt, time_steps)
|
|
else:
|
|
run_rd = True
|
|
|
|
if not(os.path.exists(folder)):
|
|
os.mkdir(folder)
|
|
file_name = folder / "a_{:04d}".format(j)
|
|
a.tofile(file_name)
|
|
file_name = folder / "b_{:04d}".format(j)
|
|
b.tofile(file_name)
|
|
|
|
timeElapsed = time.time() - start2
|
|
print(' Simulation Time:',timeElapsed)
|
|
|
|
except:
|
|
print('Not using Numba! The simulation could be slow.')
|
|
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 j in range(props.cache_frame_start, props.cache_frame_end):
|
|
for i in range(time_steps):
|
|
b += brush
|
|
lap_a = np.zeros(n_verts)
|
|
lap_b = np.zeros(n_verts)
|
|
lap_a0 = a[id1] - a[id0] # laplacian increment for first vertex of each edge
|
|
lap_b0 = b[id1] - b[id0] # laplacian increment for first vertex of each edge
|
|
|
|
np.add.at(lap_a, id0, lap_a0)
|
|
np.add.at(lap_b, id0, lap_b0)
|
|
np.add.at(lap_a, id1, -lap_a0)
|
|
np.add.at(lap_b, id1, -lap_b0)
|
|
|
|
ab2 = a*b**2
|
|
a += eval("(diff_a*lap_a - ab2 + f*(1-a))*dt")
|
|
b += eval("(diff_b*lap_b + ab2 - (k+f)*b)*dt")
|
|
|
|
a = nan_to_num(a)
|
|
b = nan_to_num(b)
|
|
|
|
if not(os.path.exists(folder)):
|
|
os.mkdir(folder)
|
|
file_name = folder / "a_{:04d}".format(j)
|
|
a.tofile(file_name)
|
|
file_name = folder / "b_{:04d}".format(j)
|
|
b.tofile(file_name)
|
|
|
|
if ob.mode == 'WEIGHT_PAINT':
|
|
# slower, but prevent crashes
|
|
vg_a = ob.vertex_groups['A']
|
|
vg_b = ob.vertex_groups['B']
|
|
for i in range(n_verts):
|
|
vg_a.add([i], a[i], 'REPLACE')
|
|
vg_b.add([i], b[i], 'REPLACE')
|
|
else:
|
|
if props.bool_mod:
|
|
bm.free() # release old bmesh
|
|
bm = bmesh.new() # create an empty BMesh
|
|
bm.from_mesh(ob.data) # fill it in from a Mesh
|
|
dvert_lay = bm.verts.layers.deform.active
|
|
# faster, but can cause crashes while painting weight
|
|
for i, v in enumerate(bm.verts):
|
|
dvert = v[dvert_lay]
|
|
dvert[group_index_a] = a[i]
|
|
dvert[group_index_b] = b[i]
|
|
bm.to_mesh(ob.data)
|
|
|
|
# Update Vertex Colors
|
|
if 'A' in ob.data.vertex_colors or 'B' in ob.data.vertex_colors:
|
|
v_id = np.array([v for p in ob.data.polygons for v in p.vertices])
|
|
|
|
if 'B' in ob.data.vertex_colors:
|
|
c_val = b[v_id]
|
|
c_val = np.repeat(c_val, 4, axis=0)
|
|
vc = ob.data.vertex_colors['B']
|
|
vc.data.foreach_set('color',c_val.tolist())
|
|
|
|
if 'A' in ob.data.vertex_colors:
|
|
c_val = a[v_id]
|
|
c_val = np.repeat(c_val, 4, axis=0)
|
|
vc = ob.data.vertex_colors['A']
|
|
vc.data.foreach_set('color',c_val.tolist())
|
|
|
|
for ps in ob.particle_systems:
|
|
if ps.vertex_group_density == 'B' or ps.vertex_group_density == 'A':
|
|
ps.invert_vertex_group_density = not ps.invert_vertex_group_density
|
|
ps.invert_vertex_group_density = not ps.invert_vertex_group_density
|
|
|
|
if props.bool_mod: bpy.data.meshes.remove(me)
|
|
bm.free()
|
|
timeElapsed = time.time() - start
|
|
print(' Closing Time:',timeElapsed)
|
|
|
|
def create_fast_bake_def(ob, frame_start=1, frame_end=250):
|
|
scene = bpy.context.scene
|
|
start = time.time()
|
|
if type(ob) == bpy.types.Scene: return None
|
|
props = ob.reaction_diffusion_settings
|
|
|
|
dt = props.dt
|
|
time_steps = props.time_steps
|
|
scale = props.diff_mult
|
|
|
|
if props.cache_dir == '':
|
|
letters = string.ascii_letters
|
|
random_name = ''.join(rnd.choice(letters) for i in range(6))
|
|
if bpy.context.blend_data.filepath == '':
|
|
folder = Path(bpy.context.preferences.filepaths.temporary_directory)
|
|
folder = folder / 'reaction_diffusion_cache' / random_name
|
|
else:
|
|
folder = '//' + Path(bpy.context.blend_data.filepath).stem
|
|
folder = Path(bpy.path.abspath(folder)) / 'reaction_diffusion_cache' / random_name
|
|
folder.mkdir(parents=True, exist_ok=True)
|
|
props.cache_dir = str(folder)
|
|
else:
|
|
folder = Path(props.cache_dir)
|
|
|
|
if props.bool_mod:
|
|
# hide deforming modifiers
|
|
mod_visibility = []
|
|
for m in ob.modifiers:
|
|
mod_visibility.append(m.show_viewport)
|
|
if not mod_preserve_shape(m): m.show_viewport = False
|
|
|
|
# evaluated mesh
|
|
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)
|
|
|
|
# set original visibility
|
|
for v, m in zip(mod_visibility, ob.modifiers):
|
|
m.show_viewport = v
|
|
ob.modifiers.update()
|
|
else:
|
|
me = ob.data
|
|
|
|
bm = bmesh.new() # create an empty BMesh
|
|
bm.from_mesh(me) # fill it in from a Mesh
|
|
verts = get_vertices_numpy(me)
|
|
dvert_lay = bm.verts.layers.deform.active
|
|
n_edges = len(me.edges)
|
|
n_verts = len(me.vertices)
|
|
group_index_x = ob.vertex_groups["x"].index
|
|
group_index_y = ob.vertex_groups["y"].index
|
|
group_index_module = ob.vertex_groups["module"].index
|
|
group_index_values = ob.vertex_groups["values"].index
|
|
|
|
if not props.bool_cache:
|
|
time_steps = props.time_steps
|
|
|
|
# store weight values
|
|
if 'dB' in ob.vertex_groups: db = np.zeros(n_verts)
|
|
if 'grad' in ob.vertex_groups: grad = np.zeros(n_verts)
|
|
vec_x = np.zeros(n_verts)
|
|
vec_y = np.zeros(n_verts)
|
|
vec_module = np.zeros(n_verts)
|
|
values = np.zeros(n_verts)
|
|
|
|
vec_x = bmesh_get_weight_numpy(group_index_x, dvert_lay, bm.verts)
|
|
vec_y = bmesh_get_weight_numpy(group_index_y, dvert_lay, bm.verts)
|
|
vec_module = bmesh_get_weight_numpy(group_index_module, dvert_lay, bm.verts)
|
|
values = bmesh_get_weight_numpy(group_index_values, dvert_lay, bm.verts)
|
|
field = np.concatenate((vec_x[:,None],vec_y[:,None],vec_y[:,None]*0),axis=1)
|
|
field = field*2-1
|
|
field[:,2] = 0
|
|
edge_verts = get_edges_numpy(me)
|
|
|
|
id0 = edge_verts[:,0]
|
|
id1 = edge_verts[:,1]
|
|
vert0 = verts[id0]
|
|
vert1 = verts[id1]
|
|
vec = vert1-vert0
|
|
edge_field = (field[id0] + field[id1])/2 # average vector associated to the edge
|
|
print(vert0.shape)
|
|
print(field.shape)
|
|
print(edge_field.shape)
|
|
# normalize vectors
|
|
vec /= np.linalg.norm(vec,axis=1)[:,None]
|
|
edge_field /= np.linalg.norm(edge_field,axis=1)[:,None]
|
|
edge_flow = np.einsum('...j,...j', vec, edge_field)
|
|
#sign = (edge_flow>0).astype(int)
|
|
#values[edge_verts[sign]] += values[edge_verts[1-sign]]*
|
|
#values[verts0] += values[verts1]*edge_flow
|
|
|
|
timeElapsed = time.time() - start
|
|
print(' Preparation Time:',timeElapsed)
|
|
start = time.time()
|
|
|
|
# Preserve energy
|
|
mult = np.zeros(values.shape)
|
|
#mult[id0] -= edge_flow
|
|
#mult[id1] += edge_flow
|
|
np.add.at(mult,id0,-edge_flow)
|
|
np.add.at(mult,id1,edge_flow)
|
|
print("mult")
|
|
mult = scale/mult
|
|
print(mult)
|
|
print(np.sum(mult))
|
|
|
|
|
|
#try:
|
|
print(vec)
|
|
print(edge_flow)
|
|
print(edge_flow)
|
|
|
|
bool_run = False
|
|
for j in range(props.cache_frame_start, props.cache_frame_end+1):
|
|
start2 = time.time()
|
|
print("{:6d} Reaction-Diffusion: {}".format(j, ob.name))
|
|
if bool_run:
|
|
print(values)
|
|
#for i in range(1):
|
|
values = integrate_field(n_edges,id0,id1,values,edge_flow,mult,time_steps)
|
|
#values0 = values
|
|
#np.add.at(values, id0, values0[id1]*edge_flow*mult[id1])
|
|
#np.add.at(values, id1, -values0[id0]*edge_flow*mult[id0])
|
|
#np.add.at(values, id0, values0[id1]*edge_flow*mult)
|
|
#np.add.at(values, id1, -values0[id0]*edge_flow*mult)
|
|
#values[id1] += values0[id0]*edge_flow/mult[id1]*dt
|
|
#values[id0] -= values0[id1]*edge_flow/mult[id0]*dt
|
|
#values[id1] = edge_flow
|
|
#values[id1] += edge_flow
|
|
#a, b = numba_reaction_diffusion(n_verts, n_edges, edge_verts, a, b, _brush, _diff_a, _diff_b, _f, _k, dt, time_steps)
|
|
|
|
'''
|
|
lap_a = np.zeros(n_verts)
|
|
lap_b = np.zeros(n_verts)
|
|
lap_a0 = a[id1] - a[id0] # laplacian increment for first vertex of each edge
|
|
lap_b0 = b[id1] - b[id0] # laplacian increment for first vertex of each edge
|
|
|
|
np.add.at(lap_a, id0, lap_a0)
|
|
np.add.at(lap_b, id0, lap_b0)
|
|
np.add.at(lap_a, id1, -lap_a0)
|
|
np.add.at(lap_b, id1, -lap_b0)
|
|
'''
|
|
else:
|
|
bool_run = True
|
|
|
|
if not(os.path.exists(folder)):
|
|
os.mkdir(folder)
|
|
file_name = folder / "a_{:04d}".format(j)
|
|
values.tofile(file_name)
|
|
file_name = folder / "b_{:04d}".format(j)
|
|
values.tofile(file_name)
|
|
|
|
|
|
timeElapsed = time.time() - start2
|
|
print(' Simulation Time:',timeElapsed)
|
|
|
|
if props.bool_mod: bpy.data.meshes.remove(me)
|
|
bm.free()
|
|
timeElapsed = time.time() - start
|
|
print(' Closing Time:',timeElapsed)
|
|
|
|
|
|
|
|
|
|
|
|
class TISSUE_PT_reaction_diffusion(Panel):
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "data"
|
|
bl_label = "Tissue Reaction-Diffusion"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return 'A' and 'B' in context.object.vertex_groups
|
|
|
|
def draw(self, context):
|
|
reaction_diffusion_add_handler(self, context)
|
|
|
|
ob = context.object
|
|
props = ob.reaction_diffusion_settings
|
|
layout = self.layout
|
|
col = layout.column(align=True)
|
|
row = col.row(align=True)
|
|
if not ("A" and "B" in ob.vertex_groups):
|
|
row.operator("object.start_reaction_diffusion",
|
|
icon="EXPERIMENTAL",
|
|
text="Reaction-Diffusion")
|
|
else:
|
|
row.operator("object.start_reaction_diffusion",
|
|
icon="EXPERIMENTAL",
|
|
text="Reset Reaction-Diffusion")
|
|
row = col.row(align=True)
|
|
row.prop(props, "run", text="Run Reaction-Diffusion")
|
|
col = layout.column(align=True)
|
|
row = col.row(align=True)
|
|
row.prop(props, "time_steps")
|
|
row.prop(props, "dt")
|
|
row.enabled = not props.bool_cache
|
|
col.separator()
|
|
row = col.row(align=True)
|
|
col1 = row.column(align=True)
|
|
col1.prop(props, "diff_a")
|
|
col1.enabled = props.vertex_group_diff_a == '' and not props.bool_cache
|
|
col1 = row.column(align=True)
|
|
col1.prop(props, "diff_b")
|
|
col1.enabled = props.vertex_group_diff_b == '' and not props.bool_cache
|
|
row = col.row(align=True)
|
|
row.prop(props, "diff_mult")
|
|
row.enabled = props.vertex_group_scale == '' and not props.bool_cache
|
|
#col.separator()
|
|
row = col.row(align=True)
|
|
col1 = row.column(align=True)
|
|
col1.prop(props, "f")
|
|
col1.enabled = props.vertex_group_f == '' and not props.bool_cache
|
|
col1 = row.column(align=True)
|
|
col1.prop(props, "k")
|
|
col1.enabled = props.vertex_group_k == '' and not props.bool_cache
|
|
col.separator()
|
|
col.label(text='Cache:')
|
|
#col.prop(props, "bool_cache")
|
|
col.prop(props, "cache_dir", text='')
|
|
col.separator()
|
|
row = col.row(align=True)
|
|
row.prop(props, "cache_frame_start")
|
|
row.prop(props, "cache_frame_end")
|
|
col.separator()
|
|
if props.bool_cache:
|
|
col.operator("object.reaction_diffusion_free_data")
|
|
else:
|
|
row = col.row(align=True)
|
|
row.operator("object.bake_reaction_diffusion")
|
|
file = bpy.context.blend_data.filepath
|
|
temp = bpy.context.preferences.filepaths.temporary_directory
|
|
if file == temp == props.cache_dir == '':
|
|
row.enabled = False
|
|
col.label(text="Cannot use cache", icon='ERROR')
|
|
col.label(text='please save the Blender or set a Cache directory')
|
|
col.prop(props, "fast_bake")
|
|
|
|
col.separator()
|
|
col.label(text='Output attributes:')
|
|
row = col.row(align=True)
|
|
col2 = row.column(align=True)
|
|
row2 = col2.row(align=True)
|
|
row2.prop(props, "update_weight_a", icon='GROUP_VERTEX', text='A')
|
|
row2.prop(props, "update_weight_b", icon='GROUP_VERTEX', text='B')
|
|
col2.enabled = props.bool_cache
|
|
row.separator()
|
|
#row.prop(props, "update_colors_a", icon='GROUP_VCOL', text='A')
|
|
#row.prop(props, "update_colors_b", icon='GROUP_VCOL', text='B')
|
|
row.prop(props, "update_colors", icon='GROUP_VCOL', text='AB')
|
|
row.separator()
|
|
row.prop(props, "update_uv", icon='GROUP_UVS', text='AB')
|
|
col.prop(props,'normalize')
|
|
|
|
#col.prop_search(props, 'vertex_group_diff_a', ob, "vertex_groups", text='Diff A')
|
|
#col.prop_search(props, 'vertex_group_diff_b', ob, "vertex_groups", text='Diff B')
|
|
#col.prop_search(props, 'vertex_group_scale', ob, "vertex_groups", text='Scale')
|
|
#col.prop_search(props, 'vertex_group_f', ob, "vertex_groups", text='f')
|
|
#col.prop_search(props, 'vertex_group_k', ob, "vertex_groups", text='k')
|
|
|
|
|
|
class TISSUE_PT_reaction_diffusion_weight(Panel):
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "data"
|
|
bl_parent_id = "TISSUE_PT_reaction_diffusion"
|
|
bl_label = "Vertex Groups"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return 'A' and 'B' in context.object.vertex_groups
|
|
|
|
def draw(self, context):
|
|
ob = context.object
|
|
props = ob.reaction_diffusion_settings
|
|
layout = self.layout
|
|
#layout.use_property_split = True
|
|
col = layout.column(align=True)
|
|
col.prop(props, "bool_mod")
|
|
if props.bool_mod and props.fast_bake:
|
|
col.label(text="When Fast Bake is on, the modifiers", icon='ERROR')
|
|
col.label(text=" are used only for the first frame")
|
|
col.separator()
|
|
insert_weight_parameter(col, ob, 'brush', text='Brush:')
|
|
insert_weight_parameter(col, ob, 'diff_a', text='Diff A:')
|
|
insert_weight_parameter(col, ob, 'diff_b', text='Diff B:')
|
|
insert_weight_parameter(col, ob, 'scale', text='Scale:')
|
|
insert_weight_parameter(col, ob, 'f', text='f:')
|
|
insert_weight_parameter(col, ob, 'k', text='k:')
|
|
col.enabled = not props.bool_cache
|
|
|
|
def insert_weight_parameter(col, ob, name, text=''):
|
|
props = ob.reaction_diffusion_settings
|
|
split = col.split(factor=0.25, align=True)
|
|
col2 = split.column(align=True)
|
|
col2.label(text=text)
|
|
col2 = split.column(align=True)
|
|
row2 = col2.row(align=True)
|
|
row2.prop_search(props, 'vertex_group_' + name, ob, "vertex_groups", text='')
|
|
if name != 'brush':
|
|
row2.prop(props, "invert_vertex_group_" + name, text="", toggle=True, icon='ARROW_LEFTRIGHT')
|
|
if 'vertex_group_' + name in props:
|
|
if props['vertex_group_' + name] != '':
|
|
if name == 'brush':
|
|
col2.prop(props, "brush_mult")
|
|
else:
|
|
row2 = col2.row(align=True)
|
|
row2.prop(props, "min_" + name, text="Min")
|
|
row2 = col2.row(align=True)
|
|
row2.prop(props, "max_" + name, text="Max")
|
|
col.separator()
|
|
|
|
def contour_edges_pattern(operator, c, verts_count, iso_val, vertices, normals, filtered_edges, weight, pattern_weight, bevel_weight):
|
|
# vertices indexes
|
|
id0 = filtered_edges[:,0]
|
|
id1 = filtered_edges[:,1]
|
|
# vertices weight
|
|
w0 = weight[id0]
|
|
w1 = weight[id1]
|
|
# weight condition
|
|
bool_w0 = w0 < iso_val
|
|
bool_w1 = w1 < iso_val
|
|
|
|
# mask all edges that have one weight value below the iso value
|
|
mask_new_verts = np.logical_xor(bool_w0, bool_w1)
|
|
if not mask_new_verts.any():
|
|
return np.array([[None]]), {}, np.array([[None]]), np.array([[None]])
|
|
|
|
id0 = id0[mask_new_verts]
|
|
id1 = id1[mask_new_verts]
|
|
# filter arrays
|
|
v0 = vertices[id0]
|
|
v1 = vertices[id1]
|
|
n0 = normals[id0]
|
|
n1 = normals[id1]
|
|
w0 = w0[mask_new_verts]
|
|
w1 = w1[mask_new_verts]
|
|
pattern0 = pattern_weight[id0]
|
|
pattern1 = pattern_weight[id1]
|
|
try:
|
|
bevel0 = bevel_weight[id0]
|
|
bevel1 = bevel_weight[id1]
|
|
except: pass
|
|
|
|
### Spiral
|
|
#edge_nor = (n0+n1)/2
|
|
#shift = np.arctan2(edge_nor[:,0], edge_nor[:,1])/2/pi*delta_iso
|
|
|
|
#param = (iso_val + shift - w0)/(w1-w0)
|
|
param = (iso_val - w0)/(w1-w0)
|
|
# pattern displace
|
|
#mult = 1 if c%2 == 0 else -1
|
|
if c%(operator.in_steps + operator.out_steps) < operator.in_steps:
|
|
mult = operator.in_displace
|
|
else:
|
|
mult = operator.out_displace
|
|
pattern_value = pattern0 + (pattern1-pattern0)*param
|
|
try:
|
|
bevel_value = bevel0 + (bevel1-bevel0)*param
|
|
bevel_value = np.expand_dims(bevel_value,axis=1)
|
|
except: bevel_value = None
|
|
disp = pattern_value * mult
|
|
|
|
param = np.expand_dims(param,axis=1)
|
|
disp = np.expand_dims(disp,axis=1)
|
|
verts = v0 + (v1-v0)*param
|
|
norm = n0 + (n1-n0)*param
|
|
if operator.limit_z: disp *= 1-abs(np.expand_dims(norm[:,2], axis=1))
|
|
verts = verts + norm*disp
|
|
#verts = verts[np.flip(np.argsort(shift))]
|
|
#verts = verts[np.argsort(shift)]
|
|
|
|
# indexes of edges with new vertices
|
|
edges_index = filtered_edges[mask_new_verts][:,2]
|
|
|
|
# remove all edges completely below the iso value
|
|
#mask_edges = np.logical_not(np.logical_and(bool_w0, bool_w1))
|
|
#filtered_edges = filtered_edges[mask_edges]
|
|
return filtered_edges, edges_index, verts, bevel_value
|
|
|
|
def contour_bmesh(me, bm, weight, iso_val):
|
|
bm.verts.ensure_lookup_table()
|
|
bm.edges.ensure_lookup_table()
|
|
bm.faces.ensure_lookup_table()
|
|
|
|
# store weight values
|
|
|
|
vertices = get_vertices_numpy(me)
|
|
faces_mask = np.array(bm.faces)
|
|
filtered_edges = get_edges_id_numpy(me)
|
|
n_verts = len(bm.verts)
|
|
|
|
#############################
|
|
|
|
# vertices indexes
|
|
id0 = filtered_edges[:,0]
|
|
id1 = filtered_edges[:,1]
|
|
# vertices weight
|
|
w0 = weight[id0]
|
|
w1 = weight[id1]
|
|
# weight condition
|
|
bool_w0 = w0 < iso_val
|
|
bool_w1 = w1 < iso_val
|
|
|
|
# mask all edges that have one weight value below the iso value
|
|
mask_new_verts = np.logical_xor(bool_w0, bool_w1)
|
|
if not mask_new_verts.any(): return np.array([[None]]), {}, np.array([[None]])
|
|
|
|
id0 = id0[mask_new_verts]
|
|
id1 = id1[mask_new_verts]
|
|
# filter arrays
|
|
v0 = vertices[id0]
|
|
v1 = vertices[id1]
|
|
w0 = w0[mask_new_verts]
|
|
w1 = w1[mask_new_verts]
|
|
param = (iso_val-w0)/(w1-w0)
|
|
param = np.expand_dims(param,axis=1)
|
|
verts = v0 + (v1-v0)*param
|
|
|
|
# indexes of edges with new vertices
|
|
#edges_index = filtered_edges[mask_new_verts][:,2]
|
|
|
|
edges_id = {}
|
|
for i, e in enumerate(filtered_edges):
|
|
#edges_id[id] = i + n_verts
|
|
edges_id['{}_{}'.format(e[0],e[1])] = i + n_verts
|
|
edges_id['{}_{}'.format(e[1],e[0])] = i + n_verts
|
|
|
|
|
|
'''
|
|
for e in filtered_edges:
|
|
id0 = e.verts[0].index
|
|
id1 = e.verts[1].index
|
|
w0 = weight[id0]
|
|
w1 = weight[id1]
|
|
|
|
if w0 == w1: continue
|
|
elif w0 > iso_val and w1 > iso_val:
|
|
continue
|
|
elif w0 < iso_val and w1 < iso_val: continue
|
|
elif w0 == iso_val or w1 == iso_val: continue
|
|
else:
|
|
v0 = me0.vertices[id0].co
|
|
v1 = me0.vertices[id1].co
|
|
v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
|
|
delete_edges.append(e)
|
|
verts.append(v)
|
|
edges_id[str(id0)+"_"+str(id1)] = count
|
|
edges_id[str(id1)+"_"+str(id0)] = count
|
|
count += 1
|
|
'''
|
|
|
|
splitted_faces = []
|
|
|
|
switch = False
|
|
# splitting faces
|
|
for f in faces_mask:
|
|
# create sub-faces slots. Once a new vertex is reached it will
|
|
# change slot, storing the next vertices for a new face.
|
|
build_faces = [[],[]]
|
|
#switch = False
|
|
verts0 = list(me.polygons[f.index].vertices)
|
|
verts1 = list(verts0)
|
|
verts1.append(verts1.pop(0)) # shift list
|
|
for id0, id1 in zip(verts0, verts1):
|
|
|
|
# add first vertex to active slot
|
|
build_faces[switch].append(id0)
|
|
|
|
# try to split edge
|
|
try:
|
|
# check if the edge must be splitted
|
|
new_vert = edges_id['{}_{}'.format(id0,id1)]
|
|
# add new vertex
|
|
build_faces[switch].append(new_vert)
|
|
# if there is an open face on the other slot
|
|
if len(build_faces[not switch]) > 0:
|
|
# store actual face
|
|
splitted_faces.append(build_faces[switch])
|
|
# reset actual faces and switch
|
|
build_faces[switch] = []
|
|
# change face slot
|
|
switch = not switch
|
|
# continue previous face
|
|
build_faces[switch].append(new_vert)
|
|
except: pass
|
|
if len(build_faces[not switch]) == 2:
|
|
build_faces[not switch].append(id0)
|
|
if len(build_faces[not switch]) > 2:
|
|
splitted_faces.append(build_faces[not switch])
|
|
# add last face
|
|
splitted_faces.append(build_faces[switch])
|
|
|
|
# adding new vertices use fast local method access
|
|
_new_vert = bm.verts.new
|
|
for v in verts: _new_vert(v)
|
|
bm.verts.ensure_lookup_table()
|
|
|
|
# deleting old edges/faces
|
|
bm.edges.ensure_lookup_table()
|
|
remove_edges = [bm.edges[i] for i in filtered_edges[:,2]]
|
|
#for e in remove_edges: bm.edges.remove(e)
|
|
#for e in delete_edges: bm.edges.remove(e)
|
|
|
|
bm.verts.ensure_lookup_table()
|
|
# adding new faces use fast local method access
|
|
_new_face = bm.faces.new
|
|
missed_faces = []
|
|
for f in splitted_faces:
|
|
try:
|
|
face_verts = [bm.verts[i] for i in f]
|
|
_new_face(face_verts)
|
|
except:
|
|
missed_faces.append(f)
|
|
|
|
#me = bpy.data.meshes.new('_tissue_tmp_')
|
|
bm.to_mesh(me)
|
|
weight = np.concatenate((weight, np.ones(len(verts))*iso_val))
|
|
|
|
return me, bm, weight
|
|
|
|
|
|
|
|
|
|
class tissue_weight_streamlines(Operator):
|
|
bl_idname = "object.tissue_weight_streamlines"
|
|
bl_label = "Streamlines Curves"
|
|
bl_description = ("")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
mode : EnumProperty(
|
|
items=(
|
|
('VERTS', "Verts", "Follow vertices"),
|
|
('EDGES', "Edges", "Follow Edges")
|
|
),
|
|
default='VERTS',
|
|
name="Streamlines path mode"
|
|
)
|
|
|
|
interpolation : EnumProperty(
|
|
items=(
|
|
('POLY', "Poly", "Generate Polylines"),
|
|
('NURBS', "NURBS", "Generate Nurbs curves")
|
|
),
|
|
default='POLY',
|
|
name="Interpolation mode"
|
|
)
|
|
|
|
use_modifiers : BoolProperty(
|
|
name="Use Modifiers", default=True,
|
|
description="Apply all the modifiers")
|
|
|
|
use_selected : BoolProperty(
|
|
name="Use Selected Vertices", default=False,
|
|
description="Use selected vertices as Seed")
|
|
|
|
same_weight : BoolProperty(
|
|
name="Same Weight", default=True,
|
|
description="Continue the streamlines when the weight is the same")
|
|
|
|
min_iso : FloatProperty(
|
|
name="Min Value", default=0., soft_min=0, soft_max=1,
|
|
description="Minimum weight value")
|
|
max_iso : FloatProperty(
|
|
name="Max Value", default=1, soft_min=0, soft_max=1,
|
|
description="Maximum weight value")
|
|
|
|
rand_seed : IntProperty(
|
|
name="Seed", default=0, min=0, soft_max=10,
|
|
description="Random Seed")
|
|
n_curves : IntProperty(
|
|
name="Curves", default=50, soft_min=1, soft_max=100000,
|
|
description="Number of Curves")
|
|
min_rad = 1
|
|
max_rad = 1
|
|
|
|
pos_steps : IntProperty(
|
|
name="High Steps", default=50, min=0, soft_max=100,
|
|
description="Number of steps in the direction of high weight")
|
|
neg_steps : IntProperty(
|
|
name="Low Steps", default=50, min=0, soft_max=100,
|
|
description="Number of steps in the direction of low weight")
|
|
|
|
bevel_depth : FloatProperty(
|
|
name="Bevel Depth", default=0, min=0, soft_max=1,
|
|
description="")
|
|
min_bevel_depth : FloatProperty(
|
|
name="Min Bevel Depth", default=0.1, min=0, soft_max=1,
|
|
description="")
|
|
max_bevel_depth : FloatProperty(
|
|
name="Max Bevel Depth", default=1, min=0, soft_max=1,
|
|
description="")
|
|
|
|
rand_dir : FloatProperty(
|
|
name="Randomize", default=0, min=0, max=1,
|
|
description="Randomize streamlines directions (Slower)")
|
|
|
|
vertex_group_seeds : StringProperty(
|
|
name="Displace", default='',
|
|
description="Vertex Group used for pattern displace")
|
|
|
|
vertex_group_bevel : StringProperty(
|
|
name="Bevel", default='',
|
|
description="Variable Bevel depth")
|
|
|
|
object_name : StringProperty(
|
|
name="Active Object", default='',
|
|
description="")
|
|
|
|
try: vg_name = bpy.context.object.vertex_groups.active.name
|
|
except: vg_name = ''
|
|
|
|
vertex_group_streamlines : StringProperty(
|
|
name="Flow", default=vg_name,
|
|
description="Vertex Group used for streamlines")
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
ob = context.object
|
|
return ob and len(ob.vertex_groups) > 0 or ob.type == 'CURVE'
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_props_dialog(self, width=250)
|
|
|
|
def draw(self, context):
|
|
if not context.object.type == 'CURVE':
|
|
self.object_name = context.object.name
|
|
ob = bpy.data.objects[self.object_name]
|
|
if self.vertex_group_streamlines not in [vg.name for vg in ob.vertex_groups]:
|
|
self.vertex_group_streamlines = ob.vertex_groups.active.name
|
|
layout = self.layout
|
|
col = layout.column(align=True)
|
|
row = col.row(align=True)
|
|
row.prop(self, 'mode', expand=True,
|
|
slider=True, toggle=False, icon_only=False, event=False,
|
|
full_event=False, emboss=True, index=-1)
|
|
col.prop(self, "use_modifiers")
|
|
col.label(text="Streamlines Curves:")
|
|
row = col.row(align=True)
|
|
row.prop(self, 'interpolation', expand=True,
|
|
slider=True, toggle=False, icon_only=False, event=False,
|
|
full_event=False, emboss=True, index=-1)
|
|
col.separator()
|
|
col.prop_search(self, 'vertex_group_streamlines', ob, "vertex_groups", text='')
|
|
if not (self.use_selected or context.mode == 'EDIT_MESH'):
|
|
row = col.row(align=True)
|
|
row.prop(self,'n_curves')
|
|
#row.enabled = context.mode != 'EDIT_MESH'
|
|
row = col.row(align=True)
|
|
row.prop(self,'rand_seed')
|
|
#row.enabled = context.mode != 'EDIT_MESH'
|
|
row = col.row(align=True)
|
|
row.prop(self,'neg_steps')
|
|
row.prop(self,'pos_steps')
|
|
#row = col.row(align=True)
|
|
#row.prop(self,'min_iso')
|
|
#row.prop(self,'max_iso')
|
|
col.prop(self, "same_weight")
|
|
col.separator()
|
|
col.label(text='Curves Bevel:')
|
|
col.prop_search(self, 'vertex_group_bevel', ob, "vertex_groups", text='')
|
|
if self.vertex_group_bevel != '':
|
|
row = col.row(align=True)
|
|
row.prop(self,'min_bevel_depth')
|
|
row.prop(self,'max_bevel_depth')
|
|
else:
|
|
col.prop(self,'bevel_depth')
|
|
col.separator()
|
|
col.prop(self, "rand_dir")
|
|
|
|
def execute(self, context):
|
|
start_time = timeit.default_timer()
|
|
try:
|
|
check = context.object.vertex_groups[0]
|
|
except:
|
|
self.report({'ERROR'}, "The object doesn't have Vertex Groups")
|
|
return {'CANCELLED'}
|
|
ob = bpy.data.objects[self.object_name]
|
|
ob.select_set(False)
|
|
|
|
|
|
seeds = []
|
|
|
|
if bpy.context.mode == 'EDIT_MESH':
|
|
self.use_selected = True
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
#ob = bpy.context.object
|
|
#me = simple_to_mesh(ob)
|
|
ob = convert_object_to_mesh(ob, apply_modifiers=self.use_modifiers)
|
|
#dg = context.evaluated_depsgraph_get()
|
|
#ob = ob.evaluated_get(dg)
|
|
me = ob.data
|
|
|
|
if self.use_selected:
|
|
# generate new bmesh
|
|
bm = bmesh.new()
|
|
bm.from_mesh(me)
|
|
print(len(me.vertices))
|
|
#for v in me.vertices:
|
|
# if v.select: seeds.append(v.index)
|
|
for v in bm.verts:
|
|
if v.select: seeds.append(v.index)
|
|
bm.free()
|
|
n_verts = len(me.vertices)
|
|
n_edges = len(me.edges)
|
|
n_faces = len(me.polygons)
|
|
|
|
# store weight values
|
|
try:
|
|
weight = get_weight_numpy(ob.vertex_groups[self.vertex_group_streamlines], n_verts)
|
|
except:
|
|
bpy.data.objects.remove(ob)
|
|
self.report({'ERROR'}, "Please select a Vertex Group for streamlines")
|
|
return {'CANCELLED'}
|
|
|
|
variable_bevel = False
|
|
bevel_weight = None
|
|
bevel_depth = self.bevel_depth
|
|
try:
|
|
if self.min_bevel_depth == self.max_bevel_depth:
|
|
#bevel_weight = np.ones((n_verts))
|
|
bevel_depth = self.min_bevel_depth
|
|
else:
|
|
b0 = min(self.min_bevel_depth, self.max_bevel_depth)
|
|
b1 = max(self.min_bevel_depth, self.max_bevel_depth)
|
|
bevel_weight = get_weight_numpy(ob.vertex_groups[self.vertex_group_bevel], n_verts)
|
|
if self.min_bevel_depth > self.max_bevel_depth:
|
|
bevel_weight = 1-bevel_weight
|
|
bevel_weight = b0/b1 + bevel_weight*((b1-b0)/b1)
|
|
bevel_depth = b1
|
|
variable_bevel = True
|
|
except:
|
|
pass#bevel_weight = np.ones((n_verts))
|
|
|
|
|
|
if not seeds:
|
|
np.random.seed(self.rand_seed)
|
|
seeds = np.random.randint(n_verts, size=self.n_curves)
|
|
|
|
#weight = np.array(get_weight(ob.vertex_groups.active, n_verts))
|
|
|
|
curves_pts = []
|
|
curves_weight = []
|
|
|
|
neigh = [[] for i in range(n_verts)]
|
|
if self.mode == 'EDGES':
|
|
# store neighbors
|
|
for e in me.edges:
|
|
ev = e.vertices
|
|
neigh[ev[0]].append(ev[1])
|
|
neigh[ev[1]].append(ev[0])
|
|
|
|
elif self.mode == 'VERTS':
|
|
# store neighbors
|
|
for p in me.polygons:
|
|
face_verts = [v for v in p.vertices]
|
|
n_face_verts = len(face_verts)
|
|
for i in range(n_face_verts):
|
|
fv = face_verts.copy()
|
|
neigh[fv.pop(i)] += fv
|
|
|
|
neigh_weight = [weight[n].tolist() for n in neigh]
|
|
|
|
# evaluate direction
|
|
next_vert = [-1]*n_verts
|
|
|
|
if self.rand_dir > 0:
|
|
for i in range(n_verts):
|
|
n = neigh[i]
|
|
nw = neigh_weight[i]
|
|
sorted_nw = neigh_weight[i].copy()
|
|
sorted_nw.sort()
|
|
for w in sorted_nw:
|
|
neigh[i] = [n[nw.index(w)] for w in sorted_nw]
|
|
else:
|
|
if self.pos_steps > 0:
|
|
for i in range(n_verts):
|
|
n = neigh[i]
|
|
nw = neigh_weight[i]
|
|
max_w = max(nw)
|
|
if self.same_weight:
|
|
if max_w >= weight[i]:
|
|
next_vert[i] = n[nw.index(max(nw))]
|
|
else:
|
|
if max_w > weight[i]:
|
|
next_vert[i] = n[nw.index(max(nw))]
|
|
|
|
if self.neg_steps > 0:
|
|
prev_vert = [-1]*n_verts
|
|
for i in range(n_verts):
|
|
n = neigh[i]
|
|
nw = neigh_weight[i]
|
|
min_w = min(nw)
|
|
if self.same_weight:
|
|
if min_w <= weight[i]:
|
|
prev_vert[i] = n[nw.index(min(nw))]
|
|
else:
|
|
if min_w < weight[i]:
|
|
prev_vert[i] = n[nw.index(min(nw))]
|
|
|
|
co = [0]*3*n_verts
|
|
me.vertices.foreach_get('co', co)
|
|
co = np.array(co).reshape((-1,3))
|
|
|
|
# create streamlines
|
|
curves = []
|
|
for i in seeds:
|
|
next_pts = [i]
|
|
for j in range(self.pos_steps):
|
|
if self.rand_dir > 0:
|
|
n = neigh[next_pts[-1]]
|
|
next = n[int(len(n) * (1-random.random() * self.rand_dir))]
|
|
else:
|
|
next = next_vert[next_pts[-1]]
|
|
if next > 0:
|
|
if next not in next_pts: next_pts.append(next)
|
|
else: break
|
|
|
|
prev_pts = [i]
|
|
for j in range(self.neg_steps):
|
|
if self.rand_dir > 0:
|
|
n = neigh[prev_pts[-1]]
|
|
prev = n[int(len(n) * random.random() * self.rand_dir)]
|
|
else:
|
|
prev = prev_vert[prev_pts[-1]]
|
|
if prev > 0:
|
|
if prev not in prev_pts:
|
|
prev_pts.append(prev)
|
|
else: break
|
|
|
|
next_pts = np.array(next_pts).astype('int')
|
|
prev_pts = np.flip(prev_pts[1:]).astype('int')
|
|
all_pts = np.concatenate((prev_pts, next_pts))
|
|
if len(all_pts) > 1:
|
|
curves.append(all_pts)
|
|
crv = nurbs_from_vertices(curves, co, bevel_weight, ob.name + '_Streamlines', True, self.interpolation)
|
|
crv.data.bevel_depth = bevel_depth
|
|
crv.matrix_world = ob.matrix_world
|
|
bpy.data.objects.remove(ob)
|
|
|
|
print("Streamlines Curves, total time: " + str(timeit.default_timer() - start_time) + " sec")
|
|
return {'FINISHED'}
|