Export_3ds: Added distance cue chunk export
[blender-addons.git] / mesh_tissue / weight_tools.py
blobcc3b0d9efca388cc21e5331be59d7bfa836b529e
1 # SPDX-FileCopyrightText: 2022-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 #-------------------------- COLORS / GROUPS EXCHANGER -------------------------#
6 # #
7 # Vertex Color to Vertex Group allow you to convert colors channles to weight #
8 # maps. #
9 # The main purpose is to use vertex colors to store information when importing #
10 # files from other softwares. The script works with the active vertex color #
11 # slot. #
12 # For use the command "Vertex Clors to Vertex Groups" use the search bar #
13 # (space bar). #
14 # #
15 # (c) Alessandro Zomparelli #
16 # (2017) #
17 # #
18 # http://www.co-de-it.com/ #
19 # #
20 ################################################################################
22 import bpy, bmesh, os
23 import numpy as np
24 import math, timeit, time
25 from math import pi
26 from statistics import mean, stdev
27 from mathutils import Vector
28 from mathutils.kdtree import KDTree
29 from numpy import *
30 try: import numexpr as ne
31 except: pass
33 from bpy.types import (
34 Operator,
35 Panel,
36 PropertyGroup,
39 from bpy.props import (
40 BoolProperty,
41 EnumProperty,
42 FloatProperty,
43 IntProperty,
44 StringProperty,
45 FloatVectorProperty,
46 IntVectorProperty,
47 PointerProperty
50 from .utils import *
52 class formula_prop(PropertyGroup):
53 name : StringProperty()
54 formula : StringProperty()
55 float_var : FloatVectorProperty(name="", description="", default=(0, 0, 0, 0, 0), size=5)
56 int_var : IntVectorProperty(name="", description="", default=(0, 0, 0, 0, 0), size=5)
58 from numpy import *
59 def compute_formula(ob=None, formula="rx", float_var=(0,0,0,0,0), int_var=(0,0,0,0,0)):
60 verts = ob.data.vertices
61 n_verts = len(verts)
63 f1,f2,f3,f4,f5 = float_var
64 i1,i2,i3,i4,i5 = int_var
66 do_groups = "w[" in formula
67 do_local = "lx" in formula or "ly" in formula or "lz" in formula
68 do_global = "gx" in formula or "gy" in formula or "gz" in formula
69 do_relative = "rx" in formula or "ry" in formula or "rz" in formula
70 do_normal = "nx" in formula or "ny" in formula or "nz" in formula
71 mat = ob.matrix_world
73 for i in range(1000):
74 if "w["+str(i)+"]" in formula and i > len(ob.vertex_groups)-1:
75 return "w["+str(i)+"] not found"
77 w = []
78 for i in range(len(ob.vertex_groups)):
79 w.append([])
80 if "w["+str(i)+"]" in formula:
81 vg = ob.vertex_groups[i]
82 for v in verts:
83 try:
84 w[i].append(vg.weight(v.index))
85 except:
86 w[i].append(0)
87 w[i] = array(w[i])
89 start_time = timeit.default_timer()
90 # compute vertex coordinates
91 if do_local or do_relative or do_global:
92 co = [0]*n_verts*3
93 verts.foreach_get('co', co)
94 np_co = array(co).reshape((n_verts, 3))
95 lx, ly, lz = array(np_co).transpose()
96 if do_relative:
97 rx = np.interp(lx, (lx.min(), lx.max()), (0, +1))
98 ry = np.interp(ly, (ly.min(), ly.max()), (0, +1))
99 rz = np.interp(lz, (lz.min(), lz.max()), (0, +1))
100 if do_global:
101 co = [v.co for v in verts]
102 global_co = []
103 for v in co:
104 global_co.append(mat @ v)
105 global_co = array(global_co).reshape((n_verts, 3))
106 gx, gy, gz = array(global_co).transpose()
107 # compute vertex normals
108 if do_normal:
109 normal = [0]*n_verts*3
110 verts.foreach_get('normal', normal)
111 normal = array(normal).reshape((n_verts, 3))
112 nx, ny, nz = array(normal).transpose()
114 try:
115 weight = eval(formula)
116 return weight
117 except:
118 return "There is something wrong"
119 print("Weight Formula: " + str(timeit.default_timer() - start_time))
121 class weight_formula_wiki(Operator):
122 bl_idname = "scene.weight_formula_wiki"
123 bl_label = "Online Documentation"
124 bl_options = {'REGISTER', 'UNDO'}
126 def execute(self, context):
127 bpy.ops.wm.url_open(url="https://github.com/alessandro-zomparelli/tissue/wiki/Weight-Tools#weight-formula")
128 return {'FINISHED'}
130 class weight_formula(Operator):
131 bl_idname = "object.weight_formula"
132 bl_label = "Weight Formula"
133 bl_description = "Generate a Vertex Group according to a mathematical formula"
134 bl_options = {'REGISTER', 'UNDO'}
136 ex_items = [
137 ('cos(arctan(nx/ny)*i1*2 + sin(rz*i3))/i2 + cos(arctan(nx/ny)*i1*2 - sin(rz*i3))/i2 + 0.5','Vertical Spots'),
138 ('cos(arctan(nx/ny)*i1*2 + sin(rz*i2))/2 + cos(arctan(nx/ny)*i1*2 - sin(rz*i2))/2','Vertical Spots'),
139 ('(sin(arctan(nx/ny)*i1*2)*sin(nz*i1*2)+1)/2','Grid Spots'),
140 ('cos(arctan(nx/ny)*f1)','Vertical Stripes'),
141 ('cos(arctan(lx/ly)*f1 + sin(rz*f2)*f3)','Curly Stripes'),
142 ('sin(rz*pi*i1+arctan2(nx,ny))/2+0.5', 'Vertical Spiral'),
143 ('sin(nx*15)<sin(ny*15)','Chess'),
144 ('cos(ny*rz**2*i1)','Hyperbolic'),
145 ('sin(rx*30) > 0','Step Stripes'),
146 ('sin(nz*i1)','Normal Stripes'),
147 ('w[0]**2','Vertex Group square'),
148 ('abs(0.5-rz)*2','Double vertical gradient'),
149 ('rz', 'Vertical Gradient')
151 _ex_items = list((str(i),'{} ( {} )'.format(s[0],s[1]),s[1]) for i,s in enumerate(ex_items))
152 _ex_items.append(('CUSTOM', "User Formula", ""))
154 examples : EnumProperty(
155 items = _ex_items, default='CUSTOM', name="Examples")
157 old_ex = ""
159 formula : StringProperty(
160 name="Formula", default="", description="Formula to Evaluate")
162 slider_f01 : FloatProperty(
163 name="f1", default=1, description="Slider Float 1")
164 slider_f02 : FloatProperty(
165 name="f2", default=1, description="Slider Float 2")
166 slider_f03 : FloatProperty(
167 name="f3", default=1, description="Slider Float 3")
168 slider_f04 : FloatProperty(
169 name="f4", default=1, description="Slider Float 4")
170 slider_f05 : FloatProperty(
171 name="f5", default=1, description="Slider Float 5")
172 slider_i01 : IntProperty(
173 name="i1", default=1, description="Slider Integer 1")
174 slider_i02 : IntProperty(
175 name="i2", default=1, description="Slider Integer 2")
176 slider_i03 : IntProperty(
177 name="i3", default=1, description="Slider Integer 3")
178 slider_i04 : IntProperty(
179 name="i4", default=1, description="Slider Integer 4")
180 slider_i05 : IntProperty(
181 name="i5", default=1, description="Slider Integer 5")
183 def invoke(self, context, event):
184 return context.window_manager.invoke_props_dialog(self, width=350)
186 def draw(self, context):
187 layout = self.layout
188 #layout.label(text="Examples")
189 layout.prop(self, "examples", text="Examples")
190 #if self.examples == 'CUSTOM':
191 layout.label(text="Formula")
192 layout.prop(self, "formula", text="")
193 #try: self.examples = self.formula
194 #except: pass
196 if self.examples != 'CUSTOM':
197 example = self.ex_items[int(self.examples)][0]
198 if example != self.old_ex:
199 self.formula = example
200 self.old_ex = example
201 elif self.formula != example:
202 self.examples = 'CUSTOM'
203 formula = self.formula
205 layout.separator()
206 if "f1" in formula: layout.prop(self, "slider_f01")
207 if "f2" in formula: layout.prop(self, "slider_f02")
208 if "f3" in formula: layout.prop(self, "slider_f03")
209 if "f4" in formula: layout.prop(self, "slider_f04")
210 if "f5" in formula: layout.prop(self, "slider_f05")
211 if "i1" in formula: layout.prop(self, "slider_i01")
212 if "i2" in formula: layout.prop(self, "slider_i02")
213 if "i3" in formula: layout.prop(self, "slider_i03")
214 if "i4" in formula: layout.prop(self, "slider_i04")
215 if "i5" in formula: layout.prop(self, "slider_i05")
217 layout.label(text="Variables (for each vertex):")
218 layout.label(text="lx, ly, lz: Local Coordinates", icon='ORIENTATION_LOCAL')
219 layout.label(text="gx, gy, gz: Global Coordinates", icon='WORLD')
220 layout.label(text="rx, ry, rz: Local Coordinates (0 to 1)", icon='NORMALIZE_FCURVES')
221 layout.label(text="nx, ny, nz: Normal Coordinates", icon='SNAP_NORMAL')
222 layout.label(text="w[0], w[1], w[2], ... : Vertex Groups", icon="GROUP_VERTEX")
223 layout.separator()
224 layout.label(text="f1, f2, f3, f4, f5: Float Sliders", icon='MOD_HUE_SATURATION')#PROPERTIES
225 layout.label(text="i1, i2, i3, i4, i5: Integer Sliders", icon='MOD_HUE_SATURATION')
226 layout.separator()
227 #layout.label(text="All mathematical functions are based on Numpy", icon='INFO')
228 #layout.label(text="https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.math.html", icon='INFO')
229 layout.operator("scene.weight_formula_wiki", icon="HELP")
230 #layout.label(text="(where 'i' is the index of the Vertex Group)")
232 def execute(self, context):
233 ob = context.active_object
234 n_verts = len(ob.data.vertices)
235 #if self.examples == 'CUSTOM':
236 # formula = self.formula
237 #else:
238 #self.formula = self.examples
239 # formula = self.examples
241 #f1, f2, f3, f4, f5 = self.slider_f01, self.slider_f02, self.slider_f03, self.slider_f04, self.slider_f05
242 #i1, i2, i3, i4, i5 = self.slider_i01, self.slider_i02, self.slider_i03, self.slider_i04, self.slider_i05
243 f_sliders = self.slider_f01, self.slider_f02, self.slider_f03, self.slider_f04, self.slider_f05
244 i_sliders = self.slider_i01, self.slider_i02, self.slider_i03, self.slider_i04, self.slider_i05
246 if self.examples != 'CUSTOM':
247 example = self.ex_items[int(self.examples)][0]
248 if example != self.old_ex:
249 self.formula = example
250 self.old_ex = example
251 elif self.formula != example:
252 self.examples = 'CUSTOM'
253 formula = self.formula
255 if formula == "": return {'FINISHED'}
256 # replace numeric sliders value
257 for i, slider in enumerate(f_sliders):
258 formula = formula.replace('f'+str(i+1),"{0:.2f}".format(slider))
259 for i, slider in enumerate(i_sliders):
260 formula =formula.replace('i'+str(i+1),str(slider))
261 vertex_group_name = "" + formula
262 ob.vertex_groups.new(name=vertex_group_name)
264 weight = compute_formula(ob, formula=formula, float_var=f_sliders, int_var=i_sliders)
265 if type(weight) == str:
266 self.report({'ERROR'}, weight)
267 return {'CANCELLED'}
269 #start_time = timeit.default_timer()
270 weight = nan_to_num(weight)
271 vg = ob.vertex_groups[-1]
272 if type(weight) == int or type(weight) == float:
273 for i in range(n_verts):
274 vg.add([i], weight, 'REPLACE')
275 elif type(weight) == ndarray:
276 for i in range(n_verts):
277 vg.add([i], weight[i], 'REPLACE')
278 ob.data.update()
279 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
281 # Store formula settings
282 new_formula = ob.formula_settings.add()
283 new_formula.name = ob.vertex_groups[-1].name
284 new_formula.formula = formula
285 new_formula.int_var = i_sliders
286 new_formula.float_var = f_sliders
288 #for f in ob.formula_settings:
289 # print(f.name, f.formula, f.int_var, f.float_var)
290 return {'FINISHED'}
293 class update_weight_formula(Operator):
294 bl_idname = "object.update_weight_formula"
295 bl_label = "Update Weight Formula"
296 bl_description = "Update an existing Vertex Group. Make sure that the name\nof the active Vertex Group is a valid formula"
297 bl_options = {'REGISTER', 'UNDO'}
299 @classmethod
300 def poll(cls, context):
301 return len(context.object.vertex_groups) > 0
303 def execute(self, context):
304 ob = context.active_object
305 n_verts = len(ob.data.vertices)
307 vg = ob.vertex_groups.active
308 formula = vg.name
309 weight = compute_formula(ob, formula=formula)
310 if type(weight) == str:
311 self.report({'ERROR'}, "The name of the active Vertex Group\nis not a valid Formula")
312 return {'CANCELLED'}
314 #start_time = timeit.default_timer()
315 weight = nan_to_num(weight)
316 if type(weight) == int or type(weight) == float:
317 for i in range(n_verts):
318 vg.add([i], weight, 'REPLACE')
319 elif type(weight) == ndarray:
320 for i in range(n_verts):
321 vg.add([i], weight[i], 'REPLACE')
322 ob.data.update()
323 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
324 return {'FINISHED'}
327 class _weight_laplacian(Operator):
328 bl_idname = "object._weight_laplacian"
329 bl_label = "Weight Laplacian"
330 bl_description = ("Compute the Vertex Group Laplacian")
331 bl_options = {'REGISTER', 'UNDO'}
333 bounds : EnumProperty(
334 items=(('MANUAL', "Manual Bounds", ""),
335 ('POSITIVE', "Positive Only", ""),
336 ('NEGATIVE', "Negative Only", ""),
337 ('AUTOMATIC', "Automatic Bounds", "")),
338 default='AUTOMATIC', name="Bounds")
340 mode : EnumProperty(
341 items=(('LENGTH', "Length Weight", ""),
342 ('SIMPLE', "Simple", "")),
343 default='SIMPLE', name="Evaluation Mode")
345 min_def : FloatProperty(
346 name="Min", default=0, soft_min=-1, soft_max=0,
347 description="Laplacian value with 0 weight")
349 max_def : FloatProperty(
350 name="Max", default=0.5, soft_min=0, soft_max=5,
351 description="Laplacian value with 1 weight")
353 bounds_string = ""
355 frame = None
357 @classmethod
358 def poll(cls, context):
359 return len(context.object.vertex_groups) > 0
361 def draw(self, context):
362 layout = self.layout
363 col = layout.column(align=True)
364 col.label(text="Evaluation Mode")
365 col.prop(self, "mode", text="")
366 col.label(text="Bounds")
367 col.prop(self, "bounds", text="")
368 if self.bounds == 'MANUAL':
369 col.label(text="Strain Rate \u03B5:")
370 col.prop(self, "min_def")
371 col.prop(self, "max_def")
372 col.label(text="\u03B5" + ": from " + self.bounds_string)
375 def execute(self, context):
376 try: ob = context.object
377 except:
378 self.report({'ERROR'}, "Please select an Object")
379 return {'CANCELLED'}
381 group_id = ob.vertex_groups.active_index
382 input_group = ob.vertex_groups[group_id].name
384 group_name = "Laplacian"
385 ob.vertex_groups.new(name=group_name)
386 me = ob.data
387 bm = bmesh.new()
388 bm.from_mesh(me)
389 bm.edges.ensure_lookup_table()
391 # store weight values
392 weight = []
393 for v in me.vertices:
394 try:
395 weight.append(ob.vertex_groups[input_group].weight(v.index))
396 except:
397 weight.append(0)
399 n_verts = len(bm.verts)
400 lap = [0]*n_verts
401 for e in bm.edges:
402 if self.mode == 'LENGTH':
403 length = e.calc_length()
404 if length == 0: continue
405 id0 = e.verts[0].index
406 id1 = e.verts[1].index
407 lap[id0] += weight[id1]/length - weight[id0]/length
408 lap[id1] += weight[id0]/length - weight[id1]/length
409 else:
410 id0 = e.verts[0].index
411 id1 = e.verts[1].index
412 lap[id0] += weight[id1] - weight[id0]
413 lap[id1] += weight[id0] - weight[id1]
415 mean_lap = mean(lap)
416 stdev_lap = stdev(lap)
417 filter_lap = [i for i in lap if mean_lap-2*stdev_lap < i < mean_lap+2*stdev_lap]
418 if self.bounds == 'MANUAL':
419 min_def = self.min_def
420 max_def = self.max_def
421 elif self.bounds == 'AUTOMATIC':
422 min_def = min(filter_lap)
423 max_def = max(filter_lap)
424 self.min_def = min_def
425 self.max_def = max_def
426 elif self.bounds == 'NEGATIVE':
427 min_def = 0
428 max_def = min(filter_lap)
429 self.min_def = min_def
430 self.max_def = max_def
431 elif self.bounds == 'POSITIVE':
432 min_def = 0
433 max_def = max(filter_lap)
434 self.min_def = min_def
435 self.max_def = max_def
436 delta_def = max_def - min_def
438 # check undeformed errors
439 if delta_def == 0: delta_def = 0.0001
441 for i in range(len(lap)):
442 val = (lap[i]-min_def)/delta_def
443 #if val > 0.7: print(str(val) + " " + str(lap[i]))
444 #val = weight[i] + 0.2*lap[i]
445 ob.vertex_groups[-1].add([i], val, 'REPLACE')
446 self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
447 ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
448 ob.vertex_groups.update()
449 ob.data.update()
450 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
451 bm.free()
452 return {'FINISHED'}
454 class ok_weight_laplacian(Operator):
455 bl_idname = "object.weight_laplacian"
456 bl_label = "Weight Laplacian"
457 bl_description = ("Compute the Vertex Group Laplacian")
458 bl_options = {'REGISTER', 'UNDO'}
460 bounds_string = ""
462 frame = None
464 @classmethod
465 def poll(cls, context):
466 return len(context.object.vertex_groups) > 0
469 def execute(self, context):
470 try: ob = context.object
471 except:
472 self.report({'ERROR'}, "Please select an Object")
473 return {'CANCELLED'}
475 me = ob.data
476 bm = bmesh.new()
477 bm.from_mesh(me)
478 bm.edges.ensure_lookup_table()
480 group_id = ob.vertex_groups.active_index
481 input_group = ob.vertex_groups[group_id].name
483 group_name = "Laplacian"
484 ob.vertex_groups.new(name=group_name)
486 # store weight values
487 a = []
488 for v in me.vertices:
489 try:
490 a.append(ob.vertex_groups[input_group].weight(v.index))
491 except:
492 a.append(0)
494 a = array(a)
497 # initialize
498 n_verts = len(bm.verts)
499 # find max number of edges for vertex
500 max_edges = 0
501 n_neighbors = []
502 id_neighbors = []
503 for v in bm.verts:
504 n_edges = len(v.link_edges)
505 max_edges = max(max_edges, n_edges)
506 n_neighbors.append(n_edges)
507 neighbors = []
508 for e in v.link_edges:
509 for v1 in e.verts:
510 if v != v1: neighbors.append(v1.index)
511 id_neighbors.append(neighbors)
512 n_neighbors = array(n_neighbors)
515 lap_map = [[] for i in range(n_verts)]
516 #lap_map = []
518 for e in bm.edges:
519 id0 = e.verts[0].index
520 id1 = e.verts[1].index
521 lap_map[id0].append(id1)
522 lap_map[id1].append(id0)
524 lap = zeros((n_verts))#[0]*n_verts
525 n_records = zeros((n_verts))
526 for e in bm.edges:
527 id0 = e.verts[0].index
528 id1 = e.verts[1].index
529 length = e.calc_length()
530 if length == 0: continue
531 #lap[id0] += abs(a[id1] - a[id0])/length
532 #lap[id1] += abs(a[id0] - a[id1])/length
533 lap[id0] += (a[id1] - a[id0])/length
534 lap[id1] += (a[id0] - a[id1])/length
535 n_records[id0]+=1
536 n_records[id1]+=1
537 lap /= n_records
538 lap /= max(lap)
540 for i in range(n_verts):
541 ob.vertex_groups['Laplacian'].add([i], lap[i], 'REPLACE')
542 ob.vertex_groups.update()
543 ob.data.update()
544 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
545 bm.free()
546 return {'FINISHED'}
548 class weight_laplacian(Operator):
549 bl_idname = "object.weight_laplacian"
550 bl_label = "Weight Laplacian"
551 bl_description = ("Compute the Vertex Group Laplacian")
552 bl_options = {'REGISTER', 'UNDO'}
554 bounds_string = ""
556 frame = None
558 @classmethod
559 def poll(cls, context):
560 return len(context.object.vertex_groups) > 0
563 def execute(self, context):
564 try: ob = context.object
565 except:
566 self.report({'ERROR'}, "Please select an Object")
567 return {'CANCELLED'}
569 me = ob.data
570 bm = bmesh.new()
571 bm.from_mesh(me)
572 bm.edges.ensure_lookup_table()
573 n_verts = len(me.vertices)
575 group_id = ob.vertex_groups.active_index
576 input_group = ob.vertex_groups[group_id].name
578 group_name = "Laplacian"
579 vg = ob.vertex_groups.new(name=group_name)
581 # store weight values
582 dvert_lay = bm.verts.layers.deform.active
583 weight = bmesh_get_weight_numpy(group_id, dvert_lay, bm.verts)
585 #verts, normals = get_vertices_and_normals_numpy(me)
587 #lap = zeros((n_verts))#[0]*n_verts
588 lap = [Vector((0,0,0)) for i in range(n_verts)]
589 n_records = zeros((n_verts))
590 for e in bm.edges:
591 vert0 = e.verts[0]
592 vert1 = e.verts[1]
593 id0 = vert0.index
594 id1 = vert1.index
595 v0 = vert0.co
596 v1 = vert1.co
597 v01 = v1-v0
598 v10 = -v01
599 v01 -= v01.project(vert0.normal)
600 v10 -= v10.project(vert1.normal)
601 length = e.calc_length()
602 if length == 0: continue
603 dw = (weight[id1] - weight[id0])/length
604 lap[id0] += v01.normalized() * dw
605 lap[id1] -= v10.normalized() * dw
606 n_records[id0]+=1
607 n_records[id1]+=1
608 #lap /= n_records[:,np.newaxis]
609 lap = [l.length/r for r,l in zip(n_records,lap)]
611 lap = np.array(lap)
612 lap /= np.max(lap)
613 lap = list(lap)
615 for i in range(n_verts):
616 vg.add([i], lap[i], 'REPLACE')
617 ob.vertex_groups.update()
618 ob.data.update()
619 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
620 bm.free()
621 return {'FINISHED'}
623 class edges_deformation(Operator):
624 bl_idname = "object.edges_deformation"
625 bl_label = "Edges Deformation"
626 bl_description = ("Compute Weight based on the deformation of edges"+
627 "according to visible modifiers.")
628 bl_options = {'REGISTER', 'UNDO'}
630 bounds : EnumProperty(
631 items=(('MANUAL', "Manual Bounds", ""),
632 ('COMPRESSION', "Compressed Only", ""),
633 ('TENSION', "Extended Only", ""),
634 ('AUTOMATIC', "Automatic Bounds", "")),
635 default='AUTOMATIC', name="Bounds")
637 mode : EnumProperty(
638 items=(('MAX', "Max Deformation", ""),
639 ('MEAN', "Average Deformation", "")),
640 default='MEAN', name="Evaluation Mode")
642 min_def : FloatProperty(
643 name="Min", default=0, soft_min=-1, soft_max=0,
644 description="Deformations with 0 weight")
646 max_def : FloatProperty(
647 name="Max", default=0.5, soft_min=0, soft_max=5,
648 description="Deformations with 1 weight")
650 bounds_string = ""
652 frame = None
654 @classmethod
655 def poll(cls, context):
656 return len(context.object.modifiers) > 0
658 def draw(self, context):
659 layout = self.layout
660 col = layout.column(align=True)
661 col.label(text="Evaluation Mode")
662 col.prop(self, "mode", text="")
663 col.label(text="Bounds")
664 col.prop(self, "bounds", text="")
665 if self.bounds == 'MANUAL':
666 col.label(text="Strain Rate \u03B5:")
667 col.prop(self, "min_def")
668 col.prop(self, "max_def")
669 col.label(text="\u03B5" + ": from " + self.bounds_string)
671 def execute(self, context):
672 try: ob = context.object
673 except:
674 self.report({'ERROR'}, "Please select an Object")
675 return {'CANCELLED'}
677 # check if the object is Cloth or Softbody
678 physics = False
679 for m in ob.modifiers:
680 if m.type == 'CLOTH' or m.type == 'SOFT_BODY':
681 physics = True
682 if context.scene.frame_current == 1 and self.frame != None:
683 context.scene.frame_current = self.frame
684 break
685 if not physics: self.frame = None
687 if self.mode == 'MEAN': group_name = "Average Deformation"
688 elif self.mode == 'MAX': group_name = "Max Deformation"
689 ob.vertex_groups.new(name=group_name)
690 me0 = ob.data
692 me = simple_to_mesh(ob) #ob.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
693 if len(me.vertices) != len(me0.vertices) or len(me.edges) != len(me0.edges):
694 self.report({'ERROR'}, "The topology of the object should be" +
695 "unaltered")
696 return {'CANCELLED'}
698 bm0 = bmesh.new()
699 bm0.from_mesh(me0)
700 bm = bmesh.new()
701 bm.from_mesh(me)
702 deformations = []
703 for e0, e in zip(bm0.edges, bm.edges):
704 try:
705 l0 = e0.calc_length()
706 l1 = e.calc_length()
707 epsilon = (l1 - l0)/l0
708 deformations.append(epsilon)
709 except: deformations.append(1)
710 v_deformations = []
711 for v in bm.verts:
712 vdef = []
713 for e in v.link_edges:
714 vdef.append(deformations[e.index])
715 if self.mode == 'MEAN': v_deformations.append(mean(vdef))
716 elif self.mode == 'MAX': v_deformations.append(max(vdef, key=abs))
717 #elif self.mode == 'MIN': v_deformations.append(min(vdef, key=abs))
719 if self.bounds == 'MANUAL':
720 min_def = self.min_def
721 max_def = self.max_def
722 elif self.bounds == 'AUTOMATIC':
723 min_def = min(v_deformations)
724 max_def = max(v_deformations)
725 self.min_def = min_def
726 self.max_def = max_def
727 elif self.bounds == 'COMPRESSION':
728 min_def = 0
729 max_def = min(v_deformations)
730 self.min_def = min_def
731 self.max_def = max_def
732 elif self.bounds == 'TENSION':
733 min_def = 0
734 max_def = max(v_deformations)
735 self.min_def = min_def
736 self.max_def = max_def
737 delta_def = max_def - min_def
739 # check undeformed errors
740 if delta_def == 0:
741 if self.bounds == 'MANUAL':
742 delta_def = 0.0001
743 else:
744 message = "The object doesn't have deformations."
745 if physics:
746 message = message + ("\nIf you are using Physics try to " +
747 "save it in the cache before.")
748 self.report({'ERROR'}, message)
749 return {'CANCELLED'}
750 else:
751 if physics:
752 self.frame = context.scene.frame_current
754 for i in range(len(v_deformations)):
755 weight = (v_deformations[i] - min_def)/delta_def
756 ob.vertex_groups[-1].add([i], weight, 'REPLACE')
757 self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
758 ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
759 ob.vertex_groups.update()
760 ob.data.update()
761 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
762 bpy.data.meshes.remove(me)
763 bm.free()
764 bm0.free()
765 return {'FINISHED'}
767 class edges_bending(Operator):
768 bl_idname = "object.edges_bending"
769 bl_label = "Edges Bending"
770 bl_description = ("Compute Weight based on the bending of edges"+
771 "according to visible modifiers.")
772 bl_options = {'REGISTER', 'UNDO'}
774 bounds : EnumProperty(
775 items=(('MANUAL', "Manual Bounds", ""),
776 ('POSITIVE', "Positive Only", ""),
777 ('NEGATIVE', "Negative Only", ""),
778 ('UNSIGNED', "Absolute Bending", ""),
779 ('AUTOMATIC', "Signed Bending", "")),
780 default='AUTOMATIC', name="Bounds")
782 min_def : FloatProperty(
783 name="Min", default=-10, soft_min=-45, soft_max=45,
784 description="Deformations with 0 weight")
786 max_def : FloatProperty(
787 name="Max", default=10, soft_min=-45, soft_max=45,
788 description="Deformations with 1 weight")
790 bounds_string = ""
791 frame = None
793 @classmethod
794 def poll(cls, context):
795 return len(context.object.modifiers) > 0
797 def draw(self, context):
798 layout = self.layout
799 layout.label(text="Bounds")
800 layout.prop(self, "bounds", text="")
801 if self.bounds == 'MANUAL':
802 layout.prop(self, "min_def")
803 layout.prop(self, "max_def")
805 def execute(self, context):
806 try: ob = context.object
807 except:
808 self.report({'ERROR'}, "Please select an Object")
809 return {'CANCELLED'}
811 group_name = "Edges Bending"
812 ob.vertex_groups.new(name=group_name)
814 # check if the object is Cloth or Softbody
815 physics = False
816 for m in ob.modifiers:
817 if m.type == 'CLOTH' or m.type == 'SOFT_BODY':
818 physics = True
819 if context.scene.frame_current == 1 and self.frame != None:
820 context.scene.frame_current = self.frame
821 break
822 if not physics: self.frame = None
824 #ob.data.update()
825 #context.scene.update()
826 me0 = ob.data
827 me = simple_to_mesh(ob) #ob.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
828 if len(me.vertices) != len(me0.vertices) or len(me.edges) != len(me0.edges):
829 self.report({'ERROR'}, "The topology of the object should be" +
830 "unaltered")
831 bm0 = bmesh.new()
832 bm0.from_mesh(me0)
833 bm = bmesh.new()
834 bm.from_mesh(me)
835 deformations = []
836 for e0, e in zip(bm0.edges, bm.edges):
837 try:
838 ang = e.calc_face_angle_signed()
839 ang0 = e0.calc_face_angle_signed()
840 if self.bounds == 'UNSIGNED':
841 deformations.append(abs(ang-ang0))
842 else:
843 deformations.append(ang-ang0)
844 except: deformations.append(0)
845 v_deformations = []
846 for v in bm.verts:
847 vdef = []
848 for e in v.link_edges:
849 vdef.append(deformations[e.index])
850 v_deformations.append(mean(vdef))
851 if self.bounds == 'MANUAL':
852 min_def = radians(self.min_def)
853 max_def = radians(self.max_def)
854 elif self.bounds == 'AUTOMATIC':
855 min_def = min(v_deformations)
856 max_def = max(v_deformations)
857 elif self.bounds == 'POSITIVE':
858 min_def = 0
859 max_def = min(v_deformations)
860 elif self.bounds == 'NEGATIVE':
861 min_def = 0
862 max_def = max(v_deformations)
863 elif self.bounds == 'UNSIGNED':
864 min_def = 0
865 max_def = max(v_deformations)
866 delta_def = max_def - min_def
868 # check undeformed errors
869 if delta_def == 0:
870 if self.bounds == 'MANUAL':
871 delta_def = 0.0001
872 else:
873 message = "The object doesn't have deformations."
874 if physics:
875 message = message + ("\nIf you are using Physics try to " +
876 "save it in the cache before.")
877 self.report({'ERROR'}, message)
878 return {'CANCELLED'}
879 else:
880 if physics:
881 self.frame = context.scene.frame_current
883 for i in range(len(v_deformations)):
884 weight = (v_deformations[i] - min_def)/delta_def
885 ob.vertex_groups[-1].add([i], weight, 'REPLACE')
886 self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
887 ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
888 ob.vertex_groups.update()
889 ob.data.update()
890 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
891 bpy.data.meshes.remove(me)
892 bm0.free()
893 bm.free()
894 return {'FINISHED'}
896 class weight_contour_displace(Operator):
897 bl_idname = "object.weight_contour_displace"
898 bl_label = "Contour Displace"
899 bl_description = ("")
900 bl_options = {'REGISTER', 'UNDO'}
902 use_modifiers : BoolProperty(
903 name="Use Modifiers", default=True,
904 description="Apply all the modifiers")
905 min_iso : FloatProperty(
906 name="Min Iso Value", default=0.49, min=0, max=1,
907 description="Threshold value")
908 max_iso : FloatProperty(
909 name="Max Iso Value", default=0.51, min=0, max=1,
910 description="Threshold value")
911 n_cuts : IntProperty(
912 name="Cuts", default=2, min=1, soft_max=10,
913 description="Number of cuts in the selected range of values")
914 bool_displace : BoolProperty(
915 name="Add Displace", default=True, description="Add Displace Modifier")
916 bool_flip : BoolProperty(
917 name="Flip", default=False, description="Flip Output Weight")
919 weight_mode : EnumProperty(
920 items=[('Remapped', 'Remapped', 'Remap values'),
921 ('Alternate', 'Alternate', 'Alternate 0 and 1'),
922 ('Original', 'Original', 'Keep original Vertex Group')],
923 name="Weight", description="Choose how to convert vertex group",
924 default="Remapped", options={'LIBRARY_EDITABLE'})
926 @classmethod
927 def poll(cls, context):
928 return len(context.object.vertex_groups) > 0
930 def invoke(self, context, event):
931 return context.window_manager.invoke_props_dialog(self, width=350)
933 def execute(self, context):
934 start_time = timeit.default_timer()
935 try:
936 check = context.object.vertex_groups[0]
937 except:
938 self.report({'ERROR'}, "The object doesn't have Vertex Groups")
939 return {'CANCELLED'}
941 ob0 = context.object
943 group_id = ob0.vertex_groups.active_index
944 vertex_group_name = ob0.vertex_groups[group_id].name
946 bpy.ops.object.mode_set(mode='EDIT')
947 bpy.ops.mesh.select_all(action='DESELECT')
948 bpy.ops.object.mode_set(mode='OBJECT')
949 if self.use_modifiers:
950 #me0 = ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
951 me0 = simple_to_mesh(ob0)
952 else:
953 me0 = ob0.data.copy()
955 # generate new bmesh
956 bm = bmesh.new()
957 bm.from_mesh(me0)
958 bm.verts.ensure_lookup_table()
959 bm.edges.ensure_lookup_table()
960 bm.faces.ensure_lookup_table()
962 # store weight values
963 weight = []
964 ob = bpy.data.objects.new("temp", me0)
965 for g in ob0.vertex_groups:
966 ob.vertex_groups.new(name=g.name)
967 for v in me0.vertices:
968 try:
969 weight.append(ob.vertex_groups[vertex_group_name].weight(v.index))
970 except:
971 weight.append(0)
973 # define iso values
974 iso_values = []
975 for i_cut in range(self.n_cuts):
976 delta_iso = abs(self.max_iso - self.min_iso)
977 min_iso = min(self.min_iso, self.max_iso)
978 max_iso = max(self.min_iso, self.max_iso)
979 if delta_iso == 0: iso_val = min_iso
980 elif self.n_cuts > 1: iso_val = i_cut/(self.n_cuts-1)*delta_iso + min_iso
981 else: iso_val = (self.max_iso + self.min_iso)/2
982 iso_values.append(iso_val)
984 # Start Cuts Iterations
985 filtered_edges = bm.edges
986 for iso_val in iso_values:
987 delete_edges = []
989 faces_mask = []
990 for f in bm.faces:
991 w_min = 2
992 w_max = 2
993 for v in f.verts:
994 w = weight[v.index]
995 if w_min == 2:
996 w_max = w_min = w
997 if w > w_max: w_max = w
998 if w < w_min: w_min = w
999 if w_min < iso_val and w_max > iso_val:
1000 faces_mask.append(f)
1001 break
1003 #link_faces = [[f for f in e.link_faces] for e in bm.edges]
1005 #faces_todo = [f.select for f in bm.faces]
1006 #faces_todo = [True for f in bm.faces]
1007 verts = []
1008 edges = []
1009 edges_id = {}
1010 _filtered_edges = []
1011 n_verts = len(bm.verts)
1012 count = n_verts
1013 for e in filtered_edges:
1014 #id0 = e.vertices[0]
1015 #id1 = e.vertices[1]
1016 id0 = e.verts[0].index
1017 id1 = e.verts[1].index
1018 w0 = weight[id0]
1019 w1 = weight[id1]
1021 if w0 == w1: continue
1022 elif w0 > iso_val and w1 > iso_val:
1023 _filtered_edges.append(e)
1024 continue
1025 elif w0 < iso_val and w1 < iso_val: continue
1026 elif w0 == iso_val or w1 == iso_val:
1027 _filtered_edges.append(e)
1028 continue
1029 else:
1030 v0 = bm.verts[id0].co
1031 v1 = bm.verts[id1].co
1032 v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
1033 if e not in delete_edges:
1034 delete_edges.append(e)
1035 verts.append(v)
1036 edges_id[str(id0)+"_"+str(id1)] = count
1037 edges_id[str(id1)+"_"+str(id0)] = count
1038 count += 1
1039 _filtered_edges.append(e)
1040 filtered_edges = _filtered_edges
1041 splitted_faces = []
1043 switch = False
1044 # splitting faces
1045 for f in faces_mask:
1046 # create sub-faces slots. Once a new vertex is reached it will
1047 # change slot, storing the next vertices for a new face.
1048 build_faces = [[],[]]
1049 #switch = False
1050 verts0 = [v.index for v in f.verts]
1051 verts1 = list(verts0)
1052 verts1.append(verts1.pop(0)) # shift list
1053 for id0, id1 in zip(verts0, verts1):
1055 # add first vertex to active slot
1056 build_faces[switch].append(id0)
1058 # try to split edge
1059 try:
1060 # check if the edge must be splitted
1061 new_vert = edges_id[str(id0)+"_"+str(id1)]
1062 # add new vertex
1063 build_faces[switch].append(new_vert)
1064 # if there is an open face on the other slot
1065 if len(build_faces[not switch]) > 0:
1066 # store actual face
1067 splitted_faces.append(build_faces[switch])
1068 # reset actual faces and switch
1069 build_faces[switch] = []
1070 # change face slot
1071 switch = not switch
1072 # continue previous face
1073 build_faces[switch].append(new_vert)
1074 except: pass
1075 if len(build_faces[not switch]) == 2:
1076 build_faces[not switch].append(id0)
1077 if len(build_faces[not switch]) > 2:
1078 splitted_faces.append(build_faces[not switch])
1079 # add last face
1080 splitted_faces.append(build_faces[switch])
1081 #del_faces.append(f.index)
1083 # adding new vertices
1084 _new_vert = bm.verts.new
1085 for v in verts: new_vert = _new_vert(v)
1086 bm.verts.index_update()
1087 bm.verts.ensure_lookup_table()
1088 # adding new faces
1089 _new_face = bm.faces.new
1090 missed_faces = []
1091 added_faces = []
1092 for f in splitted_faces:
1093 try:
1094 face_verts = [bm.verts[i] for i in f]
1095 new_face = _new_face(face_verts)
1096 for e in new_face.edges:
1097 filtered_edges.append(e)
1098 except:
1099 missed_faces.append(f)
1101 bm.faces.ensure_lookup_table()
1102 # updating weight values
1103 weight = weight + [iso_val]*len(verts)
1105 # deleting old edges/faces
1106 _remove_edge = bm.edges.remove
1107 bm.edges.ensure_lookup_table()
1108 for e in delete_edges:
1109 _remove_edge(e)
1110 _filtered_edges = []
1111 for e in filtered_edges:
1112 if e not in delete_edges: _filtered_edges.append(e)
1113 filtered_edges = _filtered_edges
1115 name = ob0.name + '_ContourDisp'
1116 me = bpy.data.meshes.new(name)
1117 bm.to_mesh(me)
1118 bm.free()
1119 ob = bpy.data.objects.new(name, me)
1121 # Link object to scene and make active
1122 scn = context.scene
1123 context.collection.objects.link(ob)
1124 context.view_layer.objects.active = ob
1125 ob.select_set(True)
1126 ob0.select_set(False)
1128 # generate new vertex group
1129 for g in ob0.vertex_groups:
1130 ob.vertex_groups.new(name=g.name)
1131 #ob.vertex_groups.new(name=vertex_group_name)
1133 all_weight = weight + [iso_val]*len(verts)
1134 #mult = 1/(1-iso_val)
1135 for id in range(len(all_weight)):
1136 #if False: w = (all_weight[id]-iso_val)*mult
1137 w = all_weight[id]
1138 if self.weight_mode == 'Alternate':
1139 direction = self.bool_flip
1140 for i in range(len(iso_values)-1):
1141 val0, val1 = iso_values[i], iso_values[i+1]
1142 if val0 < w <= val1:
1143 if direction: w1 = (w-val0)/(val1-val0)
1144 else: w1 = (val1-w)/(val1-val0)
1145 direction = not direction
1146 if w < iso_values[0]: w1 = not self.bool_flip
1147 if w > iso_values[-1]: w1 = not direction
1148 elif self.weight_mode == 'Remapped':
1149 if w < min_iso: w1 = 0
1150 elif w > max_iso: w1 = 1
1151 else: w1 = (w - min_iso)/delta_iso
1152 else:
1153 if self.bool_flip: w1 = 1-w
1154 else: w1 = w
1155 ob.vertex_groups[vertex_group_name].add([id], w1, 'REPLACE')
1157 ob.vertex_groups.active_index = group_id
1159 # align new object
1160 ob.matrix_world = ob0.matrix_world
1162 # Displace Modifier
1163 if self.bool_displace:
1164 ob.modifiers.new(type='DISPLACE', name='Displace')
1165 ob.modifiers["Displace"].mid_level = 0
1166 ob.modifiers["Displace"].strength = 0.1
1167 ob.modifiers['Displace'].vertex_group = vertex_group_name
1169 bpy.ops.object.mode_set(mode='EDIT')
1170 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
1171 print("Contour Displace time: " + str(timeit.default_timer() - start_time) + " sec")
1173 bpy.data.meshes.remove(me0)
1175 return {'FINISHED'}
1177 class weight_contour_mask(Operator):
1178 bl_idname = "object.weight_contour_mask"
1179 bl_label = "Contour Mask"
1180 bl_description = ("")
1181 bl_options = {'REGISTER', 'UNDO'}
1183 use_modifiers : BoolProperty(
1184 name="Use Modifiers", default=True,
1185 description="Apply all the modifiers")
1186 iso : FloatProperty(
1187 name="Iso Value", default=0.5, soft_min=0, soft_max=1,
1188 description="Threshold value")
1189 bool_solidify : BoolProperty(
1190 name="Solidify", default=True, description="Add Solidify Modifier")
1191 offset : FloatProperty(
1192 name="Offset", default=1, min=0, max=1,
1193 description="Offset")
1194 thickness : FloatProperty(
1195 name="Thickness", default=0.5, soft_min=0, soft_max=1,
1196 description="Thickness")
1197 normalize_weight : BoolProperty(
1198 name="Normalize Weight", default=True,
1199 description="Normalize weight of remaining vertices")
1201 @classmethod
1202 def poll(cls, context):
1203 return len(context.object.vertex_groups) > 0
1205 def invoke(self, context, event):
1206 return context.window_manager.invoke_props_dialog(self, width=350)
1208 def execute(self, context):
1209 start_time = timeit.default_timer()
1210 try:
1211 check = context.object.vertex_groups[0]
1212 except:
1213 self.report({'ERROR'}, "The object doesn't have Vertex Groups")
1214 return {'CANCELLED'}
1216 ob0 = bpy.context.object
1218 iso_val = self.iso
1219 group_id = ob0.vertex_groups.active_index
1220 vertex_group_name = ob0.vertex_groups[group_id].name
1222 bpy.ops.object.mode_set(mode='EDIT')
1223 bpy.ops.mesh.select_all(action='SELECT')
1224 bpy.ops.object.mode_set(mode='OBJECT')
1225 if self.use_modifiers:
1226 me0 = simple_to_mesh(ob0)#ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
1227 else:
1228 me0 = ob0.data.copy()
1230 # generate new bmesh
1231 bm = bmesh.new()
1232 bm.from_mesh(me0)
1233 bm.verts.ensure_lookup_table()
1234 bm.edges.ensure_lookup_table()
1235 bm.faces.ensure_lookup_table()
1237 # store weight values
1238 weight = []
1239 ob = bpy.data.objects.new("temp", me0)
1240 for g in ob0.vertex_groups:
1241 ob.vertex_groups.new(name=g.name)
1242 for v in me0.vertices:
1243 try:
1244 #weight.append(v.groups[vertex_group_name].weight)
1245 weight.append(ob.vertex_groups[vertex_group_name].weight(v.index))
1246 except:
1247 weight.append(0)
1249 faces_mask = []
1250 for f in bm.faces:
1251 w_min = 2
1252 w_max = 2
1253 for v in f.verts:
1254 w = weight[v.index]
1255 if w_min == 2:
1256 w_max = w_min = w
1257 if w > w_max: w_max = w
1258 if w < w_min: w_min = w
1259 if w_min < iso_val and w_max > iso_val:
1260 faces_mask.append(f)
1261 break
1263 filtered_edges = bm.edges# me0.edges
1264 faces_todo = [f.select for f in bm.faces]
1265 verts = []
1266 edges = []
1267 delete_edges = []
1268 edges_id = {}
1269 _filtered_edges = []
1270 n_verts = len(bm.verts)
1271 count = n_verts
1272 for e in filtered_edges:
1273 id0 = e.verts[0].index
1274 id1 = e.verts[1].index
1275 w0 = weight[id0]
1276 w1 = weight[id1]
1278 if w0 == w1: continue
1279 elif w0 > iso_val and w1 > iso_val:
1280 continue
1281 elif w0 < iso_val and w1 < iso_val: continue
1282 elif w0 == iso_val or w1 == iso_val: continue
1283 else:
1284 v0 = me0.vertices[id0].co
1285 v1 = me0.vertices[id1].co
1286 v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
1287 delete_edges.append(e)
1288 verts.append(v)
1289 edges_id[str(id0)+"_"+str(id1)] = count
1290 edges_id[str(id1)+"_"+str(id0)] = count
1291 count += 1
1293 splitted_faces = []
1295 switch = False
1296 # splitting faces
1297 for f in faces_mask:
1298 # create sub-faces slots. Once a new vertex is reached it will
1299 # change slot, storing the next vertices for a new face.
1300 build_faces = [[],[]]
1301 #switch = False
1302 verts0 = list(me0.polygons[f.index].vertices)
1303 verts1 = list(verts0)
1304 verts1.append(verts1.pop(0)) # shift list
1305 for id0, id1 in zip(verts0, verts1):
1307 # add first vertex to active slot
1308 build_faces[switch].append(id0)
1310 # try to split edge
1311 try:
1312 # check if the edge must be splitted
1313 new_vert = edges_id[str(id0)+"_"+str(id1)]
1314 # add new vertex
1315 build_faces[switch].append(new_vert)
1316 # if there is an open face on the other slot
1317 if len(build_faces[not switch]) > 0:
1318 # store actual face
1319 splitted_faces.append(build_faces[switch])
1320 # reset actual faces and switch
1321 build_faces[switch] = []
1322 # change face slot
1323 switch = not switch
1324 # continue previous face
1325 build_faces[switch].append(new_vert)
1326 except: pass
1327 if len(build_faces[not switch]) == 2:
1328 build_faces[not switch].append(id0)
1329 if len(build_faces[not switch]) > 2:
1330 splitted_faces.append(build_faces[not switch])
1331 # add last face
1332 splitted_faces.append(build_faces[switch])
1334 # adding new vertices
1335 _new_vert = bm.verts.new
1336 for v in verts: _new_vert(v)
1337 bm.verts.ensure_lookup_table()
1339 # deleting old edges/faces
1340 _remove_edge = bm.edges.remove
1341 bm.edges.ensure_lookup_table()
1342 remove_edges = []
1343 for e in delete_edges: _remove_edge(e)
1345 bm.verts.ensure_lookup_table()
1346 # adding new faces
1347 _new_face = bm.faces.new
1348 missed_faces = []
1349 for f in splitted_faces:
1350 try:
1351 face_verts = [bm.verts[i] for i in f]
1352 _new_face(face_verts)
1353 except:
1354 missed_faces.append(f)
1356 # Mask geometry
1357 if(True):
1358 _remove_vert = bm.verts.remove
1359 all_weight = weight + [iso_val+0.0001]*len(verts)
1360 weight = []
1361 for w, v in zip(all_weight, bm.verts):
1362 if w < iso_val: _remove_vert(v)
1363 else: weight.append(w)
1365 # Create mesh and object
1366 name = ob0.name + '_ContourMask_{:.3f}'.format(iso_val)
1367 me = bpy.data.meshes.new(name)
1368 bm.to_mesh(me)
1369 bm.free()
1370 ob = bpy.data.objects.new(name, me)
1372 # Link object to scene and make active
1373 scn = context.scene
1374 context.collection.objects.link(ob)
1375 context.view_layer.objects.active = ob
1376 ob.select_set(True)
1377 ob0.select_set(False)
1379 # generate new vertex group
1380 for g in ob0.vertex_groups:
1381 ob.vertex_groups.new(name=g.name)
1383 if iso_val != 1: mult = 1/(1-iso_val)
1384 else: mult = 1
1385 for id in range(len(weight)):
1386 if self.normalize_weight: w = (weight[id]-iso_val)*mult
1387 else: w = weight[id]
1388 ob.vertex_groups[vertex_group_name].add([id], w, 'REPLACE')
1389 ob.vertex_groups.active_index = group_id
1391 # align new object
1392 ob.matrix_world = ob0.matrix_world
1394 # Add Solidify
1395 if self.bool_solidify and True:
1396 ob.modifiers.new(type='SOLIDIFY', name='Solidify')
1397 ob.modifiers['Solidify'].thickness = self.thickness
1398 ob.modifiers['Solidify'].offset = self.offset
1399 ob.modifiers['Solidify'].vertex_group = vertex_group_name
1401 bpy.ops.object.mode_set(mode='EDIT')
1402 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
1403 print("Contour Mask time: " + str(timeit.default_timer() - start_time) + " sec")
1405 bpy.data.meshes.remove(me0)
1407 return {'FINISHED'}
1410 class weight_contour_mask_wip(Operator):
1411 bl_idname = "object.weight_contour_mask"
1412 bl_label = "Contour Mask"
1413 bl_description = ("")
1414 bl_options = {'REGISTER', 'UNDO'}
1416 use_modifiers : BoolProperty(
1417 name="Use Modifiers", default=True,
1418 description="Apply all the modifiers")
1419 iso : FloatProperty(
1420 name="Iso Value", default=0.5, soft_min=0, soft_max=1,
1421 description="Threshold value")
1422 bool_solidify : BoolProperty(
1423 name="Solidify", default=True, description="Add Solidify Modifier")
1424 normalize_weight : BoolProperty(
1425 name="Normalize Weight", default=True,
1426 description="Normalize weight of remaining vertices")
1428 @classmethod
1429 def poll(cls, context):
1430 return len(context.object.vertex_groups) > 0
1432 def execute(self, context):
1433 start_time = timeit.default_timer()
1434 try:
1435 check = context.object.vertex_groups[0]
1436 except:
1437 self.report({'ERROR'}, "The object doesn't have Vertex Groups")
1438 return {'CANCELLED'}
1440 ob0 = bpy.context.object
1442 iso_val = self.iso
1443 group_id = ob0.vertex_groups.active_index
1444 vertex_group_name = ob0.vertex_groups[group_id].name
1446 if self.use_modifiers:
1447 me0 = simple_to_mesh(ob0)
1448 else:
1449 me0 = ob0.data.copy()
1451 # generate new bmesh
1452 bm = bmesh.new()
1453 bm.from_mesh(me0)
1455 # store weight values
1456 weight = []
1457 ob = bpy.data.objects.new("temp", me0)
1458 for g in ob0.vertex_groups:
1459 ob.vertex_groups.new(name=g.name)
1460 weight = get_weight_numpy(ob.vertex_groups[vertex_group_name], len(me0.vertices))
1462 me0, bm, weight = contour_bmesh(me0, bm, weight, iso_val)
1464 # Mask geometry
1465 mask = weight >= iso_val
1466 weight = weight[mask]
1467 mask = np.logical_not(mask)
1468 delete_verts = np.array(bm.verts)[mask]
1470 # Create mesh and object
1471 name = ob0.name + '_ContourMask_{:.3f}'.format(iso_val)
1472 me = bpy.data.meshes.new(name)
1473 bm.to_mesh(me)
1474 bm.free()
1475 ob = bpy.data.objects.new(name, me)
1477 # Link object to scene and make active
1478 scn = context.scene
1479 context.collection.objects.link(ob)
1480 context.view_layer.objects.active = ob
1481 ob.select_set(True)
1482 ob0.select_set(False)
1484 # generate new vertex group
1485 for g in ob0.vertex_groups:
1486 ob.vertex_groups.new(name=g.name)
1488 if iso_val != 1: mult = 1/(1-iso_val)
1489 else: mult = 1
1490 for id in range(len(weight)):
1491 if self.normalize_weight: w = (weight[id]-iso_val)*mult
1492 else: w = weight[id]
1493 ob.vertex_groups[vertex_group_name].add([id], w, 'REPLACE')
1494 ob.vertex_groups.active_index = group_id
1496 # align new object
1497 ob.matrix_world = ob0.matrix_world
1499 # Add Solidify
1500 if self.bool_solidify and True:
1501 ob.modifiers.new(type='SOLIDIFY', name='Solidify')
1502 ob.modifiers['Solidify'].thickness = 0.05
1503 ob.modifiers['Solidify'].offset = 0
1504 ob.modifiers['Solidify'].vertex_group = vertex_group_name
1506 bpy.ops.object.mode_set(mode='EDIT')
1507 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
1508 print("Contour Mask time: " + str(timeit.default_timer() - start_time) + " sec")
1510 bpy.data.meshes.remove(me0)
1512 return {'FINISHED'}
1514 class vertex_colors_to_vertex_groups(Operator):
1515 bl_idname = "object.vertex_colors_to_vertex_groups"
1516 bl_label = "Vertex Color"
1517 bl_options = {'REGISTER', 'UNDO'}
1518 bl_description = ("Convert the active Vertex Color into a Vertex Group.")
1520 red : BoolProperty(
1521 name="red channel", default=False, description="convert red channel")
1522 green : BoolProperty(
1523 name="green channel", default=False,
1524 description="convert green channel")
1525 blue : BoolProperty(
1526 name="blue channel", default=False, description="convert blue channel")
1527 value : BoolProperty(
1528 name="value channel", default=True, description="convert value channel")
1529 invert : BoolProperty(
1530 name="invert", default=False, description="invert all color channels")
1532 @classmethod
1533 def poll(cls, context):
1534 try:
1535 return len(context.object.data.color_attributes) > 0
1536 except: return False
1538 def execute(self, context):
1539 ob = context.active_object
1540 id = len(ob.vertex_groups)
1541 id_red = id
1542 id_green = id
1543 id_blue = id
1544 id_value = id
1546 boolCol = len(ob.data.color_attributes)
1547 if(boolCol):
1548 col = ob.data.color_attributes.active_color
1549 bpy.ops.object.mode_set(mode='EDIT')
1550 bpy.ops.mesh.select_all(action='SELECT')
1552 if(self.red and boolCol):
1553 bpy.ops.object.vertex_group_add()
1554 bpy.ops.object.vertex_group_assign()
1555 id_red = id
1556 ob.vertex_groups[id_red].name = col.name + '_red'
1557 id+=1
1558 if(self.green and boolCol):
1559 bpy.ops.object.vertex_group_add()
1560 bpy.ops.object.vertex_group_assign()
1561 id_green = id
1562 ob.vertex_groups[id_green].name = col.name + '_green'
1563 id+=1
1564 if(self.blue and boolCol):
1565 bpy.ops.object.vertex_group_add()
1566 bpy.ops.object.vertex_group_assign()
1567 id_blue = id
1568 ob.vertex_groups[id_blue].name = col.name + '_blue'
1569 id+=1
1570 if(self.value and boolCol):
1571 bpy.ops.object.vertex_group_add()
1572 bpy.ops.object.vertex_group_assign()
1573 id_value = id
1574 ob.vertex_groups[id_value].name = col.name + '_value'
1575 id+=1
1577 mult = 1
1578 if(self.invert): mult = -1
1579 bpy.ops.object.mode_set(mode='OBJECT')
1580 sub_red = 1 + self.value + self.blue + self.green
1581 sub_green = 1 + self.value + self.blue
1582 sub_blue = 1 + self.value
1583 sub_value = 1
1585 id = len(ob.vertex_groups)
1586 if(id_red <= id and id_green <= id and id_blue <= id and id_value <= \
1587 id and boolCol):
1588 v_colors = ob.data.color_attributes.active_color.data
1590 i = 0
1591 if ob.data.color_attributes.active_color.domain == 'POINT':
1592 for v in ob.data.vertices:
1593 gr = v.groups
1594 if(self.red): gr[min(len(gr)-sub_red, id_red)].weight = \
1595 self.invert + mult * v_colors[i].color[0]
1596 if(self.green): gr[min(len(gr)-sub_green, id_green)].weight\
1597 = self.invert + mult * v_colors[i].color[1]
1598 if(self.blue): gr[min(len(gr)-sub_blue, id_blue)].weight = \
1599 self.invert + mult * v_colors[i].color[2]
1600 if(self.value):
1601 r = v_colors[i].color[0]
1602 g = v_colors[i].color[1]
1603 b = v_colors[i].color[2]
1604 gr[min(len(gr)-sub_value, id_value)].weight\
1605 = self.invert + mult * (0.2126*r + 0.7152*g + 0.0722*b)
1606 i+=1
1607 elif ob.data.color_attributes.active_color.domain == 'CORNER':
1608 for f in ob.data.polygons:
1609 for v in f.vertices:
1610 gr = ob.data.vertices[v].groups
1611 if(self.red): gr[min(len(gr)-sub_red, id_red)].weight = \
1612 self.invert + mult * v_colors[i].color[0]
1613 if(self.green): gr[min(len(gr)-sub_green, id_green)].weight\
1614 = self.invert + mult * v_colors[i].color[1]
1615 if(self.blue): gr[min(len(gr)-sub_blue, id_blue)].weight = \
1616 self.invert + mult * v_colors[i].color[2]
1617 if(self.value):
1618 r = v_colors[i].color[0]
1619 g = v_colors[i].color[1]
1620 b = v_colors[i].color[2]
1621 gr[min(len(gr)-sub_value, id_value)].weight\
1622 = self.invert + mult * (0.2126*r + 0.7152*g + 0.0722*b)
1623 i+=1
1625 bpy.ops.paint.weight_paint_toggle()
1626 return {'FINISHED'}
1628 class vertex_group_to_vertex_colors(Operator):
1629 bl_idname = "object.vertex_group_to_vertex_colors"
1630 bl_label = "Vertex Group"
1631 bl_options = {'REGISTER', 'UNDO'}
1632 bl_description = ("Convert the active Vertex Group into a Vertex Color.")
1634 channel : EnumProperty(
1635 items=[('BLUE', 'Blue Channel', 'Convert to Blue Channel'),
1636 ('GREEN', 'Green Channel', 'Convert to Green Channel'),
1637 ('RED', 'Red Channel', 'Convert to Red Channel'),
1638 ('VALUE', 'Value Channel', 'Convert to Grayscale'),
1639 ('FALSE_COLORS', 'False Colors', 'Convert to False Colors')],
1640 name="Convert to", description="Choose how to convert vertex group",
1641 default="VALUE", options={'LIBRARY_EDITABLE'})
1643 invert : BoolProperty(
1644 name="invert", default=False, description="invert color channel")
1646 @classmethod
1647 def poll(cls, context):
1648 return len(context.object.vertex_groups) > 0
1650 def execute(self, context):
1651 obj = context.active_object
1652 me = obj.data
1653 group_id = obj.vertex_groups.active_index
1654 if (group_id == -1):
1655 return {'FINISHED'}
1657 bpy.ops.object.mode_set(mode='OBJECT')
1658 group_name = obj.vertex_groups[group_id].name
1659 bpy.ops.geometry.color_attribute_add()
1660 active_color = obj.data.color_attributes.active_color
1662 colors_name = group_name
1663 if(self.channel == 'FALSE_COLORS'): colors_name += "_false_colors"
1664 elif(self.channel == 'VALUE'): colors_name += "_value"
1665 elif(self.channel == 'RED'): colors_name += "_red"
1666 elif(self.channel == 'GREEN'): colors_name += "_green"
1667 elif(self.channel == 'BLUE'): colors_name += "_blue"
1668 active_color.name = colors_name
1670 v_colors = obj.data.color_attributes.active_color.data
1672 bm = bmesh.new()
1673 bm.from_mesh(me)
1674 dvert_lay = bm.verts.layers.deform.active
1675 weight = bmesh_get_weight_numpy(group_id,dvert_lay,bm.verts)
1676 if self.invert: weight = 1-weight
1677 loops_size = get_attribute_numpy(me.polygons, attribute='loop_total', mult=1)
1678 n_colors = np.sum(loops_size)
1679 splitted_weight = weight[:,None]
1680 r = np.zeros(splitted_weight.shape)
1681 g = np.zeros(splitted_weight.shape)
1682 b = np.zeros(splitted_weight.shape)
1683 a = np.ones(splitted_weight.shape)
1684 if(self.channel == 'FALSE_COLORS'):
1685 mult = 0.6+0.4*splitted_weight
1686 mask = splitted_weight < 0.25
1687 g[mask] = splitted_weight[mask]*4
1688 b[mask] = np.ones(splitted_weight.shape)[mask]
1690 mask = np.where(np.logical_and(splitted_weight>=0.25, splitted_weight<0.5))
1691 g[mask] = np.ones(splitted_weight.shape)[mask]
1692 b[mask] = (1-(splitted_weight[mask]-0.25)*4)
1694 mask = np.where(np.logical_and(splitted_weight>=0.5, splitted_weight<0.75))
1695 r[mask] = (splitted_weight[mask]-0.5)*4
1696 g[mask] = np.ones(splitted_weight.shape)[mask]
1698 mask = 0.75 <= splitted_weight
1699 r[mask] = np.ones(splitted_weight.shape)[mask]
1700 g[mask] = (1-(splitted_weight[mask]-0.75)*4)
1701 elif(self.channel == 'VALUE'):
1702 r = splitted_weight
1703 g = splitted_weight
1704 b = splitted_weight
1705 elif(self.channel == 'RED'):
1706 r = splitted_weight
1707 elif(self.channel == 'GREEN'):
1708 g = splitted_weight
1709 elif(self.channel == 'BLUE'):
1710 b = splitted_weight
1712 colors = np.concatenate((r,g,b,a),axis=1).flatten()
1713 v_colors.foreach_set('color',colors)
1715 bpy.ops.paint.vertex_paint_toggle()
1716 bpy.ops.geometry.color_attribute_render_set(name=active_color.name)
1717 return {'FINISHED'}
1719 class vertex_group_to_uv(Operator):
1720 bl_idname = "object.vertex_group_to_uv"
1721 bl_label = "Vertex Group"
1722 bl_options = {'REGISTER', 'UNDO'}
1723 bl_description = ("Combine two Vertex Groups as UV Map Layer.")
1725 vertex_group_u : StringProperty(
1726 name="U", default='',
1727 description="Vertex Group used for the U coordinate")
1728 vertex_group_v : StringProperty(
1729 name="V", default='',
1730 description="Vertex Group used for the V coordinate")
1731 normalize_weight : BoolProperty(
1732 name="Normalize Weight", default=True,
1733 description="Normalize weight values")
1734 invert_u : BoolProperty(
1735 name="Invert U", default=False, description="Invert U")
1736 invert_v : BoolProperty(
1737 name="Invert V", default=False, description="Invert V")
1739 @classmethod
1740 def poll(cls, context):
1741 return len(context.object.vertex_groups) > 0
1743 def invoke(self, context, event):
1744 return context.window_manager.invoke_props_dialog(self, width=250)
1746 def draw(self, context):
1747 ob = context.object
1748 layout = self.layout
1749 col = layout.column(align=True)
1750 row = col.row(align=True)
1751 row.prop_search(self, 'vertex_group_u', ob, "vertex_groups", text='')
1752 row.separator()
1753 row.prop_search(self, 'vertex_group_v', ob, "vertex_groups", text='')
1754 row = col.row(align=True)
1755 row.prop(self, "invert_u")
1756 row.separator()
1757 row.prop(self, "invert_v")
1758 row = col.row(align=True)
1759 row.prop(self, "normalize_weight")
1761 def execute(self, context):
1762 ob = context.active_object
1763 me = ob.data
1764 n_verts = len(me.vertices)
1765 vg_keys = ob.vertex_groups.keys()
1766 bool_u = self.vertex_group_u in vg_keys
1767 bool_v = self.vertex_group_v in vg_keys
1768 if bool_u or bool_v:
1769 bm = bmesh.new()
1770 bm.from_mesh(me)
1771 dvert_lay = bm.verts.layers.deform.active
1772 if bool_u:
1773 u_index = ob.vertex_groups[self.vertex_group_u].index
1774 u = bmesh_get_weight_numpy(u_index, dvert_lay, bm.verts)
1775 if self.invert_u:
1776 u = 1-u
1777 if self.normalize_weight:
1778 u = np.interp(u, (u.min(), u.max()), (0, 1))
1779 else:
1780 u = np.zeros(n_verts)
1781 if bool_v:
1782 v_index = ob.vertex_groups[self.vertex_group_v].index
1783 v = bmesh_get_weight_numpy(v_index, dvert_lay, bm.verts)
1784 if self.invert_v:
1785 v = 1-v
1786 if self.normalize_weight:
1787 v = np.interp(v, (v.min(), v.max()), (0, 1))
1788 else:
1789 v = np.zeros(n_verts)
1790 else:
1791 u = v = np.zeros(n_verts)
1793 uv_layer = me.uv_layers.new(name='Weight_to_UV')
1794 loops_size = get_attribute_numpy(me.polygons, attribute='loop_total', mult=1)
1795 n_data = np.sum(loops_size)
1796 v_id = np.ones(n_data)
1797 me.polygons.foreach_get('vertices',v_id)
1798 v_id = v_id.astype(int)
1799 split_u = u[v_id,None]
1800 split_v = v[v_id,None]
1801 uv = np.concatenate((split_u,split_v),axis=1).flatten()
1802 uv_layer.data.foreach_set('uv',uv)
1803 me.uv_layers.update()
1804 return {'FINISHED'}
1806 class curvature_to_vertex_groups(Operator):
1807 bl_idname = "object.curvature_to_vertex_groups"
1808 bl_label = "Curvature"
1809 bl_options = {'REGISTER', 'UNDO'}
1810 bl_description = ("Generate a Vertex Group based on the curvature of the"
1811 "mesh. Is based on Dirty Vertex Color.")
1813 blur_strength : FloatProperty(
1814 name="Blur Strength", default=1, min=0.001,
1815 max=1, description="Blur strength per iteration")
1817 blur_iterations : IntProperty(
1818 name="Blur Iterations", default=1, min=0,
1819 max=40, description="Number of times to blur the values")
1821 angle : FloatProperty(
1822 name="Angle", default=5*pi/90, min=0,
1823 max=pi/2, subtype='ANGLE', description="Angle")
1825 invert : BoolProperty(
1826 name="Invert", default=False,
1827 description="Invert the curvature map")
1829 absolute : BoolProperty(
1830 name="Absolute", default=False, description="Absolute values")
1832 def execute(self, context):
1833 bpy.ops.object.mode_set(mode='OBJECT')
1834 bpy.ops.geometry.color_attribute_add(domain='CORNER', color = (1,1,1,1))
1835 color_attributes = context.active_object.data.color_attributes
1836 color_attributes.active = color_attributes[-1]
1837 color_attributes.active_color = color_attributes[-1]
1838 color_attributes[-1].name = "Curvature"
1839 bpy.ops.geometry.color_attribute_render_set(name=color_attributes[-1].name)
1840 bpy.ops.object.mode_set(mode='VERTEX_PAINT')
1841 bpy.ops.paint.vertex_color_dirt(
1842 blur_strength=self.blur_strength,
1843 blur_iterations=self.blur_iterations,
1844 clean_angle=pi/2 + self.angle,
1845 dirt_angle=pi/2 - self.angle,
1846 normalize=False)
1847 bpy.ops.object.vertex_colors_to_vertex_groups(invert=self.invert)
1848 if self.absolute:
1849 ob = context.object
1850 weight = get_weight_numpy(ob.vertex_groups[-1], len(ob.data.vertices))
1851 weight = np.abs(0.5-weight)*2
1852 bm = bmesh.new()
1853 bm.from_mesh(ob.data)
1854 bmesh_set_weight_numpy(bm,len(ob.vertex_groups)-1,weight)
1855 bm.to_mesh(ob.data)
1856 ob.vertex_groups.update()
1857 ob.data.update()
1858 #bpy.ops.geometry.color_attribute_remove()
1859 return {'FINISHED'}
1861 class face_area_to_vertex_groups(Operator):
1862 bl_idname = "object.face_area_to_vertex_groups"
1863 bl_label = "Area"
1864 bl_options = {'REGISTER', 'UNDO'}
1865 bl_description = ("Generate a Vertex Group based on the area of individual"
1866 "faces.")
1868 invert : BoolProperty(
1869 name="invert", default=False, description="invert values")
1870 bounds : EnumProperty(
1871 items=(('MANUAL', "Manual Bounds", ""),
1872 ('AUTOMATIC', "Automatic Bounds", "")),
1873 default='AUTOMATIC', name="Bounds")
1875 min_area : FloatProperty(
1876 name="Min", default=0.01, soft_min=0, soft_max=1,
1877 description="Faces with 0 weight")
1879 max_area : FloatProperty(
1880 name="Max", default=0.1, soft_min=0, soft_max=1,
1881 description="Faces with 1 weight")
1883 def draw(self, context):
1884 layout = self.layout
1885 layout.label(text="Bounds")
1886 layout.prop(self, "bounds", text="")
1887 if self.bounds == 'MANUAL':
1888 layout.prop(self, "min_area")
1889 layout.prop(self, "max_area")
1891 def execute(self, context):
1892 try: ob = context.object
1893 except:
1894 self.report({'ERROR'}, "Please select an Object")
1895 return {'CANCELLED'}
1896 ob.vertex_groups.new(name="Faces Area")
1898 areas = [[] for v in ob.data.vertices]
1900 for p in ob.data.polygons:
1901 for v in p.vertices:
1902 areas[v].append(p.area)
1904 for i in range(len(areas)):
1905 areas[i] = mean(areas[i])
1906 if self.bounds == 'MANUAL':
1907 min_area = self.min_area
1908 max_area = self.max_area
1909 elif self.bounds == 'AUTOMATIC':
1910 min_area = min(areas)
1911 max_area = max(areas)
1912 elif self.bounds == 'COMPRESSION':
1913 min_area = 1
1914 max_area = min(areas)
1915 elif self.bounds == 'TENSION':
1916 min_area = 1
1917 max_area = max(areas)
1918 delta_area = max_area - min_area
1919 if delta_area == 0:
1920 delta_area = 0.0001
1921 if self.bounds == 'MANUAL':
1922 delta_area = 0.0001
1923 else:
1924 self.report({'ERROR'}, "The faces have the same areas")
1925 #return {'CANCELLED'}
1926 for i in range(len(areas)):
1927 weight = (areas[i] - min_area)/delta_area
1928 ob.vertex_groups[-1].add([i], weight, 'REPLACE')
1929 ob.vertex_groups.update()
1930 ob.data.update()
1931 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
1932 return {'FINISHED'}
1934 class random_weight(Operator):
1935 bl_idname = "object.random_weight"
1936 bl_label = "Random"
1937 bl_options = {'REGISTER', 'UNDO'}
1938 bl_description = ("Generate a random Vertex Group")
1940 min_val : FloatProperty(
1941 name="Min", default=0, soft_min=0, soft_max=1,
1942 description="Minimum Value")
1944 max_val : FloatProperty(
1945 name="Max", default=1, soft_min=0, soft_max=1,
1946 description="Maximum Value")
1948 #def draw(self, context):
1949 # layout = self.layout
1950 # layout.prop(self, "min_area")
1951 # layout.prop(self, "max_area")
1952 @classmethod
1953 def poll(cls, context):
1954 return len(context.object.vertex_groups) > 0
1956 def execute(self, context):
1957 try: ob = context.object
1958 except:
1959 self.report({'ERROR'}, "Please select an Object")
1960 return {'CANCELLED'}
1961 #ob.vertex_groups.new(name="Random")
1962 n_verts = len(ob.data.vertices)
1963 weight = np.random.uniform(low=self.min_val, high=self.max_val, size=(n_verts,))
1964 np.clip(weight, 0, 1, out=weight)
1966 group_id = ob.vertex_groups.active_index
1967 for i in range(n_verts):
1968 ob.vertex_groups[group_id].add([i], weight[i], 'REPLACE')
1969 ob.vertex_groups.update()
1970 ob.data.update()
1971 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
1972 return {'FINISHED'}
1975 class harmonic_weight(Operator):
1976 bl_idname = "object.harmonic_weight"
1977 bl_label = "Harmonic"
1978 bl_options = {'REGISTER', 'UNDO'}
1979 bl_description = ("Create an harmonic variation of the active Vertex Group")
1981 freq : FloatProperty(
1982 name="Frequency", default=20, soft_min=0,
1983 soft_max=100, description="Wave frequency")
1985 amp : FloatProperty(
1986 name="Amplitude", default=1, soft_min=0,
1987 soft_max=10, description="Wave amplitude")
1989 midlevel : FloatProperty(
1990 name="Midlevel", default=0, min=-1,
1991 max=1, description="Midlevel")
1993 add : FloatProperty(
1994 name="Add", default=0, min=-1,
1995 max=1, description="Add to the Weight")
1997 mult : FloatProperty(
1998 name="Multiply", default=0, min=0,
1999 max=1, description="Multiply for he Weight")
2001 @classmethod
2002 def poll(cls, context):
2003 return len(context.object.vertex_groups) > 0
2005 def execute(self, context):
2006 ob = context.active_object
2007 if len(ob.vertex_groups) > 0:
2008 group_id = ob.vertex_groups.active_index
2009 ob.vertex_groups.new(name="Harmonic")
2010 for i in range(len(ob.data.vertices)):
2011 try: val = ob.vertex_groups[group_id].weight(i)
2012 except: val = 0
2013 weight = self.amp*(math.sin(val*self.freq) - self.midlevel)/2 + 0.5 + self.add*val*(1-(1-val)*self.mult)
2014 ob.vertex_groups[-1].add([i], weight, 'REPLACE')
2015 ob.data.update()
2016 else:
2017 self.report({'ERROR'}, "Active object doesn't have vertex groups")
2018 return {'CANCELLED'}
2019 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
2020 return {'FINISHED'}
2023 class tissue_weight_distance(Operator):
2024 bl_idname = "object.tissue_weight_distance"
2025 bl_label = "Weight Distance"
2026 bl_options = {'REGISTER', 'UNDO'}
2027 bl_description = ("Create a weight map according to the distance from the "
2028 "selected vertices along the mesh surface")
2030 mode : EnumProperty(
2031 items=(('GEOD', "Geodesic Distance", ""),
2032 ('EUCL', "Euclidean Distance", ""),
2033 ('TOPO', "Topology Distance", "")),
2034 default='GEOD', name="Distance Method")
2036 normalize : BoolProperty(
2037 name="Normalize", default=True,
2038 description="Automatically remap the distance values from 0 to 1")
2040 min_value : FloatProperty(
2041 name="Min", default=0, min=0,
2042 soft_max=100, description="Minimum Distance")
2044 max_value : FloatProperty(
2045 name="Max", default=10, min=0,
2046 soft_max=100, description="Max Distance")
2048 def invoke(self, context, event):
2049 return context.window_manager.invoke_props_dialog(self, width=250)
2051 def fill_neighbors(self,verts,weight):
2052 neigh = {}
2053 for v0 in verts:
2054 for f in v0.link_faces:
2055 for v1 in f.verts:
2056 if self.mode == 'GEOD':
2057 dist = weight[v0.index] + (v0.co-v1.co).length
2058 elif self.mode == 'TOPO':
2059 dist = weight[v0.index] + 1.0
2060 w1 = weight[v1.index]
2061 if w1 == None or w1 > dist:
2062 weight[v1.index] = dist
2063 neigh[v1] = 0
2064 if len(neigh) == 0: return weight
2065 else: return self.fill_neighbors(neigh.keys(), weight)
2067 def execute(self, context):
2068 ob = context.object
2069 old_mode = ob.mode
2070 if old_mode != 'OBJECT':
2071 bpy.ops.object.mode_set(mode='OBJECT')
2073 me = ob.data
2075 # store weight values
2076 weight = [None]*len(me.vertices)
2078 if self.mode != 'EUCL':
2079 bm = bmesh.new()
2080 bm.from_mesh(me)
2081 bm.verts.ensure_lookup_table()
2082 bm.edges.ensure_lookup_table()
2083 bm.faces.ensure_lookup_table()
2084 selected = [v for v in bm.verts if v.select]
2085 if len(selected) == 0:
2086 bpy.ops.object.mode_set(mode=old_mode)
2087 message = "Please, select one or more vertices"
2088 self.report({'ERROR'}, message)
2089 return {'CANCELLED'}
2090 for v in selected: weight[v.index] = 0
2091 weight = self.fill_neighbors(selected, weight)
2092 bm.free()
2093 else:
2094 selected = [v for v in me.vertices if v.select]
2095 kd = KDTree(len(selected))
2096 for i, v in enumerate(selected):
2097 kd.insert(v.co, i)
2098 kd.balance()
2099 for i,v in enumerate(me.vertices):
2100 co, index, dist = kd.find(v.co)
2101 weight[i] = dist
2104 for i in range(len(weight)):
2105 if weight[i] == None: weight[i] = 0
2106 weight = np.array(weight)
2107 max_dist = np.max(weight)
2108 if self.normalize:
2109 if max_dist > 0:
2110 weight /= max_dist
2111 else:
2112 delta_value = self.max_value - self.min_value
2113 if delta_value == 0: delta_value = 0.0000001
2114 weight = (weight-self.min_value)/delta_value
2116 if self.mode == 'TOPO':
2117 vg = ob.vertex_groups.new(name='Distance: {:d}'.format(int(max_dist)))
2118 else:
2119 vg = ob.vertex_groups.new(name='Distance: {:.4f}'.format(max_dist))
2120 for i, w in enumerate(weight):
2121 vg.add([i], w, 'REPLACE')
2122 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
2123 return {'FINISHED'}
2125 class TISSUE_PT_color(Panel):
2126 bl_label = "Tissue Tools"
2127 bl_category = "Tissue"
2128 bl_space_type = "VIEW_3D"
2129 bl_region_type = "UI"
2130 #bl_options = {'DEFAULT_CLOSED'}
2131 bl_context = "vertexpaint"
2133 def draw(self, context):
2134 layout = self.layout
2135 col = layout.column(align=True)
2136 col.operator("object.vertex_colors_to_vertex_groups",
2137 icon="GROUP_VERTEX", text="Convert to Weight")
2139 class TISSUE_PT_weight(Panel):
2140 bl_label = "Tissue Tools"
2141 bl_category = "Tissue"
2142 bl_space_type = "VIEW_3D"
2143 bl_region_type = "UI"
2144 #bl_options = {'DEFAULT_CLOSED'}
2145 bl_context = "weightpaint"
2147 def draw(self, context):
2148 layout = self.layout
2149 col = layout.column(align=True)
2150 #if context.object.type == 'MESH' and context.mode == 'OBJECT':
2151 #col.label(text="Transform:")
2152 #col.separator()
2153 #elif bpy.context.mode == 'PAINT_WEIGHT':
2154 col.label(text="Weight Generate:")
2155 #col.operator(
2156 # "object.vertex_colors_to_vertex_groups", icon="GROUP_VCOL")
2157 col.operator("object.face_area_to_vertex_groups", icon="FACESEL")
2158 col.operator("object.curvature_to_vertex_groups", icon="SMOOTHCURVE")
2159 col.operator("object.tissue_weight_distance", icon="TRACKING")
2160 row = col.row(align=True)
2161 try: row.operator("object.weight_formula", icon="CON_TRANSFORM")
2162 except: row.operator("object.weight_formula")#, icon="CON_TRANSFORM")
2163 row.operator("object.update_weight_formula", icon="FILE_REFRESH", text='')#, icon="CON_TRANSFORM")
2164 #col.label(text="Weight Processing:")
2165 col.separator()
2167 # TO BE FIXED
2168 col.operator("object.weight_laplacian", icon="SMOOTHCURVE")
2170 col.label(text="Weight Edit:")
2171 col.operator("object.harmonic_weight", icon="IPO_ELASTIC")
2172 col.operator("object.random_weight", icon="RNDCURVE")
2173 col.separator()
2174 col.label(text="Deformation Analysis:")
2175 col.operator("object.edges_deformation", icon="DRIVER_DISTANCE")#FULLSCREEN_ENTER")
2176 col.operator("object.edges_bending", icon="DRIVER_ROTATIONAL_DIFFERENCE")#"MOD_SIMPLEDEFORM")
2177 col.separator()
2178 col.label(text="Weight Curves:")
2179 #col.operator("object.weight_contour_curves", icon="MOD_CURVE")
2180 col.operator("object.tissue_weight_streamlines", icon="ANIM")
2181 op = col.operator("object.tissue_weight_contour_curves_pattern", icon="FORCE_TURBULENCE")
2182 op.contour_mode = 'WEIGHT'
2183 col.separator()
2184 col.operator("object.weight_contour_displace", icon="MOD_DISPLACE")
2185 col.operator("object.weight_contour_mask", icon="MOD_MASK")
2186 col.separator()
2187 col.label(text="Simulations:")
2188 col.operator("object.start_reaction_diffusion",
2189 icon="EXPERIMENTAL",
2190 text="Reaction-Diffusion")
2191 col.separator()
2192 col.label(text="Materials:")
2193 col.operator("object.random_materials", icon='COLOR')
2194 col.operator("object.weight_to_materials", icon='GROUP_VERTEX')
2195 col.separator()
2196 col.label(text="Weight Convert:")
2197 col.operator("object.vertex_group_to_vertex_colors", icon="GROUP_VCOL",
2198 text="Convert to Colors")
2199 col.operator("object.vertex_group_to_uv", icon="UV",
2200 text="Convert to UV")
2202 def contour_bmesh(me, bm, weight, iso_val):
2203 bm.verts.ensure_lookup_table()
2204 bm.edges.ensure_lookup_table()
2205 bm.faces.ensure_lookup_table()
2207 vertices = get_vertices_numpy(me)
2208 faces_mask = np.array(bm.faces)
2209 filtered_edges = get_edges_id_numpy(me)
2210 n_verts = len(bm.verts)
2212 #############################
2214 # vertices indexes
2215 id0 = filtered_edges[:,0]
2216 id1 = filtered_edges[:,1]
2217 # vertices weight
2218 w0 = weight[id0]
2219 w1 = weight[id1]
2220 # weight condition
2221 bool_w0 = w0 < iso_val
2222 bool_w1 = w1 < iso_val
2224 # mask all edges that have one weight value below the iso value
2225 mask_new_verts = np.logical_xor(bool_w0, bool_w1)
2226 if not mask_new_verts.any(): return np.array([[None]]), {}, np.array([[None]])
2228 id0 = id0[mask_new_verts]
2229 id1 = id1[mask_new_verts]
2230 # filter arrays
2231 v0 = vertices[id0]
2232 v1 = vertices[id1]
2233 w0 = w0[mask_new_verts]
2234 w1 = w1[mask_new_verts]
2235 param = (iso_val-w0)/(w1-w0)
2236 param = np.expand_dims(param,axis=1)
2237 verts = v0 + (v1-v0)*param
2239 edges_id = {}
2240 for i, e in enumerate(filtered_edges):
2241 #edges_id[id] = i + n_verts
2242 edges_id['{}_{}'.format(e[0],e[1])] = i + n_verts
2243 edges_id['{}_{}'.format(e[1],e[0])] = i + n_verts
2245 splitted_faces = []
2247 switch = False
2248 # splitting faces
2249 for f in faces_mask:
2250 # create sub-faces slots. Once a new vertex is reached it will
2251 # change slot, storing the next vertices for a new face.
2252 build_faces = [[],[]]
2253 #switch = False
2254 verts0 = list(me.polygons[f.index].vertices)
2255 verts1 = list(verts0)
2256 verts1.append(verts1.pop(0)) # shift list
2257 for id0, id1 in zip(verts0, verts1):
2259 # add first vertex to active slot
2260 build_faces[switch].append(id0)
2262 # try to split edge
2263 try:
2264 # check if the edge must be splitted
2265 new_vert = edges_id['{}_{}'.format(id0,id1)]
2266 # add new vertex
2267 build_faces[switch].append(new_vert)
2268 # if there is an open face on the other slot
2269 if len(build_faces[not switch]) > 0:
2270 # store actual face
2271 splitted_faces.append(build_faces[switch])
2272 # reset actual faces and switch
2273 build_faces[switch] = []
2274 # change face slot
2275 switch = not switch
2276 # continue previous face
2277 build_faces[switch].append(new_vert)
2278 except: pass
2279 if len(build_faces[not switch]) == 2:
2280 build_faces[not switch].append(id0)
2281 if len(build_faces[not switch]) > 2:
2282 splitted_faces.append(build_faces[not switch])
2283 # add last face
2284 splitted_faces.append(build_faces[switch])
2286 # adding new vertices use fast local method access
2287 _new_vert = bm.verts.new
2288 for v in verts: _new_vert(v)
2289 bm.verts.ensure_lookup_table()
2291 # deleting old edges/faces
2292 bm.edges.ensure_lookup_table()
2293 remove_edges = [bm.edges[i] for i in filtered_edges[:,2]]
2294 #for e in remove_edges: bm.edges.remove(e)
2295 #for e in delete_edges: bm.edges.remove(e)
2297 bm.verts.ensure_lookup_table()
2298 # adding new faces use fast local method access
2299 _new_face = bm.faces.new
2300 missed_faces = []
2301 for f in splitted_faces:
2302 try:
2303 face_verts = [bm.verts[i] for i in f]
2304 _new_face(face_verts)
2305 except:
2306 missed_faces.append(f)
2308 #me = bpy.data.meshes.new('_tissue_tmp_')
2309 bm.to_mesh(me)
2310 weight = np.concatenate((weight, np.ones(len(verts))*iso_val))
2312 return me, bm, weight
2317 class tissue_weight_streamlines(Operator):
2318 bl_idname = "object.tissue_weight_streamlines"
2319 bl_label = "Streamlines Curves"
2320 bl_description = ("")
2321 bl_options = {'REGISTER', 'UNDO'}
2323 mode : EnumProperty(
2324 items=(
2325 ('VERTS', "Verts", "Follow vertices"),
2326 ('EDGES', "Edges", "Follow Edges")
2328 default='VERTS',
2329 name="Streamlines path mode"
2332 interpolation : EnumProperty(
2333 items=(
2334 ('POLY', "Poly", "Generate Polylines"),
2335 ('NURBS', "NURBS", "Generate Nurbs curves")
2337 default='POLY',
2338 name="Interpolation mode"
2341 use_modifiers : BoolProperty(
2342 name="Use Modifiers", default=True,
2343 description="Apply all the modifiers")
2345 use_selected : BoolProperty(
2346 name="Use Selected Vertices", default=False,
2347 description="Use selected vertices as Seed")
2349 same_weight : BoolProperty(
2350 name="Same Weight", default=True,
2351 description="Continue the streamlines when the weight is the same")
2353 min_iso : FloatProperty(
2354 name="Min Value", default=0., soft_min=0, soft_max=1,
2355 description="Minimum weight value")
2356 max_iso : FloatProperty(
2357 name="Max Value", default=1, soft_min=0, soft_max=1,
2358 description="Maximum weight value")
2360 rand_seed : IntProperty(
2361 name="Seed", default=0, min=0, soft_max=10,
2362 description="Random Seed")
2363 n_curves : IntProperty(
2364 name="Curves", default=50, soft_min=1, soft_max=100000,
2365 description="Number of Curves")
2366 min_rad = 1
2367 max_rad = 1
2369 pos_steps : IntProperty(
2370 name="High Steps", default=50, min=0, soft_max=100,
2371 description="Number of steps in the direction of high weight")
2372 neg_steps : IntProperty(
2373 name="Low Steps", default=50, min=0, soft_max=100,
2374 description="Number of steps in the direction of low weight")
2376 bevel_depth : FloatProperty(
2377 name="Bevel Depth", default=0, min=0, soft_max=1,
2378 description="")
2379 min_bevel_depth : FloatProperty(
2380 name="Min Bevel Depth", default=0.1, min=0, soft_max=1,
2381 description="")
2382 max_bevel_depth : FloatProperty(
2383 name="Max Bevel Depth", default=1, min=0, soft_max=1,
2384 description="")
2386 rand_dir : FloatProperty(
2387 name="Randomize", default=0, min=0, max=1,
2388 description="Randomize streamlines directions (Slower)")
2390 vertex_group_seeds : StringProperty(
2391 name="Displace", default='',
2392 description="Vertex Group used for pattern displace")
2394 vertex_group_bevel : StringProperty(
2395 name="Bevel", default='',
2396 description="Variable Bevel depth")
2398 object_name : StringProperty(
2399 name="Active Object", default='',
2400 description="")
2402 try: vg_name = bpy.context.object.vertex_groups.active.name
2403 except: vg_name = ''
2405 vertex_group_streamlines : StringProperty(
2406 name="Flow", default=vg_name,
2407 description="Vertex Group used for streamlines")
2409 @classmethod
2410 def poll(cls, context):
2411 ob = context.object
2412 return ob and len(ob.vertex_groups) > 0 or ob.type == 'CURVE'
2414 def invoke(self, context, event):
2415 return context.window_manager.invoke_props_dialog(self, width=250)
2417 def draw(self, context):
2418 if not context.object.type == 'CURVE':
2419 self.object_name = context.object.name
2420 ob = bpy.data.objects[self.object_name]
2421 if self.vertex_group_streamlines not in [vg.name for vg in ob.vertex_groups]:
2422 self.vertex_group_streamlines = ob.vertex_groups.active.name
2423 layout = self.layout
2424 col = layout.column(align=True)
2425 row = col.row(align=True)
2426 row.prop(self, 'mode', expand=True,
2427 slider=True, toggle=False, icon_only=False, event=False,
2428 full_event=False, emboss=True, index=-1)
2429 col.prop(self, "use_modifiers")
2430 col.label(text="Streamlines Curves:")
2431 row = col.row(align=True)
2432 row.prop(self, 'interpolation', expand=True,
2433 slider=True, toggle=False, icon_only=False, event=False,
2434 full_event=False, emboss=True, index=-1)
2435 col.separator()
2436 col.prop_search(self, 'vertex_group_streamlines', ob, "vertex_groups", text='')
2437 if not (self.use_selected or context.mode == 'EDIT_MESH'):
2438 row = col.row(align=True)
2439 row.prop(self,'n_curves')
2440 #row.enabled = context.mode != 'EDIT_MESH'
2441 row = col.row(align=True)
2442 row.prop(self,'rand_seed')
2443 #row.enabled = context.mode != 'EDIT_MESH'
2444 row = col.row(align=True)
2445 row.prop(self,'neg_steps')
2446 row.prop(self,'pos_steps')
2447 #row = col.row(align=True)
2448 #row.prop(self,'min_iso')
2449 #row.prop(self,'max_iso')
2450 col.prop(self, "same_weight")
2451 col.separator()
2452 col.label(text='Curves Bevel:')
2453 col.prop_search(self, 'vertex_group_bevel', ob, "vertex_groups", text='')
2454 if self.vertex_group_bevel != '':
2455 row = col.row(align=True)
2456 row.prop(self,'min_bevel_depth')
2457 row.prop(self,'max_bevel_depth')
2458 else:
2459 col.prop(self,'bevel_depth')
2460 col.separator()
2461 col.prop(self, "rand_dir")
2463 def execute(self, context):
2464 start_time = timeit.default_timer()
2465 try:
2466 check = context.object.vertex_groups[0]
2467 except:
2468 self.report({'ERROR'}, "The object doesn't have Vertex Groups")
2469 return {'CANCELLED'}
2470 ob = bpy.data.objects[self.object_name]
2471 ob.select_set(False)
2474 seeds = []
2476 if bpy.context.mode == 'EDIT_MESH':
2477 self.use_selected = True
2478 bpy.ops.object.mode_set(mode='OBJECT')
2479 #ob = bpy.context.object
2480 #me = simple_to_mesh(ob)
2481 ob = convert_object_to_mesh(ob, apply_modifiers=self.use_modifiers)
2482 #dg = context.evaluated_depsgraph_get()
2483 #ob = ob.evaluated_get(dg)
2484 me = ob.data
2486 if self.use_selected:
2487 # generate new bmesh
2488 bm = bmesh.new()
2489 bm.from_mesh(me)
2490 #for v in me.vertices:
2491 # if v.select: seeds.append(v.index)
2492 for v in bm.verts:
2493 if v.select: seeds.append(v.index)
2494 bm.free()
2495 n_verts = len(me.vertices)
2496 n_edges = len(me.edges)
2497 n_faces = len(me.polygons)
2499 # store weight values
2500 try:
2501 weight = get_weight_numpy(ob.vertex_groups[self.vertex_group_streamlines], n_verts)
2502 except:
2503 bpy.data.objects.remove(ob)
2504 self.report({'ERROR'}, "Please select a Vertex Group for streamlines")
2505 return {'CANCELLED'}
2507 variable_bevel = False
2508 bevel_weight = None
2509 bevel_depth = self.bevel_depth
2510 try:
2511 if self.min_bevel_depth == self.max_bevel_depth:
2512 #bevel_weight = np.ones((n_verts))
2513 bevel_depth = self.min_bevel_depth
2514 else:
2515 b0 = min(self.min_bevel_depth, self.max_bevel_depth)
2516 b1 = max(self.min_bevel_depth, self.max_bevel_depth)
2517 bevel_weight = get_weight_numpy(ob.vertex_groups[self.vertex_group_bevel], n_verts)
2518 if self.min_bevel_depth > self.max_bevel_depth:
2519 bevel_weight = 1-bevel_weight
2520 bevel_weight = b0/b1 + bevel_weight*((b1-b0)/b1)
2521 bevel_depth = b1
2522 variable_bevel = True
2523 except:
2524 pass#bevel_weight = np.ones((n_verts))
2527 if not seeds:
2528 np.random.seed(self.rand_seed)
2529 seeds = np.random.randint(n_verts, size=self.n_curves)
2531 #weight = np.array(get_weight(ob.vertex_groups.active, n_verts))
2533 curves_pts = []
2534 curves_weight = []
2536 neigh = [[] for i in range(n_verts)]
2537 if self.mode == 'EDGES':
2538 # store neighbors
2539 for e in me.edges:
2540 ev = e.vertices
2541 neigh[ev[0]].append(ev[1])
2542 neigh[ev[1]].append(ev[0])
2544 elif self.mode == 'VERTS':
2545 # store neighbors
2546 for p in me.polygons:
2547 face_verts = [v for v in p.vertices]
2548 n_face_verts = len(face_verts)
2549 for i in range(n_face_verts):
2550 fv = face_verts.copy()
2551 neigh[fv.pop(i)] += fv
2553 neigh_weight = [weight[n].tolist() for n in neigh]
2555 # evaluate direction
2556 next_vert = [-1]*n_verts
2558 if self.rand_dir > 0:
2559 for i in range(n_verts):
2560 n = neigh[i]
2561 nw = neigh_weight[i]
2562 sorted_nw = neigh_weight[i].copy()
2563 sorted_nw.sort()
2564 for w in sorted_nw:
2565 neigh[i] = [n[nw.index(w)] for w in sorted_nw]
2566 else:
2567 if self.pos_steps > 0:
2568 for i in range(n_verts):
2569 n = neigh[i]
2570 if len(n) == 0: continue
2571 nw = neigh_weight[i]
2572 max_w = max(nw)
2573 if self.same_weight:
2574 if max_w >= weight[i]:
2575 next_vert[i] = n[nw.index(max(nw))]
2576 else:
2577 if max_w > weight[i]:
2578 next_vert[i] = n[nw.index(max(nw))]
2580 if self.neg_steps > 0:
2581 prev_vert = [-1]*n_verts
2582 for i in range(n_verts):
2583 n = neigh[i]
2584 if len(n) == 0: continue
2585 nw = neigh_weight[i]
2586 min_w = min(nw)
2587 if self.same_weight:
2588 if min_w <= weight[i]:
2589 prev_vert[i] = n[nw.index(min(nw))]
2590 else:
2591 if min_w < weight[i]:
2592 prev_vert[i] = n[nw.index(min(nw))]
2594 co = [0]*3*n_verts
2595 me.vertices.foreach_get('co', co)
2596 co = np.array(co).reshape((-1,3))
2598 # create streamlines
2599 curves = []
2600 for i in seeds:
2601 next_pts = [i]
2602 for j in range(self.pos_steps):
2603 if self.rand_dir > 0:
2604 n = neigh[next_pts[-1]]
2605 if len(n) == 0: break
2606 next = n[int((len(n)-1) * (1-random.random() * self.rand_dir))]
2607 else:
2608 next = next_vert[next_pts[-1]]
2609 if next > 0:
2610 if next not in next_pts: next_pts.append(next)
2611 else: break
2613 prev_pts = [i]
2614 for j in range(self.neg_steps):
2615 if self.rand_dir > 0:
2616 n = neigh[prev_pts[-1]]
2617 if len(n) == 0: break
2618 prev = n[int(len(n) * random.random() * self.rand_dir)]
2619 else:
2620 prev = prev_vert[prev_pts[-1]]
2621 if prev > 0:
2622 if prev not in prev_pts:
2623 prev_pts.append(prev)
2624 else: break
2626 next_pts = np.array(next_pts).astype('int')
2627 prev_pts = np.flip(prev_pts[1:]).astype('int')
2628 all_pts = np.concatenate((prev_pts, next_pts))
2629 if len(all_pts) > 1:
2630 curves.append(all_pts)
2631 crv = nurbs_from_vertices(curves, co, bevel_weight, ob.name + '_Streamlines', True, self.interpolation)
2632 crv.data.bevel_depth = bevel_depth
2633 crv.matrix_world = ob.matrix_world
2634 bpy.data.objects.remove(ob)
2636 print("Streamlines Curves, total time: " + str(timeit.default_timer() - start_time) + " sec")
2637 return {'FINISHED'}