Sun position: Fix T80379 - Custom startup breaks the add-on
[blender-addons.git] / mesh_tissue / colors_groups_exchanger.py
blobb3ffe31fc59afb2196286b0aae2e5091459a96e2
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 #-------------------------- COLORS / GROUPS EXCHANGER -------------------------#
20 # #
21 # Vertex Color to Vertex Group allow you to convert colors channels to weight #
22 # maps. #
23 # The main purpose is to use vertex colors to store information when importing #
24 # files from other software. The script works with the active vertex color #
25 # slot. #
26 # For use the command "Vertex Clors to Vertex Groups" use the search bar #
27 # (space bar). #
28 # #
29 # (c) Alessandro Zomparelli #
30 # (2017) #
31 # #
32 # http://www.co-de-it.com/ #
33 # #
34 ################################################################################
36 import bpy, bmesh
37 import numpy as np
38 import math, timeit, time
39 from math import *#pi, sin
40 from statistics import mean, stdev
41 from mathutils import Vector
42 from numpy import *
43 try: from .numba_functions import numba_reaction_diffusion
44 except: pass
46 from bpy.types import (
47 Operator,
48 Panel,
49 PropertyGroup,
52 from bpy.props import (
53 BoolProperty,
54 EnumProperty,
55 FloatProperty,
56 IntProperty,
57 StringProperty,
58 FloatVectorProperty,
59 IntVectorProperty
62 from .utils import *
64 def reaction_diffusion_add_handler(self, context):
65 # remove existing handlers
66 old_handlers = []
67 for h in bpy.app.handlers.frame_change_post:
68 if "reaction_diffusion" in str(h):
69 old_handlers.append(h)
70 for h in old_handlers: bpy.app.handlers.frame_change_post.remove(h)
71 # add new handler
72 bpy.app.handlers.frame_change_post.append(reaction_diffusion_def)
74 class formula_prop(PropertyGroup):
75 name : StringProperty()
76 formula : StringProperty()
77 float_var : FloatVectorProperty(name="", description="", default=(0, 0, 0, 0, 0), size=5)
78 int_var : IntVectorProperty(name="", description="", default=(0, 0, 0, 0, 0), size=5)
80 class reaction_diffusion_prop(PropertyGroup):
81 run : BoolProperty(default=False, update = reaction_diffusion_add_handler,
82 description='Compute a new iteration on frame changes. Currently is not working during Render Animation')
84 time_steps : bpy.props.IntProperty(
85 name="Steps", default=10, min=0, soft_max=50,
86 description="Number of Steps")
88 dt : bpy.props.FloatProperty(
89 name="dt", default=1, min=0, soft_max=0.2,
90 description="Time Step")
92 diff_a : bpy.props.FloatProperty(
93 name="Diff A", default=0.1, min=0, soft_max=2, precision=3,
94 description="Diffusion A")
96 diff_b : bpy.props.FloatProperty(
97 name="Diff B", default=0.05, min=0, soft_max=2, precision=3,
98 description="Diffusion B")
100 f : bpy.props.FloatProperty(
101 name="f", default=0.055, min=0, soft_max=0.5, precision=3,
102 description="Feed Rate")
104 k : bpy.props.FloatProperty(
105 name="k", default=0.062, min=0, soft_max=0.5, precision=3,
106 description="Kill Rate")
108 diff_mult : bpy.props.FloatProperty(
109 name="Scale", default=1, min=0, soft_max=1, max=2, precision=2,
110 description="Multiplier for the diffusion of both substances")
112 def compute_formula(ob=None, formula="rx", float_var=(0,0,0,0,0), int_var=(0,0,0,0,0)):
113 verts = ob.data.vertices
114 n_verts = len(verts)
116 f1,f2,f3,f4,f5 = float_var
117 i1,i2,i3,i4,i5 = int_var
119 do_groups = "w[" in formula
120 do_local = "lx" in formula or "ly" in formula or "lz" in formula
121 do_global = "gx" in formula or "gy" in formula or "gz" in formula
122 do_relative = "rx" in formula or "ry" in formula or "rz" in formula
123 do_normal = "nx" in formula or "ny" in formula or "nz" in formula
124 mat = ob.matrix_world
126 for i in range(1000):
127 if "w["+str(i)+"]" in formula and i > len(ob.vertex_groups)-1:
128 return "w["+str(i)+"] not found"
130 w = []
131 for i in range(len(ob.vertex_groups)):
132 w.append([])
133 if "w["+str(i)+"]" in formula:
134 vg = ob.vertex_groups[i]
135 for v in verts:
136 try:
137 w[i].append(vg.weight(v.index))
138 except:
139 w[i].append(0)
140 w[i] = array(w[i])
142 start_time = timeit.default_timer()
143 # compute vertex coordinates
144 if do_local or do_relative or do_global:
145 co = [0]*n_verts*3
146 verts.foreach_get('co', co)
147 np_co = array(co).reshape((n_verts, 3))
148 lx, ly, lz = array(np_co).transpose()
149 if do_relative:
150 rx = np.interp(lx, (lx.min(), lx.max()), (0, +1))
151 ry = np.interp(ly, (ly.min(), ly.max()), (0, +1))
152 rz = np.interp(lz, (lz.min(), lz.max()), (0, +1))
153 if do_global:
154 co = [v.co for v in verts]
155 global_co = []
156 for v in co:
157 global_co.append(mat * v)
158 global_co = array(global_co).reshape((n_verts, 3))
159 gx, gy, gz = array(global_co).transpose()
160 # compute vertex normals
161 if do_normal:
162 normal = [0]*n_verts*3
163 verts.foreach_get('normal', normal)
164 normal = array(normal).reshape((n_verts, 3))
165 nx, ny, nz = array(normal).transpose()
167 try:
168 weight = eval(formula)
169 return weight
170 except:
171 return "There is something wrong"
172 print("Weight Formula: " + str(timeit.default_timer() - start_time))
174 class weight_formula_wiki(bpy.types.Operator):
175 bl_idname = "scene.weight_formula_wiki"
176 bl_label = "Online Documentation"
177 bl_options = {'REGISTER', 'UNDO'}
179 def execute(self, context):
180 bpy.ops.wm.url_open(url="https://github.com/alessandro-zomparelli/tissue/wiki/Weight-Tools#weight-formula")
181 return {'FINISHED'}
183 class weight_formula(bpy.types.Operator):
184 bl_idname = "object.weight_formula"
185 bl_label = "Weight Formula"
186 bl_options = {'REGISTER', 'UNDO'}
188 ex = [
189 #'cos(arctan(nx/ny)*6 + sin(rz*30)*0.5)/2 + cos(arctan(nx/ny)*6 - sin(rz*30)*0.5 + pi/2)/2 + 0.5',
190 'cos(arctan(nx/ny)*i1*2 + sin(rz*i3))/i2 + cos(arctan(nx/ny)*i1*2 - sin(rz*i3))/i2 + 0.5',
191 'cos(arctan(nx/ny)*i1*2 + sin(rz*i2))/2 + cos(arctan(nx/ny)*i1*2 - sin(rz*i2))/2',
192 '(sin(arctan(nx/ny)*i1)*sin(nz*i1)+1)/2',
193 'cos(arctan(nx/ny)*f1)',
194 'cos(arctan(lx/ly)*f1 + sin(rz*f2)*f3)',
195 'sin(nx*15)<sin(ny*15)',
196 'cos(ny*rz**2*i1)',
197 'sin(rx*30) > 0',
198 'sin(nz*i1)',
199 'w[0]**2',
200 'sqrt((rx-0.5)**2 + (ry-0.5)**2)*2',
201 'abs(0.5-rz)*2',
202 'rx'
204 ex_items = list((s,s,"") for s in ex)
205 ex_items.append(('CUSTOM', "User Formula", ""))
207 examples : bpy.props.EnumProperty(
208 items = ex_items, default='CUSTOM', name="Examples")
210 old_ex = ""
212 formula : bpy.props.StringProperty(
213 name="Formula", default="", description="Formula to Evaluate")
214 bl_description = ("Generate a Vertex Group based on the given formula")
216 slider_f01 : bpy.props.FloatProperty(
217 name="f1", default=1, description="Slider")
218 bl_description = ("Slider Float 1")
219 slider_f02 : bpy.props.FloatProperty(
220 name="f2", default=1, description="Slider")
221 bl_description = ("Slider Float 2")
222 slider_f03 : bpy.props.FloatProperty(
223 name="f3", default=1, description="Slider")
224 bl_description = ("Slider Float 3")
225 slider_f04 : bpy.props.FloatProperty(
226 name="f4", default=1, description="Slider")
227 bl_description = ("Slider Float 4")
228 slider_f05 : bpy.props.FloatProperty(
229 name="f5", default=1, description="Slider")
230 bl_description = ("Slider Float 5")
231 slider_i01 : bpy.props.IntProperty(
232 name="i1", default=1, description="Slider")
233 bl_description = ("Slider Integer 1")
234 slider_i02 : bpy.props.IntProperty(
235 name="i2", default=1, description="Slider")
236 bl_description = ("Slider Integer 2")
237 slider_i03 : bpy.props.IntProperty(
238 name="i3", default=1, description="Slider")
239 bl_description = ("Slider Integer 3")
240 slider_i04 : bpy.props.IntProperty(
241 name="i4", default=1, description="Slider")
242 bl_description = ("Slider Integer 4")
243 slider_i05 : bpy.props.IntProperty(
244 name="i5", default=1, description="Slider")
245 bl_description = ("Slider Integer 5")
247 def invoke(self, context, event):
248 return context.window_manager.invoke_props_dialog(self, width=350)
250 def draw(self, context):
251 layout = self.layout
252 #layout.label(text="Examples")
253 layout.prop(self, "examples", text="Examples")
254 #if self.examples == 'CUSTOM':
255 layout.label(text="Formula")
256 layout.prop(self, "formula", text="")
257 #try: self.examples = self.formula
258 #except: pass
260 if self.examples != self.old_ex and self.examples != 'CUSTOM':
261 self.formula = self.examples
262 self.old_ex = self.examples
263 elif self.formula != self.examples:
264 self.examples = 'CUSTOM'
265 formula = self.formula
267 layout.separator()
268 if "f1" in formula: layout.prop(self, "slider_f01")
269 if "f2" in formula: layout.prop(self, "slider_f02")
270 if "f3" in formula: layout.prop(self, "slider_f03")
271 if "f4" in formula: layout.prop(self, "slider_f04")
272 if "f5" in formula: layout.prop(self, "slider_f05")
273 if "i1" in formula: layout.prop(self, "slider_i01")
274 if "i2" in formula: layout.prop(self, "slider_i02")
275 if "i3" in formula: layout.prop(self, "slider_i03")
276 if "i4" in formula: layout.prop(self, "slider_i04")
277 if "i5" in formula: layout.prop(self, "slider_i05")
279 layout.label(text="Variables (for each vertex):")
280 layout.label(text="lx, ly, lz: Local Coordinates", icon='ORIENTATION_LOCAL')
281 layout.label(text="gx, gy, gz: Global Coordinates", icon='WORLD')
282 layout.label(text="rx, ry, rz: Local Coordinates (0 to 1)", icon='NORMALIZE_FCURVES')
283 layout.label(text="nx, ny, nz: Normal Coordinates", icon='SNAP_NORMAL')
284 layout.label(text="w[0], w[1], w[2], ... : Vertex Groups", icon="GROUP_VERTEX")
285 layout.separator()
286 layout.label(text="f1, f2, f3, f4, f5: Float Sliders", icon='MOD_HUE_SATURATION')#PROPERTIES
287 layout.label(text="i1, i2, i3, i4, i5: Integer Sliders", icon='MOD_HUE_SATURATION')
288 layout.separator()
289 #layout.label(text="All mathematical functions are based on Numpy", icon='INFO')
290 #layout.label(text="https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.math.html", icon='INFO')
291 layout.operator("scene.weight_formula_wiki", icon="HELP")
292 #layout.label(text="(where 'i' is the index of the Vertex Group)")
294 def execute(self, context):
295 ob = bpy.context.active_object
296 n_verts = len(ob.data.vertices)
297 #if self.examples == 'CUSTOM':
298 # formula = self.formula
299 #else:
300 #self.formula = self.examples
301 # formula = self.examples
303 #f1, f2, f3, f4, f5 = self.slider_f01, self.slider_f02, self.slider_f03, self.slider_f04, self.slider_f05
304 #i1, i2, i3, i4, i5 = self.slider_i01, self.slider_i02, self.slider_i03, self.slider_i04, self.slider_i05
305 f_sliders = self.slider_f01, self.slider_f02, self.slider_f03, self.slider_f04, self.slider_f05
306 i_sliders = self.slider_i01, self.slider_i02, self.slider_i03, self.slider_i04, self.slider_i05
308 if self.examples != self.old_ex and self.examples != 'CUSTOM':
309 self.formula = self.examples
310 self.old_ex = self.examples
311 elif self.formula != self.examples:
312 self.examples = 'CUSTOM'
313 formula = self.formula
315 if formula == "": return {'FINISHED'}
316 vertex_group_name = "Formula " + formula
317 ob.vertex_groups.new(name=vertex_group_name)
319 weight = compute_formula(ob, formula=formula, float_var=f_sliders, int_var=i_sliders)
320 if type(weight) == str:
321 self.report({'ERROR'}, weight)
322 return {'CANCELLED'}
324 #start_time = timeit.default_timer()
325 weight = nan_to_num(weight)
326 if type(weight) == int or type(weight) == float:
327 for i in range(n_verts):
328 ob.vertex_groups[-1].add([i], weight, 'REPLACE')
329 elif type(weight) == ndarray:
330 for i in range(n_verts):
331 ob.vertex_groups[-1].add([i], weight[i], 'REPLACE')
332 ob.data.update()
333 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
335 # Store formula settings
336 new_formula = ob.formula_settings.add()
337 new_formula.name = ob.vertex_groups[-1].name
338 new_formula.formula = formula
339 new_formula.int_var = i_sliders
340 new_formula.float_var = f_sliders
342 #for f in ob.formula_settings:
343 # print(f.name, f.formula, f.int_var, f.float_var)
344 return {'FINISHED'}
346 class _weight_laplacian(bpy.types.Operator):
347 bl_idname = "object._weight_laplacian"
348 bl_label = "Weight Laplacian"
349 bl_description = ("Compute the Vertex Group Laplacian")
350 bl_options = {'REGISTER', 'UNDO'}
352 bounds : bpy.props.EnumProperty(
353 items=(('MANUAL', "Manual Bounds", ""),
354 ('POSITIVE', "Positive Only", ""),
355 ('NEGATIVE', "Negative Only", ""),
356 ('AUTOMATIC', "Automatic Bounds", "")),
357 default='AUTOMATIC', name="Bounds")
359 mode : bpy.props.EnumProperty(
360 items=(('LENGTH', "Length Weight", ""),
361 ('SIMPLE', "Simple", "")),
362 default='SIMPLE', name="Evaluation Mode")
364 min_def : bpy.props.FloatProperty(
365 name="Min", default=0, soft_min=-1, soft_max=0,
366 description="Laplacian value with 0 weight")
368 max_def : bpy.props.FloatProperty(
369 name="Max", default=0.5, soft_min=0, soft_max=5,
370 description="Laplacian value with 1 weight")
372 bounds_string = ""
374 frame = None
376 @classmethod
377 def poll(cls, context):
378 return len(context.object.vertex_groups) > 0
380 def draw(self, context):
381 layout = self.layout
382 col = layout.column(align=True)
383 col.label(text="Evaluation Mode")
384 col.prop(self, "mode", text="")
385 col.label(text="Bounds")
386 col.prop(self, "bounds", text="")
387 if self.bounds == 'MANUAL':
388 col.label(text="Strain Rate \u03B5:")
389 col.prop(self, "min_def")
390 col.prop(self, "max_def")
391 col.label(text="\u03B5" + ": from " + self.bounds_string)
394 def execute(self, context):
395 try: ob = context.object
396 except:
397 self.report({'ERROR'}, "Please select an Object")
398 return {'CANCELLED'}
400 group_id = ob.vertex_groups.active_index
401 input_group = ob.vertex_groups[group_id].name
403 group_name = "Laplacian"
404 ob.vertex_groups.new(name=group_name)
405 me = ob.data
406 bm = bmesh.new()
407 bm.from_mesh(me)
408 bm.edges.ensure_lookup_table()
410 # store weight values
411 weight = []
412 for v in me.vertices:
413 try:
414 weight.append(ob.vertex_groups[input_group].weight(v.index))
415 except:
416 weight.append(0)
418 n_verts = len(bm.verts)
419 lap = [0]*n_verts
420 for e in bm.edges:
421 if self.mode == 'LENGTH':
422 length = e.calc_length()
423 if length == 0: continue
424 id0 = e.verts[0].index
425 id1 = e.verts[1].index
426 lap[id0] += weight[id1]/length - weight[id0]/length
427 lap[id1] += weight[id0]/length - weight[id1]/length
428 else:
429 id0 = e.verts[0].index
430 id1 = e.verts[1].index
431 lap[id0] += weight[id1] - weight[id0]
432 lap[id1] += weight[id0] - weight[id1]
434 mean_lap = mean(lap)
435 stdev_lap = stdev(lap)
436 filter_lap = [i for i in lap if mean_lap-2*stdev_lap < i < mean_lap+2*stdev_lap]
437 if self.bounds == 'MANUAL':
438 min_def = self.min_def
439 max_def = self.max_def
440 elif self.bounds == 'AUTOMATIC':
441 min_def = min(filter_lap)
442 max_def = max(filter_lap)
443 self.min_def = min_def
444 self.max_def = max_def
445 elif self.bounds == 'NEGATIVE':
446 min_def = 0
447 max_def = min(filter_lap)
448 self.min_def = min_def
449 self.max_def = max_def
450 elif self.bounds == 'POSITIVE':
451 min_def = 0
452 max_def = max(filter_lap)
453 self.min_def = min_def
454 self.max_def = max_def
455 delta_def = max_def - min_def
457 # check undeformed errors
458 if delta_def == 0: delta_def = 0.0001
460 for i in range(len(lap)):
461 val = (lap[i]-min_def)/delta_def
462 if val > 0.7: print(str(val) + " " + str(lap[i]))
463 #val = weight[i] + 0.2*lap[i]
464 ob.vertex_groups[-1].add([i], val, 'REPLACE')
465 self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
466 ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
467 ob.vertex_groups.update()
468 ob.data.update()
469 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
470 return {'FINISHED'}
472 class weight_laplacian(bpy.types.Operator):
473 bl_idname = "object.weight_laplacian"
474 bl_label = "Weight Laplacian"
475 bl_description = ("Compute the Vertex Group Laplacian")
476 bl_options = {'REGISTER', 'UNDO'}
478 steps : bpy.props.IntProperty(
479 name="Steps", default=10, min=0, soft_max=50,
480 description="Number of Steps")
482 dt : bpy.props.FloatProperty(
483 name="dt", default=0.2, min=0, soft_max=0.2,
484 description="Time Step")
486 diff_a : bpy.props.FloatProperty(
487 name="Diff A", default=1, min=0, soft_max=2,
488 description="Diffusion A")
490 diff_b : bpy.props.FloatProperty(
491 name="Diff B", default=0.5, min=0, soft_max=2,
492 description="Diffusion B")
494 f : bpy.props.FloatProperty(
495 name="f", default=0.055, min=0, soft_max=0.5,
496 description="Feed Rate")
498 k : bpy.props.FloatProperty(
499 name="k", default=0.062, min=0, soft_max=0.5,
500 description="Kill Rate")
502 diff_mult : bpy.props.FloatProperty(
503 name="Scale", default=1, min=0, soft_max=1, max=2, precision=2,
504 description="Multiplier for the diffusion of both substances")
506 bounds_string = ""
508 frame = None
510 @classmethod
511 def poll(cls, context):
512 return len(context.object.vertex_groups) > 0
515 def execute(self, context):
516 try: ob = context.object
517 except:
518 self.report({'ERROR'}, "Please select an Object")
519 return {'CANCELLED'}
521 me = ob.data
522 bm = bmesh.new()
523 bm.from_mesh(me)
524 bm.edges.ensure_lookup_table()
526 # store weight values
527 a = []
528 b = []
529 for v in me.vertices:
530 try:
531 a.append(ob.vertex_groups["A"].weight(v.index))
532 except:
533 a.append(0)
534 try:
535 b.append(ob.vertex_groups["B"].weight(v.index))
536 except:
537 b.append(0)
539 a = array(a)
540 b = array(b)
541 f = self.f
542 k = self.k
543 diff_a = self.diff_a * self.diff_mult
544 diff_b = self.diff_b * self.diff_mult
545 dt = self.dt
547 # initialize
548 n_verts = len(bm.verts)
549 # find max number of edges for vertex
550 max_edges = 0
551 n_neighbors = []
552 id_neighbors = []
553 for v in bm.verts:
554 n_edges = len(v.link_edges)
555 max_edges = max(max_edges, n_edges)
556 n_neighbors.append(n_edges)
557 neighbors = []
558 for e in link_edges:
559 for v1 in e.verts:
560 if v != v1: neighbors.append(v1.index)
561 id_neighbors.append(neighbors)
562 n_neighbors = array(n_neighbors)
565 a = [[] for i in range(n_verts)]
566 lap_map = []
568 for e in bm.edges:
569 id0 = e.verts[0].index
570 id1 = e.verts[1].index
571 lap_map[id0].append(id1)
572 lap_map[id1].append(id0)
574 e1 = array(e1)
575 e2 = array(e2)
576 lap_a = a[e1]
578 for i in range(self.steps):
580 lap_a = zeros((n_verts))#[0]*n_verts
581 lap_b = zeros((n_verts))#[0]*n_verts
582 for e in bm.edges:
583 id0 = e.verts[0].index
584 id1 = e.verts[1].index
585 lap_a[id0] += a[id1] - a[id0]
586 lap_a[id1] += a[id0] - a[id1]
587 lap_b[id0] += b[id1] - b[id0]
588 lap_b[id1] += b[id0] - b[id1]
589 ab2 = a*b**2
590 a += (diff_a*lap_a - ab2 + f*(1-a))*dt
591 b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
593 for i in range(n_verts):
594 ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
595 ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
596 ob.vertex_groups.update()
597 ob.data.update()
598 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
599 return {'FINISHED'}
602 class reaction_diffusion(bpy.types.Operator):
603 bl_idname = "object.reaction_diffusion"
604 bl_label = "Reaction Diffusion"
605 bl_description = ("Run a Reaction-Diffusion based on existing Vertex Groups: A and B")
606 bl_options = {'REGISTER', 'UNDO'}
608 steps : bpy.props.IntProperty(
609 name="Steps", default=10, min=0, soft_max=50,
610 description="Number of Steps")
612 dt : bpy.props.FloatProperty(
613 name="dt", default=0.2, min=0, soft_max=0.2,
614 description="Time Step")
616 diff_a : bpy.props.FloatProperty(
617 name="Diff A", default=1, min=0, soft_max=2,
618 description="Diffusion A")
620 diff_b : bpy.props.FloatProperty(
621 name="Diff B", default=0.5, min=0, soft_max=2,
622 description="Diffusion B")
624 f : bpy.props.FloatProperty(
625 name="f", default=0.055, min=0, soft_max=0.5,
626 description="Feed Rate")
628 k : bpy.props.FloatProperty(
629 name="k", default=0.062, min=0, soft_max=0.5,
630 description="Kill Rate")
632 bounds_string = ""
634 frame = None
636 @classmethod
637 def poll(cls, context):
638 return len(context.object.vertex_groups) > 0
641 def execute(self, context):
642 #bpy.app.handlers.frame_change_post.remove(reaction_diffusion_def)
643 reaction_diffusion_add_handler(self, context)
644 set_animatable_fix_handler(self, context)
645 try: ob = context.object
646 except:
647 self.report({'ERROR'}, "Please select an Object")
648 return {'CANCELLED'}
650 me = ob.data
651 bm = bmesh.new()
652 bm.from_mesh(me)
653 bm.edges.ensure_lookup_table()
655 # store weight values
656 a = []
657 b = []
658 for v in me.vertices:
659 try:
660 a.append(ob.vertex_groups["A"].weight(v.index))
661 except:
662 a.append(0)
663 try:
664 b.append(ob.vertex_groups["B"].weight(v.index))
665 except:
666 b.append(0)
668 a = array(a)
669 b = array(b)
670 f = self.f
671 k = self.k
672 diff_a = self.diff_a
673 diff_b = self.diff_b
674 dt = self.dt
675 n_verts = len(bm.verts)
677 for i in range(self.steps):
679 lap_a = zeros((n_verts))#[0]*n_verts
680 lap_b = zeros((n_verts))#[0]*n_verts
681 for e in bm.edges:
682 id0 = e.verts[0].index
683 id1 = e.verts[1].index
684 lap_a[id0] += a[id1] - a[id0]
685 lap_a[id1] += a[id0] - a[id1]
686 lap_b[id0] += b[id1] - b[id0]
687 lap_b[id1] += b[id0] - b[id1]
688 ab2 = a*b**2
689 a += (diff_a*lap_a - ab2 + f*(1-a))*dt
690 b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
692 for i in range(n_verts):
693 ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
694 ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
695 ob.vertex_groups.update()
696 ob.data.update()
698 bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
700 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
701 return {'FINISHED'}
704 class edges_deformation(bpy.types.Operator):
705 bl_idname = "object.edges_deformation"
706 bl_label = "Edges Deformation"
707 bl_description = ("Compute Weight based on the deformation of edges"+
708 "according to visible modifiers.")
709 bl_options = {'REGISTER', 'UNDO'}
711 bounds : bpy.props.EnumProperty(
712 items=(('MANUAL', "Manual Bounds", ""),
713 ('COMPRESSION', "Compressed Only", ""),
714 ('TENSION', "Extended Only", ""),
715 ('AUTOMATIC', "Automatic Bounds", "")),
716 default='AUTOMATIC', name="Bounds")
718 mode : bpy.props.EnumProperty(
719 items=(('MAX', "Max Deformation", ""),
720 ('MEAN', "Average Deformation", "")),
721 default='MEAN', name="Evaluation Mode")
723 min_def : bpy.props.FloatProperty(
724 name="Min", default=0, soft_min=-1, soft_max=0,
725 description="Deformations with 0 weight")
727 max_def : bpy.props.FloatProperty(
728 name="Max", default=0.5, soft_min=0, soft_max=5,
729 description="Deformations with 1 weight")
731 bounds_string = ""
733 frame = None
735 @classmethod
736 def poll(cls, context):
737 return len(context.object.modifiers) > 0
739 def draw(self, context):
740 layout = self.layout
741 col = layout.column(align=True)
742 col.label(text="Evaluation Mode")
743 col.prop(self, "mode", text="")
744 col.label(text="Bounds")
745 col.prop(self, "bounds", text="")
746 if self.bounds == 'MANUAL':
747 col.label(text="Strain Rate \u03B5:")
748 col.prop(self, "min_def")
749 col.prop(self, "max_def")
750 col.label(text="\u03B5" + ": from " + self.bounds_string)
752 def execute(self, context):
753 try: ob = context.object
754 except:
755 self.report({'ERROR'}, "Please select an Object")
756 return {'CANCELLED'}
758 # check if the object is Cloth or Softbody
759 physics = False
760 for m in ob.modifiers:
761 if m.type == 'CLOTH' or m.type == 'SOFT_BODY':
762 physics = True
763 if context.scene.frame_current == 1 and self.frame != None:
764 context.scene.frame_current = self.frame
765 break
766 if not physics: self.frame = None
768 if self.mode == 'MEAN': group_name = "Average Deformation"
769 elif self.mode == 'MAX': group_name = "Max Deformation"
770 ob.vertex_groups.new(name=group_name)
771 me0 = ob.data
773 me = simple_to_mesh(ob) #ob.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
774 if len(me.vertices) != len(me0.vertices) or len(me.edges) != len(me0.edges):
775 self.report({'ERROR'}, "The topology of the object should be" +
776 "unaltered")
777 return {'CANCELLED'}
779 bm0 = bmesh.new()
780 bm0.from_mesh(me0)
781 bm = bmesh.new()
782 bm.from_mesh(me)
783 deformations = []
784 for e0, e in zip(bm0.edges, bm.edges):
785 try:
786 l0 = e0.calc_length()
787 l1 = e.calc_length()
788 epsilon = (l1 - l0)/l0
789 deformations.append(epsilon)
790 except: deformations.append(1)
791 v_deformations = []
792 for v in bm.verts:
793 vdef = []
794 for e in v.link_edges:
795 vdef.append(deformations[e.index])
796 if self.mode == 'MEAN': v_deformations.append(mean(vdef))
797 elif self.mode == 'MAX': v_deformations.append(max(vdef, key=abs))
798 #elif self.mode == 'MIN': v_deformations.append(min(vdef, key=abs))
800 if self.bounds == 'MANUAL':
801 min_def = self.min_def
802 max_def = self.max_def
803 elif self.bounds == 'AUTOMATIC':
804 min_def = min(v_deformations)
805 max_def = max(v_deformations)
806 self.min_def = min_def
807 self.max_def = max_def
808 elif self.bounds == 'COMPRESSION':
809 min_def = 0
810 max_def = min(v_deformations)
811 self.min_def = min_def
812 self.max_def = max_def
813 elif self.bounds == 'TENSION':
814 min_def = 0
815 max_def = max(v_deformations)
816 self.min_def = min_def
817 self.max_def = max_def
818 delta_def = max_def - min_def
820 # check undeformed errors
821 if delta_def == 0:
822 if self.bounds == 'MANUAL':
823 delta_def = 0.0001
824 else:
825 message = "The object doesn't have deformations."
826 if physics:
827 message = message + ("\nIf you are using Physics try to " +
828 "save it in the cache before.")
829 self.report({'ERROR'}, message)
830 return {'CANCELLED'}
831 else:
832 if physics:
833 self.frame = context.scene.frame_current
835 for i in range(len(v_deformations)):
836 weight = (v_deformations[i] - min_def)/delta_def
837 ob.vertex_groups[-1].add([i], weight, 'REPLACE')
838 self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
839 ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
840 ob.vertex_groups.update()
841 ob.data.update()
842 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
843 bpy.data.meshes.remove(me)
844 return {'FINISHED'}
846 class edges_bending(bpy.types.Operator):
847 bl_idname = "object.edges_bending"
848 bl_label = "Edges Bending"
849 bl_description = ("Compute Weight based on the bending of edges"+
850 "according to visible modifiers.")
851 bl_options = {'REGISTER', 'UNDO'}
853 bounds : bpy.props.EnumProperty(
854 items=(('MANUAL', "Manual Bounds", ""),
855 ('POSITIVE', "Positive Only", ""),
856 ('NEGATIVE', "Negative Only", ""),
857 ('UNSIGNED', "Absolute Bending", ""),
858 ('AUTOMATIC', "Signed Bending", "")),
859 default='AUTOMATIC', name="Bounds")
861 min_def : bpy.props.FloatProperty(
862 name="Min", default=-10, soft_min=-45, soft_max=45,
863 description="Deformations with 0 weight")
865 max_def : bpy.props.FloatProperty(
866 name="Max", default=10, soft_min=-45, soft_max=45,
867 description="Deformations with 1 weight")
869 bounds_string = ""
870 frame = None
872 @classmethod
873 def poll(cls, context):
874 return len(context.object.modifiers) > 0
876 def draw(self, context):
877 layout = self.layout
878 layout.label(text="Bounds")
879 layout.prop(self, "bounds", text="")
880 if self.bounds == 'MANUAL':
881 layout.prop(self, "min_def")
882 layout.prop(self, "max_def")
884 def execute(self, context):
885 try: ob = context.object
886 except:
887 self.report({'ERROR'}, "Please select an Object")
888 return {'CANCELLED'}
890 group_name = "Edges Bending"
891 ob.vertex_groups.new(name=group_name)
893 # check if the object is Cloth or Softbody
894 physics = False
895 for m in ob.modifiers:
896 if m.type == 'CLOTH' or m.type == 'SOFT_BODY':
897 physics = True
898 if context.scene.frame_current == 1 and self.frame != None:
899 context.scene.frame_current = self.frame
900 break
901 if not physics: self.frame = None
903 #ob.data.update()
904 #context.scene.update()
905 me0 = ob.data
906 me = simple_to_mesh(ob) #ob.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
907 if len(me.vertices) != len(me0.vertices) or len(me.edges) != len(me0.edges):
908 self.report({'ERROR'}, "The topology of the object should be" +
909 "unaltered")
910 bm0 = bmesh.new()
911 bm0.from_mesh(me0)
912 bm = bmesh.new()
913 bm.from_mesh(me)
914 deformations = []
915 for e0, e in zip(bm0.edges, bm.edges):
916 try:
917 ang = e.calc_face_angle_signed()
918 ang0 = e0.calc_face_angle_signed()
919 if self.bounds == 'UNSIGNED':
920 deformations.append(abs(ang-ang0))
921 else:
922 deformations.append(ang-ang0)
923 except: deformations.append(0)
924 v_deformations = []
925 for v in bm.verts:
926 vdef = []
927 for e in v.link_edges:
928 vdef.append(deformations[e.index])
929 v_deformations.append(mean(vdef))
930 if self.bounds == 'MANUAL':
931 min_def = radians(self.min_def)
932 max_def = radians(self.max_def)
933 elif self.bounds == 'AUTOMATIC':
934 min_def = min(v_deformations)
935 max_def = max(v_deformations)
936 elif self.bounds == 'POSITIVE':
937 min_def = 0
938 max_def = min(v_deformations)
939 elif self.bounds == 'NEGATIVE':
940 min_def = 0
941 max_def = max(v_deformations)
942 elif self.bounds == 'UNSIGNED':
943 min_def = 0
944 max_def = max(v_deformations)
945 delta_def = max_def - min_def
947 # check undeformed errors
948 if delta_def == 0:
949 if self.bounds == 'MANUAL':
950 delta_def = 0.0001
951 else:
952 message = "The object doesn't have deformations."
953 if physics:
954 message = message + ("\nIf you are using Physics try to " +
955 "save it in the cache before.")
956 self.report({'ERROR'}, message)
957 return {'CANCELLED'}
958 else:
959 if physics:
960 self.frame = context.scene.frame_current
962 for i in range(len(v_deformations)):
963 weight = (v_deformations[i] - min_def)/delta_def
964 ob.vertex_groups[-1].add([i], weight, 'REPLACE')
965 self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
966 ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
967 ob.vertex_groups.update()
968 ob.data.update()
969 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
970 bpy.data.meshes.remove(me)
971 return {'FINISHED'}
973 class weight_contour_displace(bpy.types.Operator):
974 bl_idname = "object.weight_contour_displace"
975 bl_label = "Contour Displace"
976 bl_description = ("")
977 bl_options = {'REGISTER', 'UNDO'}
979 use_modifiers : bpy.props.BoolProperty(
980 name="Use Modifiers", default=True,
981 description="Apply all the modifiers")
982 min_iso : bpy.props.FloatProperty(
983 name="Min Iso Value", default=0.49, min=0, max=1,
984 description="Threshold value")
985 max_iso : bpy.props.FloatProperty(
986 name="Max Iso Value", default=0.51, min=0, max=1,
987 description="Threshold value")
988 n_cuts : bpy.props.IntProperty(
989 name="Cuts", default=2, min=1, soft_max=10,
990 description="Number of cuts in the selected range of values")
991 bool_displace : bpy.props.BoolProperty(
992 name="Add Displace", default=True, description="Add Displace Modifier")
993 bool_flip : bpy.props.BoolProperty(
994 name="Flip", default=False, description="Flip Output Weight")
996 weight_mode : bpy.props.EnumProperty(
997 items=[('Remapped', 'Remapped', 'Remap values'),
998 ('Alternate', 'Alternate', 'Alternate 0 and 1'),
999 ('Original', 'Original', 'Keep original Vertex Group')],
1000 name="Weight", description="Choose how to convert vertex group",
1001 default="Remapped", options={'LIBRARY_EDITABLE'})
1003 @classmethod
1004 def poll(cls, context):
1005 return len(context.object.vertex_groups) > 0
1007 def invoke(self, context, event):
1008 return context.window_manager.invoke_props_dialog(self, width=350)
1010 def execute(self, context):
1011 start_time = timeit.default_timer()
1012 try:
1013 check = bpy.context.object.vertex_groups[0]
1014 except:
1015 self.report({'ERROR'}, "The object doesn't have Vertex Groups")
1016 return {'CANCELLED'}
1018 ob0 = bpy.context.object
1020 group_id = ob0.vertex_groups.active_index
1021 vertex_group_name = ob0.vertex_groups[group_id].name
1023 bpy.ops.object.mode_set(mode='EDIT')
1024 bpy.ops.mesh.select_all(action='SELECT')
1025 bpy.ops.object.mode_set(mode='OBJECT')
1026 if self.use_modifiers:
1027 #me0 = ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
1028 me0 = simple_to_mesh(ob0)
1029 else:
1030 me0 = ob0.data.copy()
1032 # generate new bmesh
1033 bm = bmesh.new()
1034 bm.from_mesh(me0)
1035 bm.verts.ensure_lookup_table()
1036 bm.edges.ensure_lookup_table()
1037 bm.faces.ensure_lookup_table()
1039 # store weight values
1040 weight = []
1041 ob = bpy.data.objects.new("temp", me0)
1042 for g in ob0.vertex_groups:
1043 ob.vertex_groups.new(name=g.name)
1044 for v in me0.vertices:
1045 try:
1046 weight.append(ob.vertex_groups[vertex_group_name].weight(v.index))
1047 except:
1048 weight.append(0)
1050 # define iso values
1051 iso_values = []
1052 for i_cut in range(self.n_cuts):
1053 delta_iso = abs(self.max_iso - self.min_iso)
1054 min_iso = min(self.min_iso, self.max_iso)
1055 max_iso = max(self.min_iso, self.max_iso)
1056 if delta_iso == 0: iso_val = min_iso
1057 elif self.n_cuts > 1: iso_val = i_cut/(self.n_cuts-1)*delta_iso + min_iso
1058 else: iso_val = (self.max_iso + self.min_iso)/2
1059 iso_values.append(iso_val)
1061 # Start Cuts Iterations
1062 filtered_edges = bm.edges
1063 for iso_val in iso_values:
1064 delete_edges = []
1066 faces_mask = []
1067 for f in bm.faces:
1068 w_min = 2
1069 w_max = 2
1070 for v in f.verts:
1071 w = weight[v.index]
1072 if w_min == 2:
1073 w_max = w_min = w
1074 if w > w_max: w_max = w
1075 if w < w_min: w_min = w
1076 if w_min < iso_val and w_max > iso_val:
1077 faces_mask.append(f)
1078 break
1080 #link_faces = [[f for f in e.link_faces] for e in bm.edges]
1082 #faces_todo = [f.select for f in bm.faces]
1083 #faces_todo = [True for f in bm.faces]
1084 verts = []
1085 edges = []
1086 edges_id = {}
1087 _filtered_edges = []
1088 n_verts = len(bm.verts)
1089 count = n_verts
1090 for e in filtered_edges:
1091 #id0 = e.vertices[0]
1092 #id1 = e.vertices[1]
1093 id0 = e.verts[0].index
1094 id1 = e.verts[1].index
1095 w0 = weight[id0]
1096 w1 = weight[id1]
1098 if w0 == w1: continue
1099 elif w0 > iso_val and w1 > iso_val:
1100 _filtered_edges.append(e)
1101 continue
1102 elif w0 < iso_val and w1 < iso_val: continue
1103 elif w0 == iso_val or w1 == iso_val:
1104 _filtered_edges.append(e)
1105 continue
1106 else:
1107 v0 = bm.verts[id0].co
1108 v1 = bm.verts[id1].co
1109 v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
1110 if e not in delete_edges:
1111 delete_edges.append(e)
1112 verts.append(v)
1113 edges_id[str(id0)+"_"+str(id1)] = count
1114 edges_id[str(id1)+"_"+str(id0)] = count
1115 count += 1
1116 _filtered_edges.append(e)
1117 filtered_edges = _filtered_edges
1118 splitted_faces = []
1120 switch = False
1121 # splitting faces
1122 for f in faces_mask:
1123 # create sub-faces slots. Once a new vertex is reached it will
1124 # change slot, storing the next vertices for a new face.
1125 build_faces = [[],[]]
1126 #switch = False
1127 verts0 = [v.index for v in f.verts]
1128 verts1 = list(verts0)
1129 verts1.append(verts1.pop(0)) # shift list
1130 for id0, id1 in zip(verts0, verts1):
1132 # add first vertex to active slot
1133 build_faces[switch].append(id0)
1135 # try to split edge
1136 try:
1137 # check if the edge must be splitted
1138 new_vert = edges_id[str(id0)+"_"+str(id1)]
1139 # add new vertex
1140 build_faces[switch].append(new_vert)
1141 # if there is an open face on the other slot
1142 if len(build_faces[not switch]) > 0:
1143 # store actual face
1144 splitted_faces.append(build_faces[switch])
1145 # reset actual faces and switch
1146 build_faces[switch] = []
1147 # change face slot
1148 switch = not switch
1149 # continue previous face
1150 build_faces[switch].append(new_vert)
1151 except: pass
1152 if len(build_faces[not switch]) == 2:
1153 build_faces[not switch].append(id0)
1154 if len(build_faces[not switch]) > 2:
1155 splitted_faces.append(build_faces[not switch])
1156 # add last face
1157 splitted_faces.append(build_faces[switch])
1158 #del_faces.append(f.index)
1160 # adding new vertices
1161 for v in verts: new_vert = bm.verts.new(v)
1162 bm.verts.index_update()
1163 bm.verts.ensure_lookup_table()
1164 # adding new faces
1165 missed_faces = []
1166 added_faces = []
1167 for f in splitted_faces:
1168 try:
1169 face_verts = [bm.verts[i] for i in f]
1170 new_face = bm.faces.new(face_verts)
1171 for e in new_face.edges:
1172 filtered_edges.append(e)
1173 except:
1174 missed_faces.append(f)
1176 bm.faces.ensure_lookup_table()
1177 # updating weight values
1178 weight = weight + [iso_val]*len(verts)
1180 # deleting old edges/faces
1181 bm.edges.ensure_lookup_table()
1182 for e in delete_edges:
1183 bm.edges.remove(e)
1184 _filtered_edges = []
1185 for e in filtered_edges:
1186 if e not in delete_edges: _filtered_edges.append(e)
1187 filtered_edges = _filtered_edges
1189 name = ob0.name + '_ContourDisp'
1190 me = bpy.data.meshes.new(name)
1191 bm.to_mesh(me)
1192 ob = bpy.data.objects.new(name, me)
1194 # Link object to scene and make active
1195 scn = bpy.context.scene
1196 bpy.context.collection.objects.link(ob)
1197 bpy.context.view_layer.objects.active = ob
1198 ob.select_set(True)
1199 ob0.select_set(False)
1201 # generate new vertex group
1202 for g in ob0.vertex_groups:
1203 ob.vertex_groups.new(name=g.name)
1204 #ob.vertex_groups.new(name=vertex_group_name)
1206 all_weight = weight + [iso_val]*len(verts)
1207 #mult = 1/(1-iso_val)
1208 for id in range(len(all_weight)):
1209 #if False: w = (all_weight[id]-iso_val)*mult
1210 w = all_weight[id]
1211 if self.weight_mode == 'Alternate':
1212 direction = self.bool_flip
1213 for i in range(len(iso_values)-1):
1214 val0, val1 = iso_values[i], iso_values[i+1]
1215 if val0 < w <= val1:
1216 if direction: w1 = (w-val0)/(val1-val0)
1217 else: w1 = (val1-w)/(val1-val0)
1218 direction = not direction
1219 if w < iso_values[0]: w1 = not self.bool_flip
1220 if w > iso_values[-1]: w1 = not direction
1221 elif self.weight_mode == 'Remapped':
1222 if w < min_iso: w1 = 0
1223 elif w > max_iso: w1 = 1
1224 else: w1 = (w - min_iso)/delta_iso
1225 else:
1226 if self.bool_flip: w1 = 1-w
1227 else: w1 = w
1228 ob.vertex_groups[vertex_group_name].add([id], w1, 'REPLACE')
1230 ob.vertex_groups.active_index = group_id
1232 # align new object
1233 ob.matrix_world = ob0.matrix_world
1235 # Displace Modifier
1236 if self.bool_displace:
1237 ob.modifiers.new(type='DISPLACE', name='Displace')
1238 ob.modifiers["Displace"].mid_level = 0
1239 ob.modifiers["Displace"].strength = 0.1
1240 ob.modifiers['Displace'].vertex_group = vertex_group_name
1242 bpy.ops.object.mode_set(mode='EDIT')
1243 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
1244 print("Contour Displace time: " + str(timeit.default_timer() - start_time) + " sec")
1246 bpy.data.meshes.remove(me0)
1248 return {'FINISHED'}
1250 class weight_contour_mask(bpy.types.Operator):
1251 bl_idname = "object.weight_contour_mask"
1252 bl_label = "Contour Mask"
1253 bl_description = ("")
1254 bl_options = {'REGISTER', 'UNDO'}
1256 use_modifiers : bpy.props.BoolProperty(
1257 name="Use Modifiers", default=True,
1258 description="Apply all the modifiers")
1259 iso : bpy.props.FloatProperty(
1260 name="Iso Value", default=0.5, soft_min=0, soft_max=1,
1261 description="Threshold value")
1262 bool_solidify : bpy.props.BoolProperty(
1263 name="Solidify", default=True, description="Add Solidify Modifier")
1264 normalize_weight : bpy.props.BoolProperty(
1265 name="Normalize Weight", default=True,
1266 description="Normalize weight of remaining vertices")
1268 @classmethod
1269 def poll(cls, context):
1270 return len(context.object.vertex_groups) > 0
1272 def execute(self, context):
1273 start_time = timeit.default_timer()
1274 try:
1275 check = bpy.context.object.vertex_groups[0]
1276 except:
1277 self.report({'ERROR'}, "The object doesn't have Vertex Groups")
1278 return {'CANCELLED'}
1280 ob0 = bpy.context.object
1282 iso_val = self.iso
1283 group_id = ob0.vertex_groups.active_index
1284 vertex_group_name = ob0.vertex_groups[group_id].name
1286 bpy.ops.object.mode_set(mode='EDIT')
1287 bpy.ops.mesh.select_all(action='SELECT')
1288 bpy.ops.object.mode_set(mode='OBJECT')
1289 if self.use_modifiers:
1290 me0 = simple_to_mesh(ob0)#ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
1291 else:
1292 me0 = ob0.data.copy()
1294 # generate new bmesh
1295 bm = bmesh.new()
1296 bm.from_mesh(me0)
1297 bm.verts.ensure_lookup_table()
1298 bm.edges.ensure_lookup_table()
1299 bm.faces.ensure_lookup_table()
1301 # store weight values
1302 weight = []
1303 ob = bpy.data.objects.new("temp", me0)
1304 for g in ob0.vertex_groups:
1305 ob.vertex_groups.new(name=g.name)
1306 for v in me0.vertices:
1307 try:
1308 #weight.append(v.groups[vertex_group_name].weight)
1309 weight.append(ob.vertex_groups[vertex_group_name].weight(v.index))
1310 except:
1311 weight.append(0)
1313 faces_mask = []
1314 for f in bm.faces:
1315 w_min = 2
1316 w_max = 2
1317 for v in f.verts:
1318 w = weight[v.index]
1319 if w_min == 2:
1320 w_max = w_min = w
1321 if w > w_max: w_max = w
1322 if w < w_min: w_min = w
1323 if w_min < iso_val and w_max > iso_val:
1324 faces_mask.append(f)
1325 break
1327 filtered_edges = bm.edges# me0.edges
1328 faces_todo = [f.select for f in bm.faces]
1329 verts = []
1330 edges = []
1331 delete_edges = []
1332 edges_id = {}
1333 _filtered_edges = []
1334 n_verts = len(bm.verts)
1335 count = n_verts
1336 for e in filtered_edges:
1337 id0 = e.verts[0].index
1338 id1 = e.verts[1].index
1339 w0 = weight[id0]
1340 w1 = weight[id1]
1342 if w0 == w1: continue
1343 elif w0 > iso_val and w1 > iso_val:
1344 continue
1345 elif w0 < iso_val and w1 < iso_val: continue
1346 elif w0 == iso_val or w1 == iso_val: continue
1347 else:
1348 v0 = me0.vertices[id0].co
1349 v1 = me0.vertices[id1].co
1350 v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
1351 delete_edges.append(e)
1352 verts.append(v)
1353 edges_id[str(id0)+"_"+str(id1)] = count
1354 edges_id[str(id1)+"_"+str(id0)] = count
1355 count += 1
1357 splitted_faces = []
1359 switch = False
1360 # splitting faces
1361 for f in faces_mask:
1362 # create sub-faces slots. Once a new vertex is reached it will
1363 # change slot, storing the next vertices for a new face.
1364 build_faces = [[],[]]
1365 #switch = False
1366 verts0 = list(me0.polygons[f.index].vertices)
1367 verts1 = list(verts0)
1368 verts1.append(verts1.pop(0)) # shift list
1369 for id0, id1 in zip(verts0, verts1):
1371 # add first vertex to active slot
1372 build_faces[switch].append(id0)
1374 # try to split edge
1375 try:
1376 # check if the edge must be splitted
1377 new_vert = edges_id[str(id0)+"_"+str(id1)]
1378 # add new vertex
1379 build_faces[switch].append(new_vert)
1380 # if there is an open face on the other slot
1381 if len(build_faces[not switch]) > 0:
1382 # store actual face
1383 splitted_faces.append(build_faces[switch])
1384 # reset actual faces and switch
1385 build_faces[switch] = []
1386 # change face slot
1387 switch = not switch
1388 # continue previous face
1389 build_faces[switch].append(new_vert)
1390 except: pass
1391 if len(build_faces[not switch]) == 2:
1392 build_faces[not switch].append(id0)
1393 if len(build_faces[not switch]) > 2:
1394 splitted_faces.append(build_faces[not switch])
1395 # add last face
1396 splitted_faces.append(build_faces[switch])
1398 # adding new vertices
1399 for v in verts: bm.verts.new(v)
1400 bm.verts.ensure_lookup_table()
1402 # deleting old edges/faces
1403 bm.edges.ensure_lookup_table()
1404 remove_edges = []
1405 for e in delete_edges: bm.edges.remove(e)
1407 bm.verts.ensure_lookup_table()
1408 # adding new faces
1409 missed_faces = []
1410 for f in splitted_faces:
1411 try:
1412 face_verts = [bm.verts[i] for i in f]
1413 bm.faces.new(face_verts)
1414 except:
1415 missed_faces.append(f)
1417 # Mask geometry
1418 if(True):
1419 all_weight = weight + [iso_val+0.0001]*len(verts)
1420 weight = []
1421 for w, v in zip(all_weight, bm.verts):
1422 if w < iso_val: bm.verts.remove(v)
1423 else: weight.append(w)
1425 # Create mesh and object
1426 name = ob0.name + '_ContourMask_{:.3f}'.format(iso_val)
1427 me = bpy.data.meshes.new(name)
1428 bm.to_mesh(me)
1429 ob = bpy.data.objects.new(name, me)
1431 # Link object to scene and make active
1432 scn = bpy.context.scene
1433 bpy.context.collection.objects.link(ob)
1434 bpy.context.view_layer.objects.active = ob
1435 ob.select_set(True)
1436 ob0.select_set(False)
1438 # generate new vertex group
1439 for g in ob0.vertex_groups:
1440 ob.vertex_groups.new(name=g.name)
1442 if iso_val != 1: mult = 1/(1-iso_val)
1443 else: mult = 1
1444 for id in range(len(weight)):
1445 if self.normalize_weight: w = (weight[id]-iso_val)*mult
1446 else: w = weight[id]
1447 ob.vertex_groups[vertex_group_name].add([id], w, 'REPLACE')
1448 ob.vertex_groups.active_index = group_id
1450 # align new object
1451 ob.matrix_world = ob0.matrix_world
1453 # Add Solidify
1454 if self.bool_solidify and True:
1455 ob.modifiers.new(type='SOLIDIFY', name='Solidify')
1456 ob.modifiers['Solidify'].thickness = 0.05
1457 ob.modifiers['Solidify'].offset = 0
1458 ob.modifiers['Solidify'].vertex_group = vertex_group_name
1460 bpy.ops.object.mode_set(mode='EDIT')
1461 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
1462 print("Contour Mask time: " + str(timeit.default_timer() - start_time) + " sec")
1464 bpy.data.meshes.remove(me0)
1466 return {'FINISHED'}
1468 class weight_contour_curves(bpy.types.Operator):
1469 bl_idname = "object.weight_contour_curves"
1470 bl_label = "Contour Curves"
1471 bl_description = ("")
1472 bl_options = {'REGISTER', 'UNDO'}
1474 use_modifiers : bpy.props.BoolProperty(
1475 name="Use Modifiers", default=True,
1476 description="Apply all the modifiers")
1478 min_iso : bpy.props.FloatProperty(
1479 name="Min Value", default=0., soft_min=0, soft_max=1,
1480 description="Minimum weight value")
1481 max_iso : bpy.props.FloatProperty(
1482 name="Max Value", default=1, soft_min=0, soft_max=1,
1483 description="Maximum weight value")
1484 n_curves : bpy.props.IntProperty(
1485 name="Curves", default=3, soft_min=1, soft_max=10,
1486 description="Number of Contour Curves")
1488 min_rad : bpy.props.FloatProperty(
1489 name="Min Radius", default=0.25, soft_min=0, soft_max=1,
1490 description="Minimum Curve Radius")
1491 max_rad : bpy.props.FloatProperty(
1492 name="Max Radius", default=0.75, soft_min=0, soft_max=1,
1493 description="Maximum Curve Radius")
1495 @classmethod
1496 def poll(cls, context):
1497 ob = context.object
1498 return len(ob.vertex_groups) > 0 or ob.type == 'CURVE'
1500 def invoke(self, context, event):
1501 return context.window_manager.invoke_props_dialog(self, width=350)
1503 def execute(self, context):
1504 start_time = timeit.default_timer()
1505 try:
1506 check = bpy.context.object.vertex_groups[0]
1507 except:
1508 self.report({'ERROR'}, "The object doesn't have Vertex Groups")
1509 return {'CANCELLED'}
1510 ob0 = bpy.context.object
1512 group_id = ob0.vertex_groups.active_index
1513 vertex_group_name = ob0.vertex_groups[group_id].name
1515 bpy.ops.object.mode_set(mode='EDIT')
1516 bpy.ops.mesh.select_all(action='SELECT')
1517 bpy.ops.object.mode_set(mode='OBJECT')
1518 if self.use_modifiers:
1519 me0 = simple_to_mesh(ob0) #ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
1520 else:
1521 me0 = ob0.data.copy()
1523 # generate new bmesh
1524 bm = bmesh.new()
1525 bm.from_mesh(me0)
1526 bm.verts.ensure_lookup_table()
1527 bm.edges.ensure_lookup_table()
1528 bm.faces.ensure_lookup_table()
1530 # store weight values
1531 weight = []
1532 ob = bpy.data.objects.new("temp", me0)
1533 for g in ob0.vertex_groups:
1534 ob.vertex_groups.new(name=g.name)
1535 for v in me0.vertices:
1536 try:
1537 #weight.append(v.groups[vertex_group_name].weight)
1538 weight.append(ob.vertex_groups[vertex_group_name].weight(v.index))
1539 except:
1540 weight.append(0)
1542 filtered_edges = bm.edges
1543 total_verts = []
1544 total_segments = []
1545 radius = []
1547 # start iterate contours levels
1548 for c in range(self.n_curves):
1549 min_iso = min(self.min_iso, self.max_iso)
1550 max_iso = max(self.min_iso, self.max_iso)
1551 try:
1552 iso_val = c*(max_iso-min_iso)/(self.n_curves-1)+min_iso
1553 if iso_val < 0: iso_val = (min_iso + max_iso)/2
1554 except:
1555 iso_val = (min_iso + max_iso)/2
1556 faces_mask = []
1557 for f in bm.faces:
1558 w_min = 2
1559 w_max = 2
1560 for v in f.verts:
1561 w = weight[v.index]
1562 if w_min == 2:
1563 w_max = w_min = w
1564 if w > w_max: w_max = w
1565 if w < w_min: w_min = w
1566 if w_min < iso_val and w_max > iso_val:
1567 faces_mask.append(f)
1568 break
1570 faces_todo = [f.select for f in bm.faces]
1571 verts = []
1573 edges_id = {}
1574 _filtered_edges = []
1575 n_verts = len(bm.verts)
1576 count = len(total_verts)
1577 for e in filtered_edges:
1578 id0 = e.verts[0].index
1579 id1 = e.verts[1].index
1580 w0 = weight[id0]
1581 w1 = weight[id1]
1583 if w0 == w1: continue
1584 elif w0 > iso_val and w1 > iso_val:
1585 _filtered_edges.append(e)
1586 continue
1587 elif w0 < iso_val and w1 < iso_val: continue
1588 elif w0 == iso_val or w1 == iso_val:
1589 _filtered_edges.append(e)
1590 continue
1591 else:
1592 #v0 = me0.vertices[id0].select = True
1593 #v1 = me0.vertices[id1].select = True
1594 v0 = me0.vertices[id0].co
1595 v1 = me0.vertices[id1].co
1596 v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
1597 verts.append(v)
1598 edges_id[e.index] = count
1599 count += 1
1600 _filtered_edges.append(e)
1601 filtered_edges = _filtered_edges
1603 if len(verts) == 0: continue
1605 # finding segments
1606 segments = []
1607 for f in faces_mask:
1608 seg = []
1609 for e in f.edges:
1610 try:
1611 seg.append(edges_id[e.index])
1612 if len(seg) == 2:
1613 segments.append(seg)
1614 seg = []
1615 except: pass
1617 total_segments = total_segments + segments
1618 total_verts = total_verts + verts
1620 # Radius
1622 try:
1623 iso_rad = c*(self.max_rad-self.min_rad)/(self.n_curves-1)+self.min_rad
1624 if iso_rad < 0: iso_rad = (self.min_rad + self.max_rad)/2
1625 except:
1626 iso_rad = (self.min_rad + self.max_rad)/2
1627 radius = radius + [iso_rad]*len(verts)
1629 bm = bmesh.new()
1630 # adding new vertices
1631 for v in total_verts: bm.verts.new(v)
1632 bm.verts.ensure_lookup_table()
1634 # adding new edges
1635 for s in total_segments:
1636 try:
1637 pts = [bm.verts[i] for i in s]
1638 bm.edges.new(pts)
1639 except: pass
1641 try:
1642 name = ob0.name + '_ContourCurves'
1643 me = bpy.data.meshes.new(name)
1644 bm.to_mesh(me)
1645 ob = bpy.data.objects.new(name, me)
1647 # Link object to scene and make active
1648 scn = bpy.context.scene
1649 bpy.context.collection.objects.link(ob)
1650 bpy.context.view_layer.objects.active = ob
1651 ob.select_set(True)
1652 ob0.select_set(False)
1654 bpy.ops.object.convert(target='CURVE')
1655 ob = context.object
1656 count = 0
1657 for s in ob.data.splines:
1658 for p in s.points:
1659 p.radius = radius[count]
1660 count += 1
1661 ob.data.bevel_depth = 0.01
1662 ob.data.fill_mode = 'FULL'
1663 ob.data.bevel_resolution = 3
1664 except:
1665 self.report({'ERROR'}, "There are no values in the chosen range")
1666 return {'CANCELLED'}
1668 # align new object
1669 ob.matrix_world = ob0.matrix_world
1670 print("Contour Curves time: " + str(timeit.default_timer() - start_time) + " sec")
1672 bpy.data.meshes.remove(me0)
1673 bpy.data.meshes.remove(me)
1675 return {'FINISHED'}
1677 class vertex_colors_to_vertex_groups(bpy.types.Operator):
1678 bl_idname = "object.vertex_colors_to_vertex_groups"
1679 bl_label = "Vertex Color"
1680 bl_options = {'REGISTER', 'UNDO'}
1681 bl_description = ("Convert the active Vertex Color into a Vertex Group.")
1683 red : bpy.props.BoolProperty(
1684 name="red channel", default=False, description="convert red channel")
1685 green : bpy.props.BoolProperty(
1686 name="green channel", default=False,
1687 description="convert green channel")
1688 blue : bpy.props.BoolProperty(
1689 name="blue channel", default=False, description="convert blue channel")
1690 value : bpy.props.BoolProperty(
1691 name="value channel", default=True, description="convert value channel")
1692 invert : bpy.props.BoolProperty(
1693 name="invert", default=False, description="invert all color channels")
1695 @classmethod
1696 def poll(cls, context):
1697 return len(context.object.data.vertex_colors) > 0
1699 def execute(self, context):
1700 obj = bpy.context.active_object
1701 id = len(obj.vertex_groups)
1702 id_red = id
1703 id_green = id
1704 id_blue = id
1705 id_value = id
1707 boolCol = len(obj.data.vertex_colors)
1708 if(boolCol): col_name = obj.data.vertex_colors.active.name
1709 bpy.ops.object.mode_set(mode='EDIT')
1710 bpy.ops.mesh.select_all(action='SELECT')
1712 if(self.red and boolCol):
1713 bpy.ops.object.vertex_group_add()
1714 bpy.ops.object.vertex_group_assign()
1715 id_red = id
1716 obj.vertex_groups[id_red].name = col_name + '_red'
1717 id+=1
1718 if(self.green and boolCol):
1719 bpy.ops.object.vertex_group_add()
1720 bpy.ops.object.vertex_group_assign()
1721 id_green = id
1722 obj.vertex_groups[id_green].name = col_name + '_green'
1723 id+=1
1724 if(self.blue and boolCol):
1725 bpy.ops.object.vertex_group_add()
1726 bpy.ops.object.vertex_group_assign()
1727 id_blue = id
1728 obj.vertex_groups[id_blue].name = col_name + '_blue'
1729 id+=1
1730 if(self.value and boolCol):
1731 bpy.ops.object.vertex_group_add()
1732 bpy.ops.object.vertex_group_assign()
1733 id_value = id
1734 obj.vertex_groups[id_value].name = col_name + '_value'
1735 id+=1
1737 mult = 1
1738 if(self.invert): mult = -1
1739 bpy.ops.object.mode_set(mode='OBJECT')
1740 sub_red = 1 + self.value + self.blue + self.green
1741 sub_green = 1 + self.value + self.blue
1742 sub_blue = 1 + self.value
1743 sub_value = 1
1745 id = len(obj.vertex_groups)
1746 if(id_red <= id and id_green <= id and id_blue <= id and id_value <= \
1747 id and boolCol):
1748 v_colors = obj.data.vertex_colors.active.data
1749 i = 0
1750 for f in obj.data.polygons:
1751 for v in f.vertices:
1752 gr = obj.data.vertices[v].groups
1753 if(self.red): gr[min(len(gr)-sub_red, id_red)].weight = \
1754 self.invert + mult * v_colors[i].color[0]
1755 if(self.green): gr[min(len(gr)-sub_green, id_green)].weight\
1756 = self.invert + mult * v_colors[i].color[1]
1757 if(self.blue): gr[min(len(gr)-sub_blue, id_blue)].weight = \
1758 self.invert + mult * v_colors[i].color[2]
1759 if(self.value):
1760 r = v_colors[i].color[0]
1761 g = v_colors[i].color[1]
1762 b = v_colors[i].color[2]
1763 gr[min(len(gr)-sub_value, id_value)].weight\
1764 = self.invert + mult * (0.2126*r + 0.7152*g + 0.0722*b)
1765 i+=1
1766 bpy.ops.paint.weight_paint_toggle()
1767 return {'FINISHED'}
1769 class vertex_group_to_vertex_colors(bpy.types.Operator):
1770 bl_idname = "object.vertex_group_to_vertex_colors"
1771 bl_label = "Vertex Group"
1772 bl_options = {'REGISTER', 'UNDO'}
1773 bl_description = ("Convert the active Vertex Group into a Vertex Color.")
1775 channel : bpy.props.EnumProperty(
1776 items=[('Blue', 'Blue Channel', 'Convert to Blue Channel'),
1777 ('Green', 'Green Channel', 'Convert to Green Channel'),
1778 ('Red', 'Red Channel', 'Convert to Red Channel'),
1779 ('Value', 'Value Channel', 'Convert to Grayscale'),
1780 ('False Colors', 'False Colors', 'Convert to False Colors')],
1781 name="Convert to", description="Choose how to convert vertex group",
1782 default="Value", options={'LIBRARY_EDITABLE'})
1784 invert : bpy.props.BoolProperty(
1785 name="invert", default=False, description="invert color channel")
1787 @classmethod
1788 def poll(cls, context):
1789 return len(context.object.vertex_groups) > 0
1791 def execute(self, context):
1792 obj = bpy.context.active_object
1793 group_id = obj.vertex_groups.active_index
1794 if (group_id == -1):
1795 return {'FINISHED'}
1797 bpy.ops.object.mode_set(mode='OBJECT')
1798 group_name = obj.vertex_groups[group_id].name
1799 bpy.ops.mesh.vertex_color_add()
1800 colors_id = obj.data.vertex_colors.active_index
1802 colors_name = group_name
1803 if(self.channel == 'False Colors'): colors_name += "_false_colors"
1804 elif(self.channel == 'Value'): colors_name += "_value"
1805 elif(self.channel == 'Red'): colors_name += "_red"
1806 elif(self.channel == 'Green'): colors_name += "_green"
1807 elif(self.channel == 'Blue'): colors_name += "_blue"
1808 bpy.context.object.data.vertex_colors[colors_id].name = colors_name
1810 v_colors = obj.data.vertex_colors.active.data
1812 mult = 1
1813 if(self.invert): mult = -1
1815 i = 0
1816 for f in obj.data.polygons:
1817 for v in f.vertices:
1818 gr = obj.data.vertices[v].groups
1820 if(self.channel == 'False Colors'): v_colors[i].color = (0,0,0.5,1)
1821 else: v_colors[i].color = (0,0,0,1)
1823 for g in gr:
1824 if g.group == group_id:
1825 w = g.weight
1826 if(self.channel == 'False Colors'):
1827 mult = 0.6+0.4*w
1828 if w < 0.25:
1829 v_colors[i].color = (0, w*4*mult, 1*mult,1)
1830 elif w < 0.5:
1831 v_colors[i].color = (0, 1*mult, (1-(w-0.25)*4)*mult,1)
1832 elif w < 0.75:
1833 v_colors[i].color = ((w-0.5)*4*mult,1*mult,0,1)
1834 else:
1835 v_colors[i].color = (1*mult,(1-(w-0.75)*4)*mult,0,1)
1836 elif(self.channel == 'Value'):
1837 v_colors[i].color = (
1838 self.invert + mult * w,
1839 self.invert + mult * w,
1840 self.invert + mult * w,
1842 elif(self.channel == 'Red'):
1843 v_colors[i].color = (
1844 self.invert + mult * w,0,0,1)
1845 elif(self.channel == 'Green'):
1846 v_colors[i].color = (
1847 0, self.invert + mult * w,0,1)
1848 elif(self.channel == 'Blue'):
1849 v_colors[i].color = (
1850 0,0, self.invert + mult * w,1)
1851 i+=1
1852 bpy.ops.paint.vertex_paint_toggle()
1853 bpy.context.object.data.vertex_colors[colors_id].active_render = True
1854 return {'FINISHED'}
1856 class curvature_to_vertex_groups(bpy.types.Operator):
1857 bl_idname = "object.curvature_to_vertex_groups"
1858 bl_label = "Curvature"
1859 bl_options = {'REGISTER', 'UNDO'}
1860 bl_description = ("Generate a Vertex Group based on the curvature of the"
1861 "mesh. Is based on Dirty Vertex Color.")
1863 invert : bpy.props.BoolProperty(
1864 name="invert", default=False, description="invert values")
1866 blur_strength : bpy.props.FloatProperty(
1867 name="Blur Strength", default=1, min=0.001,
1868 max=1, description="Blur strength per iteration")
1870 blur_iterations : bpy.props.IntProperty(
1871 name="Blur Iterations", default=1, min=0,
1872 max=40, description="Number of times to blur the values")
1874 min_angle : bpy.props.FloatProperty(
1875 name="Min Angle", default=0, min=0,
1876 max=pi/2, subtype='ANGLE', description="Minimum angle")
1878 max_angle : bpy.props.FloatProperty(
1879 name="Max Angle", default=pi, min=pi/2,
1880 max=pi, subtype='ANGLE', description="Maximum angle")
1882 invert : bpy.props.BoolProperty(
1883 name="Invert", default=False,
1884 description="Invert the curvature map")
1886 def execute(self, context):
1887 bpy.ops.object.mode_set(mode='OBJECT')
1888 bpy.ops.mesh.vertex_color_add()
1889 vertex_colors = bpy.context.active_object.data.vertex_colors
1890 vertex_colors[-1].active = True
1891 vertex_colors[-1].active_render = True
1892 vertex_colors[-1].name = "Curvature"
1893 for c in vertex_colors[-1].data: c.color = (1,1,1,1)
1894 bpy.ops.object.mode_set(mode='VERTEX_PAINT')
1895 bpy.ops.paint.vertex_color_dirt(
1896 blur_strength=self.blur_strength,
1897 blur_iterations=self.blur_iterations, clean_angle=self.max_angle,
1898 dirt_angle=self.min_angle)
1899 bpy.ops.object.vertex_colors_to_vertex_groups(invert=self.invert)
1900 bpy.ops.mesh.vertex_color_remove()
1901 return {'FINISHED'}
1904 class face_area_to_vertex_groups(bpy.types.Operator):
1905 bl_idname = "object.face_area_to_vertex_groups"
1906 bl_label = "Area"
1907 bl_options = {'REGISTER', 'UNDO'}
1908 bl_description = ("Generate a Vertex Group based on the area of individual"
1909 "faces.")
1911 invert : bpy.props.BoolProperty(
1912 name="invert", default=False, description="invert values")
1913 bounds : bpy.props.EnumProperty(
1914 items=(('MANUAL', "Manual Bounds", ""),
1915 ('AUTOMATIC', "Automatic Bounds", "")),
1916 default='AUTOMATIC', name="Bounds")
1918 min_area : bpy.props.FloatProperty(
1919 name="Min", default=0.01, soft_min=0, soft_max=1,
1920 description="Faces with 0 weight")
1922 max_area : bpy.props.FloatProperty(
1923 name="Max", default=0.1, soft_min=0, soft_max=1,
1924 description="Faces with 1 weight")
1926 def draw(self, context):
1927 layout = self.layout
1928 layout.label(text="Bounds")
1929 layout.prop(self, "bounds", text="")
1930 if self.bounds == 'MANUAL':
1931 layout.prop(self, "min_area")
1932 layout.prop(self, "max_area")
1934 def execute(self, context):
1935 try: ob = context.object
1936 except:
1937 self.report({'ERROR'}, "Please select an Object")
1938 return {'CANCELLED'}
1939 ob.vertex_groups.new(name="Faces Area")
1941 areas = [[] for v in ob.data.vertices]
1943 for p in ob.data.polygons:
1944 for v in p.vertices:
1945 areas[v].append(p.area)
1947 for i in range(len(areas)):
1948 areas[i] = mean(areas[i])
1949 if self.bounds == 'MANUAL':
1950 min_area = self.min_area
1951 max_area = self.max_area
1952 elif self.bounds == 'AUTOMATIC':
1953 min_area = min(areas)
1954 max_area = max(areas)
1955 elif self.bounds == 'COMPRESSION':
1956 min_area = 1
1957 max_area = min(areas)
1958 elif self.bounds == 'TENSION':
1959 min_area = 1
1960 max_area = max(areas)
1961 delta_area = max_area - min_area
1962 if delta_area == 0:
1963 delta_area = 0.0001
1964 if self.bounds == 'MANUAL':
1965 delta_area = 0.0001
1966 else:
1967 self.report({'ERROR'}, "The faces have the same areas")
1968 #return {'CANCELLED'}
1969 for i in range(len(areas)):
1970 weight = (areas[i] - min_area)/delta_area
1971 ob.vertex_groups[-1].add([i], weight, 'REPLACE')
1972 ob.vertex_groups.update()
1973 ob.data.update()
1974 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
1975 return {'FINISHED'}
1978 class harmonic_weight(bpy.types.Operator):
1979 bl_idname = "object.harmonic_weight"
1980 bl_label = "Harmonic"
1981 bl_options = {'REGISTER', 'UNDO'}
1982 bl_description = ("Create an harmonic variation of the active Vertex Group")
1984 freq : bpy.props.FloatProperty(
1985 name="Frequency", default=20, soft_min=0,
1986 soft_max=100, description="Wave frequency")
1988 amp : bpy.props.FloatProperty(
1989 name="Amplitude", default=1, soft_min=0,
1990 soft_max=10, description="Wave amplitude")
1992 midlevel : bpy.props.FloatProperty(
1993 name="Midlevel", default=0, min=-1,
1994 max=1, description="Midlevel")
1996 add : bpy.props.FloatProperty(
1997 name="Add", default=0, min=-1,
1998 max=1, description="Add to the Weight")
2000 mult : bpy.props.FloatProperty(
2001 name="Multiply", default=0, min=0,
2002 max=1, description="Multiply for he Weight")
2004 @classmethod
2005 def poll(cls, context):
2006 return len(context.object.vertex_groups) > 0
2008 def execute(self, context):
2009 ob = bpy.context.active_object
2010 if len(ob.vertex_groups) > 0:
2011 group_id = ob.vertex_groups.active_index
2012 ob.vertex_groups.new(name="Harmonic")
2013 for i in range(len(ob.data.vertices)):
2014 try: val = ob.vertex_groups[group_id].weight(i)
2015 except: val = 0
2016 weight = self.amp*(sin(val*self.freq) - self.midlevel)/2 + 0.5 + self.add*val*(1-(1-val)*self.mult)
2017 ob.vertex_groups[-1].add([i], weight, 'REPLACE')
2018 ob.data.update()
2019 else:
2020 self.report({'ERROR'}, "Active object doesn't have vertex groups")
2021 return {'CANCELLED'}
2022 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
2023 return {'FINISHED'}
2027 class TISSUE_PT_color(bpy.types.Panel):
2028 bl_label = "Tissue Tools"
2029 bl_category = "Tissue"
2030 bl_space_type = "VIEW_3D"
2031 bl_region_type = "UI"
2032 #bl_options = {'DEFAULT_CLOSED'}
2033 bl_context = "vertexpaint"
2035 def draw(self, context):
2036 layout = self.layout
2037 col = layout.column(align=True)
2038 col.operator("object.vertex_colors_to_vertex_groups",
2039 icon="GROUP_VERTEX", text="Convert to Weight")
2041 class TISSUE_PT_weight(bpy.types.Panel):
2042 bl_label = "Tissue Tools"
2043 bl_category = "Tissue"
2044 bl_space_type = "VIEW_3D"
2045 bl_region_type = "UI"
2046 #bl_options = {'DEFAULT_CLOSED'}
2047 bl_context = "weightpaint"
2049 def draw(self, context):
2050 layout = self.layout
2051 col = layout.column(align=True)
2052 #if context.object.type == 'MESH' and context.mode == 'OBJECT':
2053 #col.label(text="Transform:")
2054 #col.separator()
2055 #elif bpy.context.mode == 'PAINT_WEIGHT':
2056 col.label(text="Weight Generate:")
2057 #col.operator(
2058 # "object.vertex_colors_to_vertex_groups", icon="GROUP_VCOL")
2059 col.operator("object.face_area_to_vertex_groups", icon="FACESEL")
2060 col.operator("object.curvature_to_vertex_groups", icon="SMOOTHCURVE")
2061 try: col.operator("object.weight_formula", icon="CON_TRANSFORM")
2062 except: col.operator("object.weight_formula")#, icon="CON_TRANSFORM")
2063 #col.label(text="Weight Processing:")
2064 col.separator()
2066 # TO BE FIXED
2067 #col.operator("object.weight_laplacian", icon="SMOOTHCURVE")
2069 col.operator("object.harmonic_weight", icon="IPO_ELASTIC")
2070 col.operator("object.vertex_group_to_vertex_colors", icon="GROUP_VCOL",
2071 text="Convert to Colors")
2072 col.separator()
2073 col.label(text="Deformation Analysis:")
2074 col.operator("object.edges_deformation", icon="DRIVER_DISTANCE")#FULLSCREEN_ENTER")
2075 col.operator("object.edges_bending", icon="DRIVER_ROTATIONAL_DIFFERENCE")#"MOD_SIMPLEDEFORM")
2076 col.separator()
2077 col.label(text="Weight Contour:")
2078 col.operator("object.weight_contour_curves", icon="MOD_CURVE")
2079 col.operator("object.weight_contour_displace", icon="MOD_DISPLACE")
2080 col.operator("object.weight_contour_mask", icon="MOD_MASK")
2081 col.separator()
2082 col.label(text="Simulations:")
2083 #col.operator("object.reaction_diffusion", icon="MOD_OCEAN")
2084 col.operator("object.start_reaction_diffusion",
2085 icon="EXPERIMENTAL",
2086 text="Reaction-Diffusion")
2088 #col.prop(context.object, "reaction_diffusion_run", icon="PLAY", text="Run Simulation")
2089 ####col.prop(context.object, "reaction_diffusion_run")
2090 #col.separator()
2091 #col.label(text="Vertex Color from:")
2092 #col.operator("object.vertex_group_to_vertex_colors", icon="GROUP_VERTEX")
2097 class start_reaction_diffusion(bpy.types.Operator):
2098 bl_idname = "object.start_reaction_diffusion"
2099 bl_label = "Start Reaction Diffusion"
2100 bl_description = ("Run a Reaction-Diffusion based on existing Vertex Groups: A and B")
2101 bl_options = {'REGISTER', 'UNDO'}
2103 run : bpy.props.BoolProperty(
2104 name="Run Reaction-Diffusion", default=True, description="Compute a new iteration on frame changes")
2106 time_steps : bpy.props.IntProperty(
2107 name="Steps", default=10, min=0, soft_max=50,
2108 description="Number of Steps")
2110 dt : bpy.props.FloatProperty(
2111 name="dt", default=1, min=0, soft_max=0.2,
2112 description="Time Step")
2114 diff_a : bpy.props.FloatProperty(
2115 name="Diff A", default=0.18, min=0, soft_max=2,
2116 description="Diffusion A")
2118 diff_b : bpy.props.FloatProperty(
2119 name="Diff B", default=0.09, min=0, soft_max=2,
2120 description="Diffusion B")
2122 f : bpy.props.FloatProperty(
2123 name="f", default=0.055, min=0, soft_max=0.5, precision=4,
2124 description="Feed Rate")
2126 k : bpy.props.FloatProperty(
2127 name="k", default=0.062, min=0, soft_max=0.5, precision=4,
2128 description="Kill Rate")
2130 @classmethod
2131 def poll(cls, context):
2132 return context.object.type == 'MESH'
2134 def execute(self, context):
2135 reaction_diffusion_add_handler(self, context)
2136 set_animatable_fix_handler(self, context)
2138 ob = context.object
2140 ob.reaction_diffusion_settings.run = self.run
2141 ob.reaction_diffusion_settings.dt = self.dt
2142 ob.reaction_diffusion_settings.time_steps = self.time_steps
2143 ob.reaction_diffusion_settings.f = self.f
2144 ob.reaction_diffusion_settings.k = self.k
2145 ob.reaction_diffusion_settings.diff_a = self.diff_a
2146 ob.reaction_diffusion_settings.diff_b = self.diff_b
2149 # check vertex group A
2150 try:
2151 vg = ob.vertex_groups['A']
2152 except:
2153 ob.vertex_groups.new(name='A')
2154 # check vertex group B
2155 try:
2156 vg = ob.vertex_groups['B']
2157 except:
2158 ob.vertex_groups.new(name='B')
2160 for v in ob.data.vertices:
2161 ob.vertex_groups['A'].add([v.index], 1, 'REPLACE')
2162 ob.vertex_groups['B'].add([v.index], 0, 'REPLACE')
2164 ob.vertex_groups.update()
2165 ob.data.update()
2166 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
2168 return {'FINISHED'}
2170 class reset_reaction_diffusion_weight(bpy.types.Operator):
2171 bl_idname = "object.reset_reaction_diffusion_weight"
2172 bl_label = "Reset Reaction Diffusion Weight"
2173 bl_description = ("Set A and B weight to default values")
2174 bl_options = {'REGISTER', 'UNDO'}
2176 @classmethod
2177 def poll(cls, context):
2178 return context.object.type == 'MESH'
2180 def execute(self, context):
2181 reaction_diffusion_add_handler(self, context)
2182 set_animatable_fix_handler(self, context)
2184 ob = context.object
2186 # check vertex group A
2187 try:
2188 vg = ob.vertex_groups['A']
2189 except:
2190 ob.vertex_groups.new(name='A')
2191 # check vertex group B
2192 try:
2193 vg = ob.vertex_groups['B']
2194 except:
2195 ob.vertex_groups.new(name='B')
2197 for v in ob.data.vertices:
2198 ob.vertex_groups['A'].add([v.index], 1, 'REPLACE')
2199 ob.vertex_groups['B'].add([v.index], 0, 'REPLACE')
2201 ob.vertex_groups.update()
2202 ob.data.update()
2203 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
2205 return {'FINISHED'}
2207 from bpy.app.handlers import persistent
2209 @persistent
2210 def reaction_diffusion_def_blur(scene):
2211 for ob in scene.objects:
2212 if ob.reaction_diffusion_settings.run:
2213 #try:
2214 me = ob.data
2215 bm = bmesh.new()
2216 bm.from_mesh(me)
2217 bm.edges.ensure_lookup_table()
2219 # store weight values
2220 a = []
2221 b = []
2222 for v in me.vertices:
2223 try:
2224 a.append(ob.vertex_groups["A"].weight(v.index))
2225 except:
2226 a.append(0)
2227 try:
2228 b.append(ob.vertex_groups["B"].weight(v.index))
2229 except:
2230 b.append(0)
2232 a = array(a)
2233 b = array(b)
2234 props = ob.reaction_diffusion_settings
2235 dt = props.dt
2236 time_steps = props.time_steps
2237 f = props.f
2238 k = props.k
2239 diff_a = props.diff_a * props.diff_mult
2240 diff_b = props.diff_b * props.diff_mult
2242 n_verts = len(bm.verts)
2243 #bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
2244 #ob.data.use_paint_mask_vertex = True
2246 for i in range(time_steps):
2247 ab2 = a*b**2
2248 ob.vertex_groups.active = ob.vertex_groups['A']
2249 bpy.ops.object.vertex_group_smooth(group_select_mode='ACTIVE', factor=diff_a)
2250 ob.vertex_groups.active = ob.vertex_groups['B']
2251 bpy.ops.object.vertex_group_smooth(group_select_mode='ACTIVE', factor=diff_b)
2253 a = []
2254 b = []
2255 for v in me.vertices:
2256 a.append(ob.vertex_groups["A"].weight(v.index))
2257 b.append(ob.vertex_groups["B"].weight(v.index))
2258 a = array(a)
2259 b = array(b)
2261 a += - (ab2 + f*(1-a))*dt
2262 b += (ab2 - (k+f)*b)*dt
2264 a = nan_to_num(a)
2265 b = nan_to_num(b)
2267 for i in range(n_verts):
2268 ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
2269 ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
2270 ob.vertex_groups.update()
2271 ob.data.update()
2272 #bpy.ops.object.mode_set(mode='EDIT')
2273 #bpy.ops.object.mode_set(mode='WEIGHT_PAINT
2274 #bpy.ops.paint.weight_paint_toggle()
2275 #bpy.ops.paint.weight_paint_toggle()
2277 #bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
2278 #except:
2279 # pass
2281 def reaction_diffusion_def_(scene):
2282 for ob in scene.objects:
2283 if ob.reaction_diffusion_settings.run:
2284 #try:
2285 me = ob.data
2286 bm = bmesh.new()
2287 bm.from_mesh(me)
2288 bm.edges.ensure_lookup_table()
2290 # store weight values
2291 a = []
2292 b = []
2293 for v in me.vertices:
2294 try:
2295 a.append(ob.vertex_groups["A"].weight(v.index))
2296 except:
2297 a.append(0)
2298 try:
2299 b.append(ob.vertex_groups["B"].weight(v.index))
2300 except:
2301 b.append(0)
2303 a = array(a)
2304 b = array(b)
2305 props = ob.reaction_diffusion_settings
2306 dt = props.dt
2307 time_steps = props.time_steps
2308 f = props.f
2309 k = props.k
2310 diff_a = props.diff_a * props.diff_mult
2311 diff_b = props.diff_b * props.diff_mult
2313 n_verts = len(bm.verts)
2314 for i in range(time_steps):
2315 lap_a = zeros((n_verts))#[0]*n_verts
2316 lap_b = zeros((n_verts))#[0]*n_verts
2317 if i == 0:
2318 lap_map = [[] for i in range(n_verts)]
2319 lap_mult = []
2320 for e in bm.edges:
2321 id0 = e.verts[0].index
2322 id1 = e.verts[1].index
2323 lap_map[id0].append(id1)
2324 lap_map[id1].append(id0)
2325 for id in range(n_verts):
2326 lap_mult.append(len(lap_map[id]))
2327 lap_mult = array(lap_mult)
2328 lap_map = array(lap_map)
2329 for id in range(n_verts):
2330 map = lap_map[id]
2331 lap_a[id] = a[lap_map[id]].sum()
2332 lap_b[id] = b[lap_map[id]].sum()
2333 lap_a -= a*lap_mult
2334 lap_b -= b*lap_mult
2335 ab2 = a*b**2
2337 a += (diff_a*lap_a - ab2 + f*(1-a))*dt
2338 b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
2340 a = nan_to_num(a)
2341 b = nan_to_num(b)
2343 for i in range(n_verts):
2344 ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
2345 ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
2346 ob.vertex_groups.update()
2347 ob.data.update()
2348 #bpy.ops.object.mode_set(mode='EDIT')
2349 #bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
2350 bpy.ops.paint.weight_paint_toggle()
2351 bpy.ops.paint.weight_paint_toggle()
2353 #bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
2354 #except:
2355 # pass
2357 def reaction_diffusion_def(scene):
2358 for ob in scene.objects:
2359 if ob.reaction_diffusion_settings.run:
2361 start = time.time()
2363 me = ob.data
2364 n_edges = len(me.edges)
2365 n_verts = len(me.vertices)
2367 # store weight values
2368 a = np.zeros(n_verts)
2369 b = np.zeros(n_verts)
2370 #a = thread_read_weight(a, ob.vertex_groups["A"])
2371 #b = thread_read_weight(b, ob.vertex_groups["B"])
2372 #a = read_weight(a, ob.vertex_groups["A"])
2373 #b = read_weight(b, ob.vertex_groups["B"])
2375 for i in range(n_verts):
2376 try: a[i] = ob.vertex_groups["A"].weight(i)
2377 except: pass
2378 try: b[i] = ob.vertex_groups["B"].weight(i)
2379 except: pass
2381 props = ob.reaction_diffusion_settings
2382 dt = props.dt
2383 time_steps = props.time_steps
2384 f = props.f
2385 k = props.k
2386 diff_a = props.diff_a * props.diff_mult
2387 diff_b = props.diff_b * props.diff_mult
2389 edge_verts = [0]*n_edges*2
2390 me.edges.foreach_get("vertices", edge_verts)
2392 timeElapsed = time.time() - start
2393 print('RD - Preparation Time:',timeElapsed)
2394 start = time.time()
2396 try:
2397 edge_verts = np.array(edge_verts)
2398 a, b = numba_reaction_diffusion(n_verts, n_edges, edge_verts, a, b, diff_a, diff_b, f, k, dt, time_steps)
2399 a = nan_to_num(a)
2400 b = nan_to_num(b)
2401 except:
2402 edge_verts = np.array(edge_verts)
2403 arr = np.arange(n_edges)*2
2404 id0 = edge_verts[arr] # first vertex indices for each edge
2405 id1 = edge_verts[arr+1] # second vertex indices for each edge
2406 for i in range(time_steps):
2407 lap_a = np.zeros(n_verts)
2408 lap_b = np.zeros(n_verts)
2409 lap_a0 = a[id1] - a[id0] # laplacian increment for first vertex of each edge
2410 lap_b0 = b[id1] - b[id0] # laplacian increment for first vertex of each edge
2412 for i, j, la0, lb0 in np.nditer([id0,id1,lap_a0,lap_b0]):
2413 lap_a[i] += la0
2414 lap_b[i] += lb0
2415 lap_a[j] -= la0
2416 lap_b[j] -= lb0
2417 ab2 = a*b**2
2418 a += eval("(diff_a*lap_a - ab2 + f*(1-a))*dt")
2419 b += eval("(diff_b*lap_b + ab2 - (k+f)*b)*dt")
2420 #a += (diff_a*lap_a - ab2 + f*(1-a))*dt
2421 #b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
2423 a = nan_to_num(a)
2424 b = nan_to_num(b)
2426 timeElapsed = time.time() - start
2427 print('RD - Simulation Time:',timeElapsed)
2428 start = time.time()
2430 for i in range(n_verts):
2431 ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
2432 ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
2434 for ps in ob.particle_systems:
2435 if ps.vertex_group_density == 'B' or ps.vertex_group_density == 'A':
2436 ps.invert_vertex_group_density = not ps.invert_vertex_group_density
2437 ps.invert_vertex_group_density = not ps.invert_vertex_group_density
2439 timeElapsed = time.time() - start
2440 print('RD - Closing Time:',timeElapsed)
2442 class TISSUE_PT_reaction_diffusion(Panel):
2443 bl_space_type = 'PROPERTIES'
2444 bl_region_type = 'WINDOW'
2445 bl_context = "data"
2446 bl_label = "Tissue - Reaction-Diffusion"
2447 bl_options = {'DEFAULT_CLOSED'}
2449 @classmethod
2450 def poll(cls, context):
2451 return 'A' and 'B' in context.object.vertex_groups
2453 def draw(self, context):
2454 reaction_diffusion_add_handler(self, context)
2456 ob = context.object
2457 props = ob.reaction_diffusion_settings
2458 layout = self.layout
2459 col = layout.column(align=True)
2460 row = col.row(align=True)
2461 if not ("A" and "B" in ob.vertex_groups):
2462 row.operator("object.start_reaction_diffusion",
2463 icon="EXPERIMENTAL",
2464 text="Reaction-Diffusion")
2465 else:
2466 row.operator("object.start_reaction_diffusion",
2467 icon="EXPERIMENTAL",
2468 text="Reset Reaction-Diffusion")
2469 row = col.row(align=True)
2470 row.prop(props, "run", text="Run Reaction-Diffusion")
2471 col = layout.column(align=True)
2472 row = col.row(align=True)
2473 row.prop(props, "time_steps")
2474 row.prop(props, "dt")
2475 col.separator()
2476 row = col.row(align=True)
2477 row.prop(props, "diff_a")
2478 row.prop(props, "diff_b")
2479 row = col.row(align=True)
2480 row.prop(props, "diff_mult")
2481 #col.separator()
2482 row = col.row(align=True)
2483 row.prop(props, "f")
2484 row.prop(props, "k")