Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/physics/physics_fluid.c
| /* | /* | ||||
| * ***** BEGIN GPL LICENSE BLOCK ***** | |||||
| * | |||||
| * This program is free software; you can redistribute it and/or | * This program is free software; you can redistribute it and/or | ||||
| * modify it under the terms of the GNU General Public License | * modify it under the terms of the GNU General Public License | ||||
| * as published by the Free Software Foundation; either version 2 | * as published by the Free Software Foundation; either version 2 | ||||
| * of the License, or (at your option) any later version. | * of the License, or (at your option) any later version. | ||||
| * | * | ||||
| * This program is distributed in the hope that it will be useful, | * This program is distributed in the hope that it will be useful, | ||||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| * GNU General Public License for more details. | * GNU General Public License for more details. | ||||
| * | * | ||||
| * You should have received a copy of the GNU General Public License | * You should have received a copy of the GNU General Public License | ||||
| * along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
| * | * | ||||
| * The Original Code is Copyright (C) Blender Foundation | * The Original Code is Copyright (C) Blender Foundation | ||||
| * All rights reserved. | * All rights reserved. | ||||
| * | |||||
| * The Original Code is: all of this file. | |||||
| * | |||||
| * Contributor(s): Sebastian Barschkis (sebbas) | |||||
| * | |||||
| * ***** END GPL LICENSE BLOCK ***** | |||||
| */ | */ | ||||
| /** \file | /** \file blender/editors/physics/physics_fluid.c | ||||
| * \ingroup edphys | * \ingroup edphys | ||||
| */ | */ | ||||
| #include <math.h> | #include <math.h> | ||||
| #include <stdlib.h> | #include <stdlib.h> | ||||
| #include <string.h> | #include <string.h> | ||||
| #include <sys/stat.h> | #include <sys/stat.h> | ||||
| #include "MEM_guardedalloc.h" | #include "MEM_guardedalloc.h" | ||||
| /* types */ | /* types */ | ||||
| #include "DNA_action_types.h" | #include "DNA_action_types.h" | ||||
| #include "DNA_object_types.h" | #include "DNA_object_types.h" | ||||
| #include "DNA_object_fluidsim_types.h" | |||||
| #include "BLI_blenlib.h" | |||||
| #include "BLI_path_util.h" | |||||
| #include "BLI_math.h" | |||||
| #include "BLI_utildefines.h" | #include "BLI_utildefines.h" | ||||
| #include "BLT_translation.h" | |||||
| #include "BKE_context.h" | #include "BKE_context.h" | ||||
| #include "BKE_customdata.h" | #include "BKE_customdata.h" | ||||
| #include "BKE_fluidsim.h" | #include "BKE_main.h" | ||||
| #include "BKE_modifier.h" | #include "BKE_modifier.h" | ||||
| #include "BKE_object.h" | #include "BKE_object.h" | ||||
| #include "BKE_report.h" | #include "BKE_report.h" | ||||
| #include "BKE_scene.h" | #include "BKE_scene.h" | ||||
| #include "BKE_screen.h" | |||||
| #include "BKE_fluid.h" | |||||
| #include "BKE_global.h" | |||||
| #include "DEG_depsgraph.h" | #include "DEG_depsgraph.h" | ||||
| #include "ED_screen.h" | #include "ED_screen.h" | ||||
| #include "ED_object.h" | #include "PIL_time.h" | ||||
| #include "WM_types.h" | #include "WM_types.h" | ||||
| #include "WM_api.h" | #include "WM_api.h" | ||||
| #include "physics_intern.h" // own include | #include "physics_intern.h" // own include | ||||
| #include "manta_fluid_API.h" | |||||
| /* enable/disable overall compilation */ | #include "DNA_scene_types.h" | ||||
| #ifdef WITH_MOD_FLUID | #include "DNA_fluid_types.h" | ||||
| #include "DNA_mesh_types.h" | |||||
| # include "LBM_fluidsim.h" | |||||
| # include "BLI_blenlib.h" | |||||
| # include "BLI_path_util.h" | |||||
| # include "BLI_math.h" | |||||
| # include "BKE_global.h" | #define FLUID_JOB_BAKE_ALL "FLUID_OT_bake_all" | ||||
| # include "BKE_main.h" | #define FLUID_JOB_BAKE_DATA "FLUID_OT_bake_data" | ||||
| #define FLUID_JOB_BAKE_NOISE "FLUID_OT_bake_noise" | |||||
| #define FLUID_JOB_BAKE_MESH "FLUID_OT_bake_mesh" | |||||
| #define FLUID_JOB_BAKE_PARTICLES "FLUID_OT_bake_particles" | |||||
| #define FLUID_JOB_BAKE_GUIDING "FLUID_OT_bake_guiding" | |||||
| #define FLUID_JOB_FREE_ALL "FLUID_OT_free_all" | |||||
| #define FLUID_JOB_FREE_DATA "FLUID_OT_free_data" | |||||
| #define FLUID_JOB_FREE_NOISE "FLUID_OT_free_noise" | |||||
| #define FLUID_JOB_FREE_MESH "FLUID_OT_free_mesh" | |||||
| #define FLUID_JOB_FREE_PARTICLES "FLUID_OT_free_particles" | |||||
| #define FLUID_JOB_FREE_GUIDING "FLUID_OT_free_guiding" | |||||
| #define FLUID_JOB_BAKE_PAUSE "FLUID_OT_pause_bake" | |||||
| # include "WM_api.h" | typedef struct FluidJob { | ||||
| /* from wmJob */ | |||||
| void *owner; | |||||
| short *stop, *do_update; | |||||
| float *progress; | |||||
| const char *type; | |||||
| const char *name; | |||||
| # include "DNA_scene_types.h" | struct Main *bmain; | ||||
| # include "DNA_mesh_types.h" | Scene *scene; | ||||
| Depsgraph *depsgraph; | |||||
| Object *ob; | |||||
| static float get_fluid_viscosity(FluidsimSettings *settings) | FluidModifierData *mmd; | ||||
| { | |||||
| return (1.0f / powf(10.0f, settings->viscosityExponent)) * settings->viscosityValue; | |||||
| } | |||||
| static float get_fluid_rate(FluidsimSettings *settings) | int success; | ||||
| { | double start; | ||||
| float rate = 1.0f; /* default rate if not animated... */ | |||||
| rate = settings->animRate; | int *pause_frame; | ||||
| } FluidJob; | |||||
| if (rate < 0.0f) { | static inline bool fluid_is_bake_all(FluidJob *job) { | ||||
| rate = 0.0f; | return (STREQ(job->type, FLUID_JOB_BAKE_ALL)); | ||||
| } | } | ||||
| static inline bool fluid_is_bake_data(FluidJob *job) { | |||||
| return rate; | return (STREQ(job->type, FLUID_JOB_BAKE_DATA)); | ||||
| } | } | ||||
| static inline bool fluid_is_bake_noise(FluidJob *job) { | |||||
| static void get_fluid_gravity(float *gravity, Scene *scene, FluidsimSettings *fss) | return (STREQ(job->type, FLUID_JOB_BAKE_NOISE)); | ||||
| { | |||||
| if (scene->physics_settings.flag & PHYS_GLOBAL_GRAVITY) { | |||||
| copy_v3_v3(gravity, scene->physics_settings.gravity); | |||||
| } | } | ||||
| else { | static inline bool fluid_is_bake_mesh(FluidJob *job) { | ||||
| copy_v3_v3(gravity, fss->grav); | return (STREQ(job->type, FLUID_JOB_BAKE_MESH)); | ||||
| } | } | ||||
| static inline bool fluid_is_bake_particle(FluidJob *job) { | |||||
| return (STREQ(job->type, FLUID_JOB_BAKE_PARTICLES)); | |||||
| } | } | ||||
| static inline bool fluid_is_bake_guiding(FluidJob *job) { | |||||
| static float get_fluid_size_m(Scene *scene, Object *domainob, FluidsimSettings *fss) | return (STREQ(job->type, FLUID_JOB_BAKE_GUIDING)); | ||||
| { | |||||
| if (!scene->unit.system) { | |||||
| return fss->realsize; | |||||
| } | } | ||||
| else { | static inline bool fluid_is_free_all(FluidJob *job) { | ||||
| float dim[3]; | return (STREQ(job->type, FLUID_JOB_FREE_ALL)); | ||||
| float longest_axis; | |||||
| BKE_object_dimensions_get(domainob, dim); | |||||
| longest_axis = max_fff(dim[0], dim[1], dim[2]); | |||||
| return longest_axis * scene->unit.scale_length; | |||||
| } | } | ||||
| static inline bool fluid_is_free_data(FluidJob *job) { | |||||
| return (STREQ(job->type, FLUID_JOB_FREE_DATA)); | |||||
| } | } | ||||
| static inline bool fluid_is_free_noise(FluidJob *job) { | |||||
| static bool fluid_is_animated_mesh(FluidsimSettings *fss) | return (STREQ(job->type, FLUID_JOB_FREE_NOISE)); | ||||
| { | |||||
| return ((fss->type == OB_FLUIDSIM_CONTROL) || fss->domainNovecgen); | |||||
| } | } | ||||
| static inline bool fluid_is_free_mesh(FluidJob *job) { | |||||
| /* ********************** fluid sim settings struct functions ********************** */ | return (STREQ(job->type, FLUID_JOB_FREE_MESH)); | ||||
| } | |||||
| # if 0 | static inline bool fluid_is_free_particles(FluidJob *job) { | ||||
| /* helper function */ | return (STREQ(job->type, FLUID_JOB_FREE_PARTICLES)); | ||||
| void fluidsimGetGeometryObjFilename(Object *ob, char *dst) //, char *srcname) | } | ||||
| { | static inline bool fluid_is_free_guiding(FluidJob *job) { | ||||
| //BLI_snprintf(dst, FILE_MAXFILE, "%s_cfgdata_%s.bobj.gz", srcname, ob->id.name); | return (STREQ(job->type, FLUID_JOB_FREE_GUIDING)); | ||||
| BLI_snprintf(dst, FILE_MAXFILE, "fluidcfgdata_%s.bobj.gz", ob->id.name); | |||||
| } | } | ||||
| # endif | |||||
| /* ********************** fluid sim channel helper functions ********************** */ | |||||
| typedef struct FluidAnimChannels { | |||||
| int length; | |||||
| double aniFrameTime; | |||||
| float *timeAtFrame; | |||||
| float *DomainTime; | |||||
| float *DomainGravity; | |||||
| float *DomainViscosity; | |||||
| } FluidAnimChannels; | |||||
| typedef struct FluidObject { | |||||
| struct FluidObject *next, *prev; | |||||
| struct Object *object; | |||||
| float *Translation; | |||||
| float *Rotation; | |||||
| float *Scale; | |||||
| float *Active; | |||||
| float *InitialVelocity; | |||||
| float *AttractforceStrength; | |||||
| float *AttractforceRadius; | |||||
| float *VelocityforceStrength; | |||||
| float *VelocityforceRadius; | |||||
| float *VertexCache; | |||||
| int numVerts, numTris; | |||||
| } FluidObject; | |||||
| // no. of entries for the two channel sizes | |||||
| # define CHANNEL_FLOAT 1 | |||||
| # define CHANNEL_VEC 3 | |||||
| // simplify channels before printing | static bool fluid_initjob( | ||||
| // for API this is done anyway upon init | bContext *C, FluidJob *job, wmOperator *op, char *error_msg, int error_size) | ||||
| # if 0 | |||||
| static void fluidsimPrintChannel(FILE *file, float *channel, int paramsize, char *str, int entries) | |||||
| { | { | ||||
| int i, j; | FluidModifierData *mmd = NULL; | ||||
| int channelSize = paramsize; | FluidDomainSettings *mds; | ||||
| Object *ob = CTX_data_active_object(C); | |||||
| if (entries == 3) { | mmd = (FluidModifierData *)modifiers_findByType(ob, eModifierType_Fluid); | ||||
| elbeemSimplifyChannelVec3(channel, &channelSize); | if (!mmd) { | ||||
| } | BLI_strncpy(error_msg, N_("Bake failed: no Fluid modifier found"), error_size); | ||||
| else if (entries == 1) { | return false; | ||||
| elbeemSimplifyChannelFloat(channel, &channelSize); | |||||
| } | } | ||||
| else { | mds = mmd->domain; | ||||
| /* invalid, cant happen? */ | if (!mds) { | ||||
| BLI_strncpy(error_msg, N_("Bake failed: invalid domain"), error_size); | |||||
| return false; | |||||
| } | } | ||||
| fprintf(file, " CHANNEL %s =\n", str); | job->bmain = CTX_data_main(C); | ||||
| for (i = 0; i < channelSize; i++) { | job->scene = CTX_data_scene(C); | ||||
| fprintf(file, " "); | job->depsgraph = CTX_data_depsgraph_pointer(C); | ||||
| for (j = 0; j <= entries; j++) { // also print time value | job->ob = CTX_data_active_object(C); | ||||
| fprintf(file, " %f ", channel[i * (entries + 1) + j]); | job->mmd = mmd; | ||||
| if (j == entries - 1) { | job->type = op->type->idname; | ||||
| fprintf(file, " "); | job->name = op->type->name; | ||||
| } | |||||
| } | |||||
| fprintf(file, "\n"); | |||||
| } | |||||
| fprintf(file, " ;\n"); | return true; | ||||
| } | } | ||||
| # endif | |||||
| /* Note: fluid anim channel data layout | |||||
| * ------------------------------------ | |||||
| * CHANNEL_FLOAT: | |||||
| * frame 1 |frame 2 | |||||
| * [dataF][time][dataF][time] | |||||
| * | |||||
| * CHANNEL_VEC: | |||||
| * frame 1 |frame 2 | |||||
| * [dataX][dataY][dataZ][time][dataX][dataY][dataZ][time] | |||||
| */ | |||||
| static void init_time(FluidsimSettings *domainSettings, FluidAnimChannels *channels) | static bool fluid_initpaths(FluidJob *job, ReportList *reports) | ||||
| { | { | ||||
| int i; | FluidDomainSettings *mds = job->mmd->domain; | ||||
| char tmpDir[FILE_MAX]; | |||||
| channels->timeAtFrame = MEM_callocN((channels->length + 1) * sizeof(float), | tmpDir[0] = '\0'; | ||||
| "timeAtFrame channel"); | |||||
| channels->timeAtFrame[0] = channels->timeAtFrame[1] = | const char *relbase = modifier_path_relbase(job->bmain, job->ob); | ||||
| domainSettings->animStart; // start at index 1 | |||||
| for (i = 2; i <= channels->length; i++) { | /* We do not accept empty paths, they can end in random places silently, see T51176. */ | ||||
| channels->timeAtFrame[i] = channels->timeAtFrame[i - 1] + (float)channels->aniFrameTime; | if (mds->cache_directory[0] == '\0') { | ||||
| } | modifier_path_init( | ||||
| mds->cache_directory, sizeof(mds->cache_directory), FLUID_DOMAIN_DIR_DEFAULT); | |||||
| BKE_reportf(reports, | |||||
| RPT_WARNING, | |||||
| "Fluid: Empty cache path, reset to default '%s'", | |||||
| mds->cache_directory); | |||||
| } | } | ||||
| /* if this is slow, can replace with faster, less readable code */ | BLI_strncpy(tmpDir, mds->cache_directory, FILE_MAXDIR); | ||||
| static void set_channel(float *channel, float time, float *value, int i, int size) | BLI_path_abs(tmpDir, relbase); | ||||
| { | |||||
| if (size == CHANNEL_FLOAT) { | |||||
| channel[(i * 2) + 0] = value[0]; | |||||
| channel[(i * 2) + 1] = time; | |||||
| } | |||||
| else if (size == CHANNEL_VEC) { | |||||
| channel[(i * 4) + 0] = value[0]; | |||||
| channel[(i * 4) + 1] = value[1]; | |||||
| channel[(i * 4) + 2] = value[2]; | |||||
| channel[(i * 4) + 3] = time; | |||||
| } | |||||
| } | |||||
| static void set_vertex_channel(Depsgraph *depsgraph, | /* Ensure whole path exists */ | ||||
| float *channel, | const bool dir_exists = BLI_dir_create_recursive(tmpDir); | ||||
| float time, | |||||
| struct Scene *scene, | |||||
| struct FluidObject *fobj, | |||||
| int i) | |||||
| { | |||||
| Object *ob = fobj->object; | |||||
| FluidsimModifierData *fluidmd = (FluidsimModifierData *)modifiers_findByType( | |||||
| ob, eModifierType_Fluidsim); | |||||
| float *verts; | |||||
| int *tris = NULL, numVerts = 0, numTris = 0; | |||||
| int modifierIndex = BLI_findindex(&ob->modifiers, fluidmd); | |||||
| int framesize = (3 * fobj->numVerts) + 1; | |||||
| int j; | |||||
| if (channel == NULL) { | /* We change path to some presumably valid default value, but do not allow bake process to | ||||
| return; | * continue, this gives user chance to set manually another path. */ | ||||
| } | if (!dir_exists) { | ||||
| modifier_path_init( | |||||
| mds->cache_directory, sizeof(mds->cache_directory), FLUID_DOMAIN_DIR_DEFAULT); | |||||
| BKE_reportf(reports, | |||||
| RPT_ERROR, | |||||
| "Fluid: Could not create cache directory '%s', reset to default '%s'", | |||||
| tmpDir, | |||||
| mds->cache_directory); | |||||
| initElbeemMesh(depsgraph, scene, ob, &numVerts, &verts, &numTris, &tris, 1, modifierIndex); | BLI_strncpy(tmpDir, mds->cache_directory, FILE_MAXDIR); | ||||
| BLI_path_abs(tmpDir, relbase); | |||||
| /* don't allow mesh to change number of verts in anim sequence */ | /* Ensure whole path exists and is writable. */ | ||||
| if (numVerts != fobj->numVerts) { | if (!BLI_dir_create_recursive(tmpDir)) { | ||||
| MEM_freeN(channel); | BKE_reportf(reports, | ||||
| channel = NULL; | RPT_ERROR, | ||||
| return; | "Fluid: Could not use default cache directory '%s', " | ||||
| "please define a valid cache path manually", | |||||
| tmpDir); | |||||
| } | } | ||||
| /* Copy final dir back into domain settings */ | |||||
| BLI_strncpy(mds->cache_directory, tmpDir, FILE_MAXDIR); | |||||
| /* fill frame of channel with vertex locations */ | return false; | ||||
| for (j = 0; j < (3 * numVerts); j++) { | |||||
| channel[i * framesize + j] = verts[j]; | |||||
| } | } | ||||
| channel[i * framesize + framesize - 1] = time; | |||||
| MEM_freeN(verts); | /* Copy final dir back into domain settings */ | ||||
| MEM_freeN(tris); | BLI_strncpy(mds->cache_directory, tmpDir, FILE_MAXDIR); | ||||
| return true; | |||||
| } | } | ||||
| static void free_domain_channels(FluidAnimChannels *channels) | static void fluid_bake_free(void *customdata) | ||||
| { | { | ||||
| if (!channels->timeAtFrame) { | FluidJob *job = customdata; | ||||
| return; | MEM_freeN(job); | ||||
| } | |||||
| MEM_freeN(channels->timeAtFrame); | |||||
| channels->timeAtFrame = NULL; | |||||
| MEM_freeN(channels->DomainGravity); | |||||
| channels->DomainGravity = NULL; | |||||
| MEM_freeN(channels->DomainViscosity); | |||||
| channels->DomainViscosity = NULL; | |||||
| MEM_freeN(channels->DomainTime); | |||||
| channels->DomainTime = NULL; | |||||
| } | |||||
| static void free_all_fluidobject_channels(ListBase *fobjects) | |||||
| { | |||||
| FluidObject *fobj; | |||||
| for (fobj = fobjects->first; fobj; fobj = fobj->next) { | |||||
| if (fobj->Translation) { | |||||
| MEM_freeN(fobj->Translation); | |||||
| fobj->Translation = NULL; | |||||
| MEM_freeN(fobj->Rotation); | |||||
| fobj->Rotation = NULL; | |||||
| MEM_freeN(fobj->Scale); | |||||
| fobj->Scale = NULL; | |||||
| MEM_freeN(fobj->Active); | |||||
| fobj->Active = NULL; | |||||
| MEM_freeN(fobj->InitialVelocity); | |||||
| fobj->InitialVelocity = NULL; | |||||
| } | |||||
| if (fobj->AttractforceStrength) { | |||||
| MEM_freeN(fobj->AttractforceStrength); | |||||
| fobj->AttractforceStrength = NULL; | |||||
| MEM_freeN(fobj->AttractforceRadius); | |||||
| fobj->AttractforceRadius = NULL; | |||||
| MEM_freeN(fobj->VelocityforceStrength); | |||||
| fobj->VelocityforceStrength = NULL; | |||||
| MEM_freeN(fobj->VelocityforceRadius); | |||||
| fobj->VelocityforceRadius = NULL; | |||||
| } | |||||
| if (fobj->VertexCache) { | |||||
| MEM_freeN(fobj->VertexCache); | |||||
| fobj->VertexCache = NULL; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| static void fluid_init_all_channels(bContext *C, | static void fluid_bake_sequence(FluidJob *job) | ||||
| Depsgraph *depsgraph, | |||||
| Object *UNUSED(fsDomain), | |||||
| FluidsimSettings *domainSettings, | |||||
| FluidAnimChannels *channels, | |||||
| ListBase *fobjects) | |||||
| { | { | ||||
| Scene *scene = CTX_data_scene(C); | FluidDomainSettings *mds = job->mmd->domain; | ||||
| ViewLayer *view_layer = CTX_data_view_layer(C); | Scene *scene = job->scene; | ||||
| Base *base; | int frame = 1, orig_frame; | ||||
| int i; | int frames; | ||||
| int length = channels->length; | int *pause_frame = NULL; | ||||
| float eval_time; | bool is_first_frame; | ||||
| /* init time values (assuming that time moves at a constant speed; may be overridden later) */ | |||||
| init_time(domainSettings, channels); | |||||
| /* allocate domain animation channels */ | |||||
| channels->DomainGravity = MEM_callocN(length * (CHANNEL_VEC + 1) * sizeof(float), | |||||
| "channel DomainGravity"); | |||||
| channels->DomainViscosity = MEM_callocN(length * (CHANNEL_FLOAT + 1) * sizeof(float), | |||||
| "channel DomainViscosity"); | |||||
| channels->DomainTime = MEM_callocN(length * (CHANNEL_FLOAT + 1) * sizeof(float), | |||||
| "channel DomainTime"); | |||||
| /* allocate fluid objects */ | |||||
| for (base = FIRSTBASE(view_layer); base; base = base->next) { | |||||
| Object *ob = base->object; | |||||
| FluidsimModifierData *fluidmd = (FluidsimModifierData *)modifiers_findByType( | |||||
| ob, eModifierType_Fluidsim); | |||||
| if (fluidmd) { | |||||
| FluidObject *fobj = MEM_callocN(sizeof(FluidObject), "Fluid Object"); | |||||
| fobj->object = ob; | |||||
| if (ELEM(fluidmd->fss->type, OB_FLUIDSIM_DOMAIN, OB_FLUIDSIM_PARTICLE)) { | |||||
| BLI_addtail(fobjects, fobj); | |||||
| continue; | |||||
| } | |||||
| fobj->Translation = MEM_callocN(length * (CHANNEL_VEC + 1) * sizeof(float), | |||||
| "fluidobject Translation"); | |||||
| fobj->Rotation = MEM_callocN(length * (CHANNEL_VEC + 1) * sizeof(float), | |||||
| "fluidobject Rotation"); | |||||
| fobj->Scale = MEM_callocN(length * (CHANNEL_VEC + 1) * sizeof(float), "fluidobject Scale"); | |||||
| fobj->Active = MEM_callocN(length * (CHANNEL_FLOAT + 1) * sizeof(float), | |||||
| "fluidobject Active"); | |||||
| fobj->InitialVelocity = MEM_callocN(length * (CHANNEL_VEC + 1) * sizeof(float), | |||||
| "fluidobject InitialVelocity"); | |||||
| if (fluidmd->fss->type == OB_FLUIDSIM_CONTROL) { | |||||
| fobj->AttractforceStrength = MEM_callocN(length * (CHANNEL_FLOAT + 1) * sizeof(float), | |||||
| "fluidobject AttractforceStrength"); | |||||
| fobj->AttractforceRadius = MEM_callocN(length * (CHANNEL_FLOAT + 1) * sizeof(float), | |||||
| "fluidobject AttractforceRadius"); | |||||
| fobj->VelocityforceStrength = MEM_callocN(length * (CHANNEL_FLOAT + 1) * sizeof(float), | |||||
| "fluidobject VelocityforceStrength"); | |||||
| fobj->VelocityforceRadius = MEM_callocN(length * (CHANNEL_FLOAT + 1) * sizeof(float), | |||||
| "fluidobject VelocityforceRadius"); | |||||
| } | |||||
| if (fluid_is_animated_mesh(fluidmd->fss)) { | |||||
| float *verts = NULL; | |||||
| int *tris = NULL, modifierIndex = BLI_findindex(&ob->modifiers, (ModifierData *)fluidmd); | |||||
| initElbeemMesh(depsgraph, | frames = mds->cache_frame_end - mds->cache_frame_start + 1; | ||||
| scene, | |||||
| ob, | |||||
| &fobj->numVerts, | |||||
| &verts, | |||||
| &fobj->numTris, | |||||
| &tris, | |||||
| 0, | |||||
| modifierIndex); | |||||
| fobj->VertexCache = MEM_callocN(length * ((fobj->numVerts * CHANNEL_VEC) + 1) * | |||||
| sizeof(float), | |||||
| "fluidobject VertexCache"); | |||||
| MEM_freeN(verts); | if (frames <= 0) { | ||||
| MEM_freeN(tris); | BLI_strncpy(mds->error, N_("No frames to bake"), sizeof(mds->error)); | ||||
| return; | |||||
| } | } | ||||
| BLI_addtail(fobjects, fobj); | /* Show progress bar. */ | ||||
| } | if (job->do_update) | ||||
| *(job->do_update) = true; | |||||
| /* Get current pause frame (pointer) - depending on bake type */ | |||||
| pause_frame = job->pause_frame; | |||||
| /* Set frame to start point (depending on current pause frame value) */ | |||||
| is_first_frame = ((*pause_frame) == 0); | |||||
| frame = is_first_frame ? mds->cache_frame_start : (*pause_frame); | |||||
| /* Save orig frame and update scene frame */ | |||||
| orig_frame = CFRA; | |||||
| CFRA = frame; | |||||
| /* Loop through selected frames */ | |||||
| for (; frame <= mds->cache_frame_end; frame++) { | |||||
| const float progress = (frame - mds->cache_frame_start) / (float)frames; | |||||
| /* Keep track of pause frame - needed to init future loop */ | |||||
| (*pause_frame) = frame; | |||||
| /* If user requested stop, quit baking */ | |||||
| if (G.is_break) { | |||||
| job->success = 0; | |||||
| return; | |||||
| } | } | ||||
| /* now we loop over the frames and fill the allocated channels with data */ | /* Update progress bar */ | ||||
| for (i = 0; i < channels->length; i++) { | if (job->do_update) | ||||
| FluidObject *fobj; | *(job->do_update) = true; | ||||
| float viscosity, gravity[3]; | if (job->progress) | ||||
| float timeAtFrame, time; | *(job->progress) = progress; | ||||
| eval_time = domainSettings->bakeStart + i; | CFRA = frame; | ||||
| /* Modifying the global scene isn't nice, but we can do it in | /* Update animation system */ | ||||
| * this part of the process before a threaded job is created */ | ED_update_for_newframe(job->bmain, job->depsgraph); | ||||
| scene->r.cfra = (int)eval_time; | } | ||||
| ED_update_for_newframe(CTX_data_main(C), depsgraph); | |||||
| /* Restore frame position that we were on before bake */ | |||||
| /* now scene data should be current according to animation system, so we fill the channels */ | CFRA = orig_frame; | ||||
| } | |||||
| /* Domain time */ | |||||
| /* TODO: have option for not running sim, time mangling, | static void fluid_bake_endjob(void *customdata) | ||||
| * in which case second case comes in handy. */ | { | ||||
| if (channels->DomainTime) { | FluidJob *job = customdata; | ||||
| time = get_fluid_rate(domainSettings) * (float)channels->aniFrameTime; | FluidDomainSettings *mds = job->mmd->domain; | ||||
| timeAtFrame = channels->timeAtFrame[i] + time; | |||||
| if (fluid_is_bake_noise(job) || fluid_is_bake_all(job)) { | |||||
| channels->timeAtFrame[i + 1] = timeAtFrame; | mds->cache_flag &= ~FLUID_DOMAIN_BAKING_NOISE; | ||||
| set_channel(channels->DomainTime, i, &time, i, CHANNEL_FLOAT); | mds->cache_flag |= FLUID_DOMAIN_BAKED_NOISE; | ||||
| mds->cache_flag &= ~FLUID_DOMAIN_OUTDATED_NOISE; | |||||
| } | |||||
| if (fluid_is_bake_mesh(job) || fluid_is_bake_all(job)) { | |||||
| mds->cache_flag &= ~FLUID_DOMAIN_BAKING_MESH; | |||||
| mds->cache_flag |= FLUID_DOMAIN_BAKED_MESH; | |||||
| mds->cache_flag &= ~FLUID_DOMAIN_OUTDATED_MESH; | |||||
| } | |||||
| if (fluid_is_bake_particle(job) || fluid_is_bake_all(job)) { | |||||
| mds->cache_flag &= ~FLUID_DOMAIN_BAKING_PARTICLES; | |||||
| mds->cache_flag |= FLUID_DOMAIN_BAKED_PARTICLES; | |||||
| mds->cache_flag &= ~FLUID_DOMAIN_OUTDATED_PARTICLES; | |||||
| } | |||||
| if (fluid_is_bake_guiding(job) || fluid_is_bake_all(job)) { | |||||
| mds->cache_flag &= ~FLUID_DOMAIN_BAKING_GUIDING; | |||||
| mds->cache_flag |= FLUID_DOMAIN_BAKED_GUIDING; | |||||
| mds->cache_flag &= ~FLUID_DOMAIN_OUTDATED_GUIDING; | |||||
| } | |||||
| if (fluid_is_bake_data(job) || fluid_is_bake_all(job)) { | |||||
| mds->cache_flag &= ~FLUID_DOMAIN_BAKING_DATA; | |||||
| mds->cache_flag |= FLUID_DOMAIN_BAKED_DATA; | |||||
| mds->cache_flag &= ~FLUID_DOMAIN_OUTDATED_DATA; | |||||
| } | |||||
| DEG_id_tag_update(&job->ob->id, ID_RECALC_GEOMETRY); | |||||
| G.is_rendering = false; | |||||
| BKE_spacedata_draw_locks(false); | |||||
| WM_set_locked_interface(G_MAIN->wm.first, false); | |||||
| /* Bake was successful: | |||||
| * Report for ended bake and how long it took */ | |||||
| if (job->success) { | |||||
| /* Show bake info */ | |||||
| WM_reportf(RPT_INFO, | |||||
| "Fluid: %s complete! (%.2f)", | |||||
| job->name, | |||||
| PIL_check_seconds_timer() - job->start); | |||||
| } | } | ||||
| else { | else { | ||||
| timeAtFrame = channels->timeAtFrame[i + 1]; | if (mds->error != NULL && mds->error[0] != '\0') { | ||||
| WM_reportf(RPT_ERROR, "Fluid: %s failed: %s", job->name, mds->error); | |||||
| } | |||||
| else { /* User canceled the bake */ | |||||
| WM_reportf(RPT_WARNING, "Fluid: %s canceled!", job->name); | |||||
| } | |||||
| } | } | ||||
| /* Domain properties - gravity/viscosity */ | |||||
| get_fluid_gravity(gravity, scene, domainSettings); | |||||
| set_channel(channels->DomainGravity, timeAtFrame, gravity, i, CHANNEL_VEC); | |||||
| viscosity = get_fluid_viscosity(domainSettings); | |||||
| set_channel(channels->DomainViscosity, timeAtFrame, &viscosity, i, CHANNEL_FLOAT); | |||||
| /* object movement */ | |||||
| for (fobj = fobjects->first; fobj; fobj = fobj->next) { | |||||
| Object *ob = fobj->object; | |||||
| FluidsimModifierData *fluidmd = (FluidsimModifierData *)modifiers_findByType( | |||||
| ob, eModifierType_Fluidsim); | |||||
| float active = (float)((fluidmd->fss->flag & OB_FLUIDSIM_ACTIVE) ? 1 : 0); | |||||
| float rot_d[3] = {0.f, 0.f, 0.f}, old_rot[3] = {0.f, 0.f, 0.f}; | |||||
| if (ELEM(fluidmd->fss->type, OB_FLUIDSIM_DOMAIN, OB_FLUIDSIM_PARTICLE)) { | |||||
| continue; | |||||
| } | } | ||||
| /* init euler rotation values and convert to elbeem format */ | static void fluid_bake_startjob(void *customdata, short *stop, short *do_update, float *progress) | ||||
| /* get the rotation from ob->obmat rather than ob->rot to account for parent animations */ | { | ||||
| if (i) { | FluidJob *job = customdata; | ||||
| copy_v3_v3(old_rot, fobj->Rotation + 4 * (i - 1)); | FluidDomainSettings *mds = job->mmd->domain; | ||||
| mul_v3_fl(old_rot, (float)-M_PI / 180.f); | |||||
| char tmpDir[FILE_MAX]; | |||||
| tmpDir[0] = '\0'; | |||||
| job->stop = stop; | |||||
| job->do_update = do_update; | |||||
| job->progress = progress; | |||||
| job->start = PIL_check_seconds_timer(); | |||||
| job->success = 1; | |||||
| G.is_break = false; | |||||
| G.is_rendering = true; | |||||
| BKE_spacedata_draw_locks(true); | |||||
| if (fluid_is_bake_noise(job) || fluid_is_bake_all(job)) { | |||||
| tmpDir[0] = '\0'; | |||||
| BLI_path_join(tmpDir, sizeof(tmpDir), mds->cache_directory, FLUID_DOMAIN_DIR_NOISE, NULL); | |||||
| BLI_dir_create_recursive(tmpDir); /* Create 'noise' subdir if it does not exist already */ | |||||
| mds->cache_flag &= ~(FLUID_DOMAIN_BAKED_NOISE | FLUID_DOMAIN_OUTDATED_NOISE); | |||||
| mds->cache_flag |= FLUID_DOMAIN_BAKING_NOISE; | |||||
| job->pause_frame = &mds->cache_frame_pause_noise; | |||||
| } | |||||
| if (fluid_is_bake_mesh(job) || fluid_is_bake_all(job)) { | |||||
| tmpDir[0] = '\0'; | |||||
| BLI_path_join(tmpDir, sizeof(tmpDir), mds->cache_directory, FLUID_DOMAIN_DIR_MESH, NULL); | |||||
| BLI_dir_create_recursive(tmpDir); /* Create 'mesh' subdir if it does not exist already */ | |||||
| mds->cache_flag &= ~(FLUID_DOMAIN_BAKED_MESH | FLUID_DOMAIN_OUTDATED_MESH); | |||||
| mds->cache_flag |= FLUID_DOMAIN_BAKING_MESH; | |||||
| job->pause_frame = &mds->cache_frame_pause_mesh; | |||||
| } | |||||
| if (fluid_is_bake_particle(job) || fluid_is_bake_all(job)) { | |||||
| tmpDir[0] = '\0'; | |||||
| BLI_path_join(tmpDir, sizeof(tmpDir), mds->cache_directory, FLUID_DOMAIN_DIR_PARTICLES, NULL); | |||||
| BLI_dir_create_recursive(tmpDir); /* Create 'particles' subdir if it does not exist already */ | |||||
| mds->cache_flag &= ~(FLUID_DOMAIN_BAKED_PARTICLES | FLUID_DOMAIN_OUTDATED_PARTICLES); | |||||
| mds->cache_flag |= FLUID_DOMAIN_BAKING_PARTICLES; | |||||
| job->pause_frame = &mds->cache_frame_pause_particles; | |||||
| } | |||||
| if (fluid_is_bake_guiding(job) || fluid_is_bake_all(job)) { | |||||
| tmpDir[0] = '\0'; | |||||
| BLI_path_join(tmpDir, sizeof(tmpDir), mds->cache_directory, FLUID_DOMAIN_DIR_GUIDING, NULL); | |||||
| BLI_dir_create_recursive(tmpDir); /* Create 'guiding' subdir if it does not exist already */ | |||||
| mds->cache_flag &= ~(FLUID_DOMAIN_BAKED_GUIDING | FLUID_DOMAIN_OUTDATED_GUIDING); | |||||
| mds->cache_flag |= FLUID_DOMAIN_BAKING_GUIDING; | |||||
| job->pause_frame = &mds->cache_frame_pause_guiding; | |||||
| } | |||||
| if (fluid_is_bake_data(job) || fluid_is_bake_all(job)) { | |||||
| tmpDir[0] = '\0'; | |||||
| BLI_path_join(tmpDir, sizeof(tmpDir), mds->cache_directory, FLUID_DOMAIN_DIR_CONFIG, NULL); | |||||
| BLI_dir_create_recursive(tmpDir); /* Create 'config' subdir if it does not exist already */ | |||||
| tmpDir[0] = '\0'; | |||||
| BLI_path_join(tmpDir, sizeof(tmpDir), mds->cache_directory, FLUID_DOMAIN_DIR_DATA, NULL); | |||||
| BLI_dir_create_recursive(tmpDir); /* Create 'data' subdir if it does not exist already */ | |||||
| mds->cache_flag &= ~(FLUID_DOMAIN_BAKED_DATA | FLUID_DOMAIN_OUTDATED_DATA); | |||||
| mds->cache_flag |= FLUID_DOMAIN_BAKING_DATA; | |||||
| job->pause_frame = &mds->cache_frame_pause_data; | |||||
| if (mds->flags & FLUID_DOMAIN_EXPORT_MANTA_SCRIPT) { | |||||
| BLI_path_join(tmpDir, sizeof(tmpDir), mds->cache_directory, FLUID_DOMAIN_DIR_SCRIPT, NULL); | |||||
| BLI_dir_create_recursive(tmpDir); /* Create 'script' subdir if it does not exist already */ | |||||
| } | |||||
| } | } | ||||
| DEG_id_tag_update(&job->ob->id, ID_RECALC_GEOMETRY); | |||||
| mat4_to_compatible_eulO(rot_d, old_rot, 0, ob->obmat); | fluid_bake_sequence(job); | ||||
| mul_v3_fl(rot_d, -180.0f / (float)M_PI); | |||||
| set_channel(fobj->Translation, timeAtFrame, ob->loc, i, CHANNEL_VEC); | if (do_update) | ||||
| set_channel(fobj->Rotation, timeAtFrame, rot_d, i, CHANNEL_VEC); | *do_update = true; | ||||
| set_channel(fobj->Scale, timeAtFrame, ob->scale, i, CHANNEL_VEC); | if (stop) | ||||
| set_channel(fobj->Active, timeAtFrame, &active, i, CHANNEL_FLOAT); | *stop = 0; | ||||
| set_channel(fobj->InitialVelocity, timeAtFrame, &fluidmd->fss->iniVelx, i, CHANNEL_VEC); | } | ||||
| // printf("Active: %f, Frame: %f\n", active, timeAtFrame); | static void fluid_free_endjob(void *customdata) | ||||
| { | |||||
| FluidJob *job = customdata; | |||||
| FluidDomainSettings *mds = job->mmd->domain; | |||||
| if (fluidmd->fss->type == OB_FLUIDSIM_CONTROL) { | G.is_rendering = false; | ||||
| set_channel(fobj->AttractforceStrength, | BKE_spacedata_draw_locks(false); | ||||
| timeAtFrame, | WM_set_locked_interface(G_MAIN->wm.first, false); | ||||
| &fluidmd->fss->attractforceStrength, | |||||
| i, | /* Free was successful: | ||||
| CHANNEL_FLOAT); | * Report for ended free job and how long it took */ | ||||
| set_channel(fobj->AttractforceRadius, | if (job->success) { | ||||
| timeAtFrame, | /* Show free job info */ | ||||
| &fluidmd->fss->attractforceRadius, | WM_reportf(RPT_INFO, | ||||
| i, | "Fluid: %s complete! (%.2f)", | ||||
| CHANNEL_FLOAT); | job->name, | ||||
| set_channel(fobj->VelocityforceStrength, | PIL_check_seconds_timer() - job->start); | ||||
| timeAtFrame, | |||||
| &fluidmd->fss->velocityforceStrength, | |||||
| i, | |||||
| CHANNEL_FLOAT); | |||||
| set_channel(fobj->VelocityforceRadius, | |||||
| timeAtFrame, | |||||
| &fluidmd->fss->velocityforceRadius, | |||||
| i, | |||||
| CHANNEL_FLOAT); | |||||
| } | } | ||||
| else { | |||||
| if (fluid_is_animated_mesh(fluidmd->fss)) { | if (mds->error != NULL && mds->error[0] != '\0') { | ||||
| set_vertex_channel(depsgraph, fobj->VertexCache, timeAtFrame, scene, fobj, i); | WM_reportf(RPT_ERROR, "Fluid: %s failed: %s", job->name, mds->error); | ||||
| } | } | ||||
| else { /* User canceled the free job */ | |||||
| WM_reportf(RPT_WARNING, "Fluid: %s canceled!", job->name); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| static void export_fluid_objects(Depsgraph *depsgraph, | static void fluid_free_startjob(void *customdata, short *stop, short *do_update, float *progress) | ||||
| ListBase *fobjects, | |||||
| Scene *scene, | |||||
| int length) | |||||
| { | { | ||||
| FluidObject *fobj; | FluidJob *job = customdata; | ||||
| FluidDomainSettings *mds = job->mmd->domain; | |||||
| for (fobj = fobjects->first; fobj; fobj = fobj->next) { | Scene *scene = job->scene; | ||||
| Object *ob = fobj->object; | |||||
| FluidsimModifierData *fluidmd = (FluidsimModifierData *)modifiers_findByType( | |||||
| ob, eModifierType_Fluidsim); | |||||
| int modifierIndex = BLI_findindex(&ob->modifiers, fluidmd); | |||||
| float *verts = NULL; | |||||
| int *tris = NULL; | |||||
| int numVerts = 0, numTris = 0; | |||||
| bool deform = fluid_is_animated_mesh(fluidmd->fss); | |||||
| elbeemMesh fsmesh; | |||||
| if (ELEM(fluidmd->fss->type, OB_FLUIDSIM_DOMAIN, OB_FLUIDSIM_PARTICLE)) { | |||||
| continue; | |||||
| } | |||||
| elbeemResetMesh(&fsmesh); | |||||
| fsmesh.type = fluidmd->fss->type; | |||||
| fsmesh.name = ob->id.name; | |||||
| initElbeemMesh(depsgraph, scene, ob, &numVerts, &verts, &numTris, &tris, 0, modifierIndex); | char tmpDir[FILE_MAX]; | ||||
| tmpDir[0] = '\0'; | |||||
| fsmesh.numVertices = numVerts; | job->stop = stop; | ||||
| fsmesh.numTriangles = numTris; | job->do_update = do_update; | ||||
| fsmesh.vertices = verts; | job->progress = progress; | ||||
| fsmesh.triangles = tris; | job->start = PIL_check_seconds_timer(); | ||||
| job->success = 1; | |||||
| fsmesh.channelSizeTranslation = fsmesh.channelSizeRotation = fsmesh.channelSizeScale = | G.is_break = false; | ||||
| fsmesh.channelSizeInitialVel = fsmesh.channelSizeActive = length; | G.is_rendering = true; | ||||
| BKE_spacedata_draw_locks(true); | |||||
| fsmesh.channelTranslation = fobj->Translation; | int cache_map = 0; | ||||
| fsmesh.channelRotation = fobj->Rotation; | |||||
| fsmesh.channelScale = fobj->Scale; | |||||
| fsmesh.channelActive = fobj->Active; | |||||
| if (ELEM(fsmesh.type, OB_FLUIDSIM_FLUID, OB_FLUIDSIM_INFLOW)) { | if (fluid_is_free_data(job) || fluid_is_free_all(job)) { | ||||
| fsmesh.channelInitialVel = fobj->InitialVelocity; | cache_map |= (FLUID_DOMAIN_OUTDATED_DATA | FLUID_DOMAIN_OUTDATED_NOISE | | ||||
| fsmesh.localInivelCoords = ((fluidmd->fss->typeFlags & OB_FSINFLOW_LOCALCOORD) ? 1 : 0); | FLUID_DOMAIN_OUTDATED_MESH | FLUID_DOMAIN_OUTDATED_PARTICLES); | ||||
| } | } | ||||
| if (fluid_is_free_noise(job) || fluid_is_free_all(job)) { | |||||
| if (fluidmd->fss->typeFlags & OB_FSBND_NOSLIP) { | cache_map |= FLUID_DOMAIN_OUTDATED_NOISE; | ||||
| fsmesh.obstacleType = FLUIDSIM_OBSTACLE_NOSLIP; | |||||
| } | |||||
| else if (fluidmd->fss->typeFlags & OB_FSBND_PARTSLIP) { | |||||
| fsmesh.obstacleType = FLUIDSIM_OBSTACLE_PARTSLIP; | |||||
| } | } | ||||
| else if (fluidmd->fss->typeFlags & OB_FSBND_FREESLIP) { | if (fluid_is_free_mesh(job) || fluid_is_free_all(job)) { | ||||
| fsmesh.obstacleType = FLUIDSIM_OBSTACLE_FREESLIP; | cache_map |= FLUID_DOMAIN_OUTDATED_MESH; | ||||
| } | } | ||||
| if (fluid_is_free_particles(job) || fluid_is_free_all(job)) { | |||||
| fsmesh.obstaclePartslip = fluidmd->fss->partSlipValue; | cache_map |= FLUID_DOMAIN_OUTDATED_PARTICLES; | ||||
| fsmesh.volumeInitType = fluidmd->fss->volumeInitType; | |||||
| fsmesh.obstacleImpactFactor = fluidmd->fss->surfaceSmoothing; // misused value | |||||
| if (fsmesh.type == OB_FLUIDSIM_CONTROL) { | |||||
| fsmesh.cpsTimeStart = fluidmd->fss->cpsTimeStart; | |||||
| fsmesh.cpsTimeEnd = fluidmd->fss->cpsTimeEnd; | |||||
| fsmesh.cpsQuality = fluidmd->fss->cpsQuality; | |||||
| fsmesh.obstacleType = (fluidmd->fss->flag & OB_FLUIDSIM_REVERSE); | |||||
| fsmesh.channelSizeAttractforceRadius = fsmesh.channelSizeVelocityforceStrength = | |||||
| fsmesh.channelSizeVelocityforceRadius = fsmesh.channelSizeAttractforceStrength = length; | |||||
| fsmesh.channelAttractforceStrength = fobj->AttractforceStrength; | |||||
| fsmesh.channelAttractforceRadius = fobj->AttractforceRadius; | |||||
| fsmesh.channelVelocityforceStrength = fobj->VelocityforceStrength; | |||||
| fsmesh.channelVelocityforceRadius = fobj->VelocityforceRadius; | |||||
| } | } | ||||
| else { | if (fluid_is_free_guiding(job) || fluid_is_free_all(job)) { | ||||
| fsmesh.channelAttractforceStrength = fsmesh.channelAttractforceRadius = | cache_map |= FLUID_DOMAIN_OUTDATED_GUIDING; | ||||
| fsmesh.channelVelocityforceStrength = fsmesh.channelVelocityforceRadius = NULL; | |||||
| } | } | ||||
| BKE_fluid_cache_free(mds, job->ob, cache_map); | |||||
| /* animated meshes */ | *do_update = true; | ||||
| if (deform) { | *stop = 0; | ||||
| fsmesh.channelSizeVertices = length; | |||||
| fsmesh.channelVertices = fobj->VertexCache; | |||||
| /* remove channels */ | /* Reset scene frame to cache frame start */ | ||||
| fsmesh.channelTranslation = fsmesh.channelRotation = fsmesh.channelScale = NULL; | CFRA = mds->cache_frame_start; | ||||
| /* Override user settings, only noslip is supported here! */ | /* Update scene so that viewport shows freed up scene */ | ||||
| if (fsmesh.type != OB_FLUIDSIM_CONTROL) { | ED_update_for_newframe(job->bmain, job->depsgraph); | ||||
| fsmesh.obstacleType = FLUIDSIM_OBSTACLE_NOSLIP; | |||||
| } | |||||
| } | } | ||||
| elbeemAddMesh(&fsmesh); | /***************************** Operators ******************************/ | ||||
| if (verts) { | |||||
| MEM_freeN(verts); | |||||
| } | |||||
| if (tris) { | |||||
| MEM_freeN(tris); | |||||
| } | |||||
| } | |||||
| } | |||||
| static int fluid_validate_scene(ReportList *reports, ViewLayer *view_layer, Object *fsDomain) | static int fluid_bake_exec(struct bContext *C, struct wmOperator *op) | ||||
| { | { | ||||
| Base *base; | FluidJob *job = MEM_mallocN(sizeof(FluidJob), "FluidJob"); | ||||
| Object *newdomain = NULL; | char error_msg[256] = "\0"; | ||||
| int channelObjCount = 0; | |||||
| int fluidInputCount = 0; | |||||
| for (base = FIRSTBASE(view_layer); base; base = base->next) { | |||||
| Object *ob = base->object; | |||||
| FluidsimModifierData *fluidmdtmp = (FluidsimModifierData *)modifiers_findByType( | |||||
| ob, eModifierType_Fluidsim); | |||||
| /* only find objects with fluid modifiers */ | if (!fluid_initjob(C, job, op, error_msg, sizeof(error_msg))) { | ||||
| if (!fluidmdtmp || ob->type != OB_MESH) { | if (error_msg[0]) { | ||||
| continue; | BKE_report(op->reports, RPT_ERROR, error_msg); | ||||
| } | |||||
| if (fluidmdtmp->fss->type == OB_FLUIDSIM_DOMAIN) { | |||||
| /* if no initial domain object given, find another potential domain */ | |||||
| if (!fsDomain) { | |||||
| newdomain = ob; | |||||
| } | |||||
| /* if there's more than one domain, cancel */ | |||||
| else if (fsDomain && ob != fsDomain) { | |||||
| BKE_report(reports, RPT_ERROR, "There should be only one domain object"); | |||||
| return 0; | |||||
| } | } | ||||
| fluid_bake_free(job); | |||||
| return OPERATOR_CANCELLED; | |||||
| } | } | ||||
| if (!fluid_initpaths(job, op->reports)) { | |||||
| /* count number of objects needed for animation channels */ | return OPERATOR_CANCELLED; | ||||
| if (!ELEM(fluidmdtmp->fss->type, OB_FLUIDSIM_DOMAIN, OB_FLUIDSIM_PARTICLE)) { | |||||
| channelObjCount++; | |||||
| } | } | ||||
| fluid_bake_startjob(job, NULL, NULL, NULL); | |||||
| fluid_bake_endjob(job); | |||||
| fluid_bake_free(job); | |||||
| /* count number of fluid input objects */ | return OPERATOR_FINISHED; | ||||
| if (ELEM(fluidmdtmp->fss->type, OB_FLUIDSIM_FLUID, OB_FLUIDSIM_INFLOW)) { | |||||
| fluidInputCount++; | |||||
| } | |||||
| } | } | ||||
| if (newdomain) { | static int fluid_bake_invoke(struct bContext *C, | ||||
| fsDomain = newdomain; | struct wmOperator *op, | ||||
| } | const wmEvent *UNUSED(_event)) | ||||
| { | |||||
| Scene *scene = CTX_data_scene(C); | |||||
| FluidJob *job = MEM_mallocN(sizeof(FluidJob), "FluidJob"); | |||||
| char error_msg[256] = "\0"; | |||||
| if (!fsDomain) { | if (!fluid_initjob(C, job, op, error_msg, sizeof(error_msg))) { | ||||
| BKE_report(reports, RPT_ERROR, "No domain object found"); | if (error_msg[0]) { | ||||
| return 0; | BKE_report(op->reports, RPT_ERROR, error_msg); | ||||
| } | } | ||||
| fluid_bake_free(job); | |||||
| if (channelObjCount >= 255) { | return OPERATOR_CANCELLED; | ||||
| BKE_report(reports, RPT_ERROR, "Cannot bake with more than 256 objects"); | |||||
| return 0; | |||||
| } | } | ||||
| if (fluidInputCount == 0) { | if (!fluid_initpaths(job, op->reports)) { | ||||
| BKE_report(reports, RPT_ERROR, "No fluid input objects in the scene"); | return OPERATOR_CANCELLED; | ||||
| return 0; | |||||
| } | } | ||||
| return 1; | wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), | ||||
| } | CTX_wm_window(C), | ||||
| scene, | |||||
| "Fluid Bake", | |||||
| WM_JOB_PROGRESS, | |||||
| WM_JOB_TYPE_OBJECT_SIM_FLUID); | |||||
| # define FLUID_SUFFIX_CONFIG "fluidsim.cfg" | WM_jobs_customdata_set(wm_job, job, fluid_bake_free); | ||||
| # define FLUID_SUFFIX_CONFIG_TMP (FLUID_SUFFIX_CONFIG ".tmp") | WM_jobs_timer(wm_job, 0.01, NC_OBJECT | ND_MODIFIER, NC_OBJECT | ND_MODIFIER); | ||||
| # define FLUID_SUFFIX_SURFACE "fluidsurface" | WM_jobs_callbacks(wm_job, fluid_bake_startjob, NULL, NULL, fluid_bake_endjob); | ||||
| static bool fluid_init_filepaths(Main *bmain, | WM_set_locked_interface(CTX_wm_manager(C), true); | ||||
| ReportList *reports, | |||||
| FluidsimSettings *domainSettings, | |||||
| Object *fsDomain, | |||||
| char *targetDir, | |||||
| char *targetFile) | |||||
| { | |||||
| const char *suffixConfigTmp = FLUID_SUFFIX_CONFIG_TMP; | |||||
| /* prepare names... */ | WM_jobs_start(CTX_wm_manager(C), wm_job); | ||||
| const char *relbase = modifier_path_relbase(bmain, fsDomain); | WM_event_add_modal_handler(C, op); | ||||
| /* We do not accept empty paths, they can end in random places silently, see T51176. */ | return OPERATOR_RUNNING_MODAL; | ||||
| if (domainSettings->surfdataPath[0] == '\0') { | |||||
| modifier_path_init(domainSettings->surfdataPath, | |||||
| sizeof(domainSettings->surfdataPath), | |||||
| OB_FLUIDSIM_SURF_DIR_DEFAULT); | |||||
| BKE_reportf(reports, | |||||
| RPT_WARNING, | |||||
| "Fluidsim: empty cache path, reset to default '%s'", | |||||
| domainSettings->surfdataPath); | |||||
| } | } | ||||
| BLI_strncpy(targetDir, domainSettings->surfdataPath, FILE_MAXDIR); | static int fluid_bake_modal(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) | ||||
| BLI_path_abs(targetDir, relbase); | { | ||||
| /* no running blender, remove handler and pass through */ | |||||
| /* .tmp: don't overwrite/delete original file */ | if (0 == WM_jobs_test(CTX_wm_manager(C), CTX_data_scene(C), WM_JOB_TYPE_OBJECT_SIM_FLUID)) | ||||
| BLI_join_dirfile(targetFile, FILE_MAX, targetDir, suffixConfigTmp); | return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH; | ||||
| /* Ensure whole path exists and is writeable. */ | |||||
| const bool dir_exists = BLI_dir_create_recursive(targetDir); | |||||
| const bool is_writable = BLI_file_is_writable(targetFile); | |||||
| /* We change path to some presumably valid default value, | |||||
| * but do not allow bake process to continue, | |||||
| * this gives user chance to set manually another path. */ | |||||
| if (!dir_exists || !is_writable) { | |||||
| modifier_path_init(domainSettings->surfdataPath, | |||||
| sizeof(domainSettings->surfdataPath), | |||||
| OB_FLUIDSIM_SURF_DIR_DEFAULT); | |||||
| if (!dir_exists) { | switch (event->type) { | ||||
| BKE_reportf(reports, | case ESCKEY: | ||||
| RPT_ERROR, | return OPERATOR_RUNNING_MODAL; | ||||
| "Fluidsim: could not create cache directory '%s', reset to default '%s'", | |||||
| targetDir, | |||||
| domainSettings->surfdataPath); | |||||
| } | } | ||||
| else { | return OPERATOR_PASS_THROUGH; | ||||
| BKE_reportf(reports, | |||||
| RPT_ERROR, | |||||
| "Fluidsim: cache directory '%s' is not writable, reset to default '%s'", | |||||
| targetDir, | |||||
| domainSettings->surfdataPath); | |||||
| } | } | ||||
| BLI_strncpy(targetDir, domainSettings->surfdataPath, FILE_MAXDIR); | static int fluid_free_exec(struct bContext *C, struct wmOperator *op) | ||||
| BLI_path_abs(targetDir, relbase); | { | ||||
| FluidModifierData *mmd = NULL; | |||||
| /* .tmp: don't overwrite/delete original file */ | FluidDomainSettings *mds; | ||||
| BLI_join_dirfile(targetFile, FILE_MAX, targetDir, suffixConfigTmp); | Object *ob = CTX_data_active_object(C); | ||||
| Scene *scene = CTX_data_scene(C); | |||||
| /* Ensure whole path exists and is writeable. */ | /* | ||||
| if (!BLI_dir_create_recursive(targetDir) || !BLI_file_is_writable(targetFile)) { | * Get modifier data | ||||
| BKE_reportf(reports, | */ | ||||
| RPT_ERROR, | mmd = (FluidModifierData *)modifiers_findByType(ob, eModifierType_Fluid); | ||||
| "Fluidsim: could not use default cache directory '%s', " | if (!mmd) { | ||||
| "please define a valid cache path manually", | BKE_report(op->reports, RPT_ERROR, "Bake free failed: no Fluid modifier found"); | ||||
| targetDir); | return OPERATOR_CANCELLED; | ||||
| } | } | ||||
| return false; | mds = mmd->domain; | ||||
| if (!mds) { | |||||
| BKE_report(op->reports, RPT_ERROR, "Bake free failed: invalid domain"); | |||||
| return OPERATOR_CANCELLED; | |||||
| } | } | ||||
| return true; | /* Cannot free data if other bakes currently working */ | ||||
| if (mmd->domain->cache_flag & (FLUID_DOMAIN_BAKING_DATA | FLUID_DOMAIN_BAKING_NOISE | | |||||
| FLUID_DOMAIN_BAKING_MESH | FLUID_DOMAIN_BAKING_PARTICLES)) { | |||||
| BKE_report(op->reports, RPT_ERROR, "Bake free failed: pending bake jobs found"); | |||||
| return OPERATOR_CANCELLED; | |||||
| } | } | ||||
| /* ******************************************************************************** */ | FluidJob *job = MEM_mallocN(sizeof(FluidJob), "FluidJob"); | ||||
| /* ********************** write fluidsim config to file ************************* */ | job->bmain = CTX_data_main(C); | ||||
| /* ******************************************************************************** */ | job->scene = scene; | ||||
| job->depsgraph = CTX_data_depsgraph_pointer(C); | |||||
| typedef struct FluidBakeJob { | job->ob = ob; | ||||
| /* from wmJob */ | job->mmd = mmd; | ||||
| void *owner; | job->type = op->type->idname; | ||||
| short *stop, *do_update; | job->name = op->type->name; | ||||
| float *progress; | |||||
| int current_frame; | |||||
| elbeemSimulationSettings *settings; | |||||
| } FluidBakeJob; | |||||
| static void fluidbake_free(void *customdata) | if (!fluid_initpaths(job, op->reports)) { | ||||
| { | return OPERATOR_CANCELLED; | ||||
| FluidBakeJob *fb = (FluidBakeJob *)customdata; | |||||
| MEM_freeN(fb); | |||||
| } | } | ||||
| /* called by fluidbake, only to check job 'stop' value */ | wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), | ||||
| static int fluidbake_breakjob(void *customdata) | CTX_wm_window(C), | ||||
| { | scene, | ||||
| FluidBakeJob *fb = (FluidBakeJob *)customdata; | "Fluid Free", | ||||
| WM_JOB_PROGRESS, | |||||
| WM_JOB_TYPE_OBJECT_SIM_FLUID); | |||||
| if (fb->stop && *(fb->stop)) { | WM_jobs_customdata_set(wm_job, job, fluid_bake_free); | ||||
| return 1; | WM_jobs_timer(wm_job, 0.01, NC_OBJECT | ND_MODIFIER, NC_OBJECT | ND_MODIFIER); | ||||
| } | WM_jobs_callbacks(wm_job, fluid_free_startjob, NULL, NULL, fluid_free_endjob); | ||||
| /* this is not nice yet, need to make the jobs list template better | WM_set_locked_interface(CTX_wm_manager(C), true); | ||||
| * for identifying/acting upon various different jobs */ | |||||
| /* but for now we'll reuse the render break... */ | |||||
| return (G.is_break); | |||||
| } | |||||
| /* called by fluidbake, wmJob sends notifier */ | /* Free Fluid Geometry */ | ||||
| static void fluidbake_updatejob(void *customdata, float progress) | WM_jobs_start(CTX_wm_manager(C), wm_job); | ||||
| { | |||||
| FluidBakeJob *fb = (FluidBakeJob *)customdata; | |||||
| *(fb->do_update) = true; | return OPERATOR_FINISHED; | ||||
| *(fb->progress) = progress; | |||||
| } | } | ||||
| static void fluidbake_startjob(void *customdata, short *stop, short *do_update, float *progress) | static int fluid_pause_exec(struct bContext *C, struct wmOperator *op) | ||||
| { | { | ||||
| FluidBakeJob *fb = (FluidBakeJob *)customdata; | FluidModifierData *mmd = NULL; | ||||
| FluidDomainSettings *mds; | |||||
| Object *ob = CTX_data_active_object(C); | |||||
| fb->stop = stop; | /* | ||||
| fb->do_update = do_update; | * Get modifier data | ||||
| fb->progress = progress; | */ | ||||
| mmd = (FluidModifierData *)modifiers_findByType(ob, eModifierType_Fluid); | |||||
| if (!mmd) { | |||||
| BKE_report(op->reports, RPT_ERROR, "Bake free failed: no Fluid modifier found"); | |||||
| return OPERATOR_CANCELLED; | |||||
| } | |||||
| mds = mmd->domain; | |||||
| if (!mds) { | |||||
| BKE_report(op->reports, RPT_ERROR, "Bake free failed: invalid domain"); | |||||
| return OPERATOR_CANCELLED; | |||||
| } | |||||
| G.is_break = false; /* XXX shared with render - replace with job 'stop' switch */ | G.is_break = true; | ||||
| elbeemSimulate(); | return OPERATOR_FINISHED; | ||||
| *do_update = true; | |||||
| *stop = 0; | |||||
| } | } | ||||
| static void fluidbake_endjob(void *customdata) | void FLUID_OT_bake_all(wmOperatorType *ot) | ||||
| { | { | ||||
| FluidBakeJob *fb = (FluidBakeJob *)customdata; | /* identifiers */ | ||||
| ot->name = "Bake All"; | |||||
| ot->description = "Bake Entire Fluid Simulation"; | |||||
| ot->idname = FLUID_JOB_BAKE_ALL; | |||||
| if (fb->settings) { | /* api callbacks */ | ||||
| MEM_freeN(fb->settings); | ot->exec = fluid_bake_exec; | ||||
| fb->settings = NULL; | ot->invoke = fluid_bake_invoke; | ||||
| } | ot->modal = fluid_bake_modal; | ||||
| ot->poll = ED_operator_object_active_editable; | |||||
| } | } | ||||
| static int runSimulationCallback(void *data, int status, int frame) | void FLUID_OT_free_all(wmOperatorType *ot) | ||||
| { | { | ||||
| FluidBakeJob *fb = (FluidBakeJob *)data; | /* identifiers */ | ||||
| elbeemSimulationSettings *settings = fb->settings; | ot->name = "Free All"; | ||||
| ot->description = "Free Entire Fluid Simulation"; | |||||
| ot->idname = FLUID_JOB_FREE_ALL; | |||||
| if (status == FLUIDSIM_CBSTATUS_NEWFRAME) { | /* api callbacks */ | ||||
| fluidbake_updatejob(fb, frame / (float)settings->noOfFrames); | ot->exec = fluid_free_exec; | ||||
| # if 0 | ot->poll = ED_operator_object_active_editable; | ||||
| printf("elbeem blender cb s%d, f%d, domainid:%d noOfFrames: %d\n", | |||||
| status, | |||||
| frame, | |||||
| settings->domainId, | |||||
| settings->noOfFrames); // DEBUG | |||||
| # endif | |||||
| } | } | ||||
| if (fluidbake_breakjob(fb)) { | void FLUID_OT_bake_data(wmOperatorType *ot) | ||||
| return FLUIDSIM_CBRET_ABORT; | { | ||||
| } | /* identifiers */ | ||||
| ot->name = "Bake Data"; | |||||
| ot->description = "Bake Fluid Data"; | |||||
| ot->idname = FLUID_JOB_BAKE_DATA; | |||||
| return FLUIDSIM_CBRET_CONTINUE; | /* api callbacks */ | ||||
| ot->exec = fluid_bake_exec; | |||||
| ot->invoke = fluid_bake_invoke; | |||||
| ot->modal = fluid_bake_modal; | |||||
| ot->poll = ED_operator_object_active_editable; | |||||
| } | } | ||||
| static void fluidbake_free_data(FluidAnimChannels *channels, | void FLUID_OT_free_data(wmOperatorType *ot) | ||||
| ListBase *fobjects, | |||||
| elbeemSimulationSettings *fsset, | |||||
| FluidBakeJob *fb) | |||||
| { | { | ||||
| free_domain_channels(channels); | /* identifiers */ | ||||
| MEM_freeN(channels); | ot->name = "Free Data"; | ||||
| channels = NULL; | ot->description = "Free Fluid Data"; | ||||
| ot->idname = FLUID_JOB_FREE_DATA; | |||||
| free_all_fluidobject_channels(fobjects); | |||||
| BLI_freelistN(fobjects); | |||||
| MEM_freeN(fobjects); | |||||
| fobjects = NULL; | |||||
| if (fsset) { | |||||
| MEM_freeN(fsset); | |||||
| fsset = NULL; | |||||
| } | |||||
| if (fb) { | /* api callbacks */ | ||||
| MEM_freeN(fb); | ot->exec = fluid_free_exec; | ||||
| fb = NULL; | ot->poll = ED_operator_object_active_editable; | ||||
| } | |||||
| } | } | ||||
| /* copied from rna_fluidsim.c: fluidsim_find_lastframe() */ | void FLUID_OT_bake_noise(wmOperatorType *ot) | ||||
| static void fluidsim_delete_until_lastframe(FluidsimSettings *fss, const char *relbase) | |||||
| { | { | ||||
| char targetDir[FILE_MAX], targetFile[FILE_MAX]; | /* identifiers */ | ||||
| char targetDirVel[FILE_MAX], targetFileVel[FILE_MAX]; | ot->name = "Bake Noise"; | ||||
| char previewDir[FILE_MAX], previewFile[FILE_MAX]; | ot->description = "Bake Fluid Noise"; | ||||
| int curFrame = 1, exists = 0; | ot->idname = FLUID_JOB_BAKE_NOISE; | ||||
| BLI_join_dirfile( | |||||
| targetDir, sizeof(targetDir), fss->surfdataPath, OB_FLUIDSIM_SURF_FINAL_OBJ_FNAME); | |||||
| BLI_join_dirfile( | |||||
| targetDirVel, sizeof(targetDirVel), fss->surfdataPath, OB_FLUIDSIM_SURF_FINAL_VEL_FNAME); | |||||
| BLI_join_dirfile( | |||||
| previewDir, sizeof(previewDir), fss->surfdataPath, OB_FLUIDSIM_SURF_PREVIEW_OBJ_FNAME); | |||||
| BLI_path_abs(targetDir, relbase); | |||||
| BLI_path_abs(targetDirVel, relbase); | |||||
| BLI_path_abs(previewDir, relbase); | |||||
| do { | |||||
| BLI_strncpy(targetFile, targetDir, sizeof(targetFile)); | |||||
| BLI_strncpy(targetFileVel, targetDirVel, sizeof(targetFileVel)); | |||||
| BLI_strncpy(previewFile, previewDir, sizeof(previewFile)); | |||||
| BLI_path_frame(targetFile, curFrame, 0); | |||||
| BLI_path_frame(targetFileVel, curFrame, 0); | |||||
| BLI_path_frame(previewFile, curFrame, 0); | |||||
| curFrame++; | |||||
| if ((exists = BLI_exists(targetFile))) { | |||||
| BLI_delete(targetFile, false, false); | |||||
| BLI_delete(targetFileVel, false, false); | |||||
| BLI_delete(previewFile, false, false); | |||||
| } | |||||
| } while (exists); | |||||
| return; | /* api callbacks */ | ||||
| ot->exec = fluid_bake_exec; | |||||
| ot->invoke = fluid_bake_invoke; | |||||
| ot->modal = fluid_bake_modal; | |||||
| ot->poll = ED_operator_object_active_editable; | |||||
| } | } | ||||
| static int fluidsimBake(bContext *C, ReportList *reports, Object *fsDomain, short do_job) | void FLUID_OT_free_noise(wmOperatorType *ot) | ||||
| { | { | ||||
| Main *bmain = CTX_data_main(C); | /* identifiers */ | ||||
| Scene *scene = CTX_data_scene(C); | ot->name = "Free Noise"; | ||||
| ViewLayer *view_layer = CTX_data_view_layer(C); | ot->description = "Free Fluid Noise"; | ||||
| Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); | ot->idname = FLUID_JOB_FREE_NOISE; | ||||
| int i; | |||||
| FluidsimSettings *domainSettings; | |||||
| char debugStrBuffer[256]; | |||||
| int gridlevels = 0; | |||||
| const char *relbase = modifier_path_relbase(bmain, fsDomain); | |||||
| const char *strEnvName = "BLENDER_ELBEEMDEBUG"; // from blendercall.cpp | |||||
| const char *suffixConfigTmp = FLUID_SUFFIX_CONFIG_TMP; | |||||
| const char *suffixSurface = FLUID_SUFFIX_SURFACE; | |||||
| char targetDir[FILE_MAX]; // store & modify output settings | |||||
| char targetFile[FILE_MAX]; // temp. store filename from targetDir for access | |||||
| float domainMat[4][4]; | |||||
| float invDomMat[4][4]; | |||||
| int noFrames; | |||||
| int origFrame = scene->r.cfra; | |||||
| FluidAnimChannels *channels = MEM_callocN(sizeof(FluidAnimChannels), | |||||
| "fluid domain animation channels"); | |||||
| ListBase *fobjects = MEM_callocN(sizeof(ListBase), "fluid objects"); | |||||
| FluidsimModifierData *fluidmd = NULL; | |||||
| Mesh *mesh = NULL; | |||||
| FluidBakeJob *fb; | |||||
| elbeemSimulationSettings *fsset = MEM_callocN(sizeof(elbeemSimulationSettings), | |||||
| "Fluid sim settings"); | |||||
| fb = MEM_callocN(sizeof(FluidBakeJob), "fluid bake job"); | |||||
| if (BLI_getenv(strEnvName)) { | |||||
| int dlevel = atoi(BLI_getenv(strEnvName)); | |||||
| elbeemSetDebugLevel(dlevel); | |||||
| BLI_snprintf(debugStrBuffer, | |||||
| sizeof(debugStrBuffer), | |||||
| "fluidsimBake::msg: Debug messages activated due to envvar '%s'\n", | |||||
| strEnvName); | |||||
| elbeemDebugOut(debugStrBuffer); | |||||
| } | |||||
| /* Make sure it corresponds to startFrame setting | |||||
| * (old: noFrames = scene->r.efra - scene->r.sfra +1). */ | |||||
| noFrames = scene->r.efra - 0; | |||||
| if (noFrames <= 0) { | |||||
| BKE_report(reports, RPT_ERROR, "No frames to export (check your animation range settings)"); | |||||
| fluidbake_free_data(channels, fobjects, fsset, fb); | |||||
| return 0; | |||||
| } | |||||
| /* check scene for sane object/modifier settings */ | |||||
| if (!fluid_validate_scene(reports, view_layer, fsDomain)) { | |||||
| fluidbake_free_data(channels, fobjects, fsset, fb); | |||||
| return 0; | |||||
| } | |||||
| /* these both have to be valid, otherwise we wouldn't be here */ | |||||
| fluidmd = (FluidsimModifierData *)modifiers_findByType(fsDomain, eModifierType_Fluidsim); | |||||
| domainSettings = fluidmd->fss; | |||||
| mesh = fsDomain->data; | |||||
| domainSettings->bakeStart = 1; | |||||
| domainSettings->bakeEnd = scene->r.efra; | |||||
| // calculate bounding box | |||||
| fluid_get_bb(mesh->mvert, | |||||
| mesh->totvert, | |||||
| fsDomain->obmat, | |||||
| domainSettings->bbStart, | |||||
| domainSettings->bbSize); | |||||
| // reset last valid frame | |||||
| domainSettings->lastgoodframe = -1; | |||||
| /* delete old baked files */ | |||||
| fluidsim_delete_until_lastframe(domainSettings, relbase); | |||||
| /* rough check of settings... */ | |||||
| if (domainSettings->previewresxyz > domainSettings->resolutionxyz) { | |||||
| BLI_snprintf(debugStrBuffer, | |||||
| sizeof(debugStrBuffer), | |||||
| "fluidsimBake::warning - Preview (%d) >= Resolution (%d)... setting equal.\n", | |||||
| domainSettings->previewresxyz, | |||||
| domainSettings->resolutionxyz); | |||||
| elbeemDebugOut(debugStrBuffer); | |||||
| domainSettings->previewresxyz = domainSettings->resolutionxyz; | |||||
| } | |||||
| // set adaptive coarsening according to resolutionxyz | |||||
| // this should do as an approximation, with in/outflow | |||||
| // doing this more accurate would be overkill | |||||
| // perhaps add manual setting? | |||||
| if (domainSettings->maxRefine < 0) { | |||||
| if (domainSettings->resolutionxyz > 128) { | |||||
| gridlevels = 2; | |||||
| } | |||||
| else if (domainSettings->resolutionxyz > 64) { | |||||
| gridlevels = 1; | |||||
| } | |||||
| else { | |||||
| gridlevels = 0; | |||||
| } | |||||
| } | |||||
| else { | |||||
| gridlevels = domainSettings->maxRefine; | |||||
| } | |||||
| BLI_snprintf(debugStrBuffer, | |||||
| sizeof(debugStrBuffer), | |||||
| "fluidsimBake::msg: Baking %s, refine: %d\n", | |||||
| fsDomain->id.name, | |||||
| gridlevels); | |||||
| elbeemDebugOut(debugStrBuffer); | |||||
| /* ******** prepare output file paths ******** */ | |||||
| if (!fluid_init_filepaths(bmain, reports, domainSettings, fsDomain, targetDir, targetFile)) { | |||||
| fluidbake_free_data(channels, fobjects, fsset, fb); | |||||
| return false; | |||||
| } | |||||
| /* DG TODO: why using endframe and not "noFrames" here? | /* api callbacks */ | ||||
| * because "noFrames" is buggy too? (not using sfra) */ | ot->exec = fluid_free_exec; | ||||
| channels->length = scene->r.efra; | ot->poll = ED_operator_object_active_editable; | ||||
| channels->aniFrameTime = (double)((double)domainSettings->animEnd - | |||||
| (double)domainSettings->animStart) / | |||||
| (double)noFrames; | |||||
| /* ******** initialize and allocate animation channels ******** */ | |||||
| fluid_init_all_channels(C, depsgraph, fsDomain, domainSettings, channels, fobjects); | |||||
| /* reset to original current frame */ | |||||
| scene->r.cfra = origFrame; | |||||
| ED_update_for_newframe(CTX_data_main(C), CTX_data_depsgraph_pointer(C)); | |||||
| /* ******** init domain object's matrix ******** */ | |||||
| copy_m4_m4(domainMat, fsDomain->obmat); | |||||
| if (!invert_m4_m4(invDomMat, domainMat)) { | |||||
| BLI_snprintf( | |||||
| debugStrBuffer, sizeof(debugStrBuffer), "fluidsimBake::error - Invalid obj matrix?\n"); | |||||
| elbeemDebugOut(debugStrBuffer); | |||||
| BKE_report(reports, RPT_ERROR, "Invalid object matrix"); | |||||
| fluidbake_free_data(channels, fobjects, fsset, fb); | |||||
| return 0; | |||||
| } | |||||
| /* ******** start writing / exporting ******** */ | |||||
| // use .tmp, don't overwrite/delete original file | |||||
| BLI_join_dirfile(targetFile, sizeof(targetFile), targetDir, suffixConfigTmp); | |||||
| /* ******** export domain to elbeem ******** */ | |||||
| elbeemResetSettings(fsset); | |||||
| fsset->version = 1; | |||||
| fsset->threads = (domainSettings->threads == 0) ? BKE_scene_num_threads(scene) : | |||||
| domainSettings->threads; | |||||
| // setup global settings | |||||
| copy_v3_v3(fsset->geoStart, domainSettings->bbStart); | |||||
| copy_v3_v3(fsset->geoSize, domainSettings->bbSize); | |||||
| // simulate with 50^3 | |||||
| fsset->resolutionxyz = (int)domainSettings->resolutionxyz; | |||||
| fsset->previewresxyz = (int)domainSettings->previewresxyz; | |||||
| fsset->realsize = get_fluid_size_m(scene, fsDomain, domainSettings); | |||||
| fsset->viscosity = get_fluid_viscosity(domainSettings); | |||||
| get_fluid_gravity(fsset->gravity, scene, domainSettings); | |||||
| // simulate 5 frames, each 0.03 seconds, output to ./apitest_XXX.bobj.gz | |||||
| fsset->animStart = domainSettings->animStart; | |||||
| fsset->aniFrameTime = channels->aniFrameTime; | |||||
| fsset->noOfFrames = noFrames; // is otherwise subtracted in parser | |||||
| BLI_join_dirfile(targetFile, sizeof(targetFile), targetDir, suffixSurface); | |||||
| // defaults for compressibility and adaptive grids | |||||
| fsset->gstar = domainSettings->gstar; | |||||
| fsset->maxRefine = domainSettings->maxRefine; // check <-> gridlevels | |||||
| fsset->generateParticles = domainSettings->generateParticles; | |||||
| fsset->numTracerParticles = domainSettings->generateTracers; | |||||
| fsset->surfaceSmoothing = domainSettings->surfaceSmoothing; | |||||
| fsset->surfaceSubdivs = domainSettings->surfaceSubdivs; | |||||
| fsset->farFieldSize = domainSettings->farFieldSize; | |||||
| BLI_strncpy(fsset->outputPath, targetFile, sizeof(fsset->outputPath)); | |||||
| // domain channels | |||||
| fsset->channelSizeFrameTime = fsset->channelSizeViscosity = fsset->channelSizeGravity = | |||||
| channels->length; | |||||
| fsset->channelFrameTime = channels->DomainTime; | |||||
| fsset->channelViscosity = channels->DomainViscosity; | |||||
| fsset->channelGravity = channels->DomainGravity; | |||||
| fsset->runsimCallback = &runSimulationCallback; | |||||
| fsset->runsimUserData = fb; | |||||
| if (domainSettings->typeFlags & OB_FSBND_NOSLIP) { | |||||
| fsset->domainobsType = FLUIDSIM_OBSTACLE_NOSLIP; | |||||
| } | |||||
| else if (domainSettings->typeFlags & OB_FSBND_PARTSLIP) { | |||||
| fsset->domainobsType = FLUIDSIM_OBSTACLE_PARTSLIP; | |||||
| } | |||||
| else if (domainSettings->typeFlags & OB_FSBND_FREESLIP) { | |||||
| fsset->domainobsType = FLUIDSIM_OBSTACLE_FREESLIP; | |||||
| } | |||||
| fsset->domainobsPartslip = domainSettings->partSlipValue; | |||||
| /* use domainobsType also for surface generation flag (bit: >=64) */ | |||||
| if (domainSettings->typeFlags & OB_FSSG_NOOBS) { | |||||
| fsset->mFsSurfGenSetting = FLUIDSIM_FSSG_NOOBS; | |||||
| } | |||||
| else { | |||||
| fsset->mFsSurfGenSetting = 0; // "normal" mode | |||||
| } | } | ||||
| fsset->generateVertexVectors = (domainSettings->domainNovecgen == 0); | void FLUID_OT_bake_mesh(wmOperatorType *ot) | ||||
| // init blender domain transform matrix | |||||
| { | { | ||||
| int j; | /* identifiers */ | ||||
| for (i = 0; i < 4; i++) { | ot->name = "Bake Mesh"; | ||||
| for (j = 0; j < 4; j++) { | ot->description = "Bake Fluid Mesh"; | ||||
| fsset->surfaceTrafo[i * 4 + j] = invDomMat[j][i]; | ot->idname = FLUID_JOB_BAKE_MESH; | ||||
| } | |||||
| } | |||||
| } | |||||
| /* ******** init solver with settings ******** */ | |||||
| elbeemInit(); | |||||
| elbeemAddDomain(fsset); | |||||
| /* ******** export all fluid objects to elbeem ******** */ | |||||
| export_fluid_objects(depsgraph, fobjects, scene, channels->length); | |||||
| /* custom data for fluid bake job */ | |||||
| fb->settings = fsset; | |||||
| if (do_job) { | |||||
| wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), | |||||
| CTX_wm_window(C), | |||||
| scene, | |||||
| "Fluid Simulation", | |||||
| WM_JOB_PROGRESS, | |||||
| WM_JOB_TYPE_OBJECT_SIM_FLUID); | |||||
| /* setup job */ | |||||
| WM_jobs_customdata_set(wm_job, fb, fluidbake_free); | |||||
| WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME); | |||||
| WM_jobs_callbacks(wm_job, fluidbake_startjob, NULL, NULL, fluidbake_endjob); | |||||
| WM_jobs_start(CTX_wm_manager(C), wm_job); | |||||
| } | |||||
| else { | |||||
| short dummy_stop = 0, dummy_do_update = 0; | |||||
| float dummy_progress = 0.0f; | |||||
| /* blocking, use with exec() */ | /* api callbacks */ | ||||
| fluidbake_startjob((void *)fb, &dummy_stop, &dummy_do_update, &dummy_progress); | ot->exec = fluid_bake_exec; | ||||
| fluidbake_endjob((void *)fb); | ot->invoke = fluid_bake_invoke; | ||||
| fluidbake_free((void *)fb); | ot->modal = fluid_bake_modal; | ||||
| ot->poll = ED_operator_object_active_editable; | |||||
| } | } | ||||
| /* ******** free stored animation data ******** */ | void FLUID_OT_free_mesh(wmOperatorType *ot) | ||||
| fluidbake_free_data(channels, fobjects, NULL, NULL); | { | ||||
| /* identifiers */ | |||||
| ot->name = "Free Mesh"; | |||||
| ot->description = "Free Fluid Mesh"; | |||||
| ot->idname = FLUID_JOB_FREE_MESH; | |||||
| // elbeemFree(); | /* api callbacks */ | ||||
| return 1; | ot->exec = fluid_free_exec; | ||||
| ot->poll = ED_operator_object_active_editable; | |||||
| } | } | ||||
| static void UNUSED_FUNCTION(fluidsimFreeBake)(Object *UNUSED(ob)) | void FLUID_OT_bake_particles(wmOperatorType *ot) | ||||
| { | { | ||||
| /* not implemented yet */ | /* identifiers */ | ||||
| } | ot->name = "Bake Particles"; | ||||
| ot->description = "Bake Fluid Particles"; | |||||
| #else /* WITH_MOD_FLUID */ | ot->idname = FLUID_JOB_BAKE_PARTICLES; | ||||
| /* only compile dummy functions */ | /* api callbacks */ | ||||
| static int fluidsimBake(bContext *UNUSED(C), | ot->exec = fluid_bake_exec; | ||||
| ReportList *UNUSED(reports), | ot->invoke = fluid_bake_invoke; | ||||
| Object *UNUSED(ob), | ot->modal = fluid_bake_modal; | ||||
| short UNUSED(do_job)) | ot->poll = ED_operator_object_active_editable; | ||||
| { | |||||
| return 0; | |||||
| } | } | ||||
| #endif /* WITH_MOD_FLUID */ | void FLUID_OT_free_particles(wmOperatorType *ot) | ||||
| /***************************** Operators ******************************/ | |||||
| static int fluid_bake_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) | |||||
| { | { | ||||
| /* only one bake job at a time */ | /* identifiers */ | ||||
| if (WM_jobs_test(CTX_wm_manager(C), CTX_data_scene(C), WM_JOB_TYPE_OBJECT_SIM_FLUID)) { | ot->name = "Free Particles"; | ||||
| return OPERATOR_CANCELLED; | ot->description = "Free Fluid Particles"; | ||||
| } | ot->idname = FLUID_JOB_FREE_PARTICLES; | ||||
| if (!fluidsimBake(C, op->reports, ED_object_context(C), true)) { | /* api callbacks */ | ||||
| return OPERATOR_CANCELLED; | ot->exec = fluid_free_exec; | ||||
| ot->poll = ED_operator_object_active_editable; | |||||
| } | } | ||||
| return OPERATOR_FINISHED; | void FLUID_OT_bake_guiding(wmOperatorType *ot) | ||||
| { | |||||
| /* identifiers */ | |||||
| ot->name = "Bake Guiding"; | |||||
| ot->description = "Bake Fluid Guiding"; | |||||
| ot->idname = FLUID_JOB_BAKE_GUIDING; | |||||
| /* api callbacks */ | |||||
| ot->exec = fluid_bake_exec; | |||||
| ot->invoke = fluid_bake_invoke; | |||||
| ot->modal = fluid_bake_modal; | |||||
| ot->poll = ED_operator_object_active_editable; | |||||
| } | } | ||||
| static int fluid_bake_exec(bContext *C, wmOperator *op) | void FLUID_OT_free_guiding(wmOperatorType *ot) | ||||
| { | { | ||||
| if (!fluidsimBake(C, op->reports, CTX_data_active_object(C), false)) { | /* identifiers */ | ||||
| return OPERATOR_CANCELLED; | ot->name = "Free Guiding"; | ||||
| } | ot->description = "Free Fluid Guiding"; | ||||
| ot->idname = FLUID_JOB_FREE_GUIDING; | |||||
| return OPERATOR_FINISHED; | /* api callbacks */ | ||||
| ot->exec = fluid_free_exec; | |||||
| ot->poll = ED_operator_object_active_editable; | |||||
| } | } | ||||
| void FLUID_OT_bake(wmOperatorType *ot) | void FLUID_OT_pause_bake(wmOperatorType *ot) | ||||
| { | { | ||||
| /* identifiers */ | /* identifiers */ | ||||
| ot->name = "Fluid Simulation Bake"; | ot->name = "Pause Bake"; | ||||
| ot->description = "Bake fluid simulation"; | ot->description = "Pause Bake"; | ||||
| ot->idname = "FLUID_OT_bake"; | ot->idname = FLUID_JOB_BAKE_PAUSE; | ||||
| /* api callbacks */ | /* api callbacks */ | ||||
| ot->invoke = fluid_bake_invoke; | ot->exec = fluid_pause_exec; | ||||
| ot->exec = fluid_bake_exec; | |||||
| ot->poll = ED_operator_object_active_editable; | ot->poll = ED_operator_object_active_editable; | ||||
| } | } | ||||