Export_3ds: Added distance cue chunk export
[blender-addons.git] / mesh_tissue / weight_reaction_diffusion.py
blobca6bc1e3112e5134cce4df72da239f6d7d956f1c
1 # SPDX-FileCopyrightText: 2017 Alessandro Zomparelli
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 #-------------------------- COLORS / GROUPS EXCHANGER -------------------------#
6 # #
7 # Vertex Color to Vertex Group allow you to convert colors channles to weight #
8 # maps. #
9 # The main purpose is to use vertex colors to store information when importing #
10 # files from other softwares. The script works with the active vertex color #
11 # slot. #
12 # For use the command "Vertex Clors to Vertex Groups" use the search bar #
13 # (space bar). #
14 # #
15 # (c) Alessandro Zomparelli #
16 # (2017) #
17 # #
18 # http://www.co-de-it.com/ #
19 # #
20 ################################################################################
22 import bpy, bmesh, os
23 import numpy as np
24 import math, timeit, time
25 from math import pi
26 from statistics import mean, stdev
27 from mathutils import Vector
28 from mathutils.kdtree import KDTree
29 from numpy import *
30 try: from .numba_functions import numba_reaction_diffusion, numba_reaction_diffusion_anisotropic, integrate_field
31 except: pass
32 try: import numexpr as ne
33 except: pass
35 # Reaction-Diffusion cache
36 from pathlib import Path
37 import random as rnd
38 import string
40 from bpy.types import (
41 Operator,
42 Panel,
43 PropertyGroup,
46 from bpy.props import (
47 BoolProperty,
48 EnumProperty,
49 FloatProperty,
50 IntProperty,
51 StringProperty,
52 FloatVectorProperty,
53 IntVectorProperty,
54 PointerProperty
57 from .utils import *
59 def force_geometry_data_update(self, context):
60 ob = context.object
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)
68 # add new handler
69 bpy.app.handlers.frame_change_post.append(reaction_diffusion_scene)
71 def reaction_diffusion_remove_handler(self, context):
72 # remove existing handlers
73 old_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'}
85 run : BoolProperty(
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")
92 dt : FloatProperty(
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")
104 f : FloatProperty(
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")
108 k : FloatProperty(
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")
112 @classmethod
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)
120 ob = context.object
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
131 try:
132 vg = ob.vertex_groups['A']
133 except:
134 ob.vertex_groups.new(name='A')
135 # check vertex group B
136 try:
137 vg = ob.vertex_groups['B']
138 except:
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()
146 ob.data.update()
147 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
149 return {'FINISHED'}
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'}
157 @classmethod
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)
165 ob = context.object
167 # check vertex group A
168 try:
169 vg = ob.vertex_groups['A']
170 except:
171 ob.vertex_groups.new(name='A')
172 # check vertex group B
173 try:
174 vg = ob.vertex_groups['B']
175 except:
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()
183 ob.data.update()
184 bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
186 return {'FINISHED'}
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"
197 dt : FloatProperty(
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"
212 f : FloatProperty(
213 name="f", default=0.055, soft_min=0.01, soft_max=0.06, precision=4,
214 step=0.05, description="Feed Rate"
217 k : FloatProperty(
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(
373 items=(
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)")
381 default='NONE',
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,
403 name="",
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(
415 items=(
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',
420 name="Input Mode",
421 update = force_geometry_data_update
424 input_data : EnumProperty(
425 items=(
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.")
429 default='WEIGHT',
430 name="Input Data"
433 output_data : EnumProperty(
434 items=(
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.")
438 default='WEIGHT',
439 name="Output Data"
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'}
453 @classmethod
454 def poll(cls, context):
455 return context.object.type == 'MESH' and context.mode != 'EDIT_MESH'
457 def execute(self, context):
458 ob = context.object
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
462 for frame in frames:
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
468 props.run = True
469 context.scene.frame_current = props.cache_frame_start
470 return {'FINISHED'}
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'}
478 @classmethod
479 def poll(cls, context):
480 return context.object.type == 'MESH'
482 def execute(self, context):
483 ob = context.object
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):
491 os.remove(data_a)
492 data_a = folder / "b_{:04d}".format(i)
493 if os.path.exists(data_a):
494 os.remove(data_a)
495 return {'FINISHED'}
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:
505 print(message)
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)
513 return values
514 return default
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
525 start = time.time()
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
537 else:
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)
542 else:
543 folder = Path(props.cache_dir)
545 me = ob.data
546 bm = None
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:
556 is_static = False
557 use_modifiers = props.bool_mod and not is_static
559 if props.bool_cache:
560 try:
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)
565 except:
566 print(' Cannot read cache.')
567 return
568 else:
569 if use_modifiers:
570 me = rd_apply_modifiers(ob)
571 if type(me) is str:
572 return me
574 dt = props.dt
575 time_steps = props.time_steps
576 f = props.f
577 k = props.k
578 diff_a = props.diff_a
579 diff_b = props.diff_b
580 scale = props.diff_mult
581 brush_mult = props.brush_mult
582 brush = 0
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')
607 else:
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)
620 else: brush = 0
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)
636 else:
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)
647 else:
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)
658 else:
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)
669 else:
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)
680 else:
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)
689 brush *= brush_mult
690 if props.input_mode == 'STATIC':
691 store_attribute_parameter(me, 'RD_brush', brush, 'POINT', 'FLOAT')
693 diff_a *= scale
694 diff_b *= scale
696 edge_verts = None
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')
704 else:
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()
710 else:
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
725 else:
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:
733 if not bm:
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
746 vector_field = []
747 for x,y,z in zip(field_x, field_y, field_z):
748 vector_field.append(Vector((x,y,z)).normalized())
749 else:
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():
755 if not bm:
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()
770 else:
771 is_vector_field = False
772 else:
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()
785 else:
786 is_vector_field = False
788 if is_vector_field:
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):
794 id0 = pair[0]
795 id1 = pair[1]
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')
806 else:
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)
814 start = time.time()
816 frames = range(props.cache_frame_start, props.cache_frame_end+1) if bake and props.input_mode == 'STATIC' else [scene.frame_current]
817 for frame in frames:
818 if bake and props.input_mode == 'STATIC':
819 tissue_time(None,'{:7d} Tissue: Baking Reaction-Diffusion on {}...'.format(frame, ob.name), levels=0)
820 try:
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,))
824 _diff_a *= scale
825 _diff_b = diff_b if type(diff_b) is np.ndarray else np.array((diff_b,))
826 _diff_b *= scale
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)
830 else:
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)
832 except:
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):
842 b += brush
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)
852 ab2 = a*b**2
853 a += eval("(_diff_a*lap_a - ab2 + f*(1-a))*dt")
854 b += eval("(_diff_b*lap_b + ab2 - (k+f)*b)*dt")
856 a = nan_to_num(a)
857 b = nan_to_num(b)
858 tissue_time(start, "Simulation", levels=1)
859 start = time.time()
860 if bake:
861 if not(os.path.exists(folder)):
862 os.mkdir(folder)
863 file_name = folder / "a_{:04d}".format(frame)
864 a.tofile(file_name)
865 file_name = folder / "b_{:04d}".format(frame)
866 b.tofile(file_name)
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)
871 start = time.time()
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')
875 ob.data.update()
876 else:
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')
880 ob.data.update()
881 if 'A' in ob.vertex_groups.keys():
882 vg_a = ob.vertex_groups['A']
883 else:
884 vg_a = ob.vertex_groups.new(name='A')
885 if 'B' in ob.vertex_groups.keys():
886 vg_b = ob.vertex_groups['B']
887 else:
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')
894 else:
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):
904 dvert = v[dvert_lay]
905 if vg_a: dvert[index_a] = a[i]
906 if vg_b: dvert[index_b] = b[i]
907 bm.to_mesh(ob.data)
908 bm.free()
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'
922 bl_context = "data"
923 bl_label = "Tissue Reaction-Diffusion"
924 bl_options = {'DEFAULT_CLOSED'}
926 @classmethod
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)
933 ob = context.object
934 props = ob.reaction_diffusion_settings
935 layout = self.layout
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",
940 icon="EXPERIMENTAL",
941 text="Reaction-Diffusion")
942 else:
943 row.operator("object.start_reaction_diffusion",
944 icon="EXPERIMENTAL",
945 text="Reset Reaction-Diffusion")
946 row.prop(props, "bool_mod", text="", icon='MODIFIER')
947 row.prop(props, "run", text="", icon='TIME')
948 col.separator()
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
954 col.separator()
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'
976 bl_context = "data"
977 bl_parent_id = "TISSUE_PT_reaction_diffusion"
978 bl_label = "Anisotropic"
979 bl_options = {'DEFAULT_CLOSED'}
981 @classmethod
982 def poll(cls, context):
983 return 'A' and 'B' in context.object.vertex_groups
985 def draw(self, context):
986 ob = context.object
987 props = ob.reaction_diffusion_settings
988 layout = self.layout
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()
997 if 'x' not in vgk:
998 col.label(text="Vertex Group 'x' is missing", icon='ERROR')
999 if 'y' not in vgk:
1000 col.label(text="Vertex Group 'y' is missing", icon='ERROR')
1001 if 'z' not in vgk:
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':
1008 row = col.row()
1009 row.prop(props, "vector")
1010 if props.vector_field_mode != 'NONE':
1011 col.separator()
1012 row = col.row()
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'
1019 bl_context = "data"
1020 bl_parent_id = "TISSUE_PT_reaction_diffusion"
1021 bl_label = "Performance"
1022 bl_options = {'DEFAULT_CLOSED'}
1024 @classmethod
1025 def poll(cls, context):
1026 return 'A' and 'B' in context.object.vertex_groups
1028 def draw(self, context):
1029 ob = context.object
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':
1036 col.separator()
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')
1040 col.separator()
1041 col.prop(props, "input_data", text='Read from')
1042 col.prop(props, "output_data", text='Write to')
1043 col.separator()
1045 class TISSUE_PT_reaction_diffusion_weight(Panel):
1046 bl_space_type = 'PROPERTIES'
1047 bl_region_type = 'WINDOW'
1048 bl_context = "data"
1049 bl_parent_id = "TISSUE_PT_reaction_diffusion"
1050 bl_label = "Variable Parameters"
1051 bl_options = {'DEFAULT_CLOSED'}
1053 @classmethod
1054 def poll(cls, context):
1055 return 'A' and 'B' in context.object.vertex_groups
1057 def draw(self, context):
1058 ob = context.object
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:')
1069 else:
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'
1084 bl_context = "data"
1085 bl_parent_id = "TISSUE_PT_reaction_diffusion"
1086 bl_label = "Cache"
1087 bl_options = {'DEFAULT_CLOSED'}
1089 @classmethod
1090 def poll(cls, context):
1091 return 'A' and 'B' in context.object.vertex_groups
1093 def draw(self, context):
1094 ob = context.object
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='')
1100 col.separator()
1101 row = col.row(align=True)
1102 row.prop(props, "cache_frame_start")
1103 row.prop(props, "cache_frame_end")
1104 col.separator()
1105 if props.bool_cache:
1106 col.operator("object.reaction_diffusion_free_data")
1107 else:
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 == '':
1113 row.enabled = False
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='')
1125 if name != 'brush':
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] != '':
1129 if name == 'brush':
1130 col2.prop(props, "brush_mult")
1131 else:
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")
1136 col.separator()
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')
1142 else:
1143 col.label(text = text + ' Attribute "' + name + '" not found.', icon='KEYFRAME')
1144 col.separator()
1146 def rd_apply_modifiers(ob):
1147 # hide deforming modifiers
1148 mod_visibility = []
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
1153 # evaluated mesh
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):
1162 m.show_viewport = v
1163 ob.modifiers.update()
1164 return me