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 -------------------------#
21 # Vertex Color to Vertex Group allow you to convert colors channels to weight #
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 #
26 # For use the command "Vertex Clors to Vertex Groups" use the search bar #
29 # (c) Alessandro Zomparelli #
32 # http://www.co-de-it.com/ #
34 ################################################################################
38 import math
, timeit
, time
39 from math
import *#pi, sin
40 from statistics
import mean
, stdev
41 from mathutils
import Vector
43 try: from .numba_functions
import numba_reaction_diffusion
46 from bpy
.types
import (
52 from bpy
.props
import (
64 def reaction_diffusion_add_handler(self
, context
):
65 # remove existing 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
)
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
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"
131 for i
in range(len(ob
.vertex_groups
)):
133 if "w["+str(i
)+"]" in formula
:
134 vg
= ob
.vertex_groups
[i
]
137 w
[i
].append(vg
.weight(v
.index
))
142 start_time
= timeit
.default_timer()
143 # compute vertex coordinates
144 if do_local
or do_relative
or do_global
:
146 verts
.foreach_get('co', co
)
147 np_co
= array(co
).reshape((n_verts
, 3))
148 lx
, ly
, lz
= array(np_co
).transpose()
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))
154 co
= [v
.co
for v
in verts
]
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
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()
168 weight
= eval(formula
)
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")
183 class weight_formula(bpy
.types
.Operator
):
184 bl_idname
= "object.weight_formula"
185 bl_label
= "Weight Formula"
186 bl_options
= {'REGISTER', 'UNDO'}
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)',
200 'sqrt((rx-0.5)**2 + (ry-0.5)**2)*2',
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")
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
):
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
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
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")
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')
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
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
)
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')
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)
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")
377 def poll(cls
, context
):
378 return len(context
.object.vertex_groups
) > 0
380 def draw(self
, context
):
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
397 self
.report({'ERROR'}, "Please select an Object")
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
)
408 bm
.edges
.ensure_lookup_table()
410 # store weight values
412 for v
in me
.vertices
:
414 weight
.append(ob
.vertex_groups
[input_group
].weight(v
.index
))
418 n_verts
= len(bm
.verts
)
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
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
]
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':
447 max_def
= min(filter_lap
)
448 self
.min_def
= min_def
449 self
.max_def
= max_def
450 elif self
.bounds
== 'POSITIVE':
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()
469 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
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")
511 def poll(cls
, context
):
512 return len(context
.object.vertex_groups
) > 0
515 def execute(self
, context
):
516 try: ob
= context
.object
518 self
.report({'ERROR'}, "Please select an Object")
524 bm
.edges
.ensure_lookup_table()
526 # store weight values
529 for v
in me
.vertices
:
531 a
.append(ob
.vertex_groups
["A"].weight(v
.index
))
535 b
.append(ob
.vertex_groups
["B"].weight(v
.index
))
543 diff_a
= self
.diff_a
* self
.diff_mult
544 diff_b
= self
.diff_b
* self
.diff_mult
548 n_verts
= len(bm
.verts
)
549 # find max number of edges for vertex
554 n_edges
= len(v
.link_edges
)
555 max_edges
= max(max_edges
, n_edges
)
556 n_neighbors
.append(n_edges
)
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
)]
569 id0
= e
.verts
[0].index
570 id1
= e
.verts
[1].index
571 lap_map
[id0
].append(id1
)
572 lap_map
[id1
].append(id0
)
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
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
]
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()
598 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
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")
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
647 self
.report({'ERROR'}, "Please select an Object")
653 bm
.edges
.ensure_lookup_table()
655 # store weight values
658 for v
in me
.vertices
:
660 a
.append(ob
.vertex_groups
["A"].weight(v
.index
))
664 b
.append(ob
.vertex_groups
["B"].weight(v
.index
))
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
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
]
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()
698 bpy
.ops
.wm
.redraw_timer(type='DRAW_WIN_SWAP', iterations
=1)
700 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
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")
736 def poll(cls
, context
):
737 return len(context
.object.modifiers
) > 0
739 def draw(self
, context
):
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
755 self
.report({'ERROR'}, "Please select an Object")
758 # check if the object is Cloth or Softbody
760 for m
in ob
.modifiers
:
761 if m
.type == 'CLOTH' or m
.type == 'SOFT_BODY':
763 if context
.scene
.frame_current
== 1 and self
.frame
!= None:
764 context
.scene
.frame_current
= self
.frame
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
)
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" +
784 for e0
, e
in zip(bm0
.edges
, bm
.edges
):
786 l0
= e0
.calc_length()
788 epsilon
= (l1
- l0
)/l0
789 deformations
.append(epsilon
)
790 except: deformations
.append(1)
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':
810 max_def
= min(v_deformations
)
811 self
.min_def
= min_def
812 self
.max_def
= max_def
813 elif self
.bounds
== 'TENSION':
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
822 if self
.bounds
== 'MANUAL':
825 message
= "The object doesn't have deformations."
827 message
= message
+ ("\nIf you are using Physics try to " +
828 "save it in the cache before.")
829 self
.report({'ERROR'}, message
)
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()
842 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
843 bpy
.data
.meshes
.remove(me
)
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")
873 def poll(cls
, context
):
874 return len(context
.object.modifiers
) > 0
876 def draw(self
, context
):
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
887 self
.report({'ERROR'}, "Please select an Object")
890 group_name
= "Edges Bending"
891 ob
.vertex_groups
.new(name
=group_name
)
893 # check if the object is Cloth or Softbody
895 for m
in ob
.modifiers
:
896 if m
.type == 'CLOTH' or m
.type == 'SOFT_BODY':
898 if context
.scene
.frame_current
== 1 and self
.frame
!= None:
899 context
.scene
.frame_current
= self
.frame
901 if not physics
: self
.frame
= None
904 #context.scene.update()
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" +
915 for e0
, e
in zip(bm0
.edges
, bm
.edges
):
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
))
922 deformations
.append(ang
-ang0
)
923 except: deformations
.append(0)
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':
938 max_def
= min(v_deformations
)
939 elif self
.bounds
== 'NEGATIVE':
941 max_def
= max(v_deformations
)
942 elif self
.bounds
== 'UNSIGNED':
944 max_def
= max(v_deformations
)
945 delta_def
= max_def
- min_def
947 # check undeformed errors
949 if self
.bounds
== 'MANUAL':
952 message
= "The object doesn't have deformations."
954 message
= message
+ ("\nIf you are using Physics try to " +
955 "save it in the cache before.")
956 self
.report({'ERROR'}, message
)
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()
969 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
970 bpy
.data
.meshes
.remove(me
)
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'})
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()
1013 check
= bpy
.context
.object.vertex_groups
[0]
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
)
1030 me0
= ob0
.data
.copy()
1032 # generate new bmesh
1035 bm
.verts
.ensure_lookup_table()
1036 bm
.edges
.ensure_lookup_table()
1037 bm
.faces
.ensure_lookup_table()
1039 # store weight values
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
:
1046 weight
.append(ob
.vertex_groups
[vertex_group_name
].weight(v
.index
))
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
:
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
)
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]
1087 _filtered_edges
= []
1088 n_verts
= len(bm
.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
1098 if w0
== w1
: continue
1099 elif w0
> iso_val
and w1
> iso_val
:
1100 _filtered_edges
.append(e
)
1102 elif w0
< iso_val
and w1
< iso_val
: continue
1103 elif w0
== iso_val
or w1
== iso_val
:
1104 _filtered_edges
.append(e
)
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
)
1113 edges_id
[str(id0
)+"_"+str(id1
)] = count
1114 edges_id
[str(id1
)+"_"+str(id0
)] = count
1116 _filtered_edges
.append(e
)
1117 filtered_edges
= _filtered_edges
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
= [[],[]]
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
)
1137 # check if the edge must be splitted
1138 new_vert
= edges_id
[str(id0
)+"_"+str(id1
)]
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:
1144 splitted_faces
.append(build_faces
[switch
])
1145 # reset actual faces and switch
1146 build_faces
[switch
] = []
1149 # continue previous face
1150 build_faces
[switch
].append(new_vert
)
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
])
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()
1167 for f
in splitted_faces
:
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
)
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
:
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
)
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
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
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
1226 if self
.bool_flip
: w1
= 1-w
1228 ob
.vertex_groups
[vertex_group_name
].add([id], w1
, 'REPLACE')
1230 ob
.vertex_groups
.active_index
= group_id
1233 ob
.matrix_world
= ob0
.matrix_world
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
)
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")
1269 def poll(cls
, context
):
1270 return len(context
.object.vertex_groups
) > 0
1272 def execute(self
, context
):
1273 start_time
= timeit
.default_timer()
1275 check
= bpy
.context
.object.vertex_groups
[0]
1277 self
.report({'ERROR'}, "The object doesn't have Vertex Groups")
1278 return {'CANCELLED'}
1280 ob0
= bpy
.context
.object
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()
1292 me0
= ob0
.data
.copy()
1294 # generate new bmesh
1297 bm
.verts
.ensure_lookup_table()
1298 bm
.edges
.ensure_lookup_table()
1299 bm
.faces
.ensure_lookup_table()
1301 # store weight values
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
:
1308 #weight.append(v.groups[vertex_group_name].weight)
1309 weight
.append(ob
.vertex_groups
[vertex_group_name
].weight(v
.index
))
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
)
1327 filtered_edges
= bm
.edges
# me0.edges
1328 faces_todo
= [f
.select
for f
in bm
.faces
]
1333 _filtered_edges
= []
1334 n_verts
= len(bm
.verts
)
1336 for e
in filtered_edges
:
1337 id0
= e
.verts
[0].index
1338 id1
= e
.verts
[1].index
1342 if w0
== w1
: continue
1343 elif w0
> iso_val
and w1
> iso_val
:
1345 elif w0
< iso_val
and w1
< iso_val
: continue
1346 elif w0
== iso_val
or w1
== iso_val
: continue
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
)
1353 edges_id
[str(id0
)+"_"+str(id1
)] = count
1354 edges_id
[str(id1
)+"_"+str(id0
)] = count
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
= [[],[]]
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
)
1376 # check if the edge must be splitted
1377 new_vert
= edges_id
[str(id0
)+"_"+str(id1
)]
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:
1383 splitted_faces
.append(build_faces
[switch
])
1384 # reset actual faces and switch
1385 build_faces
[switch
] = []
1388 # continue previous face
1389 build_faces
[switch
].append(new_vert
)
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
])
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()
1405 for e
in delete_edges
: bm
.edges
.remove(e
)
1407 bm
.verts
.ensure_lookup_table()
1410 for f
in splitted_faces
:
1412 face_verts
= [bm
.verts
[i
] for i
in f
]
1413 bm
.faces
.new(face_verts
)
1415 missed_faces
.append(f
)
1419 all_weight
= weight
+ [iso_val
+0.0001]*len(verts
)
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
)
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
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
)
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
1451 ob
.matrix_world
= ob0
.matrix_world
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
)
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")
1496 def poll(cls
, context
):
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()
1506 check
= bpy
.context
.object.vertex_groups
[0]
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()
1521 me0
= ob0
.data
.copy()
1523 # generate new bmesh
1526 bm
.verts
.ensure_lookup_table()
1527 bm
.edges
.ensure_lookup_table()
1528 bm
.faces
.ensure_lookup_table()
1530 # store weight values
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
:
1537 #weight.append(v.groups[vertex_group_name].weight)
1538 weight
.append(ob
.vertex_groups
[vertex_group_name
].weight(v
.index
))
1542 filtered_edges
= bm
.edges
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
)
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
1555 iso_val
= (min_iso
+ max_iso
)/2
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
)
1570 faces_todo
= [f
.select
for f
in bm
.faces
]
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
1583 if w0
== w1
: continue
1584 elif w0
> iso_val
and w1
> iso_val
:
1585 _filtered_edges
.append(e
)
1587 elif w0
< iso_val
and w1
< iso_val
: continue
1588 elif w0
== iso_val
or w1
== iso_val
:
1589 _filtered_edges
.append(e
)
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
))
1598 edges_id
[e
.index
] = count
1600 _filtered_edges
.append(e
)
1601 filtered_edges
= _filtered_edges
1603 if len(verts
) == 0: continue
1607 for f
in faces_mask
:
1611 seg
.append(edges_id
[e
.index
])
1613 segments
.append(seg
)
1617 total_segments
= total_segments
+ segments
1618 total_verts
= total_verts
+ verts
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
1626 iso_rad
= (self
.min_rad
+ self
.max_rad
)/2
1627 radius
= radius
+ [iso_rad
]*len(verts
)
1630 # adding new vertices
1631 for v
in total_verts
: bm
.verts
.new(v
)
1632 bm
.verts
.ensure_lookup_table()
1635 for s
in total_segments
:
1637 pts
= [bm
.verts
[i
] for i
in s
]
1642 name
= ob0
.name
+ '_ContourCurves'
1643 me
= bpy
.data
.meshes
.new(name
)
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
1652 ob0
.select_set(False)
1654 bpy
.ops
.object.convert(target
='CURVE')
1657 for s
in ob
.data
.splines
:
1659 p
.radius
= radius
[count
]
1661 ob
.data
.bevel_depth
= 0.01
1662 ob
.data
.fill_mode
= 'FULL'
1663 ob
.data
.bevel_resolution
= 3
1665 self
.report({'ERROR'}, "There are no values in the chosen range")
1666 return {'CANCELLED'}
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
)
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")
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
)
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()
1716 obj
.vertex_groups
[id_red
].name
= col_name
+ '_red'
1718 if(self
.green
and boolCol
):
1719 bpy
.ops
.object.vertex_group_add()
1720 bpy
.ops
.object.vertex_group_assign()
1722 obj
.vertex_groups
[id_green
].name
= col_name
+ '_green'
1724 if(self
.blue
and boolCol
):
1725 bpy
.ops
.object.vertex_group_add()
1726 bpy
.ops
.object.vertex_group_assign()
1728 obj
.vertex_groups
[id_blue
].name
= col_name
+ '_blue'
1730 if(self
.value
and boolCol
):
1731 bpy
.ops
.object.vertex_group_add()
1732 bpy
.ops
.object.vertex_group_assign()
1734 obj
.vertex_groups
[id_value
].name
= col_name
+ '_value'
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
1745 id = len(obj
.vertex_groups
)
1746 if(id_red
<= id and id_green
<= id and id_blue
<= id and id_value
<= \
1748 v_colors
= obj
.data
.vertex_colors
.active
.data
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]
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
)
1766 bpy
.ops
.paint
.weight_paint_toggle()
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")
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):
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
1813 if(self
.invert
): mult
= -1
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)
1824 if g
.group
== group_id
:
1826 if(self
.channel
== 'False Colors'):
1829 v_colors
[i
].color
= (0, w
*4*mult
, 1*mult
,1)
1831 v_colors
[i
].color
= (0, 1*mult
, (1-(w
-0.25)*4)*mult
,1)
1833 v_colors
[i
].color
= ((w
-0.5)*4*mult
,1*mult
,0,1)
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)
1852 bpy
.ops
.paint
.vertex_paint_toggle()
1853 bpy
.context
.object.data
.vertex_colors
[colors_id
].active_render
= True
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()
1904 class face_area_to_vertex_groups(bpy
.types
.Operator
):
1905 bl_idname
= "object.face_area_to_vertex_groups"
1907 bl_options
= {'REGISTER', 'UNDO'}
1908 bl_description
= ("Generate a Vertex Group based on the area of individual"
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
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':
1957 max_area
= min(areas
)
1958 elif self
.bounds
== 'TENSION':
1960 max_area
= max(areas
)
1961 delta_area
= max_area
- min_area
1964 if self
.bounds
== 'MANUAL':
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()
1974 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
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")
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
)
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')
2020 self
.report({'ERROR'}, "Active object doesn't have vertex groups")
2021 return {'CANCELLED'}
2022 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
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:")
2055 #elif bpy.context.mode == 'PAINT_WEIGHT':
2056 col
.label(text
="Weight Generate:")
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:")
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")
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")
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")
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")
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")
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
)
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
2151 vg
= ob
.vertex_groups
['A']
2153 ob
.vertex_groups
.new(name
='A')
2154 # check vertex group B
2156 vg
= ob
.vertex_groups
['B']
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()
2166 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
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'}
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
)
2186 # check vertex group A
2188 vg
= ob
.vertex_groups
['A']
2190 ob
.vertex_groups
.new(name
='A')
2191 # check vertex group B
2193 vg
= ob
.vertex_groups
['B']
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()
2203 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
2207 from bpy
.app
.handlers
import persistent
2210 def reaction_diffusion_def_blur(scene
):
2211 for ob
in scene
.objects
:
2212 if ob
.reaction_diffusion_settings
.run
:
2217 bm
.edges
.ensure_lookup_table()
2219 # store weight values
2222 for v
in me
.vertices
:
2224 a
.append(ob
.vertex_groups
["A"].weight(v
.index
))
2228 b
.append(ob
.vertex_groups
["B"].weight(v
.index
))
2234 props
= ob
.reaction_diffusion_settings
2236 time_steps
= props
.time_steps
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
):
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
)
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
))
2261 a
+= - (ab2
+ f
*(1-a
))*dt
2262 b
+= (ab2
- (k
+f
)*b
)*dt
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()
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')
2281 def reaction_diffusion_def_(scene
):
2282 for ob
in scene
.objects
:
2283 if ob
.reaction_diffusion_settings
.run
:
2288 bm
.edges
.ensure_lookup_table()
2290 # store weight values
2293 for v
in me
.vertices
:
2295 a
.append(ob
.vertex_groups
["A"].weight(v
.index
))
2299 b
.append(ob
.vertex_groups
["B"].weight(v
.index
))
2305 props
= ob
.reaction_diffusion_settings
2307 time_steps
= props
.time_steps
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
2318 lap_map
= [[] for i
in range(n_verts
)]
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
):
2331 lap_a
[id] = a
[lap_map
[id]].sum()
2332 lap_b
[id] = b
[lap_map
[id]].sum()
2337 a
+= (diff_a
*lap_a
- ab2
+ f
*(1-a
))*dt
2338 b
+= (diff_b
*lap_b
+ ab2
- (k
+f
)*b
)*dt
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()
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')
2357 def reaction_diffusion_def(scene
):
2358 for ob
in scene
.objects
:
2359 if ob
.reaction_diffusion_settings
.run
:
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
)
2378 try: b
[i
] = ob
.vertex_groups
["B"].weight(i
)
2381 props
= ob
.reaction_diffusion_settings
2383 time_steps
= props
.time_steps
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
)
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
)
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
]):
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
2426 timeElapsed
= time
.time() - start
2427 print('RD - Simulation Time:',timeElapsed
)
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'
2446 bl_label
= "Tissue - Reaction-Diffusion"
2447 bl_options
= {'DEFAULT_CLOSED'}
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
)
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")
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")
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")
2482 row
= col
.row(align
=True)
2483 row
.prop(props
, "f")
2484 row
.prop(props
, "k")