1 # SPDX-License-Identifier: GPL-2.0-or-later
3 #-------------------------- COLORS / GROUPS EXCHANGER -------------------------#
5 # Vertex Color to Vertex Group allow you to convert colors channels to weight #
7 # The main purpose is to use vertex colors to store information when importing #
8 # files from other software. The script works with the active vertex color #
10 # For use the command "Vertex Clors to Vertex Groups" use the search bar #
13 # (c) Alessandro Zomparelli #
16 # http://www.co-de-it.com/ #
18 ################################################################################
22 import math
, timeit
, time
24 from statistics
import mean
, stdev
25 from mathutils
import Vector
26 from mathutils
.kdtree
import KDTree
28 try: from .numba_functions
import numba_reaction_diffusion
, numba_reaction_diffusion_anisotropic
, integrate_field
30 #from .numba_functions import integrate_field
31 #from .numba_functions import numba_reaction_diffusion
32 try: import numexpr
as ne
35 # Reaction-Diffusion cache
36 from pathlib
import Path
40 from bpy
.types
import (
46 from bpy
.props
import (
58 def reaction_diffusion_add_handler(self
, context
):
59 # remove existing handlers
60 reaction_diffusion_remove_handler(self
, context
)
62 bpy
.app
.handlers
.frame_change_post
.append(reaction_diffusion_scene
)
64 def reaction_diffusion_remove_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 class formula_prop(PropertyGroup
):
73 name
: StringProperty()
74 formula
: StringProperty()
75 float_var
: FloatVectorProperty(name
="", description
="", default
=(0, 0, 0, 0, 0), size
=5)
76 int_var
: IntVectorProperty(name
="", description
="", default
=(0, 0, 0, 0, 0), size
=5)
78 class reaction_diffusion_prop(PropertyGroup
):
79 run
: BoolProperty(default
=False, update
= reaction_diffusion_add_handler
,
80 description
='Compute a new iteration on frame changes. Currently is not working during Render Animation')
82 time_steps
: IntProperty(
83 name
="Steps", default
=10, min=0, soft_max
=50,
84 description
="Number of Steps")
87 name
="dt", default
=1, min=0, soft_max
=0.2,
88 description
="Time Step")
90 diff_a
: FloatProperty(
91 name
="Diff A", default
=0.1, min=0, soft_max
=2, precision
=3,
92 description
="Diffusion A")
94 diff_b
: FloatProperty(
95 name
="Diff B", default
=0.05, min=0, soft_max
=2, precision
=3,
96 description
="Diffusion B")
99 name
="f", default
=0.055, soft_min
=0.01, soft_max
=0.06, precision
=4, step
=0.05,
100 description
="Feed Rate")
103 name
="k", default
=0.062, soft_min
=0.035, soft_max
=0.065, precision
=4, step
=0.05,
104 description
="Kill Rate")
106 diff_mult
: FloatProperty(
107 name
="Scale", default
=1, min=0, soft_max
=1, max=10, precision
=2,
108 description
="Multiplier for the diffusion of both substances")
110 vertex_group_diff_a
: StringProperty(
111 name
="Diff A", default
='',
112 description
="Vertex Group used for A diffusion")
114 vertex_group_diff_b
: StringProperty(
115 name
="Diff B", default
='',
116 description
="Vertex Group used for B diffusion")
118 vertex_group_scale
: StringProperty(
119 name
="Scale", default
='',
120 description
="Vertex Group used for Scale value")
122 vertex_group_f
: StringProperty(
123 name
="f", default
='',
124 description
="Vertex Group used for Feed value (f)")
126 vertex_group_k
: StringProperty(
127 name
="k", default
='',
128 description
="Vertex Group used for Kill value (k)")
130 vertex_group_brush
: StringProperty(
131 name
="Brush", default
='',
132 description
="Vertex Group used for adding/removing B")
134 invert_vertex_group_diff_a
: BoolProperty(default
=False,
135 description
='Inverte the value of the Vertex Group Diff A')
137 invert_vertex_group_diff_b
: BoolProperty(default
=False,
138 description
='Inverte the value of the Vertex Group Diff B')
140 invert_vertex_group_scale
: BoolProperty(default
=False,
141 description
='Inverte the value of the Vertex Group Scale')
143 invert_vertex_group_f
: BoolProperty(default
=False,
144 description
='Inverte the value of the Vertex Group f')
146 invert_vertex_group_k
: BoolProperty(default
=False,
147 description
='Inverte the value of the Vertex Group k')
149 min_diff_a
: FloatProperty(
150 name
="Min Diff A", default
=0.1, min=0, soft_max
=2, precision
=3,
151 description
="Min Diff A")
153 max_diff_a
: FloatProperty(
154 name
="Max Diff A", default
=0.1, min=0, soft_max
=2, precision
=3,
155 description
="Max Diff A")
157 min_diff_b
: FloatProperty(
158 name
="Min Diff B", default
=0.1, min=0, soft_max
=2, precision
=3,
159 description
="Min Diff B")
161 max_diff_b
: FloatProperty(
162 name
="Max Diff B", default
=0.1, min=0, soft_max
=2, precision
=3,
163 description
="Max Diff B")
165 min_scale
: FloatProperty(
166 name
="Scale", default
=0.35, min=0, soft_max
=1, max=10, precision
=2,
167 description
="Min Scale Value")
169 max_scale
: FloatProperty(
170 name
="Scale", default
=1, min=0, soft_max
=1, max=10, precision
=2,
171 description
="Max Scale value")
173 min_f
: FloatProperty(
174 name
="Min f", default
=0.02, min=0, soft_min
=0.01, soft_max
=0.06, max=0.1, precision
=4, step
=0.05,
175 description
="Min Feed Rate")
177 max_f
: FloatProperty(
178 name
="Max f", default
=0.055, min=0, soft_min
=0.01, soft_max
=0.06, max=0.1, precision
=4, step
=0.05,
179 description
="Max Feed Rate")
181 min_k
: FloatProperty(
182 name
="Min k", default
=0.035, min=0, soft_min
=0.035, soft_max
=0.065, max=0.1, precision
=4, step
=0.05,
183 description
="Min Kill Rate")
185 max_k
: FloatProperty(
186 name
="Max k", default
=0.062, min=0, soft_min
=0.035, soft_max
=0.065, max=0.1, precision
=4, step
=0.05,
187 description
="Max Kill Rate")
189 brush_mult
: FloatProperty(
190 name
="Mult", default
=0.5, min=-1, max=1, precision
=3, step
=0.05,
191 description
="Multiplier for brush value")
193 bool_mod
: BoolProperty(
194 name
="Use Modifiers", default
=False,
195 description
="Read modifiers affect the vertex groups")
197 bool_cache
: BoolProperty(
198 name
="Use Cache", default
=False,
199 description
="Read modifiers affect the vertex groups")
201 cache_frame_start
: IntProperty(
202 name
="Start", default
=1,
203 description
="Frame on which the simulation starts")
205 cache_frame_end
: IntProperty(
206 name
="End", default
=250,
207 description
="Frame on which the simulation ends")
209 cache_dir
: StringProperty(
210 name
="Cache directory", default
="", subtype
='FILE_PATH',
211 description
= 'Directory that contains Reaction-Diffusion cache files'
214 update_weight_a
: BoolProperty(
215 name
="Update Vertex Group A", default
=True,
216 description
="Transfer Cache to the Vertex Groups named A")
218 update_weight_b
: BoolProperty(
219 name
="Update Vertex Group B", default
=True,
220 description
="Transfer Cache to the Vertex Groups named B")
222 update_colors_a
: BoolProperty(
223 name
="Update Vertex Color A", default
=False,
224 description
="Transfer Cache to the Vertex Color named A")
226 update_colors_b
: BoolProperty(
227 name
="Update Vertex Color B", default
=False,
228 description
="Transfer Cache to the Vertex Color named B")
230 update_colors
: BoolProperty(
231 name
="Update Vertex Color AB", default
=False,
232 description
="Transfer Cache to the Vertex Color named AB")
234 update_uv
: BoolProperty(
235 name
="Update UV", default
=False,
236 description
="Transfer Cache to the UV Map Layer named AB")
238 normalize
: BoolProperty(
239 name
="Normalize values", default
=False,
240 description
="Normalize values from 0 to 1")
242 fast_bake
: BoolProperty(
243 name
="Fast Bake", default
=True,
244 description
="Do not update modifiers or vertex groups while baking. Much faster!")
248 def compute_formula(ob
=None, formula
="rx", float_var
=(0,0,0,0,0), int_var
=(0,0,0,0,0)):
249 verts
= ob
.data
.vertices
252 f1
,f2
,f3
,f4
,f5
= float_var
253 i1
,i2
,i3
,i4
,i5
= int_var
255 do_groups
= "w[" in formula
256 do_local
= "lx" in formula
or "ly" in formula
or "lz" in formula
257 do_global
= "gx" in formula
or "gy" in formula
or "gz" in formula
258 do_relative
= "rx" in formula
or "ry" in formula
or "rz" in formula
259 do_normal
= "nx" in formula
or "ny" in formula
or "nz" in formula
260 mat
= ob
.matrix_world
262 for i
in range(1000):
263 if "w["+str(i
)+"]" in formula
and i
> len(ob
.vertex_groups
)-1:
264 return "w["+str(i
)+"] not found"
267 for i
in range(len(ob
.vertex_groups
)):
269 if "w["+str(i
)+"]" in formula
:
270 vg
= ob
.vertex_groups
[i
]
273 w
[i
].append(vg
.weight(v
.index
))
278 start_time
= timeit
.default_timer()
279 # compute vertex coordinates
280 if do_local
or do_relative
or do_global
:
282 verts
.foreach_get('co', co
)
283 np_co
= array(co
).reshape((n_verts
, 3))
284 lx
, ly
, lz
= array(np_co
).transpose()
286 rx
= np
.interp(lx
, (lx
.min(), lx
.max()), (0, +1))
287 ry
= np
.interp(ly
, (ly
.min(), ly
.max()), (0, +1))
288 rz
= np
.interp(lz
, (lz
.min(), lz
.max()), (0, +1))
290 co
= [v
.co
for v
in verts
]
293 global_co
.append(mat
@ v
)
294 global_co
= array(global_co
).reshape((n_verts
, 3))
295 gx
, gy
, gz
= array(global_co
).transpose()
296 # compute vertex normals
298 normal
= [0]*n_verts
*3
299 verts
.foreach_get('normal', normal
)
300 normal
= array(normal
).reshape((n_verts
, 3))
301 nx
, ny
, nz
= array(normal
).transpose()
304 weight
= eval(formula
)
307 return "There is something wrong"
308 print("Weight Formula: " + str(timeit
.default_timer() - start_time
))
310 class weight_formula_wiki(Operator
):
311 bl_idname
= "scene.weight_formula_wiki"
312 bl_label
= "Online Documentation"
313 bl_options
= {'REGISTER', 'UNDO'}
315 def execute(self
, context
):
316 bpy
.ops
.wm
.url_open(url
="https://github.com/alessandro-zomparelli/tissue/wiki/Weight-Tools#weight-formula")
319 class weight_formula(Operator
):
320 bl_idname
= "object.weight_formula"
321 bl_label
= "Weight Formula"
322 bl_description
= "Generate a Vertex Group according to a mathematical formula"
323 bl_options
= {'REGISTER', 'UNDO'}
326 ('cos(arctan(nx/ny)*i1*2 + sin(rz*i3))/i2 + cos(arctan(nx/ny)*i1*2 - sin(rz*i3))/i2 + 0.5','Vertical Spots'),
327 ('cos(arctan(nx/ny)*i1*2 + sin(rz*i2))/2 + cos(arctan(nx/ny)*i1*2 - sin(rz*i2))/2','Vertical Spots'),
328 ('(sin(arctan(nx/ny)*i1*2)*sin(nz*i1*2)+1)/2','Grid Spots'),
329 ('cos(arctan(nx/ny)*f1)','Vertical Stripes'),
330 ('cos(arctan(lx/ly)*f1 + sin(rz*f2)*f3)','Curly Stripes'),
331 ('sin(rz*pi*i1+arctan2(nx,ny))/2+0.5', 'Vertical Spiral'),
332 ('sin(nx*15)<sin(ny*15)','Chess'),
333 ('cos(ny*rz**2*i1)','Hyperbolic'),
334 ('sin(rx*30) > 0','Step Stripes'),
335 ('sin(nz*i1)','Normal Stripes'),
336 ('w[0]**2','Vertex Group square'),
337 ('abs(0.5-rz)*2','Double vertical gradient'),
338 ('rz', 'Vertical Gradient')
340 _ex_items
= list((str(i
),'{} ( {} )'.format(s
[0],s
[1]),s
[1]) for i
,s
in enumerate(ex_items
))
341 _ex_items
.append(('CUSTOM', "User Formula", ""))
343 examples
: EnumProperty(
344 items
= _ex_items
, default
='CUSTOM', name
="Examples")
348 formula
: StringProperty(
349 name
="Formula", default
="", description
="Formula to Evaluate")
351 slider_f01
: FloatProperty(
352 name
="f1", default
=1, description
="Slider Float 1")
353 slider_f02
: FloatProperty(
354 name
="f2", default
=1, description
="Slider Float 2")
355 slider_f03
: FloatProperty(
356 name
="f3", default
=1, description
="Slider Float 3")
357 slider_f04
: FloatProperty(
358 name
="f4", default
=1, description
="Slider Float 4")
359 slider_f05
: FloatProperty(
360 name
="f5", default
=1, description
="Slider Float 5")
361 slider_i01
: IntProperty(
362 name
="i1", default
=1, description
="Slider Integer 1")
363 slider_i02
: IntProperty(
364 name
="i2", default
=1, description
="Slider Integer 2")
365 slider_i03
: IntProperty(
366 name
="i3", default
=1, description
="Slider Integer 3")
367 slider_i04
: IntProperty(
368 name
="i4", default
=1, description
="Slider Integer 4")
369 slider_i05
: IntProperty(
370 name
="i5", default
=1, description
="Slider Integer 5")
372 def invoke(self
, context
, event
):
373 return context
.window_manager
.invoke_props_dialog(self
, width
=350)
375 def draw(self
, context
):
377 #layout.label(text="Examples")
378 layout
.prop(self
, "examples", text
="Examples")
379 #if self.examples == 'CUSTOM':
380 layout
.label(text
="Formula")
381 layout
.prop(self
, "formula", text
="")
382 #try: self.examples = self.formula
385 if self
.examples
!= 'CUSTOM':
386 example
= self
.ex_items
[int(self
.examples
)][0]
387 if example
!= self
.old_ex
:
388 self
.formula
= example
389 self
.old_ex
= example
390 elif self
.formula
!= example
:
391 self
.examples
= 'CUSTOM'
392 formula
= self
.formula
395 if "f1" in formula
: layout
.prop(self
, "slider_f01")
396 if "f2" in formula
: layout
.prop(self
, "slider_f02")
397 if "f3" in formula
: layout
.prop(self
, "slider_f03")
398 if "f4" in formula
: layout
.prop(self
, "slider_f04")
399 if "f5" in formula
: layout
.prop(self
, "slider_f05")
400 if "i1" in formula
: layout
.prop(self
, "slider_i01")
401 if "i2" in formula
: layout
.prop(self
, "slider_i02")
402 if "i3" in formula
: layout
.prop(self
, "slider_i03")
403 if "i4" in formula
: layout
.prop(self
, "slider_i04")
404 if "i5" in formula
: layout
.prop(self
, "slider_i05")
406 layout
.label(text
="Variables (for each vertex):")
407 layout
.label(text
="lx, ly, lz: Local Coordinates", icon
='ORIENTATION_LOCAL')
408 layout
.label(text
="gx, gy, gz: Global Coordinates", icon
='WORLD')
409 layout
.label(text
="rx, ry, rz: Local Coordinates (0 to 1)", icon
='NORMALIZE_FCURVES')
410 layout
.label(text
="nx, ny, nz: Normal Coordinates", icon
='SNAP_NORMAL')
411 layout
.label(text
="w[0], w[1], w[2], ... : Vertex Groups", icon
="GROUP_VERTEX")
413 layout
.label(text
="f1, f2, f3, f4, f5: Float Sliders", icon
='MOD_HUE_SATURATION')#PROPERTIES
414 layout
.label(text
="i1, i2, i3, i4, i5: Integer Sliders", icon
='MOD_HUE_SATURATION')
416 #layout.label(text="All mathematical functions are based on Numpy", icon='INFO')
417 #layout.label(text="https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.math.html", icon='INFO')
418 layout
.operator("scene.weight_formula_wiki", icon
="HELP")
419 #layout.label(text="(where 'i' is the index of the Vertex Group)")
421 def execute(self
, context
):
422 ob
= context
.active_object
423 n_verts
= len(ob
.data
.vertices
)
424 #if self.examples == 'CUSTOM':
425 # formula = self.formula
427 #self.formula = self.examples
428 # formula = self.examples
430 #f1, f2, f3, f4, f5 = self.slider_f01, self.slider_f02, self.slider_f03, self.slider_f04, self.slider_f05
431 #i1, i2, i3, i4, i5 = self.slider_i01, self.slider_i02, self.slider_i03, self.slider_i04, self.slider_i05
432 f_sliders
= self
.slider_f01
, self
.slider_f02
, self
.slider_f03
, self
.slider_f04
, self
.slider_f05
433 i_sliders
= self
.slider_i01
, self
.slider_i02
, self
.slider_i03
, self
.slider_i04
, self
.slider_i05
435 if self
.examples
!= 'CUSTOM':
436 example
= self
.ex_items
[int(self
.examples
)][0]
437 if example
!= self
.old_ex
:
438 self
.formula
= example
439 self
.old_ex
= example
440 elif self
.formula
!= example
:
441 self
.examples
= 'CUSTOM'
442 formula
= self
.formula
444 if formula
== "": return {'FINISHED'}
445 # replace numeric sliders value
446 for i
, slider
in enumerate(f_sliders
):
447 formula
= formula
.replace('f'+str(i
+1),"{0:.2f}".format(slider
))
448 for i
, slider
in enumerate(i_sliders
):
449 formula
=formula
.replace('i'+str(i
+1),str(slider
))
450 vertex_group_name
= "" + formula
451 ob
.vertex_groups
.new(name
=vertex_group_name
)
453 weight
= compute_formula(ob
, formula
=formula
, float_var
=f_sliders
, int_var
=i_sliders
)
454 if type(weight
) == str:
455 self
.report({'ERROR'}, weight
)
458 #start_time = timeit.default_timer()
459 weight
= nan_to_num(weight
)
460 vg
= ob
.vertex_groups
[-1]
461 if type(weight
) == int or type(weight
) == float:
462 for i
in range(n_verts
):
463 vg
.add([i
], weight
, 'REPLACE')
464 elif type(weight
) == ndarray
:
465 for i
in range(n_verts
):
466 vg
.add([i
], weight
[i
], 'REPLACE')
468 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
470 # Store formula settings
471 new_formula
= ob
.formula_settings
.add()
472 new_formula
.name
= ob
.vertex_groups
[-1].name
473 new_formula
.formula
= formula
474 new_formula
.int_var
= i_sliders
475 new_formula
.float_var
= f_sliders
477 #for f in ob.formula_settings:
478 # print(f.name, f.formula, f.int_var, f.float_var)
482 class update_weight_formula(Operator
):
483 bl_idname
= "object.update_weight_formula"
484 bl_label
= "Update Weight Formula"
485 bl_description
= "Update an existing Vertex Group. Make sure that the name\nof the active Vertex Group is a valid formula"
486 bl_options
= {'REGISTER', 'UNDO'}
489 def poll(cls
, context
):
490 return len(context
.object.vertex_groups
) > 0
492 def execute(self
, context
):
493 ob
= context
.active_object
494 n_verts
= len(ob
.data
.vertices
)
496 vg
= ob
.vertex_groups
.active
498 weight
= compute_formula(ob
, formula
=formula
)
499 if type(weight
) == str:
500 self
.report({'ERROR'}, "The name of the active Vertex Group\nis not a valid Formula")
503 #start_time = timeit.default_timer()
504 weight
= nan_to_num(weight
)
505 if type(weight
) == int or type(weight
) == float:
506 for i
in range(n_verts
):
507 vg
.add([i
], weight
, 'REPLACE')
508 elif type(weight
) == ndarray
:
509 for i
in range(n_verts
):
510 vg
.add([i
], weight
[i
], 'REPLACE')
512 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
516 class _weight_laplacian(Operator
):
517 bl_idname
= "object._weight_laplacian"
518 bl_label
= "Weight Laplacian"
519 bl_description
= ("Compute the Vertex Group Laplacian")
520 bl_options
= {'REGISTER', 'UNDO'}
522 bounds
: EnumProperty(
523 items
=(('MANUAL', "Manual Bounds", ""),
524 ('POSITIVE', "Positive Only", ""),
525 ('NEGATIVE', "Negative Only", ""),
526 ('AUTOMATIC', "Automatic Bounds", "")),
527 default
='AUTOMATIC', name
="Bounds")
530 items
=(('LENGTH', "Length Weight", ""),
531 ('SIMPLE', "Simple", "")),
532 default
='SIMPLE', name
="Evaluation Mode")
534 min_def
: FloatProperty(
535 name
="Min", default
=0, soft_min
=-1, soft_max
=0,
536 description
="Laplacian value with 0 weight")
538 max_def
: FloatProperty(
539 name
="Max", default
=0.5, soft_min
=0, soft_max
=5,
540 description
="Laplacian value with 1 weight")
547 def poll(cls
, context
):
548 return len(context
.object.vertex_groups
) > 0
550 def draw(self
, context
):
552 col
= layout
.column(align
=True)
553 col
.label(text
="Evaluation Mode")
554 col
.prop(self
, "mode", text
="")
555 col
.label(text
="Bounds")
556 col
.prop(self
, "bounds", text
="")
557 if self
.bounds
== 'MANUAL':
558 col
.label(text
="Strain Rate \u03B5:")
559 col
.prop(self
, "min_def")
560 col
.prop(self
, "max_def")
561 col
.label(text
="\u03B5" + ": from " + self
.bounds_string
)
564 def execute(self
, context
):
565 try: ob
= context
.object
567 self
.report({'ERROR'}, "Please select an Object")
570 group_id
= ob
.vertex_groups
.active_index
571 input_group
= ob
.vertex_groups
[group_id
].name
573 group_name
= "Laplacian"
574 ob
.vertex_groups
.new(name
=group_name
)
578 bm
.edges
.ensure_lookup_table()
580 # store weight values
582 for v
in me
.vertices
:
584 weight
.append(ob
.vertex_groups
[input_group
].weight(v
.index
))
588 n_verts
= len(bm
.verts
)
591 if self
.mode
== 'LENGTH':
592 length
= e
.calc_length()
593 if length
== 0: continue
594 id0
= e
.verts
[0].index
595 id1
= e
.verts
[1].index
596 lap
[id0
] += weight
[id1
]/length
- weight
[id0
]/length
597 lap
[id1
] += weight
[id0
]/length
- weight
[id1
]/length
599 id0
= e
.verts
[0].index
600 id1
= e
.verts
[1].index
601 lap
[id0
] += weight
[id1
] - weight
[id0
]
602 lap
[id1
] += weight
[id0
] - weight
[id1
]
605 stdev_lap
= stdev(lap
)
606 filter_lap
= [i
for i
in lap
if mean_lap
-2*stdev_lap
< i
< mean_lap
+2*stdev_lap
]
607 if self
.bounds
== 'MANUAL':
608 min_def
= self
.min_def
609 max_def
= self
.max_def
610 elif self
.bounds
== 'AUTOMATIC':
611 min_def
= min(filter_lap
)
612 max_def
= max(filter_lap
)
613 self
.min_def
= min_def
614 self
.max_def
= max_def
615 elif self
.bounds
== 'NEGATIVE':
617 max_def
= min(filter_lap
)
618 self
.min_def
= min_def
619 self
.max_def
= max_def
620 elif self
.bounds
== 'POSITIVE':
622 max_def
= max(filter_lap
)
623 self
.min_def
= min_def
624 self
.max_def
= max_def
625 delta_def
= max_def
- min_def
627 # check undeformed errors
628 if delta_def
== 0: delta_def
= 0.0001
630 for i
in range(len(lap
)):
631 val
= (lap
[i
]-min_def
)/delta_def
632 if val
> 0.7: print(str(val
) + " " + str(lap
[i
]))
633 #val = weight[i] + 0.2*lap[i]
634 ob
.vertex_groups
[-1].add([i
], val
, 'REPLACE')
635 self
.bounds_string
= str(round(min_def
,2)) + " to " + str(round(max_def
,2))
636 ob
.vertex_groups
[-1].name
= group_name
+ " " + self
.bounds_string
637 ob
.vertex_groups
.update()
639 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
643 class ok_weight_laplacian(Operator
):
644 bl_idname
= "object.weight_laplacian"
645 bl_label
= "Weight Laplacian"
646 bl_description
= ("Compute the Vertex Group Laplacian")
647 bl_options
= {'REGISTER', 'UNDO'}
654 def poll(cls
, context
):
655 return len(context
.object.vertex_groups
) > 0
658 def execute(self
, context
):
659 try: ob
= context
.object
661 self
.report({'ERROR'}, "Please select an Object")
667 bm
.edges
.ensure_lookup_table()
669 group_id
= ob
.vertex_groups
.active_index
670 input_group
= ob
.vertex_groups
[group_id
].name
672 group_name
= "Laplacian"
673 ob
.vertex_groups
.new(name
=group_name
)
675 # store weight values
677 for v
in me
.vertices
:
679 a
.append(ob
.vertex_groups
[input_group
].weight(v
.index
))
687 n_verts
= len(bm
.verts
)
688 # find max number of edges for vertex
693 n_edges
= len(v
.link_edges
)
694 max_edges
= max(max_edges
, n_edges
)
695 n_neighbors
.append(n_edges
)
697 for e
in v
.link_edges
:
699 if v
!= v1
: neighbors
.append(v1
.index
)
700 id_neighbors
.append(neighbors
)
701 n_neighbors
= array(n_neighbors
)
704 lap_map
= [[] for i
in range(n_verts
)]
708 id0 = e.verts[0].index
709 id1 = e.verts[1].index
710 lap_map[id0].append(id1)
711 lap_map[id1].append(id0)
713 lap
= zeros((n_verts
))#[0]*n_verts
714 n_records
= zeros((n_verts
))
716 id0
= e
.verts
[0].index
717 id1
= e
.verts
[1].index
718 length
= e
.calc_length()
719 if length
== 0: continue
720 #lap[id0] += abs(a[id1] - a[id0])/length
721 #lap[id1] += abs(a[id0] - a[id1])/length
722 lap
[id0
] += (a
[id1
] - a
[id0
])/length
723 lap
[id1
] += (a
[id0
] - a
[id1
])/length
729 for i
in range(n_verts
):
730 ob
.vertex_groups
['Laplacian'].add([i
], lap
[i
], 'REPLACE')
731 ob
.vertex_groups
.update()
733 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
737 class weight_laplacian(Operator
):
738 bl_idname
= "object.weight_laplacian"
739 bl_label
= "Weight Laplacian"
740 bl_description
= ("Compute the Vertex Group Laplacian")
741 bl_options
= {'REGISTER', 'UNDO'}
748 def poll(cls
, context
):
749 return len(context
.object.vertex_groups
) > 0
752 def execute(self
, context
):
753 try: ob
= context
.object
755 self
.report({'ERROR'}, "Please select an Object")
761 bm
.edges
.ensure_lookup_table()
762 n_verts
= len(me
.vertices
)
764 group_id
= ob
.vertex_groups
.active_index
765 input_group
= ob
.vertex_groups
[group_id
].name
767 group_name
= "Laplacian"
768 vg
= ob
.vertex_groups
.new(name
=group_name
)
770 # store weight values
771 dvert_lay
= bm
.verts
.layers
.deform
.active
772 weight
= bmesh_get_weight_numpy(group_id
, dvert_lay
, bm
.verts
)
774 #verts, normals = get_vertices_and_normals_numpy(me)
776 #lap = zeros((n_verts))#[0]*n_verts
777 lap
= [Vector((0,0,0)) for i
in range(n_verts
)]
778 n_records
= zeros((n_verts
))
788 v01
-= v01
.project(vert0
.normal
)
789 v10
-= v10
.project(vert1
.normal
)
790 length
= e
.calc_length()
791 if length
== 0: continue
792 dw
= (weight
[id1
] - weight
[id0
])/length
793 lap
[id0
] += v01
.normalized() * dw
794 lap
[id1
] -= v10
.normalized() * dw
797 #lap /= n_records[:,np.newaxis]
798 lap
= [l
.length
/r
for r
,l
in zip(n_records
,lap
)]
805 for i
in range(n_verts
):
806 vg
.add([i
], lap
[i
], 'REPLACE')
807 ob
.vertex_groups
.update()
809 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
814 class reaction_diffusion(Operator
):
815 bl_idname
= "object.reaction_diffusion"
816 bl_label
= "Reaction Diffusion"
817 bl_description
= ("Run a Reaction-Diffusion based on existing Vertex Groups: A and B")
818 bl_options
= {'REGISTER', 'UNDO'}
821 name
="Steps", default
=10, min=0, soft_max
=50,
822 description
="Number of Steps")
825 name
="dt", default
=0.2, min=0, soft_max
=0.2,
826 description
="Time Step")
828 diff_a
: FloatProperty(
829 name
="Diff A", default
=1, min=0, soft_max
=2,
830 description
="Diffusion A")
832 diff_b
: FloatProperty(
833 name
="Diff B", default
=0.5, min=0, soft_max
=2,
834 description
="Diffusion B")
837 name
="f", default
=0.055, min=0, soft_min
=0.01, soft_max
=0.06, max=0.1, precision
=4,
838 description
="Feed Rate")
841 name
="k", default
=0.062, min=0, soft_min
=0.035, soft_max
=0.065, max=0.1, precision
=4,
842 description
="Kill Rate")
849 def poll(cls
, context
):
850 return len(context
.object.vertex_groups
) > 0
853 def execute(self
, context
):
854 #bpy.app.handlers.frame_change_post.remove(reaction_diffusion_def)
855 reaction_diffusion_add_handler(self
, context
)
856 set_animatable_fix_handler(self
, context
)
857 try: ob
= context
.object
859 self
.report({'ERROR'}, "Please select an Object")
865 bm
.edges
.ensure_lookup_table()
867 # store weight values
870 for v
in me
.vertices
:
872 a
.append(ob
.vertex_groups
["A"].weight(v
.index
))
876 b
.append(ob
.vertex_groups
["B"].weight(v
.index
))
887 n_verts
= len(bm
.verts
)
889 for i
in range(self
.steps
):
891 lap_a
= zeros((n_verts
))#[0]*n_verts
892 lap_b
= zeros((n_verts
))#[0]*n_verts
894 id0
= e
.verts
[0].index
895 id1
= e
.verts
[1].index
896 lap_a
[id0
] += a
[id1
] - a
[id0
]
897 lap_a
[id1
] += a
[id0
] - a
[id1
]
898 lap_b
[id0
] += b
[id1
] - b
[id0
]
899 lap_b
[id1
] += b
[id0
] - b
[id1
]
901 a
+= (diff_a
*lap_a
- ab2
+ f
*(1-a
))*dt
902 b
+= (diff_b
*lap_b
+ ab2
- (k
+f
)*b
)*dt
904 for i
in range(n_verts
):
905 ob
.vertex_groups
['A'].add([i
], a
[i
], 'REPLACE')
906 ob
.vertex_groups
['B'].add([i
], b
[i
], 'REPLACE')
907 ob
.vertex_groups
.update()
910 bpy
.ops
.wm
.redraw_timer(type='DRAW_WIN_SWAP', iterations
=1)
912 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
917 class edges_deformation(Operator
):
918 bl_idname
= "object.edges_deformation"
919 bl_label
= "Edges Deformation"
921 "Compute Weight based on the deformation of edges "
922 "according to visible modifiers"
924 bl_options
= {'REGISTER', 'UNDO'}
926 bounds
: EnumProperty(
927 items
=(('MANUAL', "Manual Bounds", ""),
928 ('COMPRESSION', "Compressed Only", ""),
929 ('TENSION', "Extended Only", ""),
930 ('AUTOMATIC', "Automatic Bounds", "")),
931 default
='AUTOMATIC', name
="Bounds")
934 items
=(('MAX', "Max Deformation", ""),
935 ('MEAN', "Average Deformation", "")),
936 default
='MEAN', name
="Evaluation Mode")
938 min_def
: FloatProperty(
939 name
="Min", default
=0, soft_min
=-1, soft_max
=0,
940 description
="Deformations with 0 weight")
942 max_def
: FloatProperty(
943 name
="Max", default
=0.5, soft_min
=0, soft_max
=5,
944 description
="Deformations with 1 weight")
951 def poll(cls
, context
):
952 return len(context
.object.modifiers
) > 0
954 def draw(self
, context
):
956 col
= layout
.column(align
=True)
957 col
.label(text
="Evaluation Mode")
958 col
.prop(self
, "mode", text
="")
959 col
.label(text
="Bounds")
960 col
.prop(self
, "bounds", text
="")
961 if self
.bounds
== 'MANUAL':
962 col
.label(text
="Strain Rate \u03B5:")
963 col
.prop(self
, "min_def")
964 col
.prop(self
, "max_def")
965 col
.label(text
="\u03B5" + ": from " + self
.bounds_string
)
967 def execute(self
, context
):
968 try: ob
= context
.object
970 self
.report({'ERROR'}, "Please select an Object")
973 # check if the object is Cloth or Softbody
975 for m
in ob
.modifiers
:
976 if m
.type == 'CLOTH' or m
.type == 'SOFT_BODY':
978 if context
.scene
.frame_current
== 1 and self
.frame
!= None:
979 context
.scene
.frame_current
= self
.frame
981 if not physics
: self
.frame
= None
983 if self
.mode
== 'MEAN': group_name
= "Average Deformation"
984 elif self
.mode
== 'MAX': group_name
= "Max Deformation"
985 ob
.vertex_groups
.new(name
=group_name
)
988 me
= simple_to_mesh(ob
) #ob.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
989 if len(me
.vertices
) != len(me0
.vertices
) or len(me
.edges
) != len(me0
.edges
):
990 self
.report({'ERROR'}, "The topology of the object should be" +
999 for e0
, e
in zip(bm0
.edges
, bm
.edges
):
1001 l0
= e0
.calc_length()
1002 l1
= e
.calc_length()
1003 epsilon
= (l1
- l0
)/l0
1004 deformations
.append(epsilon
)
1005 except: deformations
.append(1)
1009 for e
in v
.link_edges
:
1010 vdef
.append(deformations
[e
.index
])
1011 if self
.mode
== 'MEAN': v_deformations
.append(mean(vdef
))
1012 elif self
.mode
== 'MAX': v_deformations
.append(max(vdef
, key
=abs))
1013 #elif self.mode == 'MIN': v_deformations.append(min(vdef, key=abs))
1015 if self
.bounds
== 'MANUAL':
1016 min_def
= self
.min_def
1017 max_def
= self
.max_def
1018 elif self
.bounds
== 'AUTOMATIC':
1019 min_def
= min(v_deformations
)
1020 max_def
= max(v_deformations
)
1021 self
.min_def
= min_def
1022 self
.max_def
= max_def
1023 elif self
.bounds
== 'COMPRESSION':
1025 max_def
= min(v_deformations
)
1026 self
.min_def
= min_def
1027 self
.max_def
= max_def
1028 elif self
.bounds
== 'TENSION':
1030 max_def
= max(v_deformations
)
1031 self
.min_def
= min_def
1032 self
.max_def
= max_def
1033 delta_def
= max_def
- min_def
1035 # check undeformed errors
1037 if self
.bounds
== 'MANUAL':
1040 message
= "The object doesn't have deformations."
1042 message
= message
+ ("\nIf you are using Physics try to " +
1043 "save it in the cache before.")
1044 self
.report({'ERROR'}, message
)
1045 return {'CANCELLED'}
1048 self
.frame
= context
.scene
.frame_current
1050 for i
in range(len(v_deformations
)):
1051 weight
= (v_deformations
[i
] - min_def
)/delta_def
1052 ob
.vertex_groups
[-1].add([i
], weight
, 'REPLACE')
1053 self
.bounds_string
= str(round(min_def
,2)) + " to " + str(round(max_def
,2))
1054 ob
.vertex_groups
[-1].name
= group_name
+ " " + self
.bounds_string
1055 ob
.vertex_groups
.update()
1057 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
1058 bpy
.data
.meshes
.remove(me
)
1063 class edges_bending(Operator
):
1064 bl_idname
= "object.edges_bending"
1065 bl_label
= "Edges Bending"
1067 "Compute Weight based on the bending of edges"
1068 "according to visible modifiers"
1070 bl_options
= {'REGISTER', 'UNDO'}
1072 bounds
: EnumProperty(
1073 items
=(('MANUAL', "Manual Bounds", ""),
1074 ('POSITIVE', "Positive Only", ""),
1075 ('NEGATIVE', "Negative Only", ""),
1076 ('UNSIGNED', "Absolute Bending", ""),
1077 ('AUTOMATIC', "Signed Bending", "")),
1078 default
='AUTOMATIC', name
="Bounds")
1080 min_def
: FloatProperty(
1081 name
="Min", default
=-10, soft_min
=-45, soft_max
=45,
1082 description
="Deformations with 0 weight")
1084 max_def
: FloatProperty(
1085 name
="Max", default
=10, soft_min
=-45, soft_max
=45,
1086 description
="Deformations with 1 weight")
1092 def poll(cls
, context
):
1093 return len(context
.object.modifiers
) > 0
1095 def draw(self
, context
):
1096 layout
= self
.layout
1097 layout
.label(text
="Bounds")
1098 layout
.prop(self
, "bounds", text
="")
1099 if self
.bounds
== 'MANUAL':
1100 layout
.prop(self
, "min_def")
1101 layout
.prop(self
, "max_def")
1103 def execute(self
, context
):
1104 try: ob
= context
.object
1106 self
.report({'ERROR'}, "Please select an Object")
1107 return {'CANCELLED'}
1109 group_name
= "Edges Bending"
1110 ob
.vertex_groups
.new(name
=group_name
)
1112 # check if the object is Cloth or Softbody
1114 for m
in ob
.modifiers
:
1115 if m
.type == 'CLOTH' or m
.type == 'SOFT_BODY':
1117 if context
.scene
.frame_current
== 1 and self
.frame
!= None:
1118 context
.scene
.frame_current
= self
.frame
1120 if not physics
: self
.frame
= None
1123 #context.scene.update()
1125 me
= simple_to_mesh(ob
) #ob.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
1126 if len(me
.vertices
) != len(me0
.vertices
) or len(me
.edges
) != len(me0
.edges
):
1127 self
.report({'ERROR'}, "The topology of the object should be" +
1134 for e0
, e
in zip(bm0
.edges
, bm
.edges
):
1136 ang
= e
.calc_face_angle_signed()
1137 ang0
= e0
.calc_face_angle_signed()
1138 if self
.bounds
== 'UNSIGNED':
1139 deformations
.append(abs(ang
-ang0
))
1141 deformations
.append(ang
-ang0
)
1142 except: deformations
.append(0)
1146 for e
in v
.link_edges
:
1147 vdef
.append(deformations
[e
.index
])
1148 v_deformations
.append(mean(vdef
))
1149 if self
.bounds
== 'MANUAL':
1150 min_def
= radians(self
.min_def
)
1151 max_def
= radians(self
.max_def
)
1152 elif self
.bounds
== 'AUTOMATIC':
1153 min_def
= min(v_deformations
)
1154 max_def
= max(v_deformations
)
1155 elif self
.bounds
== 'POSITIVE':
1157 max_def
= min(v_deformations
)
1158 elif self
.bounds
== 'NEGATIVE':
1160 max_def
= max(v_deformations
)
1161 elif self
.bounds
== 'UNSIGNED':
1163 max_def
= max(v_deformations
)
1164 delta_def
= max_def
- min_def
1166 # check undeformed errors
1168 if self
.bounds
== 'MANUAL':
1171 message
= "The object doesn't have deformations."
1173 message
= message
+ ("\nIf you are using Physics try to " +
1174 "save it in the cache before.")
1175 self
.report({'ERROR'}, message
)
1176 return {'CANCELLED'}
1179 self
.frame
= context
.scene
.frame_current
1181 for i
in range(len(v_deformations
)):
1182 weight
= (v_deformations
[i
] - min_def
)/delta_def
1183 ob
.vertex_groups
[-1].add([i
], weight
, 'REPLACE')
1184 self
.bounds_string
= str(round(min_def
,2)) + " to " + str(round(max_def
,2))
1185 ob
.vertex_groups
[-1].name
= group_name
+ " " + self
.bounds_string
1186 ob
.vertex_groups
.update()
1188 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
1189 bpy
.data
.meshes
.remove(me
)
1194 class weight_contour_displace(Operator
):
1195 bl_idname
= "object.weight_contour_displace"
1196 bl_label
= "Contour Displace"
1197 bl_description
= ("")
1198 bl_options
= {'REGISTER', 'UNDO'}
1200 use_modifiers
: BoolProperty(
1201 name
="Use Modifiers", default
=True,
1202 description
="Apply all the modifiers")
1203 min_iso
: FloatProperty(
1204 name
="Min Iso Value", default
=0.49, min=0, max=1,
1205 description
="Threshold value")
1206 max_iso
: FloatProperty(
1207 name
="Max Iso Value", default
=0.51, min=0, max=1,
1208 description
="Threshold value")
1209 n_cuts
: IntProperty(
1210 name
="Cuts", default
=2, min=1, soft_max
=10,
1211 description
="Number of cuts in the selected range of values")
1212 bool_displace
: BoolProperty(
1213 name
="Add Displace", default
=True, description
="Add Displace Modifier")
1214 bool_flip
: BoolProperty(
1215 name
="Flip", default
=False, description
="Flip Output Weight")
1217 weight_mode
: EnumProperty(
1218 items
=[('Remapped', 'Remapped', 'Remap values'),
1219 ('Alternate', 'Alternate', 'Alternate 0 and 1'),
1220 ('Original', 'Original', 'Keep original Vertex Group')],
1221 name
="Weight", description
="Choose how to convert vertex group",
1222 default
="Remapped", options
={'LIBRARY_EDITABLE'})
1225 def poll(cls
, context
):
1226 return len(context
.object.vertex_groups
) > 0
1228 def invoke(self
, context
, event
):
1229 return context
.window_manager
.invoke_props_dialog(self
, width
=350)
1231 def execute(self
, context
):
1232 start_time
= timeit
.default_timer()
1234 check
= context
.object.vertex_groups
[0]
1236 self
.report({'ERROR'}, "The object doesn't have Vertex Groups")
1237 return {'CANCELLED'}
1239 ob0
= context
.object
1241 group_id
= ob0
.vertex_groups
.active_index
1242 vertex_group_name
= ob0
.vertex_groups
[group_id
].name
1244 bpy
.ops
.object.mode_set(mode
='EDIT')
1245 bpy
.ops
.mesh
.select_all(action
='SELECT')
1246 bpy
.ops
.object.mode_set(mode
='OBJECT')
1247 if self
.use_modifiers
:
1248 #me0 = ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
1249 me0
= simple_to_mesh(ob0
)
1251 me0
= ob0
.data
.copy()
1253 # generate new bmesh
1256 bm
.verts
.ensure_lookup_table()
1257 bm
.edges
.ensure_lookup_table()
1258 bm
.faces
.ensure_lookup_table()
1260 # store weight values
1262 ob
= bpy
.data
.objects
.new("temp", me0
)
1263 for g
in ob0
.vertex_groups
:
1264 ob
.vertex_groups
.new(name
=g
.name
)
1265 for v
in me0
.vertices
:
1267 weight
.append(ob
.vertex_groups
[vertex_group_name
].weight(v
.index
))
1273 for i_cut
in range(self
.n_cuts
):
1274 delta_iso
= abs(self
.max_iso
- self
.min_iso
)
1275 min_iso
= min(self
.min_iso
, self
.max_iso
)
1276 max_iso
= max(self
.min_iso
, self
.max_iso
)
1277 if delta_iso
== 0: iso_val
= min_iso
1278 elif self
.n_cuts
> 1: iso_val
= i_cut
/(self
.n_cuts
-1)*delta_iso
+ min_iso
1279 else: iso_val
= (self
.max_iso
+ self
.min_iso
)/2
1280 iso_values
.append(iso_val
)
1282 # Start Cuts Iterations
1283 filtered_edges
= bm
.edges
1284 for iso_val
in iso_values
:
1295 if w
> w_max
: w_max
= w
1296 if w
< w_min
: w_min
= w
1297 if w_min
< iso_val
and w_max
> iso_val
:
1298 faces_mask
.append(f
)
1301 #link_faces = [[f for f in e.link_faces] for e in bm.edges]
1303 #faces_todo = [f.select for f in bm.faces]
1304 #faces_todo = [True for f in bm.faces]
1308 _filtered_edges
= []
1309 n_verts
= len(bm
.verts
)
1311 for e
in filtered_edges
:
1312 #id0 = e.vertices[0]
1313 #id1 = e.vertices[1]
1314 id0
= e
.verts
[0].index
1315 id1
= e
.verts
[1].index
1319 if w0
== w1
: continue
1320 elif w0
> iso_val
and w1
> iso_val
:
1321 _filtered_edges
.append(e
)
1323 elif w0
< iso_val
and w1
< iso_val
: continue
1324 elif w0
== iso_val
or w1
== iso_val
:
1325 _filtered_edges
.append(e
)
1328 v0
= bm
.verts
[id0
].co
1329 v1
= bm
.verts
[id1
].co
1330 v
= v0
.lerp(v1
, (iso_val
-w0
)/(w1
-w0
))
1331 if e
not in delete_edges
:
1332 delete_edges
.append(e
)
1334 edges_id
[str(id0
)+"_"+str(id1
)] = count
1335 edges_id
[str(id1
)+"_"+str(id0
)] = count
1337 _filtered_edges
.append(e
)
1338 filtered_edges
= _filtered_edges
1343 for f
in faces_mask
:
1344 # create sub-faces slots. Once a new vertex is reached it will
1345 # change slot, storing the next vertices for a new face.
1346 build_faces
= [[],[]]
1348 verts0
= [v
.index
for v
in f
.verts
]
1349 verts1
= list(verts0
)
1350 verts1
.append(verts1
.pop(0)) # shift list
1351 for id0
, id1
in zip(verts0
, verts1
):
1353 # add first vertex to active slot
1354 build_faces
[switch
].append(id0
)
1358 # check if the edge must be splitted
1359 new_vert
= edges_id
[str(id0
)+"_"+str(id1
)]
1361 build_faces
[switch
].append(new_vert
)
1362 # if there is an open face on the other slot
1363 if len(build_faces
[not switch
]) > 0:
1365 splitted_faces
.append(build_faces
[switch
])
1366 # reset actual faces and switch
1367 build_faces
[switch
] = []
1370 # continue previous face
1371 build_faces
[switch
].append(new_vert
)
1373 if len(build_faces
[not switch
]) == 2:
1374 build_faces
[not switch
].append(id0
)
1375 if len(build_faces
[not switch
]) > 2:
1376 splitted_faces
.append(build_faces
[not switch
])
1378 splitted_faces
.append(build_faces
[switch
])
1379 #del_faces.append(f.index)
1381 # adding new vertices
1382 _new_vert
= bm
.verts
.new
1383 for v
in verts
: new_vert
= _new_vert(v
)
1384 bm
.verts
.index_update()
1385 bm
.verts
.ensure_lookup_table()
1387 _new_face
= bm
.faces
.new
1390 for f
in splitted_faces
:
1392 face_verts
= [bm
.verts
[i
] for i
in f
]
1393 new_face
= _new_face(face_verts
)
1394 for e
in new_face
.edges
:
1395 filtered_edges
.append(e
)
1397 missed_faces
.append(f
)
1399 bm
.faces
.ensure_lookup_table()
1400 # updating weight values
1401 weight
= weight
+ [iso_val
]*len(verts
)
1403 # deleting old edges/faces
1404 _remove_edge
= bm
.edges
.remove
1405 bm
.edges
.ensure_lookup_table()
1406 for e
in delete_edges
:
1408 _filtered_edges
= []
1409 for e
in filtered_edges
:
1410 if e
not in delete_edges
: _filtered_edges
.append(e
)
1411 filtered_edges
= _filtered_edges
1413 name
= ob0
.name
+ '_ContourDisp'
1414 me
= bpy
.data
.meshes
.new(name
)
1417 ob
= bpy
.data
.objects
.new(name
, me
)
1419 # Link object to scene and make active
1421 context
.collection
.objects
.link(ob
)
1422 context
.view_layer
.objects
.active
= ob
1424 ob0
.select_set(False)
1426 # generate new vertex group
1427 for g
in ob0
.vertex_groups
:
1428 ob
.vertex_groups
.new(name
=g
.name
)
1429 #ob.vertex_groups.new(name=vertex_group_name)
1431 all_weight
= weight
+ [iso_val
]*len(verts
)
1432 #mult = 1/(1-iso_val)
1433 for id in range(len(all_weight
)):
1434 #if False: w = (all_weight[id]-iso_val)*mult
1436 if self
.weight_mode
== 'Alternate':
1437 direction
= self
.bool_flip
1438 for i
in range(len(iso_values
)-1):
1439 val0
, val1
= iso_values
[i
], iso_values
[i
+1]
1440 if val0
< w
<= val1
:
1441 if direction
: w1
= (w
-val0
)/(val1
-val0
)
1442 else: w1
= (val1
-w
)/(val1
-val0
)
1443 direction
= not direction
1444 if w
< iso_values
[0]: w1
= not self
.bool_flip
1445 if w
> iso_values
[-1]: w1
= not direction
1446 elif self
.weight_mode
== 'Remapped':
1447 if w
< min_iso
: w1
= 0
1448 elif w
> max_iso
: w1
= 1
1449 else: w1
= (w
- min_iso
)/delta_iso
1451 if self
.bool_flip
: w1
= 1-w
1453 ob
.vertex_groups
[vertex_group_name
].add([id], w1
, 'REPLACE')
1455 ob
.vertex_groups
.active_index
= group_id
1458 ob
.matrix_world
= ob0
.matrix_world
1461 if self
.bool_displace
:
1462 displace_modifier
= ob
.modifiers
.new(type='DISPLACE', name
='Displace')
1463 displace_modifier
.mid_level
= 0
1464 displace_modifier
.strength
= 0.1
1465 displace_modifier
.vertex_group
= vertex_group_name
1467 bpy
.ops
.object.mode_set(mode
='EDIT')
1468 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
1469 print("Contour Displace time: " + str(timeit
.default_timer() - start_time
) + " sec")
1471 bpy
.data
.meshes
.remove(me0
)
1475 class weight_contour_mask(Operator
):
1476 bl_idname
= "object.weight_contour_mask"
1477 bl_label
= "Contour Mask"
1478 bl_description
= ("")
1479 bl_options
= {'REGISTER', 'UNDO'}
1481 use_modifiers
: BoolProperty(
1482 name
="Use Modifiers", default
=True,
1483 description
="Apply all the modifiers")
1484 iso
: FloatProperty(
1485 name
="Iso Value", default
=0.5, soft_min
=0, soft_max
=1,
1486 description
="Threshold value")
1487 bool_solidify
: BoolProperty(
1488 name
="Solidify", default
=True, description
="Add Solidify Modifier")
1489 offset
: FloatProperty(
1490 name
="Offset", default
=1, min=0, max=1,
1491 description
="Offset")
1492 thickness
: FloatProperty(
1493 name
="Thickness", default
=0.5, soft_min
=0, soft_max
=1,
1494 description
="Thickness")
1495 normalize_weight
: BoolProperty(
1496 name
="Normalize Weight", default
=True,
1497 description
="Normalize weight of remaining vertices")
1500 def poll(cls
, context
):
1501 return len(context
.object.vertex_groups
) > 0
1503 def invoke(self
, context
, event
):
1504 return context
.window_manager
.invoke_props_dialog(self
, width
=350)
1506 def execute(self
, context
):
1507 start_time
= timeit
.default_timer()
1509 check
= context
.object.vertex_groups
[0]
1511 self
.report({'ERROR'}, "The object doesn't have Vertex Groups")
1512 return {'CANCELLED'}
1514 ob0
= bpy
.context
.object
1517 group_id
= ob0
.vertex_groups
.active_index
1518 vertex_group_name
= ob0
.vertex_groups
[group_id
].name
1520 bpy
.ops
.object.mode_set(mode
='EDIT')
1521 bpy
.ops
.mesh
.select_all(action
='SELECT')
1522 bpy
.ops
.object.mode_set(mode
='OBJECT')
1523 if self
.use_modifiers
:
1524 me0
= simple_to_mesh(ob0
)#ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
1526 me0
= ob0
.data
.copy()
1528 # generate new bmesh
1531 bm
.verts
.ensure_lookup_table()
1532 bm
.edges
.ensure_lookup_table()
1533 bm
.faces
.ensure_lookup_table()
1535 # store weight values
1537 ob
= bpy
.data
.objects
.new("temp", me0
)
1538 for g
in ob0
.vertex_groups
:
1539 ob
.vertex_groups
.new(name
=g
.name
)
1540 for v
in me0
.vertices
:
1542 #weight.append(v.groups[vertex_group_name].weight)
1543 weight
.append(ob
.vertex_groups
[vertex_group_name
].weight(v
.index
))
1555 if w
> w_max
: w_max
= w
1556 if w
< w_min
: w_min
= w
1557 if w_min
< iso_val
and w_max
> iso_val
:
1558 faces_mask
.append(f
)
1561 filtered_edges
= bm
.edges
# me0.edges
1562 faces_todo
= [f
.select
for f
in bm
.faces
]
1567 _filtered_edges
= []
1568 n_verts
= len(bm
.verts
)
1570 for e
in filtered_edges
:
1571 id0
= e
.verts
[0].index
1572 id1
= e
.verts
[1].index
1576 if w0
== w1
: continue
1577 elif w0
> iso_val
and w1
> iso_val
:
1579 elif w0
< iso_val
and w1
< iso_val
: continue
1580 elif w0
== iso_val
or w1
== iso_val
: continue
1582 v0
= me0
.vertices
[id0
].co
1583 v1
= me0
.vertices
[id1
].co
1584 v
= v0
.lerp(v1
, (iso_val
-w0
)/(w1
-w0
))
1585 delete_edges
.append(e
)
1587 edges_id
[str(id0
)+"_"+str(id1
)] = count
1588 edges_id
[str(id1
)+"_"+str(id0
)] = count
1595 for f
in faces_mask
:
1596 # create sub-faces slots. Once a new vertex is reached it will
1597 # change slot, storing the next vertices for a new face.
1598 build_faces
= [[],[]]
1600 verts0
= list(me0
.polygons
[f
.index
].vertices
)
1601 verts1
= list(verts0
)
1602 verts1
.append(verts1
.pop(0)) # shift list
1603 for id0
, id1
in zip(verts0
, verts1
):
1605 # add first vertex to active slot
1606 build_faces
[switch
].append(id0
)
1610 # check if the edge must be splitted
1611 new_vert
= edges_id
[str(id0
)+"_"+str(id1
)]
1613 build_faces
[switch
].append(new_vert
)
1614 # if there is an open face on the other slot
1615 if len(build_faces
[not switch
]) > 0:
1617 splitted_faces
.append(build_faces
[switch
])
1618 # reset actual faces and switch
1619 build_faces
[switch
] = []
1622 # continue previous face
1623 build_faces
[switch
].append(new_vert
)
1625 if len(build_faces
[not switch
]) == 2:
1626 build_faces
[not switch
].append(id0
)
1627 if len(build_faces
[not switch
]) > 2:
1628 splitted_faces
.append(build_faces
[not switch
])
1630 splitted_faces
.append(build_faces
[switch
])
1632 # adding new vertices
1633 _new_vert
= bm
.verts
.new
1634 for v
in verts
: _new_vert(v
)
1635 bm
.verts
.ensure_lookup_table()
1637 # deleting old edges/faces
1638 _remove_edge
= bm
.edges
.remove
1639 bm
.edges
.ensure_lookup_table()
1641 for e
in delete_edges
: _remove_edge(e
)
1643 bm
.verts
.ensure_lookup_table()
1645 _new_face
= bm
.faces
.new
1647 for f
in splitted_faces
:
1649 face_verts
= [bm
.verts
[i
] for i
in f
]
1650 _new_face(face_verts
)
1652 missed_faces
.append(f
)
1656 _remove_vert
= bm
.verts
.remove
1657 all_weight
= weight
+ [iso_val
+0.0001]*len(verts
)
1659 for w
, v
in zip(all_weight
, bm
.verts
):
1660 if w
< iso_val
: _remove_vert(v
)
1661 else: weight
.append(w
)
1663 # Create mesh and object
1664 name
= ob0
.name
+ '_ContourMask_{:.3f}'.format(iso_val
)
1665 me
= bpy
.data
.meshes
.new(name
)
1668 ob
= bpy
.data
.objects
.new(name
, me
)
1670 # Link object to scene and make active
1672 context
.collection
.objects
.link(ob
)
1673 context
.view_layer
.objects
.active
= ob
1675 ob0
.select_set(False)
1677 # generate new vertex group
1678 for g
in ob0
.vertex_groups
:
1679 ob
.vertex_groups
.new(name
=g
.name
)
1681 if iso_val
!= 1: mult
= 1/(1-iso_val
)
1683 for id in range(len(weight
)):
1684 if self
.normalize_weight
: w
= (weight
[id]-iso_val
)*mult
1685 else: w
= weight
[id]
1686 ob
.vertex_groups
[vertex_group_name
].add([id], w
, 'REPLACE')
1687 ob
.vertex_groups
.active_index
= group_id
1690 ob
.matrix_world
= ob0
.matrix_world
1693 if self
.bool_solidify
and True:
1694 ob
.modifiers
.new(type='SOLIDIFY', name
='Solidify')
1695 ob
.modifiers
['Solidify'].thickness
= self
.thickness
1696 ob
.modifiers
['Solidify'].offset
= self
.offset
1697 ob
.modifiers
['Solidify'].vertex_group
= vertex_group_name
1699 bpy
.ops
.object.mode_set(mode
='EDIT')
1700 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
1701 print("Contour Mask time: " + str(timeit
.default_timer() - start_time
) + " sec")
1703 bpy
.data
.meshes
.remove(me0
)
1708 class weight_contour_mask_wip(Operator
):
1709 bl_idname
= "object.weight_contour_mask"
1710 bl_label
= "Contour Mask"
1711 bl_description
= ("")
1712 bl_options
= {'REGISTER', 'UNDO'}
1714 use_modifiers
: BoolProperty(
1715 name
="Use Modifiers", default
=True,
1716 description
="Apply all the modifiers")
1717 iso
: FloatProperty(
1718 name
="Iso Value", default
=0.5, soft_min
=0, soft_max
=1,
1719 description
="Threshold value")
1720 bool_solidify
: BoolProperty(
1721 name
="Solidify", default
=True, description
="Add Solidify Modifier")
1722 normalize_weight
: BoolProperty(
1723 name
="Normalize Weight", default
=True,
1724 description
="Normalize weight of remaining vertices")
1727 def poll(cls
, context
):
1728 return len(context
.object.vertex_groups
) > 0
1730 def execute(self
, context
):
1731 start_time
= timeit
.default_timer()
1733 check
= context
.object.vertex_groups
[0]
1735 self
.report({'ERROR'}, "The object doesn't have Vertex Groups")
1736 return {'CANCELLED'}
1738 ob0
= bpy
.context
.object
1741 group_id
= ob0
.vertex_groups
.active_index
1742 vertex_group_name
= ob0
.vertex_groups
[group_id
].name
1744 #bpy.ops.object.mode_set(mode='EDIT')
1745 #bpy.ops.mesh.select_all(action='SELECT')
1746 #bpy.ops.object.mode_set(mode='OBJECT')
1747 if self
.use_modifiers
:
1748 me0
= simple_to_mesh(ob0
)#ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
1750 me0
= ob0
.data
.copy()
1752 # generate new bmesh
1756 # store weight values
1758 ob
= bpy
.data
.objects
.new("temp", me0
)
1759 for g
in ob0
.vertex_groups
:
1760 ob
.vertex_groups
.new(name
=g
.name
)
1761 weight
= get_weight_numpy(ob
.vertex_groups
[vertex_group_name
], len(me0
.vertices
))
1763 me0
, bm
, weight
= contour_bmesh(me0
, bm
, weight
, iso_val
)
1766 mask
= weight
>= iso_val
1767 weight
= weight
[mask
]
1768 mask
= np
.logical_not(mask
)
1769 delete_verts
= np
.array(bm
.verts
)[mask
]
1770 #for v in delete_verts: bm.verts.remove(v)
1772 # Create mesh and object
1773 name
= ob0
.name
+ '_ContourMask_{:.3f}'.format(iso_val
)
1774 me
= bpy
.data
.meshes
.new(name
)
1777 ob
= bpy
.data
.objects
.new(name
, me
)
1779 # Link object to scene and make active
1781 context
.collection
.objects
.link(ob
)
1782 context
.view_layer
.objects
.active
= ob
1784 ob0
.select_set(False)
1786 # generate new vertex group
1787 for g
in ob0
.vertex_groups
:
1788 ob
.vertex_groups
.new(name
=g
.name
)
1790 if iso_val
!= 1: mult
= 1/(1-iso_val
)
1792 for id in range(len(weight
)):
1793 if self
.normalize_weight
: w
= (weight
[id]-iso_val
)*mult
1794 else: w
= weight
[id]
1795 ob
.vertex_groups
[vertex_group_name
].add([id], w
, 'REPLACE')
1796 ob
.vertex_groups
.active_index
= group_id
1799 ob
.matrix_world
= ob0
.matrix_world
1802 if self
.bool_solidify
and True:
1803 ob
.modifiers
.new(type='SOLIDIFY', name
='Solidify')
1804 ob
.modifiers
['Solidify'].thickness
= 0.05
1805 ob
.modifiers
['Solidify'].offset
= 0
1806 ob
.modifiers
['Solidify'].vertex_group
= vertex_group_name
1808 bpy
.ops
.object.mode_set(mode
='EDIT')
1809 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
1810 print("Contour Mask time: " + str(timeit
.default_timer() - start_time
) + " sec")
1812 bpy
.data
.meshes
.remove(me0
)
1817 class weight_contour_curves(Operator
):
1818 bl_idname
= "object.weight_contour_curves"
1819 bl_label
= "Contour Curves"
1820 bl_description
= ("")
1821 bl_options
= {'REGISTER', 'UNDO'}
1823 use_modifiers
: BoolProperty(
1824 name
="Use Modifiers", default
=True,
1825 description
="Apply all the modifiers")
1827 min_iso
: FloatProperty(
1828 name
="Min Value", default
=0., soft_min
=0, soft_max
=1,
1829 description
="Minimum weight value")
1830 max_iso
: FloatProperty(
1831 name
="Max Value", default
=1, soft_min
=0, soft_max
=1,
1832 description
="Maximum weight value")
1833 n_curves
: IntProperty(
1834 name
="Curves", default
=3, soft_min
=1, soft_max
=10,
1835 description
="Number of Contour Curves")
1837 min_rad
: FloatProperty(
1838 name
="Min Radius", default
=1, soft_min
=0, soft_max
=1,
1839 description
="Change radius according to Iso Value")
1840 max_rad
: FloatProperty(
1841 name
="Max Radius", default
=1, soft_min
=0, soft_max
=1,
1842 description
="Change radius according to Iso Value")
1845 def poll(cls
, context
):
1847 return len(ob
.vertex_groups
) > 0 or ob
.type == 'CURVE'
1849 def invoke(self
, context
, event
):
1850 return context
.window_manager
.invoke_props_dialog(self
, width
=350)
1852 def execute(self
, context
):
1853 start_time
= timeit
.default_timer()
1855 check
= context
.object.vertex_groups
[0]
1857 self
.report({'ERROR'}, "The object doesn't have Vertex Groups")
1858 return {'CANCELLED'}
1859 ob0
= context
.object
1861 group_id
= ob0
.vertex_groups
.active_index
1862 vertex_group_name
= ob0
.vertex_groups
[group_id
].name
1864 bpy
.ops
.object.mode_set(mode
='EDIT')
1865 bpy
.ops
.mesh
.select_all(action
='SELECT')
1866 bpy
.ops
.object.mode_set(mode
='OBJECT')
1867 if self
.use_modifiers
:
1868 me0
= simple_to_mesh(ob0
) #ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
1870 me0
= ob0
.data
.copy()
1872 # generate new bmesh
1875 bm
.verts
.ensure_lookup_table()
1876 bm
.edges
.ensure_lookup_table()
1877 bm
.faces
.ensure_lookup_table()
1879 # store weight values
1881 ob
= bpy
.data
.objects
.new("temp", me0
)
1882 for g
in ob0
.vertex_groups
:
1883 ob
.vertex_groups
.new(name
=g
.name
)
1884 weight
= get_weight_numpy(ob
.vertex_groups
[vertex_group_name
], len(bm
.verts
))
1886 #filtered_edges = bm.edges
1887 total_verts
= np
.zeros((0,3))
1891 # start iterate contours levels
1892 vertices
= get_vertices_numpy(me0
)
1893 filtered_edges
= get_edges_id_numpy(me0
)
1895 faces_weight
= [np
.array([weight
[v
] for v
in p
.vertices
]) for p
in me0
.polygons
]
1896 fw_min
= np
.array([np
.min(fw
) for fw
in faces_weight
])
1897 fw_max
= np
.array([np
.max(fw
) for fw
in faces_weight
])
1899 bm_faces
= np
.array(bm
.faces
)
1902 normals
= np
.array([v
.normal
for v
in me0
.vertices
])
1904 for c
in range(self
.n_curves
):
1905 min_iso
= min(self
.min_iso
, self
.max_iso
)
1906 max_iso
= max(self
.min_iso
, self
.max_iso
)
1908 delta_iso
= (max_iso
-min_iso
)/(self
.n_curves
-1)
1909 iso_val
= c
*delta_iso
+ min_iso
1910 if iso_val
< 0: iso_val
= (min_iso
+ max_iso
)/2
1912 iso_val
= (min_iso
+ max_iso
)/2
1914 # remove passed faces
1915 bool_mask
= iso_val
< fw_max
1916 bm_faces
= bm_faces
[bool_mask
]
1917 fw_min
= fw_min
[bool_mask
]
1918 fw_max
= fw_max
[bool_mask
]
1921 bool_mask
= fw_min
< iso_val
1922 faces_mask
= bm_faces
[bool_mask
]
1924 n_verts
= len(bm
.verts
)
1925 count
= len(total_verts
)
1928 id0
= filtered_edges
[:,0]
1929 id1
= filtered_edges
[:,1]
1934 bool_w0
= w0
< iso_val
1935 bool_w1
= w1
< iso_val
1937 # mask all edges that have one weight value below the iso value
1938 mask_new_verts
= np
.logical_xor(bool_w0
, bool_w1
)
1940 id0
= id0
[mask_new_verts
]
1941 id1
= id1
[mask_new_verts
]
1945 w0
= w0
[mask_new_verts
]
1946 w1
= w1
[mask_new_verts
]
1948 if div
== 0: div
= 0.000001
1950 param
= np
.expand_dims((iso_val
-w0
)/div
,axis
=1)
1951 verts
= v0
+ (v1
-v0
)*param
1953 # indexes of edges with new vertices
1954 edges_index
= filtered_edges
[mask_new_verts
][:,2]
1956 for i
, id in enumerate(edges_index
): edges_id
[id] = i
+len(total_verts
)
1958 # remove all edges completely below the iso value
1959 mask_edges
= np
.logical_not(np
.logical_and(bool_w0
, bool_w1
))
1960 filtered_edges
= filtered_edges
[mask_edges
]
1961 if len(verts
) == 0: continue
1965 for f
in faces_mask
:
1969 seg
.append(edges_id
[e
.index
])
1971 segments
.append(seg
)
1976 #curves_points_indexes = find_curves(segments)
1977 total_segments
= total_segments
+ segments
1978 total_verts
= np
.concatenate((total_verts
,verts
))
1980 if self
.min_rad
!= self
.max_rad
:
1982 iso_rad
= c
*(self
.max_rad
-self
.min_rad
)/(self
.n_curves
-1)+self
.min_rad
1983 if iso_rad
< 0: iso_rad
= (self
.min_rad
+ self
.max_rad
)/2
1985 iso_rad
= (self
.min_rad
+ self
.max_rad
)/2
1986 radius
= radius
+ [iso_rad
]*len(verts
)
1987 print("Contour Curves, computing time: " + str(timeit
.default_timer() - start_time
) + " sec")
1990 # adding new vertices _local for fast access
1991 _new_vert
= bm
.verts
.new
1992 for v
in total_verts
: _new_vert(v
)
1993 bm
.verts
.ensure_lookup_table()
1996 _new_edge
= bm
.edges
.new
1997 for s
in total_segments
:
1999 pts
= [bm
.verts
[i
] for i
in s
]
2005 name
= ob0
.name
+ '_ContourCurves'
2006 me
= bpy
.data
.meshes
.new(name
)
2009 ob
= bpy
.data
.objects
.new(name
, me
)
2010 # Link object to scene and make active
2012 context
.collection
.objects
.link(ob
)
2013 context
.view_layer
.objects
.active
= ob
2015 ob0
.select_set(False)
2017 print("Contour Curves, bmesh time: " + str(timeit
.default_timer() - start_time
) + " sec")
2018 bpy
.ops
.object.convert(target
='CURVE')
2020 if not (self
.min_rad
== 0 and self
.max_rad
== 0):
2021 if self
.min_rad
!= self
.max_rad
:
2023 for s
in ob
.data
.splines
:
2025 p
.radius
= radius
[count
]
2028 for s
in ob
.data
.splines
:
2030 p
.radius
= self
.min_rad
2031 ob
.data
.bevel_depth
= 0.01
2032 ob
.data
.fill_mode
= 'FULL'
2033 ob
.data
.bevel_resolution
= 3
2035 self
.report({'ERROR'}, "There are no values in the chosen range")
2036 return {'CANCELLED'}
2039 ob
.matrix_world
= ob0
.matrix_world
2040 print("Contour Curves time: " + str(timeit
.default_timer() - start_time
) + " sec")
2042 bpy
.data
.meshes
.remove(me0
)
2043 bpy
.data
.meshes
.remove(me
)
2047 class tissue_weight_contour_curves_pattern(Operator
):
2048 bl_idname
= "object.tissue_weight_contour_curves_pattern"
2049 bl_label
= "Contour Curves"
2050 bl_description
= ("")
2051 bl_options
= {'REGISTER', 'UNDO'}
2053 use_modifiers
: BoolProperty(
2054 name
="Use Modifiers", default
=True,
2055 description
="Apply all the modifiers")
2057 auto_bevel
: BoolProperty(
2058 name
="Automatic Bevel", default
=False,
2059 description
="Bevel depends on weight density")
2061 min_iso
: FloatProperty(
2062 name
="Min Value", default
=0., soft_min
=0, soft_max
=1,
2063 description
="Minimum weight value")
2064 max_iso
: FloatProperty(
2065 name
="Max Value", default
=1, soft_min
=0, soft_max
=1,
2066 description
="Maximum weight value")
2067 n_curves
: IntProperty(
2068 name
="Curves", default
=10, soft_min
=1, soft_max
=100,
2069 description
="Number of Contour Curves")
2073 in_displace
: FloatProperty(
2074 name
="Displace A", default
=0, soft_min
=-10, soft_max
=10,
2075 description
="Pattern displace strength")
2076 out_displace
: FloatProperty(
2077 name
="Displace B", default
=2, soft_min
=-10, soft_max
=10,
2078 description
="Pattern displace strength")
2080 in_steps
: IntProperty(
2081 name
="Steps A", default
=1, min=0, soft_max
=10,
2082 description
="Number of layers to move inwards")
2083 out_steps
: IntProperty(
2084 name
="Steps B", default
=1, min=0, soft_max
=10,
2085 description
="Number of layers to move outwards")
2086 limit_z
: BoolProperty(
2087 name
="Limit Z", default
=False,
2088 description
="Limit Pattern in Z")
2090 merge
: BoolProperty(
2091 name
="Merge Vertices", default
=True,
2092 description
="Merge points")
2093 merge_thres
: FloatProperty(
2094 name
="Merge Threshold", default
=0.01, min=0, soft_max
=1,
2095 description
="Minimum Curve Radius")
2097 bevel_depth
: FloatProperty(
2098 name
="Bevel Depth", default
=0, min=0, soft_max
=1,
2100 min_bevel_depth
: FloatProperty(
2101 name
="Min Bevel Depth", default
=0.1, min=0, soft_max
=1,
2103 max_bevel_depth
: FloatProperty(
2104 name
="Max Bevel Depth", default
=1, min=0, soft_max
=1,
2106 remove_open_curves
: BoolProperty(
2107 name
="Remove Open Curves", default
=False,
2108 description
="Remove Open Curves")
2110 vertex_group_pattern
: StringProperty(
2111 name
="Displace", default
='',
2112 description
="Vertex Group used for pattern displace")
2114 vertex_group_bevel
: StringProperty(
2115 name
="Bevel", default
='',
2116 description
="Variable Bevel depth")
2118 object_name
: StringProperty(
2119 name
="Active Object", default
='',
2122 try: vg_name
= bpy
.context
.object.vertex_groups
.active
.name
2123 except: vg_name
= ''
2125 vertex_group_contour
: StringProperty(
2126 name
="Contour", default
=vg_name
,
2127 description
="Vertex Group used for contouring")
2128 clean_distance
: FloatProperty(
2129 name
="Clean Distance", default
=0, min=0, soft_max
=10,
2130 description
="Remove short segments")
2133 spiralized
: BoolProperty(
2134 name
='Spiralized', default
=False,
2135 description
='Create a Spiral Contour. Works better with dense meshes'
2137 spiral_axis
: FloatVectorProperty(
2138 name
="Spiral Axis", default
=(0,0,1),
2139 description
="Axis of the Spiral (in local coordinates)"
2141 spiral_rotation
: FloatProperty(
2142 name
="Spiral Rotation", default
=0, min=0, max=2*pi
,
2147 def poll(cls
, context
):
2149 return ob
and len(ob
.vertex_groups
) > 0 or ob
.type == 'CURVE'
2151 def invoke(self
, context
, event
):
2152 return context
.window_manager
.invoke_props_dialog(self
, width
=250)
2154 def draw(self
, context
):
2155 if not context
.object.type == 'CURVE':
2156 self
.object_name
= context
.object.name
2157 ob
= bpy
.data
.objects
[self
.object_name
]
2158 if self
.vertex_group_contour
not in [vg
.name
for vg
in ob
.vertex_groups
]:
2159 self
.vertex_group_contour
= ob
.vertex_groups
.active
.name
2160 layout
= self
.layout
2161 col
= layout
.column(align
=True)
2162 col
.prop(self
, "use_modifiers")
2163 col
.label(text
="Contour Curves:")
2164 col
.prop_search(self
, 'vertex_group_contour', ob
, "vertex_groups", text
='')
2165 row
= col
.row(align
=True)
2166 row
.prop(self
,'min_iso')
2167 row
.prop(self
,'max_iso')
2168 col
.prop(self
,'n_curves')
2170 col
.label(text
='Curves Bevel:')
2171 col
.prop(self
,'auto_bevel')
2172 if not self
.auto_bevel
:
2173 col
.prop_search(self
, 'vertex_group_bevel', ob
, "vertex_groups", text
='')
2174 if self
.vertex_group_bevel
!= '' or self
.auto_bevel
:
2175 row
= col
.row(align
=True)
2176 row
.prop(self
,'min_bevel_depth')
2177 row
.prop(self
,'max_bevel_depth')
2179 col
.prop(self
,'bevel_depth')
2182 col
.label(text
="Displace Pattern:")
2183 col
.prop_search(self
, 'vertex_group_pattern', ob
, "vertex_groups", text
='')
2184 if self
.vertex_group_pattern
!= '':
2185 row
= col
.row(align
=True)
2186 row
.prop(self
,'in_steps')
2187 row
.prop(self
,'out_steps')
2188 row
= col
.row(align
=True)
2189 row
.prop(self
,'in_displace')
2190 row
.prop(self
,'out_displace')
2191 col
.prop(self
,'limit_z')
2193 row
=col
.row(align
=True)
2194 row
.prop(self
,'spiralized')
2195 row
.label(icon
='MOD_SCREW')
2197 #row=col.row(align=True)
2198 #row.prop(self,'spiral_axis')
2200 col
.prop(self
,'spiral_rotation')
2203 col
.label(text
='Clean Curves:')
2204 col
.prop(self
,'clean_distance')
2205 col
.prop(self
,'remove_open_curves')
2207 def execute(self
, context
):
2208 n_curves
= self
.n_curves
2209 start_time
= timeit
.default_timer()
2211 check
= context
.object.vertex_groups
[0]
2213 self
.report({'ERROR'}, "The object doesn't have Vertex Groups")
2214 return {'CANCELLED'}
2215 ob0
= bpy
.data
.objects
[self
.object_name
]
2217 dg
= context
.evaluated_depsgraph_get()
2218 ob
= ob0
.evaluated_get(dg
)
2221 # generate new bmesh
2224 n_verts
= len(bm
.verts
)
2226 # store weight values
2228 weight
= get_weight_numpy(ob
.vertex_groups
[self
.vertex_group_contour
], len(me0
.vertices
))
2231 self
.report({'ERROR'}, "Please select a Vertex Group for contouring")
2232 return {'CANCELLED'}
2235 pattern_weight
= get_weight_numpy(ob
.vertex_groups
[self
.vertex_group_pattern
], len(me0
.vertices
))
2237 #self.report({'WARNING'}, "There is no Vertex Group assigned to the pattern displace")
2238 pattern_weight
= np
.zeros(len(me0
.vertices
))
2240 variable_bevel
= False
2242 bevel_weight
= get_weight_numpy(ob
.vertex_groups
[self
.vertex_group_bevel
], len(me0
.vertices
))
2243 variable_bevel
= True
2245 bevel_weight
= np
.ones(len(me0
.vertices
))
2248 # calc weight density
2249 bevel_weight
= np
.ones(len(me0
.vertices
))*10000
2250 bevel_weight
= np
.zeros(len(me0
.vertices
))
2251 edges_length
= np
.array([e
.calc_length() for e
in bm
.edges
])
2252 edges_dw
= np
.array([max(abs(weight
[e
.verts
[0].index
]-weight
[e
.verts
[1].index
]),0.000001) for e
in bm
.edges
])
2253 dens
= edges_length
/edges_dw
2254 n_records
= np
.zeros(len(me0
.vertices
))
2255 for i
, e
in enumerate(bm
.edges
):
2258 #bevel_weight[id] = min(bevel_weight[id], dens[i])
2259 bevel_weight
[id] += dens
[i
]
2261 bevel_weight
= bevel_weight
/n_records
2262 bevel_weight
= (bevel_weight
- min(bevel_weight
))/(max(bevel_weight
) - min(bevel_weight
))
2263 #bevel_weight = 1-bevel_weight
2264 variable_bevel
= True
2266 #filtered_edges = bm.edges
2267 total_verts
= np
.zeros((0,3))
2268 total_radii
= np
.zeros((0,1))
2269 total_segments
= []# np.array([])
2272 # start iterate contours levels
2273 vertices
, normals
= get_vertices_and_normals_numpy(me0
)
2274 filtered_edges
= get_edges_id_numpy(me0
)
2277 min_iso
= min(self
.min_iso
, self
.max_iso
)
2278 max_iso
= max(self
.min_iso
, self
.max_iso
)
2284 ang
= self
.spiral_rotation
+ weight
*pi
*n_curves
+arctan2(nx
,ny
)
2285 weight
= sin(ang
)/2+0.5
2289 delta_iso
= (max_iso
-min_iso
)/(n_curves
-1)
2294 faces_weight
= [np
.array([weight
[v
] for v
in p
.vertices
]) for p
in me0
.polygons
]
2295 fw_min
= np
.array([np
.min(fw
) for fw
in faces_weight
])
2296 fw_max
= np
.array([np
.max(fw
) for fw
in faces_weight
])
2298 bm_faces
= np
.array(bm
.faces
)
2300 #print("Contour Curves, data loaded: " + str(timeit.default_timer() - start_time) + " sec")
2301 step_time
= timeit
.default_timer()
2302 for c
in range(n_curves
):
2304 iso_val
= c
*delta_iso
+ min_iso
2305 if iso_val
< 0: iso_val
= (min_iso
+ max_iso
)/2
2307 iso_val
= (min_iso
+ max_iso
)/2
2309 #if c == 0 and self.auto_bevel:
2312 # remove passed faces
2313 bool_mask
= iso_val
< fw_max
2314 bm_faces
= bm_faces
[bool_mask
]
2315 fw_min
= fw_min
[bool_mask
]
2316 fw_max
= fw_max
[bool_mask
]
2319 bool_mask
= fw_min
< iso_val
2320 faces_mask
= bm_faces
[bool_mask
]
2322 count
= len(total_verts
)
2324 new_filtered_edges
, edges_index
, verts
, bevel
= contour_edges_pattern(self
, c
, len(total_verts
), iso_val
, vertices
, normals
, filtered_edges
, weight
, pattern_weight
, bevel_weight
)
2326 if len(edges_index
) > 0:
2327 if self
.auto_bevel
and False:
2328 bevel
= 1-dens
[edges_index
]
2329 bevel
= bevel
[:,np
.newaxis
]
2330 if self
.max_bevel_depth
!= self
.min_bevel_depth
:
2331 min_radius
= self
.min_bevel_depth
/ max(0.0001,self
.max_bevel_depth
)
2332 radii
= min_radius
+ bevel
*(1 - min_radius
)
2338 if verts
[0,0] == None: continue
2339 else: filtered_edges
= new_filtered_edges
2341 for i
, id in enumerate(edges_index
): edges_id
[id] = i
+ count
2343 if len(verts
) == 0: continue
2347 for f
in faces_mask
:
2351 #seg.append(new_ids[np.where(edges_index == e.index)[0][0]])
2352 seg
.append(edges_id
[e
.index
])
2354 segments
.append(seg
)
2358 total_segments
= total_segments
+ segments
2359 total_verts
= np
.concatenate((total_verts
, verts
))
2360 total_radii
= np
.concatenate((total_radii
, radii
))
2362 if self
.min_rad
!= self
.max_rad
:
2364 iso_rad
= c
*(self
.max_rad
-self
.min_rad
)/(self
.n_curves
-1)+self
.min_rad
2365 if iso_rad
< 0: iso_rad
= (self
.min_rad
+ self
.max_rad
)/2
2367 iso_rad
= (self
.min_rad
+ self
.max_rad
)/2
2368 radius
= radius
+ [iso_rad
]*len(verts
)
2369 #print("Contour Curves, points computing: " + str(timeit.default_timer() - step_time) + " sec")
2370 step_time
= timeit
.default_timer()
2372 if len(total_segments
) > 0:
2373 step_time
= timeit
.default_timer()
2374 ordered_points
= find_curves(total_segments
, len(total_verts
))
2376 #print("Contour Curves, point ordered in: " + str(timeit.default_timer() - step_time) + " sec")
2377 step_time
= timeit
.default_timer()
2378 crv
= curve_from_pydata(total_verts
, total_radii
, ordered_points
, ob0
.name
+ '_ContourCurves', self
.remove_open_curves
, merge_distance
=self
.clean_distance
)
2379 context
.view_layer
.objects
.active
= crv
2380 if variable_bevel
: crv
.data
.bevel_depth
= self
.max_bevel_depth
2381 else: crv
.data
.bevel_depth
= self
.bevel_depth
2383 crv
.select_set(True)
2384 ob0
.select_set(False)
2385 crv
.matrix_world
= ob0
.matrix_world
2386 #print("Contour Curves, curves created in: " + str(timeit.default_timer() - step_time) + " sec")
2389 self
.report({'ERROR'}, "There are no values in the chosen range")
2390 return {'CANCELLED'}
2392 print("Contour Curves, total time: " + str(timeit
.default_timer() - start_time
) + " sec")
2395 class vertex_colors_to_vertex_groups(Operator
):
2396 bl_idname
= "object.vertex_colors_to_vertex_groups"
2397 bl_label
= "Vertex Color"
2398 bl_options
= {'REGISTER', 'UNDO'}
2399 bl_description
= ("Convert the active Vertex Color into a Vertex Group")
2402 name
="red channel", default
=False, description
="convert red channel")
2403 green
: BoolProperty(
2404 name
="green channel", default
=False,
2405 description
="convert green channel")
2406 blue
: BoolProperty(
2407 name
="blue channel", default
=False, description
="convert blue channel")
2408 value
: BoolProperty(
2409 name
="value channel", default
=True, description
="convert value channel")
2410 invert
: BoolProperty(
2411 name
="invert", default
=False, description
="invert all color channels")
2414 def poll(cls
, context
):
2416 return len(context
.object.data
.vertex_colors
) > 0
2417 except: return False
2419 def execute(self
, context
):
2420 obj
= context
.active_object
2421 id = len(obj
.vertex_groups
)
2427 boolCol
= len(obj
.data
.vertex_colors
)
2428 if(boolCol
): col_name
= obj
.data
.vertex_colors
.active
.name
2429 bpy
.ops
.object.mode_set(mode
='EDIT')
2430 bpy
.ops
.mesh
.select_all(action
='SELECT')
2432 if(self
.red
and boolCol
):
2433 bpy
.ops
.object.vertex_group_add()
2434 bpy
.ops
.object.vertex_group_assign()
2436 obj
.vertex_groups
[id_red
].name
= col_name
+ '_red'
2438 if(self
.green
and boolCol
):
2439 bpy
.ops
.object.vertex_group_add()
2440 bpy
.ops
.object.vertex_group_assign()
2442 obj
.vertex_groups
[id_green
].name
= col_name
+ '_green'
2444 if(self
.blue
and boolCol
):
2445 bpy
.ops
.object.vertex_group_add()
2446 bpy
.ops
.object.vertex_group_assign()
2448 obj
.vertex_groups
[id_blue
].name
= col_name
+ '_blue'
2450 if(self
.value
and boolCol
):
2451 bpy
.ops
.object.vertex_group_add()
2452 bpy
.ops
.object.vertex_group_assign()
2454 obj
.vertex_groups
[id_value
].name
= col_name
+ '_value'
2458 if(self
.invert
): mult
= -1
2459 bpy
.ops
.object.mode_set(mode
='OBJECT')
2460 sub_red
= 1 + self
.value
+ self
.blue
+ self
.green
2461 sub_green
= 1 + self
.value
+ self
.blue
2462 sub_blue
= 1 + self
.value
2465 id = len(obj
.vertex_groups
)
2466 if(id_red
<= id and id_green
<= id and id_blue
<= id and id_value
<= \
2468 v_colors
= obj
.data
.vertex_colors
.active
.data
2470 for f
in obj
.data
.polygons
:
2471 for v
in f
.vertices
:
2472 gr
= obj
.data
.vertices
[v
].groups
2473 if(self
.red
): gr
[min(len(gr
)-sub_red
, id_red
)].weight
= \
2474 self
.invert
+ mult
* v_colors
[i
].color
[0]
2475 if(self
.green
): gr
[min(len(gr
)-sub_green
, id_green
)].weight\
2476 = self
.invert
+ mult
* v_colors
[i
].color
[1]
2477 if(self
.blue
): gr
[min(len(gr
)-sub_blue
, id_blue
)].weight
= \
2478 self
.invert
+ mult
* v_colors
[i
].color
[2]
2480 r
= v_colors
[i
].color
[0]
2481 g
= v_colors
[i
].color
[1]
2482 b
= v_colors
[i
].color
[2]
2483 gr
[min(len(gr
)-sub_value
, id_value
)].weight\
2484 = self
.invert
+ mult
* (0.2126*r
+ 0.7152*g
+ 0.0722*b
)
2486 bpy
.ops
.paint
.weight_paint_toggle()
2489 class vertex_group_to_vertex_colors(Operator
):
2490 bl_idname
= "object.vertex_group_to_vertex_colors"
2491 bl_label
= "Vertex Group"
2492 bl_options
= {'REGISTER', 'UNDO'}
2493 bl_description
= ("Convert the active Vertex Group into a Vertex Color")
2495 channel
: EnumProperty(
2496 items
=[('BLUE', 'Blue Channel', 'Convert to Blue Channel'),
2497 ('GREEN', 'Green Channel', 'Convert to Green Channel'),
2498 ('RED', 'Red Channel', 'Convert to Red Channel'),
2499 ('VALUE', 'Value Channel', 'Convert to Grayscale'),
2500 ('FALSE_COLORS', 'False Colors', 'Convert to False Colors')],
2501 name
="Convert to", description
="Choose how to convert vertex group",
2502 default
="VALUE", options
={'LIBRARY_EDITABLE'})
2504 invert
: BoolProperty(
2505 name
="invert", default
=False, description
="invert color channel")
2508 def poll(cls
, context
):
2509 return len(context
.object.vertex_groups
) > 0
2511 def execute(self
, context
):
2512 obj
= context
.active_object
2514 group_id
= obj
.vertex_groups
.active_index
2515 if (group_id
== -1):
2518 bpy
.ops
.object.mode_set(mode
='OBJECT')
2519 group_name
= obj
.vertex_groups
[group_id
].name
2520 me
.vertex_colors
.new()
2521 colors_id
= obj
.data
.vertex_colors
.active_index
2523 colors_name
= group_name
2524 if(self
.channel
== 'FALSE_COLORS'): colors_name
+= "_false_colors"
2525 elif(self
.channel
== 'VALUE'): colors_name
+= "_value"
2526 elif(self
.channel
== 'RED'): colors_name
+= "_red"
2527 elif(self
.channel
== 'GREEN'): colors_name
+= "_green"
2528 elif(self
.channel
== 'BLUE'): colors_name
+= "_blue"
2529 context
.object.data
.vertex_colors
[colors_id
].name
= colors_name
2531 v_colors
= obj
.data
.vertex_colors
.active
.data
2535 dvert_lay
= bm
.verts
.layers
.deform
.active
2536 weight
= bmesh_get_weight_numpy(group_id
,dvert_lay
,bm
.verts
)
2537 if self
.invert
: weight
= 1-weight
2538 loops_size
= get_attribute_numpy(me
.polygons
, attribute
='loop_total', mult
=1)
2539 n_colors
= np
.sum(loops_size
)
2540 verts
= np
.ones(n_colors
)
2541 me
.polygons
.foreach_get('vertices',verts
)
2542 splitted_weight
= weight
[verts
.astype(int)][:,None]
2543 r
= np
.zeros(splitted_weight
.shape
)
2544 g
= np
.zeros(splitted_weight
.shape
)
2545 b
= np
.zeros(splitted_weight
.shape
)
2546 a
= np
.ones(splitted_weight
.shape
)
2547 if(self
.channel
== 'FALSE_COLORS'):
2548 mult
= 0.6+0.4*splitted_weight
2549 mask
= splitted_weight
< 0.25
2550 g
[mask
] = splitted_weight
[mask
]*4
2551 b
[mask
] = np
.ones(splitted_weight
.shape
)[mask
]
2553 mask
= np
.where(np
.logical_and(splitted_weight
>=0.25, splitted_weight
<0.5))
2554 g
[mask
] = np
.ones(splitted_weight
.shape
)[mask
]
2555 b
[mask
] = (1-(splitted_weight
[mask
]-0.25)*4)
2557 mask
= np
.where(np
.logical_and(splitted_weight
>=0.5, splitted_weight
<0.75))
2558 r
[mask
] = (splitted_weight
[mask
]-0.5)*4
2559 g
[mask
] = np
.ones(splitted_weight
.shape
)[mask
]
2561 mask
= 0.75 <= splitted_weight
2562 r
[mask
] = np
.ones(splitted_weight
.shape
)[mask
]
2563 g
[mask
] = (1-(splitted_weight
[mask
]-0.75)*4)
2564 elif(self
.channel
== 'VALUE'):
2568 elif(self
.channel
== 'RED'):
2570 elif(self
.channel
== 'GREEN'):
2572 elif(self
.channel
== 'BLUE'):
2575 colors
= np
.concatenate((r
,g
,b
,a
),axis
=1).flatten()
2576 v_colors
.foreach_set('color',colors
)
2578 bpy
.ops
.paint
.vertex_paint_toggle()
2579 context
.object.data
.vertex_colors
[colors_id
].active_render
= True
2582 class vertex_group_to_uv(Operator
):
2583 bl_idname
= "object.vertex_group_to_uv"
2584 bl_label
= "Vertex Group"
2585 bl_options
= {'REGISTER', 'UNDO'}
2586 bl_description
= ("Combine two Vertex Groups as UV Map Layer")
2588 vertex_group_u
: StringProperty(
2589 name
="U", default
='',
2590 description
="Vertex Group used for the U coordinate")
2591 vertex_group_v
: StringProperty(
2592 name
="V", default
='',
2593 description
="Vertex Group used for the V coordinate")
2594 normalize_weight
: BoolProperty(
2595 name
="Normalize Weight", default
=True,
2596 description
="Normalize weight values")
2597 invert_u
: BoolProperty(
2598 name
="Invert U", default
=False, description
="Invert U")
2599 invert_v
: BoolProperty(
2600 name
="Invert V", default
=False, description
="Invert V")
2603 def poll(cls
, context
):
2604 return len(context
.object.vertex_groups
) > 0
2606 def invoke(self
, context
, event
):
2607 return context
.window_manager
.invoke_props_dialog(self
, width
=250)
2609 def draw(self
, context
):
2611 layout
= self
.layout
2612 col
= layout
.column(align
=True)
2613 row
= col
.row(align
=True)
2614 row
.prop_search(self
, 'vertex_group_u', ob
, "vertex_groups", text
='')
2616 row
.prop_search(self
, 'vertex_group_v', ob
, "vertex_groups", text
='')
2617 row
= col
.row(align
=True)
2618 row
.prop(self
, "invert_u")
2620 row
.prop(self
, "invert_v")
2621 row
= col
.row(align
=True)
2622 row
.prop(self
, "normalize_weight")
2624 def execute(self
, context
):
2625 ob
= context
.active_object
2627 n_verts
= len(me
.vertices
)
2628 vg_keys
= ob
.vertex_groups
.keys()
2629 bool_u
= self
.vertex_group_u
in vg_keys
2630 bool_v
= self
.vertex_group_v
in vg_keys
2631 if bool_u
or bool_v
:
2634 dvert_lay
= bm
.verts
.layers
.deform
.active
2636 u_index
= ob
.vertex_groups
[self
.vertex_group_u
].index
2637 u
= bmesh_get_weight_numpy(u_index
, dvert_lay
, bm
.verts
)
2640 if self
.normalize_weight
:
2641 u
= np
.interp(u
, (u
.min(), u
.max()), (0, 1))
2643 u
= np
.zeros(n_verts
)
2645 v_index
= ob
.vertex_groups
[self
.vertex_group_v
].index
2646 v
= bmesh_get_weight_numpy(v_index
, dvert_lay
, bm
.verts
)
2649 if self
.normalize_weight
:
2650 v
= np
.interp(v
, (v
.min(), v
.max()), (0, 1))
2652 v
= np
.zeros(n_verts
)
2654 u
= v
= np
.zeros(n_verts
)
2656 uv_layer
= me
.uv_layers
.new(name
='Weight_to_UV')
2657 loops_size
= get_attribute_numpy(me
.polygons
, attribute
='loop_total', mult
=1)
2658 n_data
= np
.sum(loops_size
)
2659 v_id
= np
.ones(n_data
)
2660 me
.polygons
.foreach_get('vertices',v_id
)
2661 v_id
= v_id
.astype(int)
2662 split_u
= u
[v_id
,None]
2663 split_v
= v
[v_id
,None]
2664 uv
= np
.concatenate((split_u
,split_v
),axis
=1).flatten()
2665 uv_layer
.data
.foreach_set('uv',uv
)
2666 me
.uv_layers
.update()
2669 class curvature_to_vertex_groups(Operator
):
2670 bl_idname
= "object.curvature_to_vertex_groups"
2671 bl_label
= "Curvature"
2672 bl_options
= {'REGISTER', 'UNDO'}
2673 bl_description
= ("Generate a Vertex Group based on the curvature of the"
2674 "mesh. Is based on Dirty Vertex Color")
2676 invert
: BoolProperty(
2677 name
="invert", default
=False, description
="invert values")
2679 blur_strength
: FloatProperty(
2680 name
="Blur Strength", default
=1, min=0.001,
2681 max=1, description
="Blur strength per iteration")
2683 blur_iterations
: IntProperty(
2684 name
="Blur Iterations", default
=1, min=0,
2685 max=40, description
="Number of times to blur the values")
2687 min_angle
: FloatProperty(
2688 name
="Min Angle", default
=0, min=0,
2689 max=pi
/2, subtype
='ANGLE', description
="Minimum angle")
2691 max_angle
: FloatProperty(
2692 name
="Max Angle", default
=pi
, min=pi
/2,
2693 max=pi
, subtype
='ANGLE', description
="Maximum angle")
2695 invert
: BoolProperty(
2696 name
="Invert", default
=False,
2697 description
="Invert the curvature map")
2699 def execute(self
, context
):
2700 bpy
.ops
.object.mode_set(mode
='OBJECT')
2701 vertex_colors
= context
.active_object
.data
.vertex_colors
2703 vertex_colors
[-1].active
= True
2704 vertex_colors
[-1].active_render
= True
2705 vertex_colors
[-1].name
= "Curvature"
2706 for c
in vertex_colors
[-1].data
: c
.color
= (1,1,1,1)
2707 bpy
.ops
.object.mode_set(mode
='VERTEX_PAINT')
2708 bpy
.ops
.paint
.vertex_color_dirt(
2709 blur_strength
=self
.blur_strength
,
2710 blur_iterations
=self
.blur_iterations
, clean_angle
=self
.max_angle
,
2711 dirt_angle
=self
.min_angle
)
2712 bpy
.ops
.object.vertex_colors_to_vertex_groups(invert
=self
.invert
)
2713 vertex_colors
.remove(vertex_colors
.active
)
2716 class face_area_to_vertex_groups(Operator
):
2717 bl_idname
= "object.face_area_to_vertex_groups"
2719 bl_options
= {'REGISTER', 'UNDO'}
2720 bl_description
= ("Generate a Vertex Group based on the area of individual"
2723 invert
: BoolProperty(
2724 name
="invert", default
=False, description
="invert values")
2725 bounds
: EnumProperty(
2726 items
=(('MANUAL', "Manual Bounds", ""),
2727 ('AUTOMATIC', "Automatic Bounds", "")),
2728 default
='AUTOMATIC', name
="Bounds")
2730 min_area
: FloatProperty(
2731 name
="Min", default
=0.01, soft_min
=0, soft_max
=1,
2732 description
="Faces with 0 weight")
2734 max_area
: FloatProperty(
2735 name
="Max", default
=0.1, soft_min
=0, soft_max
=1,
2736 description
="Faces with 1 weight")
2738 def draw(self
, context
):
2739 layout
= self
.layout
2740 layout
.label(text
="Bounds")
2741 layout
.prop(self
, "bounds", text
="")
2742 if self
.bounds
== 'MANUAL':
2743 layout
.prop(self
, "min_area")
2744 layout
.prop(self
, "max_area")
2746 def execute(self
, context
):
2747 try: ob
= context
.object
2749 self
.report({'ERROR'}, "Please select an Object")
2750 return {'CANCELLED'}
2751 ob
.vertex_groups
.new(name
="Faces Area")
2753 areas
= [[] for v
in ob
.data
.vertices
]
2755 for p
in ob
.data
.polygons
:
2756 for v
in p
.vertices
:
2757 areas
[v
].append(p
.area
)
2759 for i
in range(len(areas
)):
2760 areas
[i
] = mean(areas
[i
])
2761 if self
.bounds
== 'MANUAL':
2762 min_area
= self
.min_area
2763 max_area
= self
.max_area
2764 elif self
.bounds
== 'AUTOMATIC':
2765 min_area
= min(areas
)
2766 max_area
= max(areas
)
2767 elif self
.bounds
== 'COMPRESSION':
2769 max_area
= min(areas
)
2770 elif self
.bounds
== 'TENSION':
2772 max_area
= max(areas
)
2773 delta_area
= max_area
- min_area
2776 if self
.bounds
== 'MANUAL':
2779 self
.report({'ERROR'}, "The faces have the same areas")
2780 #return {'CANCELLED'}
2781 for i
in range(len(areas
)):
2782 weight
= (areas
[i
] - min_area
)/delta_area
2783 ob
.vertex_groups
[-1].add([i
], weight
, 'REPLACE')
2784 ob
.vertex_groups
.update()
2786 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
2789 class random_weight(Operator
):
2790 bl_idname
= "object.random_weight"
2792 bl_options
= {'REGISTER', 'UNDO'}
2793 bl_description
= ("Generate a random Vertex Group")
2795 min_val
: FloatProperty(
2796 name
="Min", default
=0, soft_min
=0, soft_max
=1,
2797 description
="Minimum Value")
2799 max_val
: FloatProperty(
2800 name
="Max", default
=1, soft_min
=0, soft_max
=1,
2801 description
="Maximum Value")
2803 #def draw(self, context):
2804 # layout = self.layout
2805 # layout.prop(self, "min_area")
2806 # layout.prop(self, "max_area")
2808 def poll(cls
, context
):
2809 return len(context
.object.vertex_groups
) > 0
2811 def execute(self
, context
):
2812 try: ob
= context
.object
2814 self
.report({'ERROR'}, "Please select an Object")
2815 return {'CANCELLED'}
2816 #ob.vertex_groups.new(name="Random")
2817 n_verts
= len(ob
.data
.vertices
)
2818 weight
= np
.random
.uniform(low
=self
.min_val
, high
=self
.max_val
, size
=(n_verts
,))
2819 np
.clip(weight
, 0, 1, out
=weight
)
2821 group_id
= ob
.vertex_groups
.active_index
2822 for i
in range(n_verts
):
2823 ob
.vertex_groups
[group_id
].add([i
], weight
[i
], 'REPLACE')
2824 ob
.vertex_groups
.update()
2826 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
2830 class harmonic_weight(Operator
):
2831 bl_idname
= "object.harmonic_weight"
2832 bl_label
= "Harmonic"
2833 bl_options
= {'REGISTER', 'UNDO'}
2834 bl_description
= ("Create an harmonic variation of the active Vertex Group")
2836 freq
: FloatProperty(
2837 name
="Frequency", default
=20, soft_min
=0,
2838 soft_max
=100, description
="Wave frequency")
2840 amp
: FloatProperty(
2841 name
="Amplitude", default
=1, soft_min
=0,
2842 soft_max
=10, description
="Wave amplitude")
2844 midlevel
: FloatProperty(
2845 name
="Midlevel", default
=0, min=-1,
2846 max=1, description
="Midlevel")
2848 add
: FloatProperty(
2849 name
="Add", default
=0, min=-1,
2850 max=1, description
="Add to the Weight")
2852 mult
: FloatProperty(
2853 name
="Multiply", default
=0, min=0,
2854 max=1, description
="Multiply for he Weight")
2857 def poll(cls
, context
):
2858 return len(context
.object.vertex_groups
) > 0
2860 def execute(self
, context
):
2861 ob
= context
.active_object
2862 if len(ob
.vertex_groups
) > 0:
2863 group_id
= ob
.vertex_groups
.active_index
2864 ob
.vertex_groups
.new(name
="Harmonic")
2865 for i
in range(len(ob
.data
.vertices
)):
2866 try: val
= ob
.vertex_groups
[group_id
].weight(i
)
2868 weight
= self
.amp
*(math
.sin(val
*self
.freq
) - self
.midlevel
)/2 + 0.5 + self
.add
*val
*(1-(1-val
)*self
.mult
)
2869 ob
.vertex_groups
[-1].add([i
], weight
, 'REPLACE')
2872 self
.report({'ERROR'}, "Active object doesn't have vertex groups")
2873 return {'CANCELLED'}
2874 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
2878 class tissue_weight_distance(Operator
):
2879 bl_idname
= "object.tissue_weight_distance"
2880 bl_label
= "Weight Distance"
2881 bl_options
= {'REGISTER', 'UNDO'}
2882 bl_description
= ("Create a weight map according to the distance from the "
2883 "selected vertices along the mesh surface")
2885 mode
: EnumProperty(
2886 items
=(('GEOD', "Geodesic Distance", ""),
2887 ('EUCL', "Euclidean Distance", ""),
2888 ('TOPO', "Topology Distance", "")),
2889 default
='GEOD', name
="Distance Method")
2891 normalize
: BoolProperty(
2892 name
="Normalize", default
=True,
2893 description
="Automatically remap the distance values from 0 to 1")
2895 min_value
: FloatProperty(
2896 name
="Min", default
=0, min=0,
2897 soft_max
=100, description
="Minimum Distance")
2899 max_value
: FloatProperty(
2900 name
="Max", default
=10, min=0,
2901 soft_max
=100, description
="Max Distance")
2903 def invoke(self
, context
, event
):
2904 return context
.window_manager
.invoke_props_dialog(self
, width
=250)
2906 def fill_neighbors(self
,verts
,weight
):
2909 for f
in v0
.link_faces
:
2911 if self
.mode
== 'GEOD':
2912 dist
= weight
[v0
.index
] + (v0
.co
-v1
.co
).length
2913 elif self
.mode
== 'TOPO':
2914 dist
= weight
[v0
.index
] + 1.0
2915 w1
= weight
[v1
.index
]
2916 if w1
== None or w1
> dist
:
2917 weight
[v1
.index
] = dist
2919 if len(neigh
) == 0: return weight
2920 else: return self
.fill_neighbors(neigh
.keys(), weight
)
2922 def execute(self
, context
):
2925 if old_mode
!= 'OBJECT':
2926 bpy
.ops
.object.mode_set(mode
='OBJECT')
2930 # store weight values
2931 weight
= [None]*len(me
.vertices
)
2933 if self
.mode
!= 'EUCL':
2936 bm
.verts
.ensure_lookup_table()
2937 bm
.edges
.ensure_lookup_table()
2938 bm
.faces
.ensure_lookup_table()
2939 selected
= [v
for v
in bm
.verts
if v
.select
]
2940 if len(selected
) == 0:
2941 bpy
.ops
.object.mode_set(mode
=old_mode
)
2942 message
= "Please, select one or more vertices"
2943 self
.report({'ERROR'}, message
)
2944 return {'CANCELLED'}
2945 for v
in selected
: weight
[v
.index
] = 0
2946 weight
= self
.fill_neighbors(selected
, weight
)
2949 selected
= [v
for v
in me
.vertices
if v
.select
]
2950 kd
= KDTree(len(selected
))
2951 for i
, v
in enumerate(selected
):
2954 for i
,v
in enumerate(me
.vertices
):
2955 co
, index
, dist
= kd
.find(v
.co
)
2959 for i
in range(len(weight
)):
2960 if weight
[i
] == None: weight
[i
] = 0
2961 weight
= np
.array(weight
)
2962 max_dist
= np
.max(weight
)
2967 delta_value
= self
.max_value
- self
.min_value
2968 if delta_value
== 0: delta_value
= 0.0000001
2969 weight
= (weight
-self
.min_value
)/delta_value
2971 if self
.mode
== 'TOPO':
2972 vg
= ob
.vertex_groups
.new(name
='Distance: {:d}'.format(int(max_dist
)))
2974 vg
= ob
.vertex_groups
.new(name
='Distance: {:.4f}'.format(max_dist
))
2975 for i
, w
in enumerate(weight
):
2976 vg
.add([i
], w
, 'REPLACE')
2977 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
2980 class TISSUE_PT_color(Panel
):
2981 bl_label
= "Tissue Tools"
2982 bl_category
= "Tissue"
2983 bl_space_type
= "VIEW_3D"
2984 bl_region_type
= "UI"
2985 #bl_options = {'DEFAULT_CLOSED'}
2986 bl_context
= "vertexpaint"
2988 def draw(self
, context
):
2989 layout
= self
.layout
2990 col
= layout
.column(align
=True)
2991 col
.operator("object.vertex_colors_to_vertex_groups",
2992 icon
="GROUP_VERTEX", text
="Convert to Weight")
2994 class TISSUE_PT_weight(Panel
):
2995 bl_label
= "Tissue Tools"
2996 bl_category
= "Tissue"
2997 bl_space_type
= "VIEW_3D"
2998 bl_region_type
= "UI"
2999 #bl_options = {'DEFAULT_CLOSED'}
3000 bl_context
= "weightpaint"
3002 def draw(self
, context
):
3003 layout
= self
.layout
3004 col
= layout
.column(align
=True)
3005 #if context.object.type == 'MESH' and context.mode == 'OBJECT':
3006 #col.label(text="Transform:")
3008 #elif bpy.context.mode == 'PAINT_WEIGHT':
3009 col
.label(text
="Weight Generate:")
3011 # "object.vertex_colors_to_vertex_groups", icon="GROUP_VCOL")
3012 col
.operator("object.face_area_to_vertex_groups", icon
="FACESEL")
3013 col
.operator("object.curvature_to_vertex_groups", icon
="SMOOTHCURVE")
3014 col
.operator("object.tissue_weight_distance", icon
="TRACKING")
3015 row
= col
.row(align
=True)
3016 try: row
.operator("object.weight_formula", icon
="CON_TRANSFORM")
3017 except: row
.operator("object.weight_formula")#, icon="CON_TRANSFORM")
3018 row
.operator("object.update_weight_formula", icon
="FILE_REFRESH", text
='')#, icon="CON_TRANSFORM")
3019 #col.label(text="Weight Processing:")
3023 col
.operator("object.weight_laplacian", icon
="SMOOTHCURVE")
3025 col
.label(text
="Weight Edit:")
3026 col
.operator("object.harmonic_weight", icon
="IPO_ELASTIC")
3027 col
.operator("object.random_weight", icon
="RNDCURVE")
3029 col
.label(text
="Deformation Analysis:")
3030 col
.operator("object.edges_deformation", icon
="DRIVER_DISTANCE")#FULLSCREEN_ENTER")
3031 col
.operator("object.edges_bending", icon
="DRIVER_ROTATIONAL_DIFFERENCE")#"MOD_SIMPLEDEFORM")
3033 col
.label(text
="Weight Curves:")
3034 #col.operator("object.weight_contour_curves", icon="MOD_CURVE")
3035 col
.operator("object.tissue_weight_streamlines", icon
="ANIM")
3036 col
.operator("object.tissue_weight_contour_curves_pattern", icon
="FORCE_TURBULENCE")
3038 col
.operator("object.weight_contour_displace", icon
="MOD_DISPLACE")
3039 col
.operator("object.weight_contour_mask", icon
="MOD_MASK")
3041 col
.label(text
="Simulations:")
3042 #col.operator("object.reaction_diffusion", icon="MOD_OCEAN")
3043 col
.operator("object.start_reaction_diffusion",
3044 icon
="EXPERIMENTAL",
3045 text
="Reaction-Diffusion")
3047 col
.label(text
="Materials:")
3048 col
.operator("object.random_materials", icon
='COLOR')
3049 col
.operator("object.weight_to_materials", icon
='GROUP_VERTEX')
3051 col
.label(text
="Weight Convert:")
3052 col
.operator("object.vertex_group_to_vertex_colors", icon
="GROUP_VCOL",
3053 text
="Convert to Colors")
3054 col
.operator("object.vertex_group_to_uv", icon
="UV",
3055 text
="Convert to UV")
3057 #col.prop(context.object, "reaction_diffusion_run", icon="PLAY", text="Run Simulation")
3058 ####col.prop(context.object, "reaction_diffusion_run")
3060 #col.label(text="Vertex Color from:")
3061 #col.operator("object.vertex_group_to_vertex_colors", icon="GROUP_VERTEX")
3066 class start_reaction_diffusion(Operator
):
3067 bl_idname
= "object.start_reaction_diffusion"
3068 bl_label
= "Start Reaction Diffusion"
3069 bl_description
= ("Run a Reaction-Diffusion based on existing Vertex Groups: A and B")
3070 bl_options
= {'REGISTER', 'UNDO'}
3073 name
="Run Reaction-Diffusion", default
=True, description
="Compute a new iteration on frame changes")
3075 time_steps
: IntProperty(
3076 name
="Steps", default
=10, min=0, soft_max
=50,
3077 description
="Number of Steps")
3080 name
="dt", default
=1, min=0, soft_max
=0.2,
3081 description
="Time Step")
3083 diff_a
: FloatProperty(
3084 name
="Diff A", default
=0.18, min=0, soft_max
=2,
3085 description
="Diffusion A")
3087 diff_b
: FloatProperty(
3088 name
="Diff B", default
=0.09, min=0, soft_max
=2,
3089 description
="Diffusion B")
3092 name
="f", default
=0.055, min=0, soft_min
=0.01, soft_max
=0.06, max=0.1, precision
=4,
3093 description
="Feed Rate")
3096 name
="k", default
=0.062, min=0, soft_min
=0.035, soft_max
=0.065, max=0.1, precision
=4,
3097 description
="Kill Rate")
3100 def poll(cls
, context
):
3101 return context
.object.type == 'MESH' and context
.mode
!= 'EDIT_MESH'
3103 def execute(self
, context
):
3104 reaction_diffusion_add_handler(self
, context
)
3105 set_animatable_fix_handler(self
, context
)
3109 ob
.reaction_diffusion_settings
.run
= self
.run
3110 ob
.reaction_diffusion_settings
.dt
= self
.dt
3111 ob
.reaction_diffusion_settings
.time_steps
= self
.time_steps
3112 ob
.reaction_diffusion_settings
.f
= self
.f
3113 ob
.reaction_diffusion_settings
.k
= self
.k
3114 ob
.reaction_diffusion_settings
.diff_a
= self
.diff_a
3115 ob
.reaction_diffusion_settings
.diff_b
= self
.diff_b
3118 # check vertex group A
3120 vg
= ob
.vertex_groups
['A']
3122 ob
.vertex_groups
.new(name
='A')
3123 # check vertex group B
3125 vg
= ob
.vertex_groups
['B']
3127 ob
.vertex_groups
.new(name
='B')
3129 for v
in ob
.data
.vertices
:
3130 ob
.vertex_groups
['A'].add([v
.index
], 1, 'REPLACE')
3131 ob
.vertex_groups
['B'].add([v
.index
], 0, 'REPLACE')
3133 ob
.vertex_groups
.update()
3135 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
3139 class reset_reaction_diffusion_weight(Operator
):
3140 bl_idname
= "object.reset_reaction_diffusion_weight"
3141 bl_label
= "Reset Reaction Diffusion Weight"
3142 bl_description
= ("Set A and B weight to default values")
3143 bl_options
= {'REGISTER', 'UNDO'}
3146 def poll(cls
, context
):
3147 return context
.object.type == 'MESH' and context
.mode
!= 'EDIT_MESH'
3149 def execute(self
, context
):
3150 reaction_diffusion_add_handler(self
, context
)
3151 set_animatable_fix_handler(self
, context
)
3155 # check vertex group A
3157 vg
= ob
.vertex_groups
['A']
3159 ob
.vertex_groups
.new(name
='A')
3160 # check vertex group B
3162 vg
= ob
.vertex_groups
['B']
3164 ob
.vertex_groups
.new(name
='B')
3166 for v
in ob
.data
.vertices
:
3167 ob
.vertex_groups
['A'].add([v
.index
], 1, 'REPLACE')
3168 ob
.vertex_groups
['B'].add([v
.index
], 0, 'REPLACE')
3170 ob
.vertex_groups
.update()
3172 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
3176 class bake_reaction_diffusion(Operator
):
3177 bl_idname
= "object.bake_reaction_diffusion"
3178 bl_label
= "Bake Data"
3179 bl_description
= ("Bake the Reaction-Diffusion to the cache directory")
3180 bl_options
= {'REGISTER', 'UNDO'}
3183 def poll(cls
, context
):
3184 return context
.object.type == 'MESH' and context
.mode
!= 'EDIT_MESH'
3186 def execute(self
, context
):
3188 props
= ob
.reaction_diffusion_settings
3190 bool_run
= props
.run
3192 context
.scene
.frame_current
= props
.cache_frame_start
3193 fast_bake_def(ob
, frame_start
=props
.cache_frame_start
, frame_end
=props
.cache_frame_end
)
3194 #create_fast_bake_def(ob, frame_start=props.cache_frame_start, frame_end=props.cache_frame_end)
3195 context
.scene
.frame_current
= props
.cache_frame_end
3196 props
.run
= bool_run
3198 for i
in range(props
.cache_frame_start
, props
.cache_frame_end
):
3199 context
.scene
.frame_current
= i
3200 reaction_diffusion_def(ob
, bake
=True)
3201 props
.bool_cache
= True
3205 class reaction_diffusion_free_data(Operator
):
3206 bl_idname
= "object.reaction_diffusion_free_data"
3207 bl_label
= "Free Data"
3208 bl_description
= ("Free Reaction-Diffusion data")
3209 bl_options
= {'REGISTER', 'UNDO'}
3212 def poll(cls
, context
):
3213 return context
.object.type == 'MESH'
3215 def execute(self
, context
):
3217 props
= ob
.reaction_diffusion_settings
3218 props
.bool_cache
= False
3220 folder
= Path(props
.cache_dir
)
3221 for i
in range(props
.cache_frame_start
, props
.cache_frame_end
):
3222 data_a
= folder
/ "a_{:04d}".format(i
)
3223 if os
.path
.exists(data_a
):
3225 data_a
= folder
/ "b_{:04d}".format(i
)
3226 if os
.path
.exists(data_a
):
3230 from bpy
.app
.handlers
import persistent
3232 def reaction_diffusion_scene(scene
, bake
=False):
3233 for ob
in scene
.objects
:
3234 if ob
.reaction_diffusion_settings
.run
:
3235 reaction_diffusion_def(ob
)
3237 def reaction_diffusion_def(ob
, bake
=False):
3239 scene
= bpy
.context
.scene
3241 if type(ob
) == bpy
.types
.Scene
: return None
3242 props
= ob
.reaction_diffusion_settings
3244 if bake
or props
.bool_cache
:
3245 if props
.cache_dir
== '':
3246 letters
= string
.ascii_letters
3247 random_name
= ''.join(rnd
.choice(letters
) for i
in range(6))
3248 if bpy
.context
.blend_data
.filepath
== '':
3249 folder
= Path(bpy
.context
.preferences
.filepaths
.temporary_directory
)
3250 folder
= folder
/ 'reaction_diffusion_cache' / random_name
3252 folder
= '//' + Path(bpy
.context
.blend_data
.filepath
).stem
3253 folder
= Path(bpy
.path
.abspath(folder
)) / 'reaction_diffusion_cache' / random_name
3254 folder
.mkdir(parents
=True, exist_ok
=True)
3255 props
.cache_dir
= str(folder
)
3257 folder
= Path(props
.cache_dir
)
3260 n_edges
= len(me
.edges
)
3261 n_verts
= len(me
.vertices
)
3262 a
= np
.zeros(n_verts
)
3263 b
= np
.zeros(n_verts
)
3265 print("{:6d} Reaction-Diffusion: {}".format(scene
.frame_current
, ob
.name
))
3267 if not props
.bool_cache
:
3270 # hide deforming modifiers
3272 for m
in ob
.modifiers
:
3273 mod_visibility
.append(m
.show_viewport
)
3274 if not mod_preserve_shape(m
): m
.show_viewport
= False
3277 dg
= bpy
.context
.evaluated_depsgraph_get()
3278 ob_eval
= ob
.evaluated_get(dg
)
3279 me
= bpy
.data
.meshes
.new_from_object(ob_eval
, preserve_all_data_layers
=True, depsgraph
=dg
)
3281 # set original visibility
3282 for v
, m
in zip(mod_visibility
, ob
.modifiers
):
3284 ob
.modifiers
.update()
3286 bm
= bmesh
.new() # create an empty BMesh
3287 bm
.from_mesh(me
) # fill it in from a Mesh
3288 dvert_lay
= bm
.verts
.layers
.deform
.active
3291 time_steps
= props
.time_steps
3294 diff_a
= props
.diff_a
3295 diff_b
= props
.diff_b
3296 scale
= props
.diff_mult
3298 brush_mult
= props
.brush_mult
3300 # store weight values
3301 if 'dB' in ob
.vertex_groups
: db
= np
.zeros(n_verts
)
3302 if 'grad' in ob
.vertex_groups
: grad
= np
.zeros(n_verts
)
3304 if props
.vertex_group_diff_a
!= '': diff_a
= np
.zeros(n_verts
)
3305 if props
.vertex_group_diff_b
!= '': diff_b
= np
.zeros(n_verts
)
3306 if props
.vertex_group_scale
!= '': scale
= np
.zeros(n_verts
)
3307 if props
.vertex_group_f
!= '': f
= np
.zeros(n_verts
)
3308 if props
.vertex_group_k
!= '': k
= np
.zeros(n_verts
)
3309 if props
.vertex_group_brush
!= '': brush
= np
.zeros(n_verts
)
3312 group_index_a
= ob
.vertex_groups
["A"].index
3313 group_index_b
= ob
.vertex_groups
["B"].index
3314 a
= bmesh_get_weight_numpy(group_index_a
, dvert_lay
, bm
.verts
)
3315 b
= bmesh_get_weight_numpy(group_index_b
, dvert_lay
, bm
.verts
)
3317 if props
.vertex_group_diff_a
!= '':
3318 group_index
= ob
.vertex_groups
[props
.vertex_group_diff_a
].index
3319 diff_a
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3320 if props
.invert_vertex_group_diff_a
:
3321 vg_bounds
= (props
.min_diff_a
, props
.max_diff_a
)
3323 vg_bounds
= (props
.max_diff_a
, props
.min_diff_a
)
3324 diff_a
= np
.interp(diff_a
, (0,1), vg_bounds
)
3326 if props
.vertex_group_diff_b
!= '':
3327 group_index
= ob
.vertex_groups
[props
.vertex_group_diff_b
].index
3328 diff_b
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3329 if props
.invert_vertex_group_diff_b
:
3330 vg_bounds
= (props
.max_diff_b
, props
.min_diff_b
)
3332 vg_bounds
= (props
.min_diff_b
, props
.max_diff_b
)
3333 diff_b
= np
.interp(diff_b
, (0,1), vg_bounds
)
3335 if props
.vertex_group_scale
!= '':
3336 group_index
= ob
.vertex_groups
[props
.vertex_group_scale
].index
3337 scale
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3338 if props
.invert_vertex_group_scale
:
3339 vg_bounds
= (props
.max_scale
, props
.min_scale
)
3341 vg_bounds
= (props
.min_scale
, props
.max_scale
)
3342 scale
= np
.interp(scale
, (0,1), vg_bounds
)
3344 if props
.vertex_group_f
!= '':
3345 group_index
= ob
.vertex_groups
[props
.vertex_group_f
].index
3346 f
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3347 if props
.invert_vertex_group_f
:
3348 vg_bounds
= (props
.max_f
, props
.min_f
)
3350 vg_bounds
= (props
.min_f
, props
.max_f
)
3351 f
= np
.interp(f
, (0,1), vg_bounds
, )
3353 if props
.vertex_group_k
!= '':
3354 group_index
= ob
.vertex_groups
[props
.vertex_group_k
].index
3355 k
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3356 if props
.invert_vertex_group_k
:
3357 vg_bounds
= (props
.max_k
, props
.min_k
)
3359 vg_bounds
= (props
.min_k
, props
.max_k
)
3360 k
= np
.interp(k
, (0,1), vg_bounds
)
3362 if props
.vertex_group_brush
!= '':
3363 group_index
= ob
.vertex_groups
[props
.vertex_group_brush
].index
3364 brush
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3368 #timeElapsed = time.time() - start
3369 #print('RD - Read Vertex Groups:',timeElapsed)
3370 #start = time.time()
3375 edge_verts
= [0]*n_edges
*2
3376 me
.edges
.foreach_get("vertices", edge_verts
)
3377 edge_verts
= np
.array(edge_verts
)
3379 if 'gradient' in ob
.vertex_groups
.keys() and False:
3380 group_index
= ob
.vertex_groups
['gradient'].index
3381 gradient
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3383 arr
= (np
.arange(n_edges
)*2).astype(int)
3384 id0
= edge_verts
[arr
]
3385 id1
= edge_verts
[arr
+1]
3387 #gradient = np.abs(gradient[id0] - gradient[id1])
3388 gradient
= gradient
[id1
] - gradient
[id0
]
3389 gradient
/= np
.max(gradient
)
3390 sign
= np
.sign(gradient
)
3392 gradient
= (0.05*abs(gradient
) + 0.95)*sign
3393 #gradient *= (1-abs(gradient)
3394 #gradient = 0.2*(1-gradient) + 0.95
3396 #gradient = get_uv_edge_vectors(me)
3397 #uv_dir = Vector((0.5,0.5,0)).normalized()
3398 #gradient = np.array([abs(g.dot(uv_dir.normalized())) for g in gradient])
3399 #gradient = (gradient + 0.5)/2
3400 #gradient = np.array([max(0,g.dot(uv_dir.normalized())) for g in gradient])
3402 timeElapsed
= time
.time() - start
3403 print(' Preparation Time:',timeElapsed
)
3407 _f
= f
if type(f
) is np
.ndarray
else np
.array((f
,))
3408 _k
= k
if type(k
) is np
.ndarray
else np
.array((k
,))
3409 _diff_a
= diff_a
if type(diff_a
) is np
.ndarray
else np
.array((diff_a
,))
3410 _diff_b
= diff_b
if type(diff_b
) is np
.ndarray
else np
.array((diff_b
,))
3411 _brush
= brush
if type(brush
) is np
.ndarray
else np
.array((brush
,))
3413 #a, b = numba_reaction_diffusion_anisotropic(n_verts, n_edges, edge_verts, a, b, _brush, _diff_a, _diff_b, _f, _k, dt, time_steps, gradient)
3414 a
, b
= numba_reaction_diffusion(n_verts
, n_edges
, edge_verts
, a
, b
, _brush
, _diff_a
, _diff_b
, _f
, _k
, dt
, time_steps
)
3416 print('Not using Numba! The simulation could be slow.')
3417 arr
= np
.arange(n_edges
)*2
3418 id0
= edge_verts
[arr
] # first vertex indices for each edge
3419 id1
= edge_verts
[arr
+1] # second vertex indices for each edge
3420 for i
in range(time_steps
):
3422 lap_a
= np
.zeros(n_verts
)
3423 lap_b
= np
.zeros(n_verts
)
3424 lap_a0
= a
[id1
] - a
[id0
] # laplacian increment for first vertex of each edge
3425 lap_b0
= b
[id1
] - b
[id0
] # laplacian increment for first vertex of each edge
3427 np
.add
.at(lap_a
, id0
, lap_a0
)
3428 np
.add
.at(lap_b
, id0
, lap_b0
)
3429 np
.add
.at(lap_a
, id1
, -lap_a0
)
3430 np
.add
.at(lap_b
, id1
, -lap_b0
)
3433 a
+= eval("(diff_a*lap_a - ab2 + f*(1-a))*dt")
3434 b
+= eval("(diff_b*lap_b + ab2 - (k+f)*b)*dt")
3435 #a += (diff_a*lap_a - ab2 + f*(1-a))*dt
3436 #b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
3441 timeElapsed
= time
.time() - start
3442 print(' Simulation Time:',timeElapsed
)
3445 if not(os
.path
.exists(folder
)):
3447 file_name
= folder
/ "a_{:04d}".format(scene
.frame_current
)
3449 file_name
= folder
/ "b_{:04d}".format(scene
.frame_current
)
3451 elif props
.bool_cache
:
3453 file_name
= folder
/ "a_{:04d}".format(scene
.frame_current
)
3454 a
= np
.fromfile(file_name
)
3455 file_name
= folder
/ "b_{:04d}".format(scene
.frame_current
)
3456 b
= np
.fromfile(file_name
)
3458 print(' Cannot read cache.')
3461 if props
.update_weight_a
or props
.update_weight_b
:
3463 if props
.update_weight_a
:
3464 if 'A' in ob
.vertex_groups
.keys():
3465 vg_a
= ob
.vertex_groups
['A']
3467 vg_a
= ob
.vertex_groups
.new(name
='A')
3470 if props
.update_weight_b
:
3471 if 'B' in ob
.vertex_groups
.keys():
3472 vg_b
= ob
.vertex_groups
['B']
3474 vg_b
= ob
.vertex_groups
.new(name
='B')
3477 if vg_a
== vg_b
== None:
3480 if ob
.mode
== 'WEIGHT_PAINT':# or props.bool_cache:
3481 # slower, but prevent crashes
3482 for i
in range(n_verts
):
3483 if vg_a
: vg_a
.add([i
], a
[i
], 'REPLACE')
3484 if vg_b
: vg_b
.add([i
], b
[i
], 'REPLACE')
3486 if props
.bool_mod
or props
.bool_cache
:
3487 #bm.free() # release old bmesh
3488 bm
= bmesh
.new() # create an empty BMesh
3489 bm
.from_mesh(ob
.data
) # fill it in from a Mesh
3490 dvert_lay
= bm
.verts
.layers
.deform
.active
3491 # faster, but can cause crashes while painting weight
3492 if vg_a
: index_a
= vg_a
.index
3493 if vg_b
: index_b
= vg_b
.index
3494 for i
, v
in enumerate(bm
.verts
):
3495 dvert
= v
[dvert_lay
]
3496 if vg_a
: dvert
[index_a
] = a
[i
]
3497 if vg_b
: dvert
[index_b
] = b
[i
]
3500 print(' Writing Vertex Groups Time:',time
.time() - start
)
3506 a
= (a
- min_a
)/(max_a
- min_a
)
3507 b
= (b
- min_b
)/(max_b
- min_b
)
3511 if props
.update_colors
:#_a or props.update_colors_b:
3513 loops_size
= get_attribute_numpy(me
.polygons
, attribute
='loop_total', mult
=1)
3514 n_colors
= np
.sum(loops_size
)
3515 v_id
= np
.ones(n_colors
)
3516 me
.polygons
.foreach_get('vertices',v_id
)
3517 v_id
= v_id
.astype(int)
3518 #v_id = np.array([v for p in ob.data.polygons for v in p.vertices])
3520 if props.update_colors_b:
3521 if 'B' in ob.data.vertex_colors.keys():
3522 vc = ob.data.vertex_colors['B']
3524 vc = ob.data.vertex_colors.new(name='B')
3526 c_val = np.repeat(c_val, 4, axis=0)
3527 vc.data.foreach_set('color',c_val)
3529 if props.update_colors_a:
3530 if 'A' in ob.data.vertex_colors.keys():
3531 vc = ob.data.vertex_colors['A']
3533 vc = ob.data.vertex_colors.new(name='A')
3535 c_val = np.repeat(c_val, 4, axis=0)
3536 vc.data.foreach_set('color',c_val)
3538 split_a
= a
[v_id
,None]
3539 split_b
= b
[v_id
,None]
3541 ones
= np
.ones((n_colors
,1))
3542 #rgba = np.concatenate((split_a,split_b,-split_b+split_a,ones),axis=1).flatten()
3543 rgba
= np
.concatenate((split_a
,split_b
,ones
,ones
),axis
=1).flatten()
3544 if 'AB' in ob
.data
.vertex_colors
.keys():
3545 vc
= ob
.data
.vertex_colors
['AB']
3547 vc
= ob
.data
.vertex_colors
.new(name
='AB')
3548 vc
.data
.foreach_set('color',rgba
)
3549 ob
.data
.vertex_colors
.update()
3551 print(' Writing Vertex Colors Time:',time
.time() - start
)
3554 if 'AB' in me
.uv_layers
.keys():
3555 uv_layer
= me
.uv_layers
['AB']
3557 uv_layer
= me
.uv_layers
.new(name
='AB')
3559 loops_size
= get_attribute_numpy(me
.polygons
, attribute
='loop_total', mult
=1)
3560 n_data
= np
.sum(loops_size
)
3561 v_id
= np
.ones(n_data
)
3562 me
.polygons
.foreach_get('vertices',v_id
)
3563 v_id
= v_id
.astype(int)
3564 split_a
= a
[v_id
,None]
3565 split_b
= b
[v_id
,None]
3566 uv
= np
.concatenate((split_a
,split_b
),axis
=1).flatten()
3567 uv_layer
.data
.foreach_set('uv',uv
)
3568 me
.uv_layers
.update()
3569 print(' Writing UV Map Time:',time
.time() - start
)
3571 for ps
in ob
.particle_systems
:
3572 if ps
.vertex_group_density
== 'B' or ps
.vertex_group_density
== 'A':
3573 ps
.invert_vertex_group_density
= not ps
.invert_vertex_group_density
3574 ps
.invert_vertex_group_density
= not ps
.invert_vertex_group_density
3576 if props
.bool_mod
and not props
.bool_cache
: bpy
.data
.meshes
.remove(me
)
3578 def fast_bake_def(ob
, frame_start
=1, frame_end
=250):
3579 scene
= bpy
.context
.scene
3581 if type(ob
) == bpy
.types
.Scene
: return None
3582 props
= ob
.reaction_diffusion_settings
3584 # Define cache folder
3585 if props
.cache_dir
== '':
3586 letters
= string
.ascii_letters
3587 random_name
= ''.join(rnd
.choice(letters
) for i
in range(6))
3588 if bpy
.context
.blend_data
.filepath
== '':
3589 folder
= Path(bpy
.context
.preferences
.filepaths
.temporary_directory
)
3590 folder
= folder
/ 'reaction_diffusion_cache' / random_name
3592 folder
= '//' + Path(bpy
.context
.blend_data
.filepath
).stem
3593 folder
= Path(bpy
.path
.abspath(folder
)) / 'reaction_diffusion_cache' / random_name
3594 folder
.mkdir(parents
=True, exist_ok
=True)
3595 props
.cache_dir
= str(folder
)
3597 folder
= Path(props
.cache_dir
)
3600 # hide deforming modifiers
3602 for m
in ob
.modifiers
:
3603 mod_visibility
.append(m
.show_viewport
)
3604 if not mod_preserve_shape(m
): m
.show_viewport
= False
3607 dg
= bpy
.context
.evaluated_depsgraph_get()
3608 ob_eval
= ob
.evaluated_get(dg
)
3609 me
= bpy
.data
.meshes
.new_from_object(ob_eval
, preserve_all_data_layers
=True, depsgraph
=dg
)
3611 # set original visibility
3612 for v
, m
in zip(mod_visibility
, ob
.modifiers
):
3614 ob
.modifiers
.update()
3618 bm
= bmesh
.new() # create an empty BMesh
3619 bm
.from_mesh(me
) # fill it in from a Mesh
3620 dvert_lay
= bm
.verts
.layers
.deform
.active
3621 n_edges
= len(me
.edges
)
3622 n_verts
= len(me
.vertices
)
3623 a
= np
.zeros(n_verts
)
3624 b
= np
.zeros(n_verts
)
3625 group_index_a
= ob
.vertex_groups
["A"].index
3626 group_index_b
= ob
.vertex_groups
["B"].index
3629 time_steps
= props
.time_steps
3632 diff_a
= props
.diff_a
3633 diff_b
= props
.diff_b
3634 scale
= props
.diff_mult
3636 brush_mult
= props
.brush_mult
3638 # store weight values
3639 if 'dB' in ob
.vertex_groups
: db
= np
.zeros(n_verts
)
3640 if 'grad' in ob
.vertex_groups
: grad
= np
.zeros(n_verts
)
3642 if props
.vertex_group_diff_a
!= '': diff_a
= np
.zeros(n_verts
)
3643 if props
.vertex_group_diff_b
!= '': diff_b
= np
.zeros(n_verts
)
3644 if props
.vertex_group_scale
!= '': scale
= np
.zeros(n_verts
)
3645 if props
.vertex_group_f
!= '': f
= np
.zeros(n_verts
)
3646 if props
.vertex_group_k
!= '': k
= np
.zeros(n_verts
)
3647 if props
.vertex_group_brush
!= '': brush
= np
.zeros(n_verts
)
3650 a
= bmesh_get_weight_numpy(group_index_a
, dvert_lay
, bm
.verts
)
3651 b
= bmesh_get_weight_numpy(group_index_b
, dvert_lay
, bm
.verts
)
3653 if props
.vertex_group_diff_a
!= '':
3654 group_index
= ob
.vertex_groups
[props
.vertex_group_diff_a
].index
3655 diff_a
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3656 if props
.invert_vertex_group_diff_a
:
3657 vg_bounds
= (props
.min_diff_a
, props
.max_diff_a
)
3659 vg_bounds
= (props
.max_diff_a
, props
.min_diff_a
)
3660 diff_a
= np
.interp(diff_a
, (0,1), vg_bounds
)
3662 if props
.vertex_group_diff_b
!= '':
3663 group_index
= ob
.vertex_groups
[props
.vertex_group_diff_b
].index
3664 diff_b
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3665 if props
.invert_vertex_group_diff_b
:
3666 vg_bounds
= (props
.max_diff_b
, props
.min_diff_b
)
3668 vg_bounds
= (props
.min_diff_b
, props
.max_diff_b
)
3669 diff_b
= np
.interp(diff_b
, (0,1), vg_bounds
)
3671 if props
.vertex_group_scale
!= '':
3672 group_index
= ob
.vertex_groups
[props
.vertex_group_scale
].index
3673 scale
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3674 if props
.invert_vertex_group_scale
:
3675 vg_bounds
= (props
.max_scale
, props
.min_scale
)
3677 vg_bounds
= (props
.min_scale
, props
.max_scale
)
3678 scale
= np
.interp(scale
, (0,1), vg_bounds
)
3680 if props
.vertex_group_f
!= '':
3681 group_index
= ob
.vertex_groups
[props
.vertex_group_f
].index
3682 f
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3683 if props
.invert_vertex_group_f
:
3684 vg_bounds
= (props
.max_f
, props
.min_f
)
3686 vg_bounds
= (props
.min_f
, props
.max_f
)
3687 f
= np
.interp(f
, (0,1), vg_bounds
, )
3689 if props
.vertex_group_k
!= '':
3690 group_index
= ob
.vertex_groups
[props
.vertex_group_k
].index
3691 k
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3692 if props
.invert_vertex_group_k
:
3693 vg_bounds
= (props
.max_k
, props
.min_k
)
3695 vg_bounds
= (props
.min_k
, props
.max_k
)
3696 k
= np
.interp(k
, (0,1), vg_bounds
)
3698 if props
.vertex_group_brush
!= '':
3699 group_index
= ob
.vertex_groups
[props
.vertex_group_brush
].index
3700 brush
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3706 edge_verts
= [0]*n_edges
*2
3707 me
.edges
.foreach_get("vertices", edge_verts
)
3709 gradient
= get_uv_edge_vectors(me
)
3710 uv_dir
= Vector((0.5,0.5,0))
3711 #gradient = [abs(g.dot(uv_dir)) for g in gradient]
3712 gradient
= [max(0,g
.dot(uv_dir
)) for g
in gradient
]
3714 timeElapsed
= time
.time() - start
3715 print(' Preparation Time:',timeElapsed
)
3719 edge_verts
= np
.array(edge_verts
)
3720 _f
= f
if type(f
) is np
.ndarray
else np
.array((f
,))
3721 _k
= k
if type(k
) is np
.ndarray
else np
.array((k
,))
3722 _diff_a
= diff_a
if type(diff_a
) is np
.ndarray
else np
.array((diff_a
,))
3723 _diff_b
= diff_b
if type(diff_b
) is np
.ndarray
else np
.array((diff_b
,))
3724 _brush
= brush
if type(brush
) is np
.ndarray
else np
.array((brush
,))
3727 for j
in range(props
.cache_frame_start
, props
.cache_frame_end
+1):
3728 start2
= time
.time()
3729 print("{:6d} Reaction-Diffusion: {}".format(j
, ob
.name
))
3732 a
, b
= numba_reaction_diffusion(n_verts
, n_edges
, edge_verts
, a
, b
, _brush
, _diff_a
, _diff_b
, _f
, _k
, dt
, time_steps
)
3736 if not(os
.path
.exists(folder
)):
3738 file_name
= folder
/ "a_{:04d}".format(j
)
3740 file_name
= folder
/ "b_{:04d}".format(j
)
3743 timeElapsed
= time
.time() - start2
3744 print(' Simulation Time:',timeElapsed
)
3747 print('Not using Numba! The simulation could be slow.')
3748 edge_verts
= np
.array(edge_verts
)
3749 arr
= np
.arange(n_edges
)*2
3750 id0
= edge_verts
[arr
] # first vertex indices for each edge
3751 id1
= edge_verts
[arr
+1] # second vertex indices for each edge
3752 for j
in range(props
.cache_frame_start
, props
.cache_frame_end
):
3753 for i
in range(time_steps
):
3755 lap_a
= np
.zeros(n_verts
)
3756 lap_b
= np
.zeros(n_verts
)
3757 lap_a0
= a
[id1
] - a
[id0
] # laplacian increment for first vertex of each edge
3758 lap_b0
= b
[id1
] - b
[id0
] # laplacian increment for first vertex of each edge
3760 np
.add
.at(lap_a
, id0
, lap_a0
)
3761 np
.add
.at(lap_b
, id0
, lap_b0
)
3762 np
.add
.at(lap_a
, id1
, -lap_a0
)
3763 np
.add
.at(lap_b
, id1
, -lap_b0
)
3766 a
+= eval("(diff_a*lap_a - ab2 + f*(1-a))*dt")
3767 b
+= eval("(diff_b*lap_b + ab2 - (k+f)*b)*dt")
3772 if not(os
.path
.exists(folder
)):
3774 file_name
= folder
/ "a_{:04d}".format(j
)
3776 file_name
= folder
/ "b_{:04d}".format(j
)
3779 if ob
.mode
== 'WEIGHT_PAINT':
3780 # slower, but prevent crashes
3781 vg_a
= ob
.vertex_groups
['A']
3782 vg_b
= ob
.vertex_groups
['B']
3783 for i
in range(n_verts
):
3784 vg_a
.add([i
], a
[i
], 'REPLACE')
3785 vg_b
.add([i
], b
[i
], 'REPLACE')
3788 bm
.free() # release old bmesh
3789 bm
= bmesh
.new() # create an empty BMesh
3790 bm
.from_mesh(ob
.data
) # fill it in from a Mesh
3791 dvert_lay
= bm
.verts
.layers
.deform
.active
3792 # faster, but can cause crashes while painting weight
3793 for i
, v
in enumerate(bm
.verts
):
3794 dvert
= v
[dvert_lay
]
3795 dvert
[group_index_a
] = a
[i
]
3796 dvert
[group_index_b
] = b
[i
]
3799 # Update Vertex Colors
3800 if 'A' in ob
.data
.vertex_colors
or 'B' in ob
.data
.vertex_colors
:
3801 v_id
= np
.array([v
for p
in ob
.data
.polygons
for v
in p
.vertices
])
3803 if 'B' in ob
.data
.vertex_colors
:
3805 c_val
= np
.repeat(c_val
, 4, axis
=0)
3806 vc
= ob
.data
.vertex_colors
['B']
3807 vc
.data
.foreach_set('color',c_val
.tolist())
3809 if 'A' in ob
.data
.vertex_colors
:
3811 c_val
= np
.repeat(c_val
, 4, axis
=0)
3812 vc
= ob
.data
.vertex_colors
['A']
3813 vc
.data
.foreach_set('color',c_val
.tolist())
3815 for ps
in ob
.particle_systems
:
3816 if ps
.vertex_group_density
== 'B' or ps
.vertex_group_density
== 'A':
3817 ps
.invert_vertex_group_density
= not ps
.invert_vertex_group_density
3818 ps
.invert_vertex_group_density
= not ps
.invert_vertex_group_density
3820 if props
.bool_mod
: bpy
.data
.meshes
.remove(me
)
3822 timeElapsed
= time
.time() - start
3823 print(' Closing Time:',timeElapsed
)
3825 def create_fast_bake_def(ob
, frame_start
=1, frame_end
=250):
3826 scene
= bpy
.context
.scene
3828 if type(ob
) == bpy
.types
.Scene
: return None
3829 props
= ob
.reaction_diffusion_settings
3832 time_steps
= props
.time_steps
3833 scale
= props
.diff_mult
3835 if props
.cache_dir
== '':
3836 letters
= string
.ascii_letters
3837 random_name
= ''.join(rnd
.choice(letters
) for i
in range(6))
3838 if bpy
.context
.blend_data
.filepath
== '':
3839 folder
= Path(bpy
.context
.preferences
.filepaths
.temporary_directory
)
3840 folder
= folder
/ 'reaction_diffusion_cache' / random_name
3842 folder
= '//' + Path(bpy
.context
.blend_data
.filepath
).stem
3843 folder
= Path(bpy
.path
.abspath(folder
)) / 'reaction_diffusion_cache' / random_name
3844 folder
.mkdir(parents
=True, exist_ok
=True)
3845 props
.cache_dir
= str(folder
)
3847 folder
= Path(props
.cache_dir
)
3850 # hide deforming modifiers
3852 for m
in ob
.modifiers
:
3853 mod_visibility
.append(m
.show_viewport
)
3854 if not mod_preserve_shape(m
): m
.show_viewport
= False
3857 dg
= bpy
.context
.evaluated_depsgraph_get()
3858 ob_eval
= ob
.evaluated_get(dg
)
3859 me
= bpy
.data
.meshes
.new_from_object(ob_eval
, preserve_all_data_layers
=True, depsgraph
=dg
)
3861 # set original visibility
3862 for v
, m
in zip(mod_visibility
, ob
.modifiers
):
3864 ob
.modifiers
.update()
3868 bm
= bmesh
.new() # create an empty BMesh
3869 bm
.from_mesh(me
) # fill it in from a Mesh
3870 verts
= get_vertices_numpy(me
)
3871 dvert_lay
= bm
.verts
.layers
.deform
.active
3872 n_edges
= len(me
.edges
)
3873 n_verts
= len(me
.vertices
)
3874 group_index_x
= ob
.vertex_groups
["x"].index
3875 group_index_y
= ob
.vertex_groups
["y"].index
3876 group_index_module
= ob
.vertex_groups
["module"].index
3877 group_index_values
= ob
.vertex_groups
["values"].index
3879 if not props
.bool_cache
:
3880 time_steps
= props
.time_steps
3882 # store weight values
3883 if 'dB' in ob
.vertex_groups
: db
= np
.zeros(n_verts
)
3884 if 'grad' in ob
.vertex_groups
: grad
= np
.zeros(n_verts
)
3885 vec_x
= np
.zeros(n_verts
)
3886 vec_y
= np
.zeros(n_verts
)
3887 vec_module
= np
.zeros(n_verts
)
3888 values
= np
.zeros(n_verts
)
3890 vec_x
= bmesh_get_weight_numpy(group_index_x
, dvert_lay
, bm
.verts
)
3891 vec_y
= bmesh_get_weight_numpy(group_index_y
, dvert_lay
, bm
.verts
)
3892 vec_module
= bmesh_get_weight_numpy(group_index_module
, dvert_lay
, bm
.verts
)
3893 values
= bmesh_get_weight_numpy(group_index_values
, dvert_lay
, bm
.verts
)
3894 field
= np
.concatenate((vec_x
[:,None],vec_y
[:,None],vec_y
[:,None]*0),axis
=1)
3897 edge_verts
= get_edges_numpy(me
)
3899 id0
= edge_verts
[:,0]
3900 id1
= edge_verts
[:,1]
3904 edge_field
= (field
[id0
] + field
[id1
])/2 # average vector associated to the edge
3907 print(edge_field
.shape
)
3909 vec
/= np
.linalg
.norm(vec
,axis
=1)[:,None]
3910 edge_field
/= np
.linalg
.norm(edge_field
,axis
=1)[:,None]
3911 edge_flow
= np
.einsum('...j,...j', vec
, edge_field
)
3912 #sign = (edge_flow>0).astype(int)
3913 #values[edge_verts[sign]] += values[edge_verts[1-sign]]*
3914 #values[verts0] += values[verts1]*edge_flow
3916 timeElapsed
= time
.time() - start
3917 print(' Preparation Time:',timeElapsed
)
3921 mult
= np
.zeros(values
.shape
)
3922 #mult[id0] -= edge_flow
3923 #mult[id1] += edge_flow
3924 np
.add
.at(mult
,id0
,-edge_flow
)
3925 np
.add
.at(mult
,id1
,edge_flow
)
3938 for j
in range(props
.cache_frame_start
, props
.cache_frame_end
+1):
3939 start2
= time
.time()
3940 print("{:6d} Reaction-Diffusion: {}".format(j
, ob
.name
))
3944 values
= integrate_field(n_edges
,id0
,id1
,values
,edge_flow
,mult
,time_steps
)
3946 #np.add.at(values, id0, values0[id1]*edge_flow*mult[id1])
3947 #np.add.at(values, id1, -values0[id0]*edge_flow*mult[id0])
3948 #np.add.at(values, id0, values0[id1]*edge_flow*mult)
3949 #np.add.at(values, id1, -values0[id0]*edge_flow*mult)
3950 #values[id1] += values0[id0]*edge_flow/mult[id1]*dt
3951 #values[id0] -= values0[id1]*edge_flow/mult[id0]*dt
3952 #values[id1] = edge_flow
3953 #values[id1] += edge_flow
3954 #a, b = numba_reaction_diffusion(n_verts, n_edges, edge_verts, a, b, _brush, _diff_a, _diff_b, _f, _k, dt, time_steps)
3957 lap_a = np.zeros(n_verts)
3958 lap_b = np.zeros(n_verts)
3959 lap_a0 = a[id1] - a[id0] # laplacian increment for first vertex of each edge
3960 lap_b0 = b[id1] - b[id0] # laplacian increment for first vertex of each edge
3962 np.add.at(lap_a, id0, lap_a0)
3963 np.add.at(lap_b, id0, lap_b0)
3964 np.add.at(lap_a, id1, -lap_a0)
3965 np.add.at(lap_b, id1, -lap_b0)
3970 if not(os
.path
.exists(folder
)):
3972 file_name
= folder
/ "a_{:04d}".format(j
)
3973 values
.tofile(file_name
)
3974 file_name
= folder
/ "b_{:04d}".format(j
)
3975 values
.tofile(file_name
)
3978 timeElapsed
= time
.time() - start2
3979 print(' Simulation Time:',timeElapsed
)
3981 if props
.bool_mod
: bpy
.data
.meshes
.remove(me
)
3983 timeElapsed
= time
.time() - start
3984 print(' Closing Time:',timeElapsed
)
3990 class TISSUE_PT_reaction_diffusion(Panel
):
3991 bl_space_type
= 'PROPERTIES'
3992 bl_region_type
= 'WINDOW'
3994 bl_label
= "Tissue Reaction-Diffusion"
3995 bl_options
= {'DEFAULT_CLOSED'}
3998 def poll(cls
, context
):
3999 return 'A' and 'B' in context
.object.vertex_groups
4001 def draw(self
, context
):
4002 reaction_diffusion_add_handler(self
, context
)
4005 props
= ob
.reaction_diffusion_settings
4006 layout
= self
.layout
4007 col
= layout
.column(align
=True)
4008 row
= col
.row(align
=True)
4009 if not ("A" and "B" in ob
.vertex_groups
):
4010 row
.operator("object.start_reaction_diffusion",
4011 icon
="EXPERIMENTAL",
4012 text
="Reaction-Diffusion")
4014 row
.operator("object.start_reaction_diffusion",
4015 icon
="EXPERIMENTAL",
4016 text
="Reset Reaction-Diffusion")
4017 row
= col
.row(align
=True)
4018 row
.prop(props
, "run", text
="Run Reaction-Diffusion")
4019 col
= layout
.column(align
=True)
4020 row
= col
.row(align
=True)
4021 row
.prop(props
, "time_steps")
4022 row
.prop(props
, "dt")
4023 row
.enabled
= not props
.bool_cache
4025 row
= col
.row(align
=True)
4026 col1
= row
.column(align
=True)
4027 col1
.prop(props
, "diff_a")
4028 col1
.enabled
= props
.vertex_group_diff_a
== '' and not props
.bool_cache
4029 col1
= row
.column(align
=True)
4030 col1
.prop(props
, "diff_b")
4031 col1
.enabled
= props
.vertex_group_diff_b
== '' and not props
.bool_cache
4032 row
= col
.row(align
=True)
4033 row
.prop(props
, "diff_mult")
4034 row
.enabled
= props
.vertex_group_scale
== '' and not props
.bool_cache
4036 row
= col
.row(align
=True)
4037 col1
= row
.column(align
=True)
4038 col1
.prop(props
, "f")
4039 col1
.enabled
= props
.vertex_group_f
== '' and not props
.bool_cache
4040 col1
= row
.column(align
=True)
4041 col1
.prop(props
, "k")
4042 col1
.enabled
= props
.vertex_group_k
== '' and not props
.bool_cache
4044 col
.label(text
='Cache:')
4045 #col.prop(props, "bool_cache")
4046 col
.prop(props
, "cache_dir", text
='')
4048 row
= col
.row(align
=True)
4049 row
.prop(props
, "cache_frame_start")
4050 row
.prop(props
, "cache_frame_end")
4052 if props
.bool_cache
:
4053 col
.operator("object.reaction_diffusion_free_data")
4055 row
= col
.row(align
=True)
4056 row
.operator("object.bake_reaction_diffusion")
4057 file = bpy
.context
.blend_data
.filepath
4058 temp
= bpy
.context
.preferences
.filepaths
.temporary_directory
4059 if file == temp
== props
.cache_dir
== '':
4061 col
.label(text
="Cannot use cache", icon
='ERROR')
4062 col
.label(text
='please save the Blender or set a Cache directory')
4063 col
.prop(props
, "fast_bake")
4066 col
.label(text
='Output attributes:')
4067 row
= col
.row(align
=True)
4068 col2
= row
.column(align
=True)
4069 row2
= col2
.row(align
=True)
4070 row2
.prop(props
, "update_weight_a", icon
='GROUP_VERTEX', text
='A')
4071 row2
.prop(props
, "update_weight_b", icon
='GROUP_VERTEX', text
='B')
4072 col2
.enabled
= props
.bool_cache
4074 #row.prop(props, "update_colors_a", icon='GROUP_VCOL', text='A')
4075 #row.prop(props, "update_colors_b", icon='GROUP_VCOL', text='B')
4076 row
.prop(props
, "update_colors", icon
='GROUP_VCOL', text
='AB')
4078 row
.prop(props
, "update_uv", icon
='GROUP_UVS', text
='AB')
4079 col
.prop(props
,'normalize')
4081 #col.prop_search(props, 'vertex_group_diff_a', ob, "vertex_groups", text='Diff A')
4082 #col.prop_search(props, 'vertex_group_diff_b', ob, "vertex_groups", text='Diff B')
4083 #col.prop_search(props, 'vertex_group_scale', ob, "vertex_groups", text='Scale')
4084 #col.prop_search(props, 'vertex_group_f', ob, "vertex_groups", text='f')
4085 #col.prop_search(props, 'vertex_group_k', ob, "vertex_groups", text='k')
4088 class TISSUE_PT_reaction_diffusion_weight(Panel
):
4089 bl_space_type
= 'PROPERTIES'
4090 bl_region_type
= 'WINDOW'
4092 bl_parent_id
= "TISSUE_PT_reaction_diffusion"
4093 bl_label
= "Vertex Groups"
4094 bl_options
= {'DEFAULT_CLOSED'}
4097 def poll(cls
, context
):
4098 return 'A' and 'B' in context
.object.vertex_groups
4100 def draw(self
, context
):
4102 props
= ob
.reaction_diffusion_settings
4103 layout
= self
.layout
4104 #layout.use_property_split = True
4105 col
= layout
.column(align
=True)
4106 col
.prop(props
, "bool_mod")
4107 if props
.bool_mod
and props
.fast_bake
:
4108 col
.label(text
="When Fast Bake is on, the modifiers", icon
='ERROR')
4109 col
.label(text
=" are used only for the first frame")
4111 insert_weight_parameter(col
, ob
, 'brush', text
='Brush:')
4112 insert_weight_parameter(col
, ob
, 'diff_a', text
='Diff A:')
4113 insert_weight_parameter(col
, ob
, 'diff_b', text
='Diff B:')
4114 insert_weight_parameter(col
, ob
, 'scale', text
='Scale:')
4115 insert_weight_parameter(col
, ob
, 'f', text
='f:')
4116 insert_weight_parameter(col
, ob
, 'k', text
='k:')
4117 col
.enabled
= not props
.bool_cache
4119 def insert_weight_parameter(col
, ob
, name
, text
=''):
4120 props
= ob
.reaction_diffusion_settings
4121 split
= col
.split(factor
=0.25, align
=True)
4122 col2
= split
.column(align
=True)
4123 col2
.label(text
=text
)
4124 col2
= split
.column(align
=True)
4125 row2
= col2
.row(align
=True)
4126 row2
.prop_search(props
, 'vertex_group_' + name
, ob
, "vertex_groups", text
='')
4128 row2
.prop(props
, "invert_vertex_group_" + name
, text
="", toggle
=True, icon
='ARROW_LEFTRIGHT')
4129 if 'vertex_group_' + name
in props
:
4130 if props
['vertex_group_' + name
] != '':
4132 col2
.prop(props
, "brush_mult")
4134 row2
= col2
.row(align
=True)
4135 row2
.prop(props
, "min_" + name
, text
="Min")
4136 row2
= col2
.row(align
=True)
4137 row2
.prop(props
, "max_" + name
, text
="Max")
4140 def contour_edges_pattern(operator
, c
, verts_count
, iso_val
, vertices
, normals
, filtered_edges
, weight
, pattern_weight
, bevel_weight
):
4142 id0
= filtered_edges
[:,0]
4143 id1
= filtered_edges
[:,1]
4148 bool_w0
= w0
< iso_val
4149 bool_w1
= w1
< iso_val
4151 # mask all edges that have one weight value below the iso value
4152 mask_new_verts
= np
.logical_xor(bool_w0
, bool_w1
)
4153 if not mask_new_verts
.any():
4154 return np
.array([[None]]), {}, np
.array([[None]]), np
.array([[None]])
4156 id0
= id0
[mask_new_verts
]
4157 id1
= id1
[mask_new_verts
]
4163 w0
= w0
[mask_new_verts
]
4164 w1
= w1
[mask_new_verts
]
4165 pattern0
= pattern_weight
[id0
]
4166 pattern1
= pattern_weight
[id1
]
4168 bevel0
= bevel_weight
[id0
]
4169 bevel1
= bevel_weight
[id1
]
4173 #edge_nor = (n0+n1)/2
4174 #shift = np.arctan2(edge_nor[:,0], edge_nor[:,1])/2/pi*delta_iso
4176 #param = (iso_val + shift - w0)/(w1-w0)
4177 param
= (iso_val
- w0
)/(w1
-w0
)
4179 #mult = 1 if c%2 == 0 else -1
4180 if c
%(operator
.in_steps
+ operator
.out_steps
) < operator
.in_steps
:
4181 mult
= operator
.in_displace
4183 mult
= operator
.out_displace
4184 pattern_value
= pattern0
+ (pattern1
-pattern0
)*param
4186 bevel_value
= bevel0
+ (bevel1
-bevel0
)*param
4187 bevel_value
= np
.expand_dims(bevel_value
,axis
=1)
4188 except: bevel_value
= None
4189 disp
= pattern_value
* mult
4191 param
= np
.expand_dims(param
,axis
=1)
4192 disp
= np
.expand_dims(disp
,axis
=1)
4193 verts
= v0
+ (v1
-v0
)*param
4194 norm
= n0
+ (n1
-n0
)*param
4195 if operator
.limit_z
: disp
*= 1-abs(np
.expand_dims(norm
[:,2], axis
=1))
4196 verts
= verts
+ norm
*disp
4197 #verts = verts[np.flip(np.argsort(shift))]
4198 #verts = verts[np.argsort(shift)]
4200 # indexes of edges with new vertices
4201 edges_index
= filtered_edges
[mask_new_verts
][:,2]
4203 # remove all edges completely below the iso value
4204 #mask_edges = np.logical_not(np.logical_and(bool_w0, bool_w1))
4205 #filtered_edges = filtered_edges[mask_edges]
4206 return filtered_edges
, edges_index
, verts
, bevel_value
4208 def contour_bmesh(me
, bm
, weight
, iso_val
):
4209 bm
.verts
.ensure_lookup_table()
4210 bm
.edges
.ensure_lookup_table()
4211 bm
.faces
.ensure_lookup_table()
4213 # store weight values
4215 vertices
= get_vertices_numpy(me
)
4216 faces_mask
= np
.array(bm
.faces
)
4217 filtered_edges
= get_edges_id_numpy(me
)
4218 n_verts
= len(bm
.verts
)
4220 #############################
4223 id0
= filtered_edges
[:,0]
4224 id1
= filtered_edges
[:,1]
4229 bool_w0
= w0
< iso_val
4230 bool_w1
= w1
< iso_val
4232 # mask all edges that have one weight value below the iso value
4233 mask_new_verts
= np
.logical_xor(bool_w0
, bool_w1
)
4234 if not mask_new_verts
.any(): return np
.array([[None]]), {}, np
.array([[None]])
4236 id0
= id0
[mask_new_verts
]
4237 id1
= id1
[mask_new_verts
]
4241 w0
= w0
[mask_new_verts
]
4242 w1
= w1
[mask_new_verts
]
4243 param
= (iso_val
-w0
)/(w1
-w0
)
4244 param
= np
.expand_dims(param
,axis
=1)
4245 verts
= v0
+ (v1
-v0
)*param
4247 # indexes of edges with new vertices
4248 #edges_index = filtered_edges[mask_new_verts][:,2]
4251 for i
, e
in enumerate(filtered_edges
):
4252 #edges_id[id] = i + n_verts
4253 edges_id
['{}_{}'.format(e
[0],e
[1])] = i
+ n_verts
4254 edges_id
['{}_{}'.format(e
[1],e
[0])] = i
+ n_verts
4258 for e in filtered_edges:
4259 id0 = e.verts[0].index
4260 id1 = e.verts[1].index
4264 if w0 == w1: continue
4265 elif w0 > iso_val and w1 > iso_val:
4267 elif w0 < iso_val and w1 < iso_val: continue
4268 elif w0 == iso_val or w1 == iso_val: continue
4270 v0 = me0.vertices[id0].co
4271 v1 = me0.vertices[id1].co
4272 v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
4273 delete_edges.append(e)
4275 edges_id[str(id0)+"_"+str(id1)] = count
4276 edges_id[str(id1)+"_"+str(id0)] = count
4284 for f
in faces_mask
:
4285 # create sub-faces slots. Once a new vertex is reached it will
4286 # change slot, storing the next vertices for a new face.
4287 build_faces
= [[],[]]
4289 verts0
= list(me
.polygons
[f
.index
].vertices
)
4290 verts1
= list(verts0
)
4291 verts1
.append(verts1
.pop(0)) # shift list
4292 for id0
, id1
in zip(verts0
, verts1
):
4294 # add first vertex to active slot
4295 build_faces
[switch
].append(id0
)
4299 # check if the edge must be splitted
4300 new_vert
= edges_id
['{}_{}'.format(id0
,id1
)]
4302 build_faces
[switch
].append(new_vert
)
4303 # if there is an open face on the other slot
4304 if len(build_faces
[not switch
]) > 0:
4306 splitted_faces
.append(build_faces
[switch
])
4307 # reset actual faces and switch
4308 build_faces
[switch
] = []
4311 # continue previous face
4312 build_faces
[switch
].append(new_vert
)
4314 if len(build_faces
[not switch
]) == 2:
4315 build_faces
[not switch
].append(id0
)
4316 if len(build_faces
[not switch
]) > 2:
4317 splitted_faces
.append(build_faces
[not switch
])
4319 splitted_faces
.append(build_faces
[switch
])
4321 # adding new vertices use fast local method access
4322 _new_vert
= bm
.verts
.new
4323 for v
in verts
: _new_vert(v
)
4324 bm
.verts
.ensure_lookup_table()
4326 # deleting old edges/faces
4327 bm
.edges
.ensure_lookup_table()
4328 remove_edges
= [bm
.edges
[i
] for i
in filtered_edges
[:,2]]
4329 #for e in remove_edges: bm.edges.remove(e)
4330 #for e in delete_edges: bm.edges.remove(e)
4332 bm
.verts
.ensure_lookup_table()
4333 # adding new faces use fast local method access
4334 _new_face
= bm
.faces
.new
4336 for f
in splitted_faces
:
4338 face_verts
= [bm
.verts
[i
] for i
in f
]
4339 _new_face(face_verts
)
4341 missed_faces
.append(f
)
4343 #me = bpy.data.meshes.new('_tissue_tmp_')
4345 weight
= np
.concatenate((weight
, np
.ones(len(verts
))*iso_val
))
4347 return me
, bm
, weight
4352 class tissue_weight_streamlines(Operator
):
4353 bl_idname
= "object.tissue_weight_streamlines"
4354 bl_label
= "Streamlines Curves"
4355 bl_description
= ("")
4356 bl_options
= {'REGISTER', 'UNDO'}
4358 mode
: EnumProperty(
4360 ('VERTS', "Verts", "Follow vertices"),
4361 ('EDGES', "Edges", "Follow Edges")
4364 name
="Streamlines path mode"
4367 interpolation
: EnumProperty(
4369 ('POLY', "Poly", "Generate Polylines"),
4370 ('NURBS', "NURBS", "Generate Nurbs curves")
4373 name
="Interpolation mode"
4376 use_modifiers
: BoolProperty(
4377 name
="Use Modifiers", default
=True,
4378 description
="Apply all the modifiers")
4380 use_selected
: BoolProperty(
4381 name
="Use Selected Vertices", default
=False,
4382 description
="Use selected vertices as Seed")
4384 same_weight
: BoolProperty(
4385 name
="Same Weight", default
=True,
4386 description
="Continue the streamlines when the weight is the same")
4388 min_iso
: FloatProperty(
4389 name
="Min Value", default
=0., soft_min
=0, soft_max
=1,
4390 description
="Minimum weight value")
4391 max_iso
: FloatProperty(
4392 name
="Max Value", default
=1, soft_min
=0, soft_max
=1,
4393 description
="Maximum weight value")
4395 rand_seed
: IntProperty(
4396 name
="Seed", default
=0, min=0, soft_max
=10,
4397 description
="Random Seed")
4398 n_curves
: IntProperty(
4399 name
="Curves", default
=50, soft_min
=1, soft_max
=100000,
4400 description
="Number of Curves")
4404 pos_steps
: IntProperty(
4405 name
="High Steps", default
=50, min=0, soft_max
=100,
4406 description
="Number of steps in the direction of high weight")
4407 neg_steps
: IntProperty(
4408 name
="Low Steps", default
=50, min=0, soft_max
=100,
4409 description
="Number of steps in the direction of low weight")
4411 bevel_depth
: FloatProperty(
4412 name
="Bevel Depth", default
=0, min=0, soft_max
=1,
4414 min_bevel_depth
: FloatProperty(
4415 name
="Min Bevel Depth", default
=0.1, min=0, soft_max
=1,
4417 max_bevel_depth
: FloatProperty(
4418 name
="Max Bevel Depth", default
=1, min=0, soft_max
=1,
4421 rand_dir
: FloatProperty(
4422 name
="Randomize", default
=0, min=0, max=1,
4423 description
="Randomize streamlines directions (Slower)")
4425 vertex_group_seeds
: StringProperty(
4426 name
="Displace", default
='',
4427 description
="Vertex Group used for pattern displace")
4429 vertex_group_bevel
: StringProperty(
4430 name
="Bevel", default
='',
4431 description
="Variable Bevel depth")
4433 object_name
: StringProperty(
4434 name
="Active Object", default
='',
4437 try: vg_name
= bpy
.context
.object.vertex_groups
.active
.name
4438 except: vg_name
= ''
4440 vertex_group_streamlines
: StringProperty(
4441 name
="Flow", default
=vg_name
,
4442 description
="Vertex Group used for streamlines")
4445 def poll(cls
, context
):
4447 return ob
and len(ob
.vertex_groups
) > 0 or ob
.type == 'CURVE'
4449 def invoke(self
, context
, event
):
4450 return context
.window_manager
.invoke_props_dialog(self
, width
=250)
4452 def draw(self
, context
):
4453 if not context
.object.type == 'CURVE':
4454 self
.object_name
= context
.object.name
4455 ob
= bpy
.data
.objects
[self
.object_name
]
4456 if self
.vertex_group_streamlines
not in [vg
.name
for vg
in ob
.vertex_groups
]:
4457 self
.vertex_group_streamlines
= ob
.vertex_groups
.active
.name
4458 layout
= self
.layout
4459 col
= layout
.column(align
=True)
4460 row
= col
.row(align
=True)
4461 row
.prop(self
, 'mode', expand
=True,
4462 slider
=True, toggle
=False, icon_only
=False, event
=False,
4463 full_event
=False, emboss
=True, index
=-1)
4464 col
.prop(self
, "use_modifiers")
4465 col
.label(text
="Streamlines Curves:")
4466 row
= col
.row(align
=True)
4467 row
.prop(self
, 'interpolation', expand
=True,
4468 slider
=True, toggle
=False, icon_only
=False, event
=False,
4469 full_event
=False, emboss
=True, index
=-1)
4471 col
.prop_search(self
, 'vertex_group_streamlines', ob
, "vertex_groups", text
='')
4472 if not (self
.use_selected
or context
.mode
== 'EDIT_MESH'):
4473 row
= col
.row(align
=True)
4474 row
.prop(self
,'n_curves')
4475 #row.enabled = context.mode != 'EDIT_MESH'
4476 row
= col
.row(align
=True)
4477 row
.prop(self
,'rand_seed')
4478 #row.enabled = context.mode != 'EDIT_MESH'
4479 row
= col
.row(align
=True)
4480 row
.prop(self
,'neg_steps')
4481 row
.prop(self
,'pos_steps')
4482 #row = col.row(align=True)
4483 #row.prop(self,'min_iso')
4484 #row.prop(self,'max_iso')
4485 col
.prop(self
, "same_weight")
4487 col
.label(text
='Curves Bevel:')
4488 col
.prop_search(self
, 'vertex_group_bevel', ob
, "vertex_groups", text
='')
4489 if self
.vertex_group_bevel
!= '':
4490 row
= col
.row(align
=True)
4491 row
.prop(self
,'min_bevel_depth')
4492 row
.prop(self
,'max_bevel_depth')
4494 col
.prop(self
,'bevel_depth')
4496 col
.prop(self
, "rand_dir")
4498 def execute(self
, context
):
4499 start_time
= timeit
.default_timer()
4501 check
= context
.object.vertex_groups
[0]
4503 self
.report({'ERROR'}, "The object doesn't have Vertex Groups")
4504 return {'CANCELLED'}
4505 ob
= bpy
.data
.objects
[self
.object_name
]
4506 ob
.select_set(False)
4511 if bpy
.context
.mode
== 'EDIT_MESH':
4512 self
.use_selected
= True
4513 bpy
.ops
.object.mode_set(mode
='OBJECT')
4514 #ob = bpy.context.object
4515 #me = simple_to_mesh(ob)
4516 ob
= convert_object_to_mesh(ob
, apply_modifiers
=self
.use_modifiers
)
4517 #dg = context.evaluated_depsgraph_get()
4518 #ob = ob.evaluated_get(dg)
4521 if self
.use_selected
:
4522 # generate new bmesh
4525 print(len(me
.vertices
))
4526 #for v in me.vertices:
4527 # if v.select: seeds.append(v.index)
4529 if v
.select
: seeds
.append(v
.index
)
4531 n_verts
= len(me
.vertices
)
4532 n_edges
= len(me
.edges
)
4533 n_faces
= len(me
.polygons
)
4535 # store weight values
4537 weight
= get_weight_numpy(ob
.vertex_groups
[self
.vertex_group_streamlines
], n_verts
)
4539 bpy
.data
.objects
.remove(ob
)
4540 self
.report({'ERROR'}, "Please select a Vertex Group for streamlines")
4541 return {'CANCELLED'}
4543 variable_bevel
= False
4545 bevel_depth
= self
.bevel_depth
4547 if self
.min_bevel_depth
== self
.max_bevel_depth
:
4548 #bevel_weight = np.ones((n_verts))
4549 bevel_depth
= self
.min_bevel_depth
4551 b0
= min(self
.min_bevel_depth
, self
.max_bevel_depth
)
4552 b1
= max(self
.min_bevel_depth
, self
.max_bevel_depth
)
4553 bevel_weight
= get_weight_numpy(ob
.vertex_groups
[self
.vertex_group_bevel
], n_verts
)
4554 if self
.min_bevel_depth
> self
.max_bevel_depth
:
4555 bevel_weight
= 1-bevel_weight
4556 bevel_weight
= b0
/b1
+ bevel_weight
*((b1
-b0
)/b1
)
4558 variable_bevel
= True
4560 pass#bevel_weight = np.ones((n_verts))
4564 np
.random
.seed(self
.rand_seed
)
4565 seeds
= np
.random
.randint(n_verts
, size
=self
.n_curves
)
4567 #weight = np.array(get_weight(ob.vertex_groups.active, n_verts))
4572 neigh
= [[] for i
in range(n_verts
)]
4573 if self
.mode
== 'EDGES':
4577 neigh
[ev
[0]].append(ev
[1])
4578 neigh
[ev
[1]].append(ev
[0])
4580 elif self
.mode
== 'VERTS':
4582 for p
in me
.polygons
:
4583 face_verts
= [v
for v
in p
.vertices
]
4584 n_face_verts
= len(face_verts
)
4585 for i
in range(n_face_verts
):
4586 fv
= face_verts
.copy()
4587 neigh
[fv
.pop(i
)] += fv
4589 neigh_weight
= [weight
[n
].tolist() for n
in neigh
]
4591 # evaluate direction
4592 next_vert
= [-1]*n_verts
4594 if self
.rand_dir
> 0:
4595 for i
in range(n_verts
):
4597 nw
= neigh_weight
[i
]
4598 sorted_nw
= neigh_weight
[i
].copy()
4601 neigh
[i
] = [n
[nw
.index(w
)] for w
in sorted_nw
]
4603 if self
.pos_steps
> 0:
4604 for i
in range(n_verts
):
4606 nw
= neigh_weight
[i
]
4608 if self
.same_weight
:
4609 if max_w
>= weight
[i
]:
4610 next_vert
[i
] = n
[nw
.index(max(nw
))]
4612 if max_w
> weight
[i
]:
4613 next_vert
[i
] = n
[nw
.index(max(nw
))]
4615 if self
.neg_steps
> 0:
4616 prev_vert
= [-1]*n_verts
4617 for i
in range(n_verts
):
4619 nw
= neigh_weight
[i
]
4621 if self
.same_weight
:
4622 if min_w
<= weight
[i
]:
4623 prev_vert
[i
] = n
[nw
.index(min(nw
))]
4625 if min_w
< weight
[i
]:
4626 prev_vert
[i
] = n
[nw
.index(min(nw
))]
4629 me
.vertices
.foreach_get('co', co
)
4630 co
= np
.array(co
).reshape((-1,3))
4632 # create streamlines
4636 for j
in range(self
.pos_steps
):
4637 if self
.rand_dir
> 0:
4638 n
= neigh
[next_pts
[-1]]
4639 next
= n
[int(len(n
) * (1-random
.random() * self
.rand_dir
))]
4641 next
= next_vert
[next_pts
[-1]]
4643 if next
not in next_pts
: next_pts
.append(next
)
4647 for j
in range(self
.neg_steps
):
4648 if self
.rand_dir
> 0:
4649 n
= neigh
[prev_pts
[-1]]
4650 prev
= n
[int(len(n
) * random
.random() * self
.rand_dir
)]
4652 prev
= prev_vert
[prev_pts
[-1]]
4654 if prev
not in prev_pts
:
4655 prev_pts
.append(prev
)
4658 next_pts
= np
.array(next_pts
).astype('int')
4659 prev_pts
= np
.flip(prev_pts
[1:]).astype('int')
4660 all_pts
= np
.concatenate((prev_pts
, next_pts
))
4661 if len(all_pts
) > 1:
4662 curves
.append(all_pts
)
4663 crv
= nurbs_from_vertices(curves
, co
, bevel_weight
, ob
.name
+ '_Streamlines', True, self
.interpolation
)
4664 crv
.data
.bevel_depth
= bevel_depth
4665 crv
.matrix_world
= ob
.matrix_world
4666 bpy
.data
.objects
.remove(ob
)
4668 print("Streamlines Curves, total time: " + str(timeit
.default_timer() - start_time
) + " sec")