1 # SPDX-FileCopyrightText: 2017 Alessandro Zomparelli
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 #-------------------------- COLORS / GROUPS EXCHANGER -------------------------#
7 # Vertex Color to Vertex Group allow you to convert colors channles to weight #
9 # The main purpose is to use vertex colors to store information when importing #
10 # files from other softwares. The script works with the active vertex color #
12 # For use the command "Vertex Clors to Vertex Groups" use the search bar #
15 # (c) Alessandro Zomparelli #
18 # http://www.co-de-it.com/ #
20 ################################################################################
24 import math
, timeit
, time
26 from statistics
import mean
, stdev
27 from mathutils
import Vector
28 from mathutils
.kdtree
import KDTree
30 try: from .numba_functions
import numba_reaction_diffusion
, numba_reaction_diffusion_anisotropic
, integrate_field
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 (
59 def force_geometry_data_update(self
, context
):
61 props
= ob
.reaction_diffusion_settings
62 if props
.input_mode
== 'STATIC':
63 props
.update_geometry_data
= True
65 def reaction_diffusion_add_handler(self
, context
):
66 # remove existing handlers
67 reaction_diffusion_remove_handler(self
, context
)
69 bpy
.app
.handlers
.frame_change_post
.append(reaction_diffusion_scene
)
71 def reaction_diffusion_remove_handler(self
, context
):
72 # remove existing handlers
74 for h
in bpy
.app
.handlers
.frame_change_post
:
75 if "reaction_diffusion" in str(h
):
76 old_handlers
.append(h
)
77 for h
in old_handlers
: bpy
.app
.handlers
.frame_change_post
.remove(h
)
79 class start_reaction_diffusion(Operator
):
80 bl_idname
= "object.start_reaction_diffusion"
81 bl_label
= "Start Reaction Diffusion"
82 bl_description
= ("Run a Reaction-Diffusion based on existing Vertex Groups: A and B")
83 bl_options
= {'REGISTER', 'UNDO'}
86 name
="Run Reaction-Diffusion", default
=True, description
="Compute a new iteration on frame changes")
88 time_steps
: IntProperty(
89 name
="Steps", default
=10, min=0, soft_max
=50,
90 description
="Number of Steps")
93 name
="dt", default
=0.5, min=0, soft_max
=1,
94 description
="Time Step")
96 diff_a
: FloatProperty(
97 name
="Diff A", default
=0.18, min=0, soft_max
=2,
98 description
="Diffusion A")
100 diff_b
: FloatProperty(
101 name
="Diff B", default
=0.09, min=0, soft_max
=2,
102 description
="Diffusion B")
105 name
="f", default
=0.055, min=0, soft_min
=0.01, soft_max
=0.06, max=0.1, precision
=4,
106 description
="Feed Rate")
109 name
="k", default
=0.062, min=0, soft_min
=0.035, soft_max
=0.065, max=0.1, precision
=4,
110 description
="Kill Rate")
113 def poll(cls
, context
):
114 return context
.object.type == 'MESH' and context
.mode
!= 'EDIT_MESH'
116 def execute(self
, context
):
117 reaction_diffusion_add_handler(self
, context
)
118 set_animatable_fix_handler(self
, context
)
122 ob
.reaction_diffusion_settings
.run
= self
.run
123 ob
.reaction_diffusion_settings
.dt
= self
.dt
124 ob
.reaction_diffusion_settings
.time_steps
= self
.time_steps
125 ob
.reaction_diffusion_settings
.f
= self
.f
126 ob
.reaction_diffusion_settings
.k
= self
.k
127 ob
.reaction_diffusion_settings
.diff_a
= self
.diff_a
128 ob
.reaction_diffusion_settings
.diff_b
= self
.diff_b
130 # check vertex group A
132 vg
= ob
.vertex_groups
['A']
134 ob
.vertex_groups
.new(name
='A')
135 # check vertex group B
137 vg
= ob
.vertex_groups
['B']
139 ob
.vertex_groups
.new(name
='B')
141 for v
in ob
.data
.vertices
:
142 ob
.vertex_groups
['A'].add([v
.index
], 1, 'REPLACE')
143 ob
.vertex_groups
['B'].add([v
.index
], 0, 'REPLACE')
145 ob
.vertex_groups
.update()
147 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
151 class reset_reaction_diffusion_weight(Operator
):
152 bl_idname
= "object.reset_reaction_diffusion_weight"
153 bl_label
= "Reset Reaction Diffusion Weight"
154 bl_description
= ("Set A and B weight to default values")
155 bl_options
= {'REGISTER', 'UNDO'}
158 def poll(cls
, context
):
159 return context
.object.type == 'MESH' and context
.mode
!= 'EDIT_MESH'
161 def execute(self
, context
):
162 reaction_diffusion_add_handler(self
, context
)
163 set_animatable_fix_handler(self
, context
)
167 # check vertex group A
169 vg
= ob
.vertex_groups
['A']
171 ob
.vertex_groups
.new(name
='A')
172 # check vertex group B
174 vg
= ob
.vertex_groups
['B']
176 ob
.vertex_groups
.new(name
='B')
178 for v
in ob
.data
.vertices
:
179 ob
.vertex_groups
['A'].add([v
.index
], 1, 'REPLACE')
180 ob
.vertex_groups
['B'].add([v
.index
], 0, 'REPLACE')
182 ob
.vertex_groups
.update()
184 bpy
.ops
.object.mode_set(mode
='WEIGHT_PAINT')
188 class reaction_diffusion_prop(PropertyGroup
):
189 run
: BoolProperty(default
=False, update
= reaction_diffusion_add_handler
,
190 description
='Compute a new iteration on frame changes. Currently is not working during Render Animation')
192 time_steps
: IntProperty(
193 name
="Steps", default
=10, min=0, soft_max
=50,
194 description
="Number of Steps"
198 name
="dt", default
=0.5, min=0, soft_max
=1,
199 description
="Time Step"
202 diff_a
: FloatProperty(
203 name
="Diff A", default
=0.1, min=0, soft_max
=2, precision
=3,
204 description
="Diffusion A"
207 diff_b
: FloatProperty(
208 name
="Diff B", default
=0.05, min=0, soft_max
=2, precision
=3,
209 description
="Diffusion B"
213 name
="f", default
=0.055, soft_min
=0.01, soft_max
=0.06, precision
=4,
214 step
=0.05, description
="Feed Rate"
218 name
="k", default
=0.062, soft_min
=0.035, soft_max
=0.065, precision
=4,
219 step
=0.05, description
="Kill Rate"
222 diff_mult
: FloatProperty(
223 name
="Scale", default
=1, min=0, soft_max
=1, max=10, precision
=2,
224 description
="Multiplier for the diffusion of both substances"
227 vertex_group_diff_a
: StringProperty(
228 name
="Diff A", default
='',
229 description
="Vertex Group used for A diffusion"
232 vertex_group_diff_b
: StringProperty(
233 name
="Diff B", default
='',
234 description
="Vertex Group used for B diffusion"
237 vertex_group_scale
: StringProperty(
238 name
="Scale", default
='',
239 description
="Vertex Group used for Scale value"
242 vertex_group_f
: StringProperty(
243 name
="f", default
='',
244 description
="Vertex Group used for Feed value (f)"
247 vertex_group_k
: StringProperty(
248 name
="k", default
='',
249 description
="Vertex Group used for Kill value (k)"
252 vertex_group_brush
: StringProperty(
253 name
="Brush", default
='',
254 description
="Vertex Group used for adding/removing B"
257 invert_vertex_group_diff_a
: BoolProperty(default
=False,
258 description
='Inverte the value of the Vertex Group Diff A'
261 invert_vertex_group_diff_b
: BoolProperty(default
=False,
262 description
='Inverte the value of the Vertex Group Diff B'
265 invert_vertex_group_scale
: BoolProperty(default
=False,
266 description
='Inverte the value of the Vertex Group Scale'
269 invert_vertex_group_f
: BoolProperty(default
=False,
270 description
='Inverte the value of the Vertex Group f'
273 invert_vertex_group_k
: BoolProperty(default
=False,
274 description
='Inverte the value of the Vertex Group k'
277 min_diff_a
: FloatProperty(
278 name
="Min Diff A", default
=0.1, min=0, soft_max
=2, precision
=3,
279 description
="Min Diff A"
282 max_diff_a
: FloatProperty(
283 name
="Max Diff A", default
=0.1, min=0, soft_max
=2, precision
=3,
284 description
="Max Diff A"
287 min_diff_b
: FloatProperty(
288 name
="Min Diff B", default
=0.1, min=0, soft_max
=2, precision
=3,
289 description
="Min Diff B"
292 max_diff_b
: FloatProperty(
293 name
="Max Diff B", default
=0.1, min=0, soft_max
=2, precision
=3,
294 description
="Max Diff B"
297 min_scale
: FloatProperty(
298 name
="Scale", default
=0.35, min=0, soft_max
=1, max=10, precision
=2,
299 description
="Min Scale Value"
302 max_scale
: FloatProperty(
303 name
="Scale", default
=1, min=0, soft_max
=1, max=10, precision
=2,
304 description
="Max Scale value"
307 min_f
: FloatProperty(
308 name
="Min f", default
=0.02, min=0, soft_min
=0.01, soft_max
=0.06, max=0.1, precision
=4, step
=0.05,
309 description
="Min Feed Rate"
312 max_f
: FloatProperty(
313 name
="Max f", default
=0.055, min=0, soft_min
=0.01, soft_max
=0.06, max=0.1, precision
=4, step
=0.05,
314 description
="Max Feed Rate"
317 min_k
: FloatProperty(
318 name
="Min k", default
=0.035, min=0, soft_min
=0.035, soft_max
=0.065, max=0.1, precision
=4, step
=0.05,
319 description
="Min Kill Rate"
322 max_k
: FloatProperty(
323 name
="Max k", default
=0.062, min=0, soft_min
=0.035, soft_max
=0.065, max=0.1, precision
=4, step
=0.05,
324 description
="Max Kill Rate"
327 brush_mult
: FloatProperty(
328 name
="Mult", default
=0.5, min=-1, max=1, precision
=3, step
=0.05,
329 description
="Multiplier for brush value"
332 bool_mod
: BoolProperty(
333 name
="Use Modifiers", default
=False,
334 description
="Read modifiers affect the vertex groups or attributes"
337 bool_cache
: BoolProperty(
338 name
="Use Cache", default
=False,
339 description
="Read modifiers affect the vertex groups"
342 cache_frame_start
: IntProperty(
343 name
="Start", default
=1,
344 description
="Frame on which the simulation starts"
347 cache_frame_end
: IntProperty(
348 name
="End", default
=250,
349 description
="Frame on which the simulation ends"
352 cache_dir
: StringProperty(
353 name
="Cache directory", default
="", subtype
='FILE_PATH',
354 description
= 'Directory that contains Reaction-Diffusion cache files'
357 reload_at_start
: BoolProperty(
358 name
="Reload at Start", default
=True,
359 description
="Values from A and B are loaded from Vertex Groups or Modifiers after the first frame"
362 update_geometry_data
: BoolProperty(
363 name
="Update Geometry Data", default
=True,
364 description
="Update geometry data and vector field data at the next frame"
367 update_baked_geometry
: BoolProperty(
368 name
="Update Baked Geometry", default
=False,
369 description
="Force to update geometry data on the next iteration"
372 vector_field_mode
: EnumProperty(
374 ('NONE', "None", "Isotropic Reaction-Diffusion"),
375 ('VECTOR', "Vector", "Uniform vector"),
376 ('OBJECT', "Object", "Orient the field with a target object's Z"),
377 ('GRADIENT', "Gradient", "Gradient vertex group"),
378 ('XYZ', "x, y, z", "Vector field defined by vertex groups 'x', 'y' and 'z'"),
379 ('VECTOR_ATTRIBUTE', "Vector Field", "'RD_vector_field' attribute (Vertex > Vector)")
382 name
="Vector Field controlling the direction of the Reaction-Diffusion",
383 update
= force_geometry_data_update
386 anisotropy
: FloatProperty(
387 name
="Anisotropy", default
=0.5, min=0, max=1, precision
=2,
388 description
="Influence of the Vector Field"
391 vector
: FloatVectorProperty(
392 name
='Vector', description
='Constant Vector', default
=(0.0, 0.0, 1.0),
393 update
= force_geometry_data_update
396 perp_vector_field
: BoolProperty(default
=False,
397 description
='Use the perpendicular direction',
398 update
= force_geometry_data_update
401 vector_field_object
: PointerProperty(
402 type=bpy
.types
.Object
,
404 description
="Target Object",
405 update
= force_geometry_data_update
408 vertex_group_gradient
: StringProperty(
409 name
="Gradient", default
='',
410 description
="Vertex Group for the gradient vector field",
411 update
= force_geometry_data_update
414 input_mode
: EnumProperty(
416 ('STATIC', "Static input (faster)", "Information about geometry and input values are loaded once in the first frame and then stored as attributes. This includes also the effects of modifiers on vertex groups or attributes. Geometry data and Vector Field data are stored instead in a newly mesh."),
417 ('INTERACTIVE', "Interactive (slower)", "Information about geometry and input values are updated dynamically. This includes also the effects of modifiers on vertex groups or attributes.")
419 default
='INTERACTIVE',
421 update
= force_geometry_data_update
424 input_data
: EnumProperty(
426 ('WEIGHT', "Vertex Groups (default)", "The fields A and B are loaded from vertex groups. If 'Input Mode' is 'Static', then the Vertex Groups are loaded only for the first frame."),
427 ('ATTRIBUTES', "Attributes (faster)", "The fields A and B are loaded from the attributes 'RD_A' and 'RD_B'. If 'Input Mode' is 'Static', then this is the automatic mode for every frame except the first one.")
433 output_data
: EnumProperty(
435 ('WEIGHT', "Vertex Groups (default)", "The fields A and B are saved as Vertex Group at evry frame."),
436 ('ATTRIBUTES', "Attributes (faster)", "The fields A and B are saved as attributes 'RD_A' and 'RD_B' at every frame. If 'Input Mode' is 'Static', then this happens automatically.")
442 cache_mesh
: StringProperty(
443 name
="Cache Mesh", default
='',
444 description
="Mesh used to store data for 'Static' mode."
447 class bake_reaction_diffusion(Operator
):
448 bl_idname
= "object.bake_reaction_diffusion"
449 bl_label
= "Bake Data"
450 bl_description
= ("Bake the Reaction-Diffusion to the cache directory")
451 bl_options
= {'REGISTER', 'UNDO'}
454 def poll(cls
, context
):
455 return context
.object.type == 'MESH' and context
.mode
!= 'EDIT_MESH'
457 def execute(self
, context
):
459 props
= ob
.reaction_diffusion_settings
460 frames
= range(props
.cache_frame_start
, props
.cache_frame_end
) if props
.input_mode
== 'INTERACTIVE' else [props
.cache_frame_start
]
461 props
.run
= False if props
.input_mode
== 'STATIC' else True
463 context
.scene
.frame_current
= frame
464 message
= reaction_diffusion_def(ob
, bake
=True)
465 if type(message
) is str:
466 self
.report({'ERROR'}, message
)
467 props
.bool_cache
= True
469 context
.scene
.frame_current
= props
.cache_frame_start
472 class reaction_diffusion_free_data(Operator
):
473 bl_idname
= "object.reaction_diffusion_free_data"
474 bl_label
= "Free Data"
475 bl_description
= ("Free Reaction-Diffusion data")
476 bl_options
= {'REGISTER', 'UNDO'}
479 def poll(cls
, context
):
480 return context
.object.type == 'MESH'
482 def execute(self
, context
):
484 props
= ob
.reaction_diffusion_settings
485 props
.bool_cache
= False
487 folder
= Path(props
.cache_dir
)
488 for i
in range(props
.cache_frame_start
, props
.cache_frame_end
):
489 data_a
= folder
/ "a_{:04d}".format(i
)
490 if os
.path
.exists(data_a
):
492 data_a
= folder
/ "b_{:04d}".format(i
)
493 if os
.path
.exists(data_a
):
497 from bpy
.app
.handlers
import persistent
499 def reaction_diffusion_scene(scene
, bake
=False):
500 tissue_time(None,'{:7d} Tissue: Reaction-Diffusion...'.format(scene
.frame_current
), levels
=0)
501 for ob
in scene
.objects
:
502 if ob
.reaction_diffusion_settings
.run
:
503 message
= reaction_diffusion_def(ob
)
504 if type(message
) is str:
507 def load_attribute_parameter(mesh
, name
, default
, domain
, data_type
):
508 if name
in mesh
.attributes
:
509 att
= mesh
.attributes
[name
]
510 if att
.domain
== domain
and att
.data_type
== data_type
:
511 values
= np
.zeros((len(att
.data
)))
512 att
.data
.foreach_get('value', values
)
516 def store_attribute_parameter(mesh
, name
, values
, domain
, data_type
):
517 if name
not in mesh
.attributes
:
518 mesh
.attributes
.new(name
, data_type
, domain
)
519 att
= mesh
.attributes
[name
]
520 if att
.domain
== domain
and att
.data_type
== data_type
and len(values
) == len(att
.data
):
521 att
.data
.foreach_set('value', values
)
523 def reaction_diffusion_def(ob
, bake
=False):
524 scene
= bpy
.context
.scene
526 beginning
= time
.time()
527 if type(ob
) == bpy
.types
.Scene
: return None
528 props
= ob
.reaction_diffusion_settings
530 if bake
or props
.bool_cache
:
531 if props
.cache_dir
== '':
532 letters
= string
.ascii_letters
533 random_name
= ''.join(rnd
.choice(letters
) for i
in range(6))
534 if bpy
.context
.blend_data
.filepath
== '':
535 folder
= Path(bpy
.context
.preferences
.filepaths
.temporary_directory
)
536 folder
= folder
/ 'reaction_diffusion_cache' / random_name
538 folder
= '//' + Path(bpy
.context
.blend_data
.filepath
).stem
539 folder
= Path(bpy
.path
.abspath(folder
)) / 'reaction_diffusion_cache' / random_name
540 folder
.mkdir(parents
=True, exist_ok
=True)
541 props
.cache_dir
= str(folder
)
543 folder
= Path(props
.cache_dir
)
547 n_verts
= len(me
.vertices
)
548 a
= np
.zeros(n_verts
)
549 b
= np
.zeros(n_verts
)
551 if bake
and props
.input_mode
== 'INTERACTIVE':
552 tissue_time(None,'{:7d} Tissue: Reaction-Diffusion...'.format(scene
.frame_current
), levels
=0)
553 tissue_time(None,"Running on {}...".format(ob
.name
),levels
=0)
554 is_static
= props
.input_mode
== 'STATIC'
555 if props
.reload_at_start
and scene
.frame_current
== props
.cache_frame_start
:
557 use_modifiers
= props
.bool_mod
and not is_static
561 file_name
= folder
/ "a_{:04d}".format(scene
.frame_current
)
562 a
= np
.fromfile(file_name
)
563 file_name
= folder
/ "b_{:04d}".format(scene
.frame_current
)
564 b
= np
.fromfile(file_name
)
566 print(' Cannot read cache.')
570 me
= rd_apply_modifiers(ob
)
575 time_steps
= props
.time_steps
578 diff_a
= props
.diff_a
579 diff_b
= props
.diff_b
580 scale
= props
.diff_mult
581 brush_mult
= props
.brush_mult
584 if is_static
or props
.input_data
== 'ATTRIBUTES':
585 if not 'RD_A' in me
.attributes
:
586 me
.attributes
.new('RD_A', 'FLOAT', 'POINT')
587 if not 'RD_B' in me
.attributes
:
588 me
.attributes
.new('RD_B', 'FLOAT', 'POINT')
589 a
= np
.zeros((n_verts
))
590 b
= np
.zeros((n_verts
))
591 me
.attributes
['RD_A'].data
.foreach_get('value', a
)
592 me
.attributes
['RD_B'].data
.foreach_get('value', b
)
593 a
= load_attribute_parameter(me
, 'RD_A', np
.zeros((n_verts
)), 'POINT', 'FLOAT')
594 b
= load_attribute_parameter(me
, 'RD_B', np
.zeros((n_verts
)), 'POINT', 'FLOAT')
595 if not (props
.input_data
== 'WEIGHT' and not props
.vertex_group_brush
in ob
.vertex_groups
):
596 brush
= load_attribute_parameter(me
, 'RD_brush', 0, 'POINT', 'FLOAT')
597 if not (props
.input_data
== 'WEIGHT' and not props
.vertex_group_diff_a
in ob
.vertex_groups
):
598 diff_a
= load_attribute_parameter(me
, 'RD_diff_a', diff_a
, 'POINT', 'FLOAT')
599 if not (props
.input_data
== 'WEIGHT' and not props
.vertex_group_diff_b
in ob
.vertex_groups
):
600 diff_b
= load_attribute_parameter(me
, 'RD_diff_b', diff_b
, 'POINT', 'FLOAT')
601 if not (props
.input_data
== 'WEIGHT' and not props
.vertex_group_scale
in ob
.vertex_groups
):
602 scale
= load_attribute_parameter(me
, 'RD_scale', scale
, 'POINT', 'FLOAT')
603 if not (props
.input_data
== 'WEIGHT' and not props
.vertex_group_f
in ob
.vertex_groups
):
604 f
= load_attribute_parameter(me
, 'RD_f', f
, 'POINT', 'FLOAT')
605 if not (props
.input_data
== 'WEIGHT' and not props
.vertex_group_k
in ob
.vertex_groups
):
606 k
= load_attribute_parameter(me
, 'RD_k', k
, 'POINT', 'FLOAT')
608 if props
.vertex_group_diff_a
!= '':
609 diff_a
= np
.zeros(n_verts
)
610 if props
.vertex_group_diff_b
!= '':
611 diff_b
= np
.zeros(n_verts
)
612 if props
.vertex_group_scale
!= '':
613 scale
= np
.zeros(n_verts
)
614 if props
.vertex_group_f
!= '':
615 f
= np
.zeros(n_verts
)
616 if props
.vertex_group_k
!= '':
617 k
= np
.zeros(n_verts
)
618 if props
.vertex_group_brush
!= '':
619 brush
= np
.zeros(n_verts
)
622 bm
= bmesh
.new() # create an empty BMesh
623 bm
.from_mesh(me
) # fill it in from a Mesh
624 dvert_lay
= bm
.verts
.layers
.deform
.active
626 group_index_a
= ob
.vertex_groups
["A"].index
627 group_index_b
= ob
.vertex_groups
["B"].index
628 a
= bmesh_get_weight_numpy(group_index_a
, dvert_lay
, bm
.verts
)
629 b
= bmesh_get_weight_numpy(group_index_b
, dvert_lay
, bm
.verts
)
631 if props
.vertex_group_diff_a
!= '':
632 group_index
= ob
.vertex_groups
[props
.vertex_group_diff_a
].index
633 diff_a
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
, normalized
=True)
634 if props
.invert_vertex_group_diff_a
:
635 vg_bounds
= (props
.min_diff_a
, props
.max_diff_a
)
637 vg_bounds
= (props
.max_diff_a
, props
.min_diff_a
)
638 diff_a
= np
.interp(diff_a
, (0,1), vg_bounds
)
639 if props
.input_mode
== 'STATIC':
640 store_attribute_parameter(me
, 'RD_diff_a', diff_a
, 'POINT', 'FLOAT')
642 if props
.vertex_group_diff_b
!= '':
643 group_index
= ob
.vertex_groups
[props
.vertex_group_diff_b
].index
644 diff_b
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
, normalized
=True)
645 if props
.invert_vertex_group_diff_b
:
646 vg_bounds
= (props
.max_diff_b
, props
.min_diff_b
)
648 vg_bounds
= (props
.min_diff_b
, props
.max_diff_b
)
649 diff_b
= np
.interp(diff_b
, (0,1), vg_bounds
)
650 if props
.input_mode
== 'STATIC':
651 store_attribute_parameter(me
, 'RD_diff_b', diff_b
, 'POINT', 'FLOAT')
653 if props
.vertex_group_scale
!= '':
654 group_index
= ob
.vertex_groups
[props
.vertex_group_scale
].index
655 scale
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
, normalized
=True)
656 if props
.invert_vertex_group_scale
:
657 vg_bounds
= (props
.max_scale
, props
.min_scale
)
659 vg_bounds
= (props
.min_scale
, props
.max_scale
)
660 scale
= np
.interp(scale
, (0,1), vg_bounds
)
661 if props
.input_mode
== 'STATIC':
662 store_attribute_parameter(me
, 'RD_scale', scale
, 'POINT', 'FLOAT')
664 if props
.vertex_group_f
!= '':
665 group_index
= ob
.vertex_groups
[props
.vertex_group_f
].index
666 f
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
, normalized
=True)
667 if props
.invert_vertex_group_f
:
668 vg_bounds
= (props
.max_f
, props
.min_f
)
670 vg_bounds
= (props
.min_f
, props
.max_f
)
671 f
= np
.interp(f
, (0,1), vg_bounds
, )
672 if props
.input_mode
== 'STATIC':
673 store_attribute_parameter(me
, 'RD_f', f
, 'POINT', 'FLOAT')
675 if props
.vertex_group_k
!= '':
676 group_index
= ob
.vertex_groups
[props
.vertex_group_k
].index
677 k
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
, normalized
=True)
678 if props
.invert_vertex_group_k
:
679 vg_bounds
= (props
.max_k
, props
.min_k
)
681 vg_bounds
= (props
.min_k
, props
.max_k
)
682 k
= np
.interp(k
, (0,1), vg_bounds
)
683 if props
.input_mode
== 'STATIC':
684 store_attribute_parameter(me
, 'RD_k', k
, 'POINT', 'FLOAT')
686 if props
.vertex_group_brush
!= '':
687 group_index
= ob
.vertex_groups
[props
.vertex_group_brush
].index
688 brush
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
, normalized
=True)
690 if props
.input_mode
== 'STATIC':
691 store_attribute_parameter(me
, 'RD_brush', brush
, 'POINT', 'FLOAT')
697 field_mult
= np
.zeros((1))
698 if is_static
and props
.cache_mesh
in bpy
.data
.meshes
and not props
.update_geometry_data
:
699 rd_mesh
= bpy
.data
.meshes
[props
.cache_mesh
]
700 edge_verts
= get_edges_numpy_ex(rd_mesh
)
701 n_edges
= len(edge_verts
)
702 if props
.vector_field_mode
!= 'NONE' and 'RD_vector_field' in rd_mesh
.attributes
:
703 field_mult
= load_attribute_parameter(rd_mesh
, 'RD_vector_field', np
.ones((n_edges
)), 'EDGE', 'FLOAT')
705 edge_verts
= get_edges_numpy_ex(me
)
706 n_edges
= len(edge_verts
)
707 if props
.cache_mesh
in bpy
.data
.meshes
:
708 rd_mesh
= bpy
.data
.meshes
[props
.cache_mesh
]
709 rd_mesh
.clear_geometry()
711 rd_mesh
= bpy
.data
.meshes
.new('RD_' + me
.name
)
712 props
.cache_mesh
= rd_mesh
.name
713 rd_mesh
.from_pydata(get_vertices_numpy(me
), edge_verts
, [])
715 is_vector_field
= True
717 if props
.vector_field_mode
!= 'NONE':
718 if props
.vector_field_mode
== 'VECTOR':
719 vec
= Vector(props
.vector
)
720 vector_field
= [vec
]*n_edges
722 if props
.vector_field_mode
== 'OBJECT':
723 if props
.vector_field_object
:
724 mat
= props
.vector_field_object
.matrix_world
726 mat
= ob
.matrix_world
727 vec
= Vector((mat
[0][2],mat
[1][2],mat
[2][2]))
728 vector_field
= [vec
]*n_edges
730 if props
.vector_field_mode
== 'XYZ':
731 vgk
= ob
.vertex_groups
.keys()
732 if 'x' in vgk
and 'y' in vgk
and 'z' in vgk
:
734 bm
= bmesh
.new() # create an empty BMesh
735 bm
.from_mesh(me
) # fill it in from a Mesh
736 dvert_lay
= bm
.verts
.layers
.deform
.active
737 group_index
= ob
.vertex_groups
["x"].index
738 field_x
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
739 group_index
= ob
.vertex_groups
["y"].index
740 field_y
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
741 group_index
= ob
.vertex_groups
["z"].index
742 field_z
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
743 field_x
= field_x
*2-1
744 field_y
= field_y
*2-1
745 field_z
= field_z
*2-1
747 for x
,y
,z
in zip(field_x
, field_y
, field_z
):
748 vector_field
.append(Vector((x
,y
,z
)).normalized())
750 is_vector_field
= False
752 if props
.vector_field_mode
== 'GRADIENT':
753 if props
.vertex_group_gradient
:
754 if props
.vertex_group_gradient
in ob
.vertex_groups
.keys():
756 bm
= bmesh
.new() # create an empty BMesh
757 bm
.from_mesh(me
) # fill it in from a Mesh
758 dvert_lay
= bm
.verts
.layers
.deform
.active
759 group_index
= ob
.vertex_groups
[props
.vertex_group_gradient
].index
760 weight
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
761 vector_field
= [None]*n_verts
762 for i
,v0
in enumerate(bm
.verts
):
763 vec
= Vector((0,0,0))
764 w0
= weight
[v0
.index
]
765 for e
in v0
.link_edges
:
766 v1
= e
.other_vert(v0
)
767 dw
= weight
[v1
.index
]-w0
768 vec
+= (v1
.co
-v0
.co
)*dw
769 vector_field
[i
] = vec
.normalized()
771 is_vector_field
= False
773 is_vector_field
= False
775 if props
.vector_field_mode
== 'VECTOR_ATTRIBUTE':
776 if 'RD_vector_field' in me
.attributes
:
777 vectors_components
= [0]*n_verts
*3
778 me
.attributes
['RD_vector_field'].data
.foreach_get('vector', vectors_components
)
779 vector_field
= [None]*n_verts
780 for i
in range(n_verts
):
781 x
= vectors_components
[i
*3]
782 y
= vectors_components
[i
*3+1]
783 z
= vectors_components
[i
*3+2]
784 vector_field
[i
] = Vector((x
,y
,z
)).normalized()
786 is_vector_field
= False
789 if props
.perp_vector_field
:
790 for i
, vert
in enumerate(bm
.verts
):
791 vector_field
[i
] = vector_field
[i
].cross(vert
.normal
)
792 field_mult
= [1]*n_edges
793 for i
, pair
in enumerate(edge_verts
):
796 v0
= me
.vertices
[id0
].co
797 v1
= me
.vertices
[id1
].co
798 vec
= (v1
-v0
).normalized()
799 mult0
= abs(vec
.dot(vector_field
[id0
]))
800 mult1
= abs(vec
.dot(vector_field
[id1
]))
801 field_mult
[i
] = (mult0
+ mult1
)/2
802 field_mult
= np
.array(field_mult
)
803 if props
.cache_mesh
in bpy
.data
.meshes
and props
.input_mode
== 'STATIC':
804 rd_mesh
= bpy
.data
.meshes
[props
.cache_mesh
]
805 store_attribute_parameter(rd_mesh
, 'RD_vector_field', field_mult
, 'EDGE', 'FLOAT')
807 is_vector_field
= False
808 props
.update_geometry_data
= False
810 edge_verts
= edge_verts
.reshape((-1))
811 field_mult
= field_mult
*props
.anisotropy
+ (1-props
.anisotropy
)
813 tissue_time(start
, "Preparation", levels
=1)
816 frames
= range(props
.cache_frame_start
, props
.cache_frame_end
+1) if bake
and props
.input_mode
== 'STATIC' else [scene
.frame_current
]
818 if bake
and props
.input_mode
== 'STATIC':
819 tissue_time(None,'{:7d} Tissue: Baking Reaction-Diffusion on {}...'.format(frame
, ob
.name
), levels
=0)
821 _f
= f
if type(f
) is np
.ndarray
else np
.array((f
,))
822 _k
= k
if type(k
) is np
.ndarray
else np
.array((k
,))
823 _diff_a
= diff_a
if type(diff_a
) is np
.ndarray
else np
.array((diff_a
,))
825 _diff_b
= diff_b
if type(diff_b
) is np
.ndarray
else np
.array((diff_b
,))
827 _brush
= brush
if type(brush
) is np
.ndarray
else np
.array((brush
,))
828 if len(field_mult
) == 1:
829 a
, b
= numba_reaction_diffusion(n_verts
, n_edges
, edge_verts
, a
, b
, _brush
, _diff_a
, _diff_b
, _f
, _k
, dt
, time_steps
)
831 a
, b
= numba_reaction_diffusion_anisotropic(n_verts
, n_edges
, edge_verts
, a
, b
, _brush
, _diff_a
, _diff_b
, _f
, _k
, dt
, time_steps
, field_mult
)
833 print('Not using Numba! The simulation could be slow.')
834 arr
= np
.arange(n_edges
)
835 id0
= edge_verts
[arr
*2] # first vertex indices for each edge
836 id1
= edge_verts
[arr
*2+1] # second vertex indices for each edge
837 if len(field_mult
) == 1: mult
= 1
838 else: mult
= field_mult
[arr
] # second vertex indices for each edge
839 _diff_a
= diff_a
*scale
840 _diff_b
= diff_b
*scale
841 for i
in range(time_steps
):
843 lap_a
= np
.zeros(n_verts
)
844 lap_b
= np
.zeros(n_verts
)
845 lap_a0
= (a
[id1
] - a
[id0
])*mult
# laplacian increment for first vertex of each edge
846 lap_b0
= (b
[id1
] - b
[id0
])*mult
# laplacian increment for first vertex of each edge
848 np
.add
.at(lap_a
, id0
, lap_a0
)
849 np
.add
.at(lap_b
, id0
, lap_b0
)
850 np
.add
.at(lap_a
, id1
, -lap_a0
)
851 np
.add
.at(lap_b
, id1
, -lap_b0
)
853 a
+= eval("(_diff_a*lap_a - ab2 + f*(1-a))*dt")
854 b
+= eval("(_diff_b*lap_b + ab2 - (k+f)*b)*dt")
858 tissue_time(start
, "Simulation", levels
=1)
861 if not(os
.path
.exists(folder
)):
863 file_name
= folder
/ "a_{:04d}".format(frame
)
865 file_name
= folder
/ "b_{:04d}".format(frame
)
867 if props
.input_mode
== 'STATIC':
868 tissue_time(start
, "Baked", levels
=1)
869 tissue_time(beginning
, "Reaction-Diffusion on {}".format(ob
.name
), levels
=0)
872 if props
.output_data
== 'ATTRIBUTES':
873 store_attribute_parameter(ob
.data
, 'RD_A', a
, 'POINT', 'FLOAT')
874 store_attribute_parameter(ob
.data
, 'RD_B', b
, 'POINT', 'FLOAT')
877 if props
.input_mode
== 'STATIC':
878 store_attribute_parameter(ob
.data
, 'RD_A', a
, 'POINT', 'FLOAT')
879 store_attribute_parameter(ob
.data
, 'RD_B', b
, 'POINT', 'FLOAT')
881 if 'A' in ob
.vertex_groups
.keys():
882 vg_a
= ob
.vertex_groups
['A']
884 vg_a
= ob
.vertex_groups
.new(name
='A')
885 if 'B' in ob
.vertex_groups
.keys():
886 vg_b
= ob
.vertex_groups
['B']
888 vg_b
= ob
.vertex_groups
.new(name
='B')
889 if ob
.mode
== 'WEIGHT_PAINT':
890 # slower, but prevent crashes
891 for i
in range(n_verts
):
892 if vg_a
: vg_a
.add([i
], a
[i
], 'REPLACE')
893 if vg_b
: vg_b
.add([i
], b
[i
], 'REPLACE')
895 if use_modifiers
or props
.bool_cache
:
896 #bm.free() # release old bmesh
897 bm
= bmesh
.new() # create an empty BMesh
898 bm
.from_mesh(ob
.data
) # fill it in from a Mesh
899 dvert_lay
= bm
.verts
.layers
.deform
.active
900 # faster, but can cause crashes while painting weight
901 if vg_a
: index_a
= vg_a
.index
902 if vg_b
: index_b
= vg_b
.index
903 for i
, v
in enumerate(bm
.verts
):
905 if vg_a
: dvert
[index_a
] = a
[i
]
906 if vg_b
: dvert
[index_b
] = b
[i
]
910 for ps
in ob
.particle_systems
:
911 if ps
.vertex_group_density
== 'B' or ps
.vertex_group_density
== 'A':
912 ps
.invert_vertex_group_density
= not ps
.invert_vertex_group_density
913 ps
.invert_vertex_group_density
= not ps
.invert_vertex_group_density
915 if use_modifiers
and not props
.bool_cache
: bpy
.data
.meshes
.remove(me
)
916 tissue_time(start
, "Writing data", levels
=1)
917 tissue_time(beginning
, "Reaction-Diffusion on {}".format(ob
.name
), levels
=0)
919 class TISSUE_PT_reaction_diffusion(Panel
):
920 bl_space_type
= 'PROPERTIES'
921 bl_region_type
= 'WINDOW'
923 bl_label
= "Tissue Reaction-Diffusion"
924 bl_options
= {'DEFAULT_CLOSED'}
927 def poll(cls
, context
):
928 return 'A' and 'B' in context
.object.vertex_groups
930 def draw(self
, context
):
931 reaction_diffusion_add_handler(self
, context
)
934 props
= ob
.reaction_diffusion_settings
936 col
= layout
.column(align
=True)
937 row
= col
.row(align
=True)
938 if not ("A" and "B" in ob
.vertex_groups
):
939 row
.operator("object.start_reaction_diffusion",
941 text
="Reaction-Diffusion")
943 row
.operator("object.start_reaction_diffusion",
945 text
="Reset Reaction-Diffusion")
946 row
.prop(props
, "bool_mod", text
="", icon
='MODIFIER')
947 row
.prop(props
, "run", text
="", icon
='TIME')
949 col
= layout
.column(align
=True)
950 row
= col
.row(align
=True)
951 row
.prop(props
, "time_steps")
952 row
.prop(props
, "dt")
953 row
.enabled
= not props
.bool_cache
955 row
= col
.row(align
=True)
956 col1
= row
.column(align
=True)
957 col1
.prop(props
, "diff_a")
958 col1
.enabled
= props
.vertex_group_diff_a
== '' and not props
.bool_cache
959 col1
= row
.column(align
=True)
960 col1
.prop(props
, "diff_b")
961 col1
.enabled
= props
.vertex_group_diff_b
== '' and not props
.bool_cache
962 row
= col
.row(align
=True)
963 row
.prop(props
, "diff_mult")
964 row
.enabled
= props
.vertex_group_scale
== '' and not props
.bool_cache
965 row
= col
.row(align
=True)
966 col1
= row
.column(align
=True)
967 col1
.prop(props
, "f")
968 col1
.enabled
= props
.vertex_group_f
== '' and not props
.bool_cache
969 col1
= row
.column(align
=True)
970 col1
.prop(props
, "k")
971 col1
.enabled
= props
.vertex_group_k
== '' and not props
.bool_cache
973 class TISSUE_PT_reaction_diffusion_vector_field(Panel
):
974 bl_space_type
= 'PROPERTIES'
975 bl_region_type
= 'WINDOW'
977 bl_parent_id
= "TISSUE_PT_reaction_diffusion"
978 bl_label
= "Anisotropic"
979 bl_options
= {'DEFAULT_CLOSED'}
982 def poll(cls
, context
):
983 return 'A' and 'B' in context
.object.vertex_groups
985 def draw(self
, context
):
987 props
= ob
.reaction_diffusion_settings
989 col
= layout
.column(align
=True)
990 col
.prop(props
, "vector_field_mode", text
="Mode")
991 if props
.vector_field_mode
== 'OBJECT':
992 col
.prop_search(props
, "vector_field_object", context
.scene
, "objects", text
='Object')
993 if props
.vector_field_mode
== 'GRADIENT':
994 col
.prop_search(props
, 'vertex_group_gradient', ob
, "vertex_groups")
995 if props
.vector_field_mode
== 'XYZ':
996 vgk
= ob
.vertex_groups
.keys()
998 col
.label(text
="Vertex Group 'x' is missing", icon
='ERROR')
1000 col
.label(text
="Vertex Group 'y' is missing", icon
='ERROR')
1002 col
.label(text
="Vertex Group 'z' is missing", icon
='ERROR')
1003 if props
.vector_field_mode
== 'VECTOR_ATTRIBUTE':
1004 vgk
= ob
.vertex_groups
.keys()
1005 if 'RD_vector_field' not in ob
.data
.attributes
:
1006 col
.label(text
="Vector Attribute 'RD_vector_field' is missing", icon
='ERROR')
1007 if props
.vector_field_mode
== 'VECTOR':
1009 row
.prop(props
, "vector")
1010 if props
.vector_field_mode
!= 'NONE':
1013 row
.prop(props
, 'perp_vector_field', text
='Perpendicular')
1014 row
.prop(props
, "anisotropy")
1016 class TISSUE_PT_reaction_diffusion_performance(Panel
):
1017 bl_space_type
= 'PROPERTIES'
1018 bl_region_type
= 'WINDOW'
1020 bl_parent_id
= "TISSUE_PT_reaction_diffusion"
1021 bl_label
= "Performance"
1022 bl_options
= {'DEFAULT_CLOSED'}
1025 def poll(cls
, context
):
1026 return 'A' and 'B' in context
.object.vertex_groups
1028 def draw(self
, context
):
1030 props
= ob
.reaction_diffusion_settings
1031 layout
= self
.layout
1032 col
= layout
.column(align
=True)
1033 row
= col
.row(align
=True)
1034 row
.prop(props
, "input_mode", text
='Mode')
1035 if props
.input_mode
== 'STATIC':
1037 row
= col
.row(align
=True)
1038 row
.prop(props
, "reload_at_start", icon
= 'SORTTIME')
1039 row
.prop(props
, "update_geometry_data", icon
='MOD_DATA_TRANSFER')
1041 col
.prop(props
, "input_data", text
='Read from')
1042 col
.prop(props
, "output_data", text
='Write to')
1045 class TISSUE_PT_reaction_diffusion_weight(Panel
):
1046 bl_space_type
= 'PROPERTIES'
1047 bl_region_type
= 'WINDOW'
1049 bl_parent_id
= "TISSUE_PT_reaction_diffusion"
1050 bl_label
= "Variable Parameters"
1051 bl_options
= {'DEFAULT_CLOSED'}
1054 def poll(cls
, context
):
1055 return 'A' and 'B' in context
.object.vertex_groups
1057 def draw(self
, context
):
1059 props
= ob
.reaction_diffusion_settings
1060 layout
= self
.layout
1061 col
= layout
.column(align
=True)
1062 if props
.input_data
== 'WEIGHT':
1063 insert_weight_parameter(col
, ob
, 'brush', text
='Brush:')
1064 insert_weight_parameter(col
, ob
, 'diff_a', text
='Diff A:')
1065 insert_weight_parameter(col
, ob
, 'diff_b', text
='Diff B:')
1066 insert_weight_parameter(col
, ob
, 'scale', text
='Scale:')
1067 insert_weight_parameter(col
, ob
, 'f', text
='f:')
1068 insert_weight_parameter(col
, ob
, 'k', text
='k:')
1070 col
.label(text
='Using Attributes (Vertex > Float) if existing:')
1071 insert_attribute_parameter(col
, ob
, 'RD_brush', text
='Brush:')
1072 insert_attribute_parameter(col
, ob
, 'RD_diff_a', text
='Diff A:')
1073 insert_attribute_parameter(col
, ob
, 'RD_diff_b', text
='Diff B:')
1074 insert_attribute_parameter(col
, ob
, 'RD_scale', text
='Scale:')
1075 insert_attribute_parameter(col
, ob
, 'RD_f', text
='f:')
1076 insert_attribute_parameter(col
, ob
, 'RD_k', text
='k:')
1077 if not props
.bool_mod
:
1078 col
.label(text
="'Use Modifiers' is disabled.", icon
='INFO')
1079 col
.enabled
= not props
.bool_cache
1081 class TISSUE_PT_reaction_diffusion_cache(Panel
):
1082 bl_space_type
= 'PROPERTIES'
1083 bl_region_type
= 'WINDOW'
1085 bl_parent_id
= "TISSUE_PT_reaction_diffusion"
1087 bl_options
= {'DEFAULT_CLOSED'}
1090 def poll(cls
, context
):
1091 return 'A' and 'B' in context
.object.vertex_groups
1093 def draw(self
, context
):
1095 props
= ob
.reaction_diffusion_settings
1096 layout
= self
.layout
1097 col
= layout
.column(align
=True)
1098 col
.label(text
='Cache:')
1099 col
.prop(props
, "cache_dir", text
='')
1101 row
= col
.row(align
=True)
1102 row
.prop(props
, "cache_frame_start")
1103 row
.prop(props
, "cache_frame_end")
1105 if props
.bool_cache
:
1106 col
.operator("object.reaction_diffusion_free_data")
1108 row
= col
.row(align
=True)
1109 row
.operator("object.bake_reaction_diffusion")
1110 file = bpy
.context
.blend_data
.filepath
1111 temp
= bpy
.context
.preferences
.filepaths
.temporary_directory
1112 if file == temp
== props
.cache_dir
== '':
1114 col
.label(text
="Cannot use cache", icon
='ERROR')
1115 col
.label(text
='please save the Blender or set a Cache directory')
1117 def insert_weight_parameter(col
, ob
, name
, text
=''):
1118 props
= ob
.reaction_diffusion_settings
1119 split
= col
.split(factor
=0.25, align
=True)
1120 col2
= split
.column(align
=True)
1121 col2
.label(text
=text
)
1122 col2
= split
.column(align
=True)
1123 row2
= col2
.row(align
=True)
1124 row2
.prop_search(props
, 'vertex_group_' + name
, ob
, "vertex_groups", text
='')
1126 row2
.prop(props
, "invert_vertex_group_" + name
, text
="", toggle
=True, icon
='ARROW_LEFTRIGHT')
1127 if 'vertex_group_' + name
in props
:
1128 if props
['vertex_group_' + name
] != '':
1130 col2
.prop(props
, "brush_mult")
1132 row2
= col2
.row(align
=True)
1133 row2
.prop(props
, "min_" + name
, text
="Min")
1134 row2
= col2
.row(align
=True)
1135 row2
.prop(props
, "max_" + name
, text
="Max")
1138 def insert_attribute_parameter(col
, ob
, name
, text
=''):
1139 props
= ob
.reaction_diffusion_settings
1140 if name
in ob
.data
.attributes
.keys():
1141 col
.label(text
= text
+ ' Attribute "' + name
+ '" found!', icon
='KEYFRAME_HLT')
1143 col
.label(text
= text
+ ' Attribute "' + name
+ '" not found.', icon
='KEYFRAME')
1146 def rd_apply_modifiers(ob
):
1147 # hide deforming modifiers
1149 for m
in ob
.modifiers
:
1150 mod_visibility
.append(m
.show_viewport
)
1151 if not (mod_preserve_shape(m
) or 'RD' in m
.name
): m
.show_viewport
= False
1154 dg
= bpy
.context
.evaluated_depsgraph_get()
1155 ob_eval
= ob
.evaluated_get(dg
)
1156 me
= bpy
.data
.meshes
.new_from_object(ob_eval
, preserve_all_data_layers
=True, depsgraph
=dg
)
1157 if len(me
.vertices
) != len(ob
.data
.vertices
):
1158 return "TISSUE: Modifiers used for Reaction-Diffusion cannot change the number of vertices."
1160 # set original visibility
1161 for v
, m
in zip(mod_visibility
, ob
.modifiers
):
1163 ob
.modifiers
.update()