Merge branch 'master' into blender2.8
[blender.git] / source / blender / editors / physics / physics_fluid.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) Blender Foundation
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): none yet.
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/editors/physics/physics_fluid.c
29  *  \ingroup edphys
30  */
31
32 #include <math.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/stat.h>
36
37 #include "MEM_guardedalloc.h"
38
39 /* types */
40 #include "DNA_action_types.h"
41 #include "DNA_object_types.h"
42 #include "DNA_object_fluidsim_types.h"  
43
44 #include "BLI_blenlib.h"
45 #include "BLI_math.h"
46 #include "BLI_utildefines.h"
47
48 #include "BKE_context.h"
49 #include "BKE_customdata.h"
50 #include "BKE_fluidsim.h"
51 #include "BKE_global.h"
52 #include "BKE_main.h"
53 #include "BKE_modifier.h"
54 #include "BKE_object.h"
55 #include "BKE_report.h"
56 #include "BKE_scene.h"
57
58 #include "DEG_depsgraph.h"
59
60 #include "LBM_fluidsim.h"
61
62 #include "ED_screen.h"
63
64 #include "WM_types.h"
65 #include "WM_api.h"
66
67 #include "physics_intern.h" // own include
68
69 /* enable/disable overall compilation */
70 #ifdef WITH_MOD_FLUID
71
72 #include "WM_api.h"
73
74 #include "DNA_scene_types.h"
75 #include "DNA_mesh_types.h"
76
77
78 static float get_fluid_viscosity(FluidsimSettings *settings)
79 {
80         return (1.0f/powf(10.0f, settings->viscosityExponent)) * settings->viscosityValue;
81 }
82
83 static float get_fluid_rate(FluidsimSettings *settings)
84 {
85         float rate = 1.0f; /* default rate if not animated... */
86         
87         rate = settings->animRate;
88         
89         if (rate < 0.0f)
90                 rate = 0.0f;
91         
92         return rate;
93 }
94
95 static void get_fluid_gravity(float *gravity, Scene *scene, FluidsimSettings *fss)
96 {
97         if (scene->physics_settings.flag & PHYS_GLOBAL_GRAVITY) {
98                 copy_v3_v3(gravity, scene->physics_settings.gravity);
99         }
100         else {
101                 copy_v3_v3(gravity, fss->grav);
102         }
103 }
104
105 static float get_fluid_size_m(Scene *scene, Object *domainob, FluidsimSettings *fss)
106 {
107         if (!scene->unit.system) {
108                 return fss->realsize;
109         }
110         else {
111                 float dim[3];
112                 float longest_axis;
113                 
114                 BKE_object_dimensions_get(domainob, dim);
115                 longest_axis = max_fff(dim[0], dim[1], dim[2]);
116                 
117                 return longest_axis * scene->unit.scale_length;
118         }
119 }
120
121 static bool fluid_is_animated_mesh(FluidsimSettings *fss)
122 {
123         return ((fss->type == OB_FLUIDSIM_CONTROL) || fss->domainNovecgen);
124 }
125
126 /* ********************** fluid sim settings struct functions ********************** */
127
128 #if 0
129 /* helper function */
130 void fluidsimGetGeometryObjFilename(Object *ob, char *dst)  //, char *srcname)
131 {
132         //BLI_snprintf(dst, FILE_MAXFILE, "%s_cfgdata_%s.bobj.gz", srcname, ob->id.name);
133         BLI_snprintf(dst, FILE_MAXFILE, "fluidcfgdata_%s.bobj.gz", ob->id.name);
134 }
135 #endif
136
137
138 /* ********************** fluid sim channel helper functions ********************** */
139
140 typedef struct FluidAnimChannels {
141         int length;
142         
143         double aniFrameTime;
144         
145         float *timeAtFrame;
146         float *DomainTime;
147         float *DomainGravity;
148         float *DomainViscosity;
149 } FluidAnimChannels;
150
151 typedef struct FluidObject {
152         struct FluidObject *next, *prev;
153         
154         struct Object *object;
155         
156         float *Translation;
157         float *Rotation;
158         float *Scale;
159         float *Active;
160         
161         float *InitialVelocity;
162         
163         float *AttractforceStrength;
164         float *AttractforceRadius;
165         float *VelocityforceStrength;
166         float *VelocityforceRadius;
167         
168         float *VertexCache;
169         int numVerts, numTris;
170 } FluidObject;
171
172 // no. of entries for the two channel sizes
173 #define CHANNEL_FLOAT 1
174 #define CHANNEL_VEC   3
175
176 // simplify channels before printing
177 // for API this is done anyway upon init
178 #if 0
179 static void fluidsimPrintChannel(FILE *file, float *channel, int paramsize, char *str, int entries) 
180
181         int i, j;
182         int channelSize = paramsize; 
183
184         if (entries == 3) {
185                 elbeemSimplifyChannelVec3(channel, &channelSize);
186         }
187         else if (entries == 1) {
188                 elbeemSimplifyChannelFloat(channel, &channelSize);
189         }
190         else {
191                 /* invalid, cant happen? */
192         }
193
194         fprintf(file, "      CHANNEL %s =\n", str);
195         for (i=0; i < channelSize; i++) {
196                 fprintf(file, "        ");
197                 for (j=0;j <= entries;j++) {  // also print time value
198                         fprintf(file, " %f ", channel[i*(entries + 1) + j]);
199                         if (j == entries-1) { fprintf(file, "  "); }
200                 }
201                 fprintf(file, "\n");
202         }
203
204         fprintf(file,  "      ;\n");
205 }
206 #endif
207
208
209 /* Note: fluid anim channel data layout
210  * ------------------------------------
211  * CHANNEL_FLOAT:
212  * frame 1     |frame 2
213  * [dataF][time][dataF][time]
214  *
215  * CHANNEL_VEC:
216  * frame 1                   |frame 2
217  * [dataX][dataY][dataZ][time][dataX][dataY][dataZ][time]
218  *
219  */
220
221 static void init_time(FluidsimSettings *domainSettings, FluidAnimChannels *channels)
222 {
223         int i;
224         
225         channels->timeAtFrame = MEM_callocN((channels->length + 1) * sizeof(float), "timeAtFrame channel");
226         
227         channels->timeAtFrame[0] = channels->timeAtFrame[1] = domainSettings->animStart; // start at index 1
228         
229         for (i=2; i <= channels->length; i++) {
230                 channels->timeAtFrame[i] = channels->timeAtFrame[i - 1] + (float)channels->aniFrameTime;
231         }
232 }
233
234 /* if this is slow, can replace with faster, less readable code */
235 static void set_channel(float *channel, float time, float *value, int i, int size)
236 {
237         if (size == CHANNEL_FLOAT) {
238                 channel[(i * 2) + 0] = value[0];
239                 channel[(i * 2) + 1] = time;
240         }
241         else if (size == CHANNEL_VEC) {
242                 channel[(i * 4) + 0] = value[0];
243                 channel[(i * 4) + 1] = value[1];
244                 channel[(i * 4) + 2] = value[2];
245                 channel[(i * 4) + 3] = time;
246         }
247 }
248
249 static void set_vertex_channel(EvaluationContext *eval_ctx, float *channel, float time, struct Scene *scene, struct FluidObject *fobj, int i)
250 {
251         Object *ob = fobj->object;
252         FluidsimModifierData *fluidmd = (FluidsimModifierData *)modifiers_findByType(ob, eModifierType_Fluidsim);
253         float *verts;
254         int *tris=NULL, numVerts=0, numTris=0;
255         int modifierIndex = BLI_findindex(&ob->modifiers, fluidmd);
256         int framesize = (3*fobj->numVerts) + 1;
257         int j;
258         
259         if (channel == NULL)
260                 return;
261         
262         initElbeemMesh(eval_ctx, scene, ob, &numVerts, &verts, &numTris, &tris, 1, modifierIndex);
263         
264         /* don't allow mesh to change number of verts in anim sequence */
265         if (numVerts != fobj->numVerts) {
266                 MEM_freeN(channel);
267                 channel = NULL;
268                 return;
269         }
270         
271         /* fill frame of channel with vertex locations */
272         for (j=0; j < (3*numVerts); j++) {
273                 channel[i*framesize + j] = verts[j];
274         }
275         channel[i*framesize + framesize-1] = time;
276         
277         MEM_freeN(verts);
278         MEM_freeN(tris);
279 }
280
281 static void free_domain_channels(FluidAnimChannels *channels)
282 {
283         if (!channels->timeAtFrame)
284                 return;
285         MEM_freeN(channels->timeAtFrame);
286         channels->timeAtFrame = NULL;
287         MEM_freeN(channels->DomainGravity);
288         channels->DomainGravity = NULL;
289         MEM_freeN(channels->DomainViscosity);
290         channels->DomainViscosity = NULL;
291         MEM_freeN(channels->DomainTime);
292         channels->DomainTime = NULL;
293 }
294
295 static void free_all_fluidobject_channels(ListBase *fobjects)
296 {
297         FluidObject *fobj;
298         
299         for (fobj=fobjects->first; fobj; fobj=fobj->next) {
300                 if (fobj->Translation) {
301                         MEM_freeN(fobj->Translation);
302                         fobj->Translation = NULL;
303                         MEM_freeN(fobj->Rotation);
304                         fobj->Rotation = NULL;
305                         MEM_freeN(fobj->Scale);
306                         fobj->Scale = NULL;
307                         MEM_freeN(fobj->Active);
308                         fobj->Active = NULL;
309                         MEM_freeN(fobj->InitialVelocity);
310                         fobj->InitialVelocity = NULL;
311                 }
312                 
313                 if (fobj->AttractforceStrength) {
314                         MEM_freeN(fobj->AttractforceStrength);
315                         fobj->AttractforceStrength = NULL;
316                         MEM_freeN(fobj->AttractforceRadius);
317                         fobj->AttractforceRadius = NULL;
318                         MEM_freeN(fobj->VelocityforceStrength);
319                         fobj->VelocityforceStrength = NULL;
320                         MEM_freeN(fobj->VelocityforceRadius);
321                         fobj->VelocityforceRadius = NULL;
322                 }
323                 
324                 if (fobj->VertexCache) {
325                         MEM_freeN(fobj->VertexCache);
326                         fobj->VertexCache = NULL;
327                 }
328         }
329 }
330
331 static void fluid_init_all_channels(bContext *C, Object *UNUSED(fsDomain), FluidsimSettings *domainSettings, FluidAnimChannels *channels, ListBase *fobjects)
332 {
333         Scene *scene = CTX_data_scene(C);
334         ViewLayer *view_layer = CTX_data_view_layer(C);
335         Depsgraph *depsgraph = CTX_data_depsgraph(C);
336         EvaluationContext eval_ctx;
337         Base *base;
338         int i;
339         int length = channels->length;
340         float eval_time;
341
342         CTX_data_eval_ctx(C, &eval_ctx);
343         
344         /* init time values (assuming that time moves at a constant speed; may be overridden later) */
345         init_time(domainSettings, channels);
346         
347         /* allocate domain animation channels */
348         channels->DomainGravity = MEM_callocN(length * (CHANNEL_VEC+1) * sizeof(float), "channel DomainGravity");
349         channels->DomainViscosity = MEM_callocN(length * (CHANNEL_FLOAT+1) * sizeof(float), "channel DomainViscosity");
350         channels->DomainTime = MEM_callocN(length * (CHANNEL_FLOAT+1) * sizeof(float), "channel DomainTime");
351         
352         /* allocate fluid objects */
353         for (base = FIRSTBASE(view_layer); base; base = base->next) {
354                 Object *ob = base->object;
355                 FluidsimModifierData *fluidmd = (FluidsimModifierData *)modifiers_findByType(ob, eModifierType_Fluidsim);
356                 
357                 if (fluidmd) {
358                         FluidObject *fobj = MEM_callocN(sizeof(FluidObject), "Fluid Object");
359                         fobj->object = ob;
360                         
361                         if (ELEM(fluidmd->fss->type, OB_FLUIDSIM_DOMAIN, OB_FLUIDSIM_PARTICLE)) {
362                                 BLI_addtail(fobjects, fobj);
363                                 continue;
364                         }
365                         
366                         fobj->Translation = MEM_callocN(length * (CHANNEL_VEC+1) * sizeof(float), "fluidobject Translation");
367                         fobj->Rotation = MEM_callocN(length * (CHANNEL_VEC+1) * sizeof(float), "fluidobject Rotation");
368                         fobj->Scale = MEM_callocN(length * (CHANNEL_VEC+1) * sizeof(float), "fluidobject Scale");
369                         fobj->Active = MEM_callocN(length * (CHANNEL_FLOAT+1) * sizeof(float), "fluidobject Active");
370                         fobj->InitialVelocity = MEM_callocN(length * (CHANNEL_VEC+1) * sizeof(float), "fluidobject InitialVelocity");
371                         
372                         if (fluidmd->fss->type == OB_FLUIDSIM_CONTROL) {
373                                 fobj->AttractforceStrength = MEM_callocN(length * (CHANNEL_FLOAT+1) * sizeof(float), "fluidobject AttractforceStrength");
374                                 fobj->AttractforceRadius = MEM_callocN(length * (CHANNEL_FLOAT+1) * sizeof(float), "fluidobject AttractforceRadius");
375                                 fobj->VelocityforceStrength = MEM_callocN(length * (CHANNEL_FLOAT+1) * sizeof(float), "fluidobject VelocityforceStrength");
376                                 fobj->VelocityforceRadius = MEM_callocN(length * (CHANNEL_FLOAT+1) * sizeof(float), "fluidobject VelocityforceRadius");
377                         }
378                         
379                         if (fluid_is_animated_mesh(fluidmd->fss)) {
380                                 float *verts=NULL;
381                                 int *tris=NULL, modifierIndex = BLI_findindex(&ob->modifiers, (ModifierData *)fluidmd);
382
383                                 initElbeemMesh(&eval_ctx, scene, ob, &fobj->numVerts, &verts, &fobj->numTris, &tris, 0, modifierIndex);
384                                 fobj->VertexCache = MEM_callocN(length *((fobj->numVerts*CHANNEL_VEC)+1) * sizeof(float), "fluidobject VertexCache");
385                                 
386                                 MEM_freeN(verts);
387                                 MEM_freeN(tris);
388                         }
389                         
390                         BLI_addtail(fobjects, fobj);
391                 }
392         }
393         
394         /* now we loop over the frames and fill the allocated channels with data */
395         for (i=0; i < channels->length; i++) {
396                 FluidObject *fobj;
397                 float viscosity, gravity[3];
398                 float timeAtFrame, time;
399                 
400                 eval_time = domainSettings->bakeStart + i;
401                 
402                 /* XXX: This can't be used due to an anim sys optimization that ignores recalc object animation,
403                  * leaving it for the depgraph (this ignores object animation such as modifier properties though... :/ )
404                  * --> BKE_animsys_evaluate_all_animation(G.main, eval_time);
405                  * This doesn't work with drivers:
406                  * --> BKE_animsys_evaluate_animdata(&fsDomain->id, fsDomain->adt, eval_time, ADT_RECALC_ALL);
407                  */
408                 
409                 /* Modifying the global scene isn't nice, but we can do it in 
410                  * this part of the process before a threaded job is created */
411                 scene->r.cfra = (int)eval_time;
412                 ED_update_for_newframe(CTX_data_main(C), scene, view_layer, depsgraph);
413                 
414                 /* now scene data should be current according to animation system, so we fill the channels */
415                 
416                 /* Domain time */
417                 // TODO: have option for not running sim, time mangling, in which case second case comes in handy
418                 if (channels->DomainTime) {
419                         time = get_fluid_rate(domainSettings) * (float)channels->aniFrameTime;
420                         timeAtFrame = channels->timeAtFrame[i] + time;
421                         
422                         channels->timeAtFrame[i+1] = timeAtFrame;
423                         set_channel(channels->DomainTime, i, &time, i, CHANNEL_FLOAT);
424                 }
425                 else {
426                         timeAtFrame = channels->timeAtFrame[i+1];
427                 }
428                 
429                 /* Domain properties - gravity/viscosity */
430                 get_fluid_gravity(gravity, scene, domainSettings);
431                 set_channel(channels->DomainGravity, timeAtFrame, gravity, i, CHANNEL_VEC);
432                 viscosity = get_fluid_viscosity(domainSettings);
433                 set_channel(channels->DomainViscosity, timeAtFrame, &viscosity, i, CHANNEL_FLOAT);
434                 
435                 /* object movement */
436                 for (fobj=fobjects->first; fobj; fobj=fobj->next) {
437                         Object *ob = fobj->object;
438                         FluidsimModifierData *fluidmd = (FluidsimModifierData *)modifiers_findByType(ob, eModifierType_Fluidsim);
439                         float active= (float) ((fluidmd->fss->flag & OB_FLUIDSIM_ACTIVE) > 0 ? 1 : 0);
440                         float rot_d[3] = {0.f, 0.f, 0.f}, old_rot[3] = {0.f, 0.f, 0.f};
441                         
442                         if (ELEM(fluidmd->fss->type, OB_FLUIDSIM_DOMAIN, OB_FLUIDSIM_PARTICLE))
443                                 continue;
444                         
445                         /* init euler rotation values and convert to elbeem format */
446                         /* get the rotation from ob->obmat rather than ob->rot to account for parent animations */
447                         if (i) {
448                                 copy_v3_v3(old_rot, fobj->Rotation + 4*(i-1));
449                                 mul_v3_fl(old_rot, (float)-M_PI / 180.f);
450                         }
451
452                         mat4_to_compatible_eulO(rot_d, old_rot, 0, ob->obmat);
453                         mul_v3_fl(rot_d, -180.0f / (float)M_PI);
454                         
455                         set_channel(fobj->Translation, timeAtFrame, ob->loc, i, CHANNEL_VEC);
456                         set_channel(fobj->Rotation, timeAtFrame, rot_d, i, CHANNEL_VEC);
457                         set_channel(fobj->Scale, timeAtFrame, ob->size, i, CHANNEL_VEC);
458                         set_channel(fobj->Active, timeAtFrame, &active, i, CHANNEL_FLOAT);
459                         set_channel(fobj->InitialVelocity, timeAtFrame, &fluidmd->fss->iniVelx, i, CHANNEL_VEC);
460
461                         // printf("Active: %f, Frame: %f\n", active, timeAtFrame);
462                         
463                         if (fluidmd->fss->type == OB_FLUIDSIM_CONTROL) {
464                                 set_channel(fobj->AttractforceStrength, timeAtFrame, &fluidmd->fss->attractforceStrength, i, CHANNEL_FLOAT);
465                                 set_channel(fobj->AttractforceRadius, timeAtFrame, &fluidmd->fss->attractforceRadius, i, CHANNEL_FLOAT);
466                                 set_channel(fobj->VelocityforceStrength, timeAtFrame, &fluidmd->fss->velocityforceStrength, i, CHANNEL_FLOAT);
467                                 set_channel(fobj->VelocityforceRadius, timeAtFrame, &fluidmd->fss->velocityforceRadius, i, CHANNEL_FLOAT);
468                         }
469                         
470                         if (fluid_is_animated_mesh(fluidmd->fss)) {
471                                 set_vertex_channel(&eval_ctx, fobj->VertexCache, timeAtFrame, scene, fobj, i);
472                         }
473                 }
474         }
475 }
476
477 static void export_fluid_objects(const bContext *C, ListBase *fobjects, Scene *scene, int length)
478 {
479         FluidObject *fobj;
480         EvaluationContext eval_ctx;
481
482         CTX_data_eval_ctx(C, &eval_ctx);
483         
484         for (fobj=fobjects->first; fobj; fobj=fobj->next) {
485                 Object *ob = fobj->object;
486                 FluidsimModifierData *fluidmd = (FluidsimModifierData *)modifiers_findByType(ob, eModifierType_Fluidsim);
487                 int modifierIndex = BLI_findindex(&ob->modifiers, fluidmd);
488                 
489                 float *verts=NULL;
490                 int *tris=NULL;
491                 int numVerts=0, numTris=0;
492                 bool deform = fluid_is_animated_mesh(fluidmd->fss);
493                 
494                 elbeemMesh fsmesh;
495                 
496                 if (ELEM(fluidmd->fss->type, OB_FLUIDSIM_DOMAIN, OB_FLUIDSIM_PARTICLE))
497                         continue;
498                 
499                 elbeemResetMesh(&fsmesh);
500                 
501                 fsmesh.type = fluidmd->fss->type;
502                 fsmesh.name = ob->id.name;
503                 
504                 initElbeemMesh(&eval_ctx, scene, ob, &numVerts, &verts, &numTris, &tris, 0, modifierIndex);
505                 
506                 fsmesh.numVertices   = numVerts;
507                 fsmesh.numTriangles  = numTris;
508                 fsmesh.vertices      = verts;
509                 fsmesh.triangles     = tris;
510                 
511                 fsmesh.channelSizeTranslation  = 
512                 fsmesh.channelSizeRotation     = 
513                 fsmesh.channelSizeScale        = 
514                 fsmesh.channelSizeInitialVel   = 
515                 fsmesh.channelSizeActive       = length;
516                 
517                 fsmesh.channelTranslation      = fobj->Translation;
518                 fsmesh.channelRotation         = fobj->Rotation;
519                 fsmesh.channelScale            = fobj->Scale;
520                 fsmesh.channelActive           = fobj->Active;
521                 
522                 if ( ELEM(fsmesh.type, OB_FLUIDSIM_FLUID, OB_FLUIDSIM_INFLOW)) {
523                         fsmesh.channelInitialVel = fobj->InitialVelocity;
524                         fsmesh.localInivelCoords = ((fluidmd->fss->typeFlags & OB_FSINFLOW_LOCALCOORD) ? 1 : 0);
525                 }
526                 
527                 if (fluidmd->fss->typeFlags & OB_FSBND_NOSLIP)
528                         fsmesh.obstacleType = FLUIDSIM_OBSTACLE_NOSLIP;
529                 else if (fluidmd->fss->typeFlags & OB_FSBND_PARTSLIP)
530                         fsmesh.obstacleType = FLUIDSIM_OBSTACLE_PARTSLIP;
531                 else if (fluidmd->fss->typeFlags & OB_FSBND_FREESLIP)
532                         fsmesh.obstacleType = FLUIDSIM_OBSTACLE_FREESLIP;
533                 
534                 fsmesh.obstaclePartslip = fluidmd->fss->partSlipValue;
535                 fsmesh.volumeInitType = fluidmd->fss->volumeInitType;
536                 fsmesh.obstacleImpactFactor = fluidmd->fss->surfaceSmoothing; // misused value
537                 
538                 if (fsmesh.type == OB_FLUIDSIM_CONTROL) {
539                         fsmesh.cpsTimeStart = fluidmd->fss->cpsTimeStart;
540                         fsmesh.cpsTimeEnd = fluidmd->fss->cpsTimeEnd;
541                         fsmesh.cpsQuality = fluidmd->fss->cpsQuality;
542                         fsmesh.obstacleType = (fluidmd->fss->flag & OB_FLUIDSIM_REVERSE);
543                         
544                         fsmesh.channelSizeAttractforceRadius = 
545                         fsmesh.channelSizeVelocityforceStrength = 
546                         fsmesh.channelSizeVelocityforceRadius = 
547                         fsmesh.channelSizeAttractforceStrength = length;
548                         
549                         fsmesh.channelAttractforceStrength = fobj->AttractforceStrength;
550                         fsmesh.channelAttractforceRadius = fobj->AttractforceRadius;
551                         fsmesh.channelVelocityforceStrength = fobj->VelocityforceStrength;
552                         fsmesh.channelVelocityforceRadius = fobj->VelocityforceRadius;
553                 }
554                 else {
555                         fsmesh.channelAttractforceStrength =
556                         fsmesh.channelAttractforceRadius = 
557                         fsmesh.channelVelocityforceStrength = 
558                         fsmesh.channelVelocityforceRadius = NULL; 
559                 }
560                 
561                 /* animated meshes */
562                 if (deform) {
563                         fsmesh.channelSizeVertices = length;
564                         fsmesh.channelVertices = fobj->VertexCache;
565                         
566                         /* remove channels */
567                         fsmesh.channelTranslation      = 
568                         fsmesh.channelRotation         = 
569                         fsmesh.channelScale            = NULL;
570                         
571                         /* Override user settings, only noslip is supported here! */
572                         if (fsmesh.type != OB_FLUIDSIM_CONTROL)
573                                 fsmesh.obstacleType = FLUIDSIM_OBSTACLE_NOSLIP;
574                 }
575                 
576                 elbeemAddMesh(&fsmesh);
577                 
578                 if (verts) MEM_freeN(verts);
579                 if (tris) MEM_freeN(tris);
580         }
581 }
582
583 static int fluid_validate_scene(ReportList *reports, ViewLayer *view_layer, Object *fsDomain)
584 {
585         Base *base;
586         Object *newdomain = NULL;
587         int channelObjCount = 0;
588         int fluidInputCount = 0;
589
590         for (base = FIRSTBASE(view_layer); base; base = base->next) {
591                 Object *ob = base->object;
592                 FluidsimModifierData *fluidmdtmp = (FluidsimModifierData *)modifiers_findByType(ob, eModifierType_Fluidsim);
593
594                 /* only find objects with fluid modifiers */
595                 if (!fluidmdtmp || ob->type != OB_MESH) continue;
596                         
597                 if (fluidmdtmp->fss->type == OB_FLUIDSIM_DOMAIN) {
598                         /* if no initial domain object given, find another potential domain */
599                         if (!fsDomain) {
600                                 newdomain = ob;
601                         }
602                         /* if there's more than one domain, cancel */
603                         else if (fsDomain && ob != fsDomain) {
604                                 BKE_report(reports, RPT_ERROR, "There should be only one domain object");
605                                 return 0;
606                         }
607                 }
608                 
609                 /* count number of objects needed for animation channels */
610                 if ( !ELEM(fluidmdtmp->fss->type, OB_FLUIDSIM_DOMAIN, OB_FLUIDSIM_PARTICLE) )
611                         channelObjCount++;
612                 
613                 /* count number of fluid input objects */
614                 if (ELEM(fluidmdtmp->fss->type, OB_FLUIDSIM_FLUID, OB_FLUIDSIM_INFLOW))
615                         fluidInputCount++;
616         }
617
618         if (newdomain)
619                 fsDomain = newdomain;
620         
621         if (!fsDomain) {
622                 BKE_report(reports, RPT_ERROR, "No domain object found");
623                 return 0;
624         }
625         
626         if (channelObjCount >= 255) {
627                 BKE_report(reports, RPT_ERROR, "Cannot bake with more than 256 objects");
628                 return 0;
629         }
630         
631         if (fluidInputCount == 0) {
632                 BKE_report(reports, RPT_ERROR, "No fluid input objects in the scene");
633                 return 0;
634         }
635         
636         return 1;
637 }
638
639
640 #define FLUID_SUFFIX_CONFIG             "fluidsim.cfg"
641 #define FLUID_SUFFIX_CONFIG_TMP (FLUID_SUFFIX_CONFIG ".tmp")
642 #define FLUID_SUFFIX_SURFACE    "fluidsurface"
643
644 static bool fluid_init_filepaths(
645         ReportList *reports, FluidsimSettings *domainSettings, Object *fsDomain,
646         char *targetDir, char *targetFile)
647 {
648         const char *suffixConfigTmp = FLUID_SUFFIX_CONFIG_TMP;
649
650         /* prepare names... */
651         const char *relbase = modifier_path_relbase(fsDomain);
652
653         /* We do not accept empty paths, they can end in random places silently, see T51176. */
654         if (domainSettings->surfdataPath[0] == '\0') {
655                 modifier_path_init(domainSettings->surfdataPath, sizeof(domainSettings->surfdataPath),
656                                    OB_FLUIDSIM_SURF_DIR_DEFAULT);
657                 BKE_reportf(reports, RPT_WARNING, "Fluidsim: empty cache path, reset to default '%s'",
658                             domainSettings->surfdataPath);
659         }
660
661         BLI_strncpy(targetDir, domainSettings->surfdataPath, FILE_MAXDIR);
662         BLI_path_abs(targetDir, relbase);
663
664         /* .tmp: don't overwrite/delete original file */
665         BLI_join_dirfile(targetFile, FILE_MAX, targetDir, suffixConfigTmp);
666
667         /* Ensure whole path exists and is wirtable. */
668         const bool dir_exists = BLI_dir_create_recursive(targetDir);
669         const bool is_writable = BLI_file_is_writable(targetFile);
670
671         /* We change path to some presumably valid default value, but do not allow bake process to continue,
672          * this gives user chance to set manually another path. */
673         if (!dir_exists || !is_writable) {
674                 modifier_path_init(domainSettings->surfdataPath, sizeof(domainSettings->surfdataPath),
675                                    OB_FLUIDSIM_SURF_DIR_DEFAULT);
676
677                 if (!dir_exists) {
678                         BKE_reportf(reports, RPT_ERROR, "Fluidsim: could not create cache directory '%s', reset to default '%s'",
679                                     targetDir, domainSettings->surfdataPath);
680                 }
681                 else {
682                         BKE_reportf(reports, RPT_ERROR, "Fluidsim: cache directory '%s' is not writable, reset to default '%s'",
683                                     targetDir, domainSettings->surfdataPath);
684                 }
685
686                 BLI_strncpy(targetDir, domainSettings->surfdataPath, FILE_MAXDIR);
687                 BLI_path_abs(targetDir, relbase);
688
689                 /* .tmp: don't overwrite/delete original file */
690                 BLI_join_dirfile(targetFile, FILE_MAX, targetDir, suffixConfigTmp);
691
692                 /* Ensure whole path exists and is wirtable. */
693                 if (!BLI_dir_create_recursive(targetDir) || !BLI_file_is_writable(targetFile)) {
694                         BKE_reportf(reports, RPT_ERROR, "Fluidsim: could not use default cache directory '%s', "
695                                                         "please define a valid cache path manually", targetDir);
696                 }
697                 return false;
698         }
699
700         return true;
701 }
702
703 /* ******************************************************************************** */
704 /* ********************** write fluidsim config to file ************************* */
705 /* ******************************************************************************** */
706
707 typedef struct FluidBakeJob {
708         /* from wmJob */
709         void *owner;
710         short *stop, *do_update;
711         float *progress;
712         int current_frame;
713         elbeemSimulationSettings *settings;
714 } FluidBakeJob;
715
716 static void fluidbake_free(void *customdata)
717 {
718         FluidBakeJob *fb= (FluidBakeJob *)customdata;
719         MEM_freeN(fb);
720 }
721
722 /* called by fluidbake, only to check job 'stop' value */
723 static int fluidbake_breakjob(void *customdata)
724 {
725         FluidBakeJob *fb= (FluidBakeJob *)customdata;
726
727         if (fb->stop && *(fb->stop))
728                 return 1;
729         
730         /* this is not nice yet, need to make the jobs list template better 
731          * for identifying/acting upon various different jobs */
732         /* but for now we'll reuse the render break... */
733         return (G.is_break);
734 }
735
736 /* called by fluidbake, wmJob sends notifier */
737 static void fluidbake_updatejob(void *customdata, float progress)
738 {
739         FluidBakeJob *fb= (FluidBakeJob *)customdata;
740         
741         *(fb->do_update) = true;
742         *(fb->progress) = progress;
743 }
744
745 static void fluidbake_startjob(void *customdata, short *stop, short *do_update, float *progress)
746 {
747         FluidBakeJob *fb= (FluidBakeJob *)customdata;
748         
749         fb->stop= stop;
750         fb->do_update = do_update;
751         fb->progress = progress;
752         
753         G.is_break = false;  /* XXX shared with render - replace with job 'stop' switch */
754         
755         elbeemSimulate();
756         *do_update = true;
757         *stop = 0;
758 }
759
760 static void fluidbake_endjob(void *customdata)
761 {
762         FluidBakeJob *fb= (FluidBakeJob *)customdata;
763         
764         if (fb->settings) {
765                 MEM_freeN(fb->settings);
766                 fb->settings = NULL;
767         }
768 }
769
770 static int runSimulationCallback(void *data, int status, int frame)
771 {
772         FluidBakeJob *fb = (FluidBakeJob *)data;
773         elbeemSimulationSettings *settings = fb->settings;
774         
775         if (status == FLUIDSIM_CBSTATUS_NEWFRAME) {
776                 fluidbake_updatejob(fb, frame / (float)settings->noOfFrames);
777                 //printf("elbeem blender cb s%d, f%d, domainid:%d noOfFrames: %d\n", status, frame, settings->domainId, settings->noOfFrames ); // DEBUG
778         }
779         
780         if (fluidbake_breakjob(fb)) {
781                 return FLUIDSIM_CBRET_ABORT;
782         }
783         
784         return FLUIDSIM_CBRET_CONTINUE;
785 }
786
787 static void fluidbake_free_data(FluidAnimChannels *channels, ListBase *fobjects, elbeemSimulationSettings *fsset, FluidBakeJob *fb)
788 {
789         free_domain_channels(channels);
790         MEM_freeN(channels);
791         channels = NULL;
792
793         free_all_fluidobject_channels(fobjects);
794         BLI_freelistN(fobjects);
795         MEM_freeN(fobjects);
796         fobjects = NULL;
797         
798         if (fsset) {
799                 MEM_freeN(fsset);
800                 fsset = NULL;
801         }
802         
803         if (fb) {
804                 MEM_freeN(fb);
805                 fb = NULL;
806         }
807 }
808
809 /* copied from rna_fluidsim.c: fluidsim_find_lastframe() */
810 static void fluidsim_delete_until_lastframe(FluidsimSettings *fss, const char *relbase)
811 {
812         char targetDir[FILE_MAX], targetFile[FILE_MAX];
813         char targetDirVel[FILE_MAX], targetFileVel[FILE_MAX];
814         char previewDir[FILE_MAX], previewFile[FILE_MAX];
815         int curFrame = 1, exists = 0;
816
817         BLI_join_dirfile(targetDir,    sizeof(targetDir),    fss->surfdataPath, OB_FLUIDSIM_SURF_FINAL_OBJ_FNAME);
818         BLI_join_dirfile(targetDirVel, sizeof(targetDirVel), fss->surfdataPath, OB_FLUIDSIM_SURF_FINAL_VEL_FNAME);
819         BLI_join_dirfile(previewDir,   sizeof(previewDir),   fss->surfdataPath, OB_FLUIDSIM_SURF_PREVIEW_OBJ_FNAME);
820
821         BLI_path_abs(targetDir,    relbase);
822         BLI_path_abs(targetDirVel, relbase);
823         BLI_path_abs(previewDir,   relbase);
824
825         do {
826                 BLI_strncpy(targetFile, targetDir, sizeof(targetFile));
827                 BLI_strncpy(targetFileVel, targetDirVel, sizeof(targetFileVel));
828                 BLI_strncpy(previewFile, previewDir, sizeof(previewFile));
829
830                 BLI_path_frame(targetFile, curFrame, 0);
831                 BLI_path_frame(targetFileVel, curFrame, 0);
832                 BLI_path_frame(previewFile, curFrame, 0);
833
834                 curFrame++;
835
836                 if ((exists = BLI_exists(targetFile))) {
837                         BLI_delete(targetFile, false, false);
838                         BLI_delete(targetFileVel, false, false);
839                         BLI_delete(previewFile, false, false);
840                 }
841         } while (exists);
842
843         return;
844 }
845
846 static int fluidsimBake(bContext *C, ReportList *reports, Object *fsDomain, short do_job)
847 {
848         Scene *scene = CTX_data_scene(C);
849         ViewLayer *view_layer = CTX_data_view_layer(C);
850         Depsgraph *depsgraph = CTX_data_depsgraph(C);
851         int i;
852         FluidsimSettings *domainSettings;
853
854         char debugStrBuffer[256];
855         
856         int gridlevels = 0;
857         const char *relbase= modifier_path_relbase(fsDomain);
858         const char *strEnvName = "BLENDER_ELBEEMDEBUG"; // from blendercall.cpp
859         const char *suffixConfigTmp = FLUID_SUFFIX_CONFIG_TMP;
860         const char *suffixSurface = FLUID_SUFFIX_SURFACE;
861
862         char targetDir[FILE_MAX];  // store & modify output settings
863         char targetFile[FILE_MAX]; // temp. store filename from targetDir for access
864
865         float domainMat[4][4];
866         float invDomMat[4][4];
867
868         int noFrames;
869         int origFrame = scene->r.cfra;
870         
871         FluidAnimChannels *channels = MEM_callocN(sizeof(FluidAnimChannels), "fluid domain animation channels");
872         ListBase *fobjects = MEM_callocN(sizeof(ListBase), "fluid objects");
873         FluidsimModifierData *fluidmd = NULL;
874         Mesh *mesh = NULL;
875
876         FluidBakeJob *fb;
877         elbeemSimulationSettings *fsset= MEM_callocN(sizeof(elbeemSimulationSettings), "Fluid sim settings");
878
879         fb= MEM_callocN(sizeof(FluidBakeJob), "fluid bake job");
880         
881         if (getenv(strEnvName)) {
882                 int dlevel = atoi(getenv(strEnvName));
883                 elbeemSetDebugLevel(dlevel);
884                 BLI_snprintf(debugStrBuffer, sizeof(debugStrBuffer), "fluidsimBake::msg: Debug messages activated due to envvar '%s'\n", strEnvName);
885                 elbeemDebugOut(debugStrBuffer);
886         }
887         
888         /* make sure it corresponds to startFrame setting (old: noFrames = scene->r.efra - scene->r.sfra +1) */;
889         noFrames = scene->r.efra - 0;
890         if (noFrames<=0) {
891                 BKE_report(reports, RPT_ERROR, "No frames to export (check your animation range settings)");
892                 fluidbake_free_data(channels, fobjects, fsset, fb);
893                 return 0;
894         }
895         
896         /* check scene for sane object/modifier settings */
897         if (!fluid_validate_scene(reports, view_layer, fsDomain)) {
898                 fluidbake_free_data(channels, fobjects, fsset, fb);
899                 return 0;
900         }
901         
902         /* these both have to be valid, otherwise we wouldn't be here */
903         fluidmd = (FluidsimModifierData *)modifiers_findByType(fsDomain, eModifierType_Fluidsim);
904         domainSettings = fluidmd->fss;
905         mesh = fsDomain->data;
906         
907         domainSettings->bakeStart = 1;
908         domainSettings->bakeEnd = scene->r.efra;
909         
910         // calculate bounding box
911         fluid_get_bb(mesh->mvert, mesh->totvert, fsDomain->obmat, domainSettings->bbStart, domainSettings->bbSize);
912         
913         // reset last valid frame
914         domainSettings->lastgoodframe = -1;
915
916         /* delete old baked files */
917         fluidsim_delete_until_lastframe(domainSettings, relbase);
918         
919         /* rough check of settings... */
920         if (domainSettings->previewresxyz > domainSettings->resolutionxyz) {
921                 BLI_snprintf(debugStrBuffer, sizeof(debugStrBuffer), "fluidsimBake::warning - Preview (%d) >= Resolution (%d)... setting equal.\n", domainSettings->previewresxyz,  domainSettings->resolutionxyz);
922                 elbeemDebugOut(debugStrBuffer);
923                 domainSettings->previewresxyz = domainSettings->resolutionxyz;
924         }
925         // set adaptive coarsening according to resolutionxyz
926         // this should do as an approximation, with in/outflow
927         // doing this more accurate would be overkill
928         // perhaps add manual setting?
929         if (domainSettings->maxRefine <0) {
930                 if (domainSettings->resolutionxyz>128) {
931                         gridlevels = 2;
932                 }
933                 else if (domainSettings->resolutionxyz > 64) {
934                         gridlevels = 1;
935                 }
936                 else {
937                         gridlevels = 0;
938                 }
939         }
940         else {
941                 gridlevels = domainSettings->maxRefine;
942         }
943         BLI_snprintf(debugStrBuffer, sizeof(debugStrBuffer), "fluidsimBake::msg: Baking %s, refine: %d\n", fsDomain->id.name, gridlevels);
944         elbeemDebugOut(debugStrBuffer);
945         
946         
947         
948         /* ******** prepare output file paths ******** */
949         if (!fluid_init_filepaths(reports, domainSettings, fsDomain, targetDir, targetFile)) {
950                 fluidbake_free_data(channels, fobjects, fsset, fb);
951                 return false;
952         }
953
954         channels->length = scene->r.efra; // DG TODO: why using endframe and not "noFrames" here? .. because "noFrames" is buggy too? (not using sfra)
955         channels->aniFrameTime = (double)((double)domainSettings->animEnd - (double)domainSettings->animStart) / (double)noFrames;
956         
957         /* ******** initialize and allocate animation channels ******** */
958         fluid_init_all_channels(C, fsDomain, domainSettings, channels, fobjects);
959
960         /* reset to original current frame */
961         scene->r.cfra = origFrame;
962         ED_update_for_newframe(CTX_data_main(C), scene, view_layer, depsgraph);
963                 
964         /* ******** init domain object's matrix ******** */
965         copy_m4_m4(domainMat, fsDomain->obmat);
966         if (!invert_m4_m4(invDomMat, domainMat)) {
967                 BLI_snprintf(debugStrBuffer, sizeof(debugStrBuffer), "fluidsimBake::error - Invalid obj matrix?\n");
968                 elbeemDebugOut(debugStrBuffer);
969                 BKE_report(reports, RPT_ERROR, "Invalid object matrix"); 
970
971                 fluidbake_free_data(channels, fobjects, fsset, fb);
972                 return 0;
973         }
974
975         /* ********  start writing / exporting ******** */
976         // use .tmp, don't overwrite/delete original file
977         BLI_join_dirfile(targetFile, sizeof(targetFile), targetDir, suffixConfigTmp);
978
979         /* ******** export domain to elbeem ******** */
980         elbeemResetSettings(fsset);
981         fsset->version = 1;
982         fsset->threads = (domainSettings->threads == 0) ? BKE_scene_num_threads(scene) : domainSettings->threads;
983         // setup global settings
984         copy_v3_v3(fsset->geoStart, domainSettings->bbStart);
985         copy_v3_v3(fsset->geoSize, domainSettings->bbSize);
986         
987         // simulate with 50^3
988         fsset->resolutionxyz = (int)domainSettings->resolutionxyz;
989         fsset->previewresxyz = (int)domainSettings->previewresxyz;
990
991         fsset->realsize = get_fluid_size_m(scene, fsDomain, domainSettings);
992         fsset->viscosity = get_fluid_viscosity(domainSettings);
993         get_fluid_gravity(fsset->gravity, scene, domainSettings);
994
995         // simulate 5 frames, each 0.03 seconds, output to ./apitest_XXX.bobj.gz
996         fsset->animStart = domainSettings->animStart;
997         fsset->aniFrameTime = channels->aniFrameTime;
998         fsset->noOfFrames = noFrames; // is otherwise subtracted in parser
999
1000         BLI_join_dirfile(targetFile, sizeof(targetFile), targetDir, suffixSurface);
1001
1002         // defaults for compressibility and adaptive grids
1003         fsset->gstar = domainSettings->gstar;
1004         fsset->maxRefine = domainSettings->maxRefine; // check <-> gridlevels
1005         fsset->generateParticles = domainSettings->generateParticles; 
1006         fsset->numTracerParticles = domainSettings->generateTracers; 
1007         fsset->surfaceSmoothing = domainSettings->surfaceSmoothing; 
1008         fsset->surfaceSubdivs = domainSettings->surfaceSubdivs; 
1009         fsset->farFieldSize = domainSettings->farFieldSize; 
1010         BLI_strncpy(fsset->outputPath, targetFile, sizeof(fsset->outputPath));
1011
1012         // domain channels
1013         fsset->channelSizeFrameTime = 
1014         fsset->channelSizeViscosity = 
1015         fsset->channelSizeGravity = channels->length;
1016         fsset->channelFrameTime = channels->DomainTime;
1017         fsset->channelViscosity = channels->DomainViscosity;
1018         fsset->channelGravity = channels->DomainGravity;
1019         
1020         fsset->runsimCallback = &runSimulationCallback;
1021         fsset->runsimUserData = fb;
1022
1023         if (domainSettings->typeFlags & OB_FSBND_NOSLIP)                fsset->domainobsType = FLUIDSIM_OBSTACLE_NOSLIP;
1024         else if (domainSettings->typeFlags&OB_FSBND_PARTSLIP)   fsset->domainobsType = FLUIDSIM_OBSTACLE_PARTSLIP;
1025         else if (domainSettings->typeFlags&OB_FSBND_FREESLIP)   fsset->domainobsType = FLUIDSIM_OBSTACLE_FREESLIP;
1026         fsset->domainobsPartslip = domainSettings->partSlipValue;
1027
1028         /* use domainobsType also for surface generation flag (bit: >=64) */
1029         if (domainSettings->typeFlags & OB_FSSG_NOOBS)
1030                 fsset->mFsSurfGenSetting = FLUIDSIM_FSSG_NOOBS;
1031         else
1032                 fsset->mFsSurfGenSetting = 0; // "normal" mode
1033
1034         fsset->generateVertexVectors = (domainSettings->domainNovecgen==0);
1035
1036         // init blender domain transform matrix
1037         { int j;
1038         for (i=0; i<4; i++) {
1039                 for (j=0; j<4; j++) {
1040                         fsset->surfaceTrafo[i*4+j] = invDomMat[j][i];
1041                 }
1042         } }
1043
1044         /* ******** init solver with settings ******** */
1045         elbeemInit();
1046         elbeemAddDomain(fsset);
1047         
1048         /* ******** export all fluid objects to elbeem ******** */
1049         export_fluid_objects(C, fobjects, scene, channels->length);
1050         
1051         /* custom data for fluid bake job */
1052         fb->settings = fsset;
1053         
1054         if (do_job) {
1055                 wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), scene, "Fluid Simulation",
1056                                             WM_JOB_PROGRESS, WM_JOB_TYPE_OBJECT_SIM_FLUID);
1057
1058                 /* setup job */
1059                 WM_jobs_customdata_set(wm_job, fb, fluidbake_free);
1060                 WM_jobs_timer(wm_job, 0.1, NC_SCENE|ND_FRAME, NC_SCENE|ND_FRAME);
1061                 WM_jobs_callbacks(wm_job, fluidbake_startjob, NULL, NULL, fluidbake_endjob);
1062
1063                 WM_jobs_start(CTX_wm_manager(C), wm_job);
1064         }
1065         else {
1066                 short dummy_stop = 0, dummy_do_update = 0;
1067                 float dummy_progress = 0.0f;
1068
1069                 /* blocking, use with exec() */
1070                 fluidbake_startjob((void *)fb, &dummy_stop, &dummy_do_update, &dummy_progress);
1071                 fluidbake_endjob((void *)fb);
1072                 fluidbake_free((void *)fb);
1073         }
1074
1075         /* ******** free stored animation data ******** */
1076         fluidbake_free_data(channels, fobjects, NULL, NULL);
1077
1078         // elbeemFree();
1079         return 1;
1080 }
1081
1082 static void UNUSED_FUNCTION(fluidsimFreeBake)(Object *UNUSED(ob))
1083 {
1084         /* not implemented yet */
1085 }
1086
1087 #else /* WITH_MOD_FLUID */
1088
1089 /* only compile dummy functions */
1090 static int fluidsimBake(bContext *UNUSED(C), ReportList *UNUSED(reports), Object *UNUSED(ob), short UNUSED(do_job))
1091 {
1092         return 0;
1093 }
1094
1095 #endif /* WITH_MOD_FLUID */
1096
1097 /***************************** Operators ******************************/
1098
1099 static int fluid_bake_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
1100 {
1101         /* only one bake job at a time */
1102         if (WM_jobs_test(CTX_wm_manager(C), CTX_data_scene(C), WM_JOB_TYPE_OBJECT_SIM_FLUID))
1103                 return OPERATOR_CANCELLED;
1104
1105         if (!fluidsimBake(C, op->reports, CTX_data_active_object(C), true))
1106                 return OPERATOR_CANCELLED;
1107
1108         return OPERATOR_FINISHED;
1109 }
1110
1111 static int fluid_bake_exec(bContext *C, wmOperator *op)
1112 {
1113         if (!fluidsimBake(C, op->reports, CTX_data_active_object(C), false))
1114                 return OPERATOR_CANCELLED;
1115
1116         return OPERATOR_FINISHED;
1117 }
1118
1119 void FLUID_OT_bake(wmOperatorType *ot)
1120 {
1121         /* identifiers */
1122         ot->name = "Fluid Simulation Bake";
1123         ot->description = "Bake fluid simulation";
1124         ot->idname = "FLUID_OT_bake";
1125         
1126         /* api callbacks */
1127         ot->invoke = fluid_bake_invoke;
1128         ot->exec = fluid_bake_exec;
1129         ot->poll = ED_operator_object_active_editable;
1130 }
1131