Files
test2/source/blender/editors/object/object_bake_simulation.cc
Jacques Lucke ad169ba67a Geometry Nodes: support baking individual simulations
Previously, it was only possible to bake all simulations at once. This is great
for simple use-cases that, but in more complex setups one can have independent
simulations that should also be baked independently. This patch allows baking
individual simulation zones.

Furthermore, each simulation zone can now also have its own bake path and
simulation frame range. By default the simulation frame range is the scene frame
range, but it can also be customized on the scene or simulation zone level. The
bake path is generated based on the modifier bake path by default, but can be
set to another absolute or relative (to the .blend file) path.

The timeline drawing has been modified as well to be able to show more information
in the case when some simulations are baked and others are not. Instead of showing
a line for every simulation, it shows a condensed view of the important information
using at most two lines:
Is something baked? Is something valid or invalid? Also see #112232.

Pull Request: https://projects.blender.org/blender/blender/pulls/112723
2023-09-26 20:30:46 +02:00

939 lines
28 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <fstream>
#include <iomanip>
#include <random>
#include "BLI_endian_defines.h"
#include "BLI_endian_switch.h"
#include "BLI_fileops.hh"
#include "BLI_path_util.h"
#include "BLI_serialize.hh"
#include "BLI_string.h"
#include "BLI_string_utils.h"
#include "BLI_vector.hh"
#include "PIL_time.h"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_screen.hh"
#include "DNA_curves_types.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "DNA_pointcloud_types.h"
#include "DNA_windowmanager_types.h"
#include "BKE_bake_geometry_nodes_modifier.hh"
#include "BKE_context.h"
#include "BKE_curves.hh"
#include "BKE_global.h"
#include "BKE_instances.hh"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_mesh.hh"
#include "BKE_modifier.h"
#include "BKE_node_runtime.hh"
#include "BKE_object.h"
#include "BKE_pointcloud.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "MOD_nodes.hh"
#include "object_intern.h"
#include "WM_api.hh"
#include "UI_interface.hh"
namespace bake = blender::bke::bake;
namespace blender::ed::object::bake_simulation {
static bool calculate_to_frame_poll(bContext *C)
{
if (!ED_operator_object_active(C)) {
return false;
}
return true;
}
struct CalculateSimulationJob {
wmWindowManager *wm;
Main *bmain;
Depsgraph *depsgraph;
Scene *scene;
Vector<Object *> objects;
int start_frame;
int end_frame;
};
static void calculate_simulation_job_startjob(void *customdata,
bool *stop,
bool *do_update,
float *progress)
{
CalculateSimulationJob &job = *static_cast<CalculateSimulationJob *>(customdata);
G.is_rendering = true;
G.is_break = false;
WM_set_locked_interface(job.wm, true);
Vector<Object *> objects_to_calc;
for (Object *object : job.objects) {
if (!BKE_id_is_editable(job.bmain, &object->id)) {
continue;
}
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type != eModifierType_Nodes) {
continue;
}
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
if (!nmd->runtime->cache) {
continue;
}
for (auto item : nmd->runtime->cache->cache_by_id.items()) {
if (item.value->cache_status != bake::CacheStatus::Baked) {
item.value->reset();
}
}
}
objects_to_calc.append(object);
}
*progress = 0.0f;
*do_update = true;
const float frame_step_size = 1.0f;
const float progress_per_frame = 1.0f /
(float(job.end_frame - job.start_frame + 1) / frame_step_size);
const int old_frame = job.scene->r.cfra;
for (float frame_f = job.start_frame; frame_f <= job.end_frame; frame_f += frame_step_size) {
const SubFrame frame{frame_f};
if (G.is_break || (stop != nullptr && *stop)) {
break;
}
job.scene->r.cfra = frame.frame();
job.scene->r.subframe = frame.subframe();
BKE_scene_graph_update_for_newframe(job.depsgraph);
*progress += progress_per_frame;
*do_update = true;
}
job.scene->r.cfra = old_frame;
DEG_time_tag_update(job.bmain);
*progress = 1.0f;
*do_update = true;
}
static void calculate_simulation_job_endjob(void *customdata)
{
CalculateSimulationJob &job = *static_cast<CalculateSimulationJob *>(customdata);
WM_set_locked_interface(job.wm, false);
G.is_rendering = false;
WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, nullptr);
}
static int calculate_to_frame_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
wmWindowManager *wm = CTX_wm_manager(C);
Scene *scene = CTX_data_scene(C);
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
Main *bmain = CTX_data_main(C);
CalculateSimulationJob *job = MEM_new<CalculateSimulationJob>(__func__);
job->wm = wm;
job->bmain = bmain;
job->depsgraph = depsgraph;
job->scene = scene;
job->start_frame = scene->r.sfra;
job->end_frame = scene->r.cfra;
if (RNA_boolean_get(op->ptr, "selected")) {
CTX_DATA_BEGIN (C, Object *, object, selected_objects) {
job->objects.append(object);
}
CTX_DATA_END;
}
else {
if (Object *object = CTX_data_active_object(C)) {
job->objects.append(object);
}
}
wmJob *wm_job = WM_jobs_get(wm,
CTX_wm_window(C),
CTX_data_scene(C),
"Calculate Simulation",
WM_JOB_PROGRESS,
WM_JOB_TYPE_CALCULATE_SIMULATION_NODES);
WM_jobs_customdata_set(
wm_job, job, [](void *job) { MEM_delete(static_cast<CalculateSimulationJob *>(job)); });
WM_jobs_timer(wm_job, 0.1, NC_OBJECT | ND_MODIFIER, NC_OBJECT | ND_MODIFIER);
WM_jobs_callbacks(wm_job,
calculate_simulation_job_startjob,
nullptr,
nullptr,
calculate_simulation_job_endjob);
WM_jobs_start(CTX_wm_manager(C), wm_job);
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
static int calculate_to_frame_modal(bContext *C, wmOperator * /*op*/, const wmEvent * /*event*/)
{
if (!WM_jobs_test(CTX_wm_manager(C), CTX_data_scene(C), WM_JOB_TYPE_CALCULATE_SIMULATION_NODES))
{
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
}
return OPERATOR_PASS_THROUGH;
}
static bool bake_simulation_poll(bContext *C)
{
if (!ED_operator_object_active(C)) {
return false;
}
Main *bmain = CTX_data_main(C);
const StringRefNull path = BKE_main_blendfile_path(bmain);
if (path.is_empty()) {
CTX_wm_operator_poll_msg_set(C, "File has to be saved");
return false;
}
return true;
}
struct NodeBakeData {
int id;
bake::BakePath path;
int frame_start;
int frame_end;
std::unique_ptr<bake::BlobSharing> blob_sharing;
};
struct ModifierBakeData {
NodesModifierData *nmd;
Vector<NodeBakeData> nodes;
};
struct ObjectBakeData {
Object *object;
Vector<ModifierBakeData> modifiers;
};
struct BakeSimulationJob {
wmWindowManager *wm;
Main *bmain;
Depsgraph *depsgraph;
Scene *scene;
Vector<ObjectBakeData> objects;
};
static void bake_simulation_job_startjob(void *customdata,
bool *stop,
bool *do_update,
float *progress)
{
BakeSimulationJob &job = *static_cast<BakeSimulationJob *>(customdata);
G.is_rendering = true;
G.is_break = false;
WM_set_locked_interface(job.wm, true);
int global_bake_start_frame = INT32_MAX;
int global_bake_end_frame = INT32_MIN;
for (ObjectBakeData &object_bake : job.objects) {
for (ModifierBakeData &modifier_bake : object_bake.modifiers) {
for (NodeBakeData &node_bake : modifier_bake.nodes) {
global_bake_start_frame = std::min(global_bake_start_frame, node_bake.frame_start);
global_bake_end_frame = std::max(global_bake_end_frame, node_bake.frame_end);
}
}
}
*progress = 0.0f;
*do_update = true;
const int frames_to_bake = global_bake_end_frame - global_bake_start_frame + 1;
const float frame_step_size = 1.0f;
const float progress_per_frame = frame_step_size / frames_to_bake;
const int old_frame = job.scene->r.cfra;
for (float frame_f = global_bake_start_frame; frame_f <= global_bake_end_frame;
frame_f += frame_step_size)
{
const SubFrame frame{frame_f};
if (G.is_break || (stop != nullptr && *stop)) {
break;
}
job.scene->r.cfra = frame.frame();
job.scene->r.subframe = frame.subframe();
BKE_scene_graph_update_for_newframe(job.depsgraph);
const std::string frame_file_name = bake::frame_to_file_name(frame);
for (ObjectBakeData &object_bake_data : job.objects) {
for (ModifierBakeData &modifier_bake_data : object_bake_data.modifiers) {
NodesModifierData &nmd = *modifier_bake_data.nmd;
const bake::ModifierCache &modifier_cache = *nmd.runtime->cache;
for (NodeBakeData &node_bake_data : modifier_bake_data.nodes) {
if (!modifier_cache.cache_by_id.contains(node_bake_data.id)) {
continue;
}
const bake::NodeCache &node_cache = *modifier_cache.cache_by_id.lookup(
node_bake_data.id);
if (node_cache.frame_caches.is_empty()) {
continue;
}
const bake::FrameCache &frame_cache = *node_cache.frame_caches.last();
if (frame_cache.frame != frame) {
continue;
}
const bake::BakePath path = node_bake_data.path;
const std::string blob_file_name = frame_file_name + ".blob";
char blob_path[FILE_MAX];
BLI_path_join(
blob_path, sizeof(blob_path), path.blobs_dir.c_str(), blob_file_name.c_str());
char meta_path[FILE_MAX];
BLI_path_join(meta_path,
sizeof(meta_path),
path.meta_dir.c_str(),
(frame_file_name + ".json").c_str());
BLI_file_ensure_parent_dir_exists(meta_path);
BLI_file_ensure_parent_dir_exists(blob_path);
fstream blob_file{blob_path, std::ios::out | std::ios::binary};
bake::DiskBlobWriter blob_writer{blob_file_name, blob_file, 0};
fstream meta_file{meta_path, std::ios::out};
bake::serialize_bake(
frame_cache.state, blob_writer, *node_bake_data.blob_sharing, meta_file);
}
}
}
*progress += progress_per_frame;
*do_update = true;
}
for (ObjectBakeData &object_bake_data : job.objects) {
for (ModifierBakeData &modifier_bake_data : object_bake_data.modifiers) {
NodesModifierData &nmd = *modifier_bake_data.nmd;
for (NodeBakeData &node_bake_data : modifier_bake_data.nodes) {
if (std::unique_ptr<bake::NodeCache> *node_cache_ptr =
nmd.runtime->cache->cache_by_id.lookup_ptr(node_bake_data.id))
{
bake::NodeCache &node_cache = **node_cache_ptr;
if (!node_cache.frame_caches.is_empty()) {
/* Tag the caches as being baked so that they are not changed anymore. */
node_cache.cache_status = bake::CacheStatus::Baked;
}
}
}
}
DEG_id_tag_update(&object_bake_data.object->id, ID_RECALC_GEOMETRY);
}
job.scene->r.cfra = old_frame;
DEG_time_tag_update(job.bmain);
*progress = 1.0f;
*do_update = true;
}
static void bake_simulation_job_endjob(void *customdata)
{
BakeSimulationJob &job = *static_cast<BakeSimulationJob *>(customdata);
WM_set_locked_interface(job.wm, false);
G.is_rendering = false;
WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, nullptr);
}
static int start_bake_job(bContext *C, Vector<ObjectBakeData> objects_to_bake, wmOperator *op)
{
BakeSimulationJob *job = MEM_new<BakeSimulationJob>(__func__);
job->wm = CTX_wm_manager(C);
job->bmain = CTX_data_main(C);
job->depsgraph = CTX_data_depsgraph_pointer(C);
job->scene = CTX_data_scene(C);
job->objects = std::move(objects_to_bake);
wmJob *wm_job = WM_jobs_get(job->wm,
CTX_wm_window(C),
job->scene,
"Bake Simulation Nodes",
WM_JOB_PROGRESS,
WM_JOB_TYPE_BAKE_SIMULATION_NODES);
WM_jobs_customdata_set(
wm_job, job, [](void *job) { MEM_delete(static_cast<BakeSimulationJob *>(job)); });
WM_jobs_timer(wm_job, 0.1, NC_OBJECT | ND_MODIFIER, NC_OBJECT | ND_MODIFIER);
WM_jobs_callbacks(
wm_job, bake_simulation_job_startjob, nullptr, nullptr, bake_simulation_job_endjob);
WM_jobs_start(CTX_wm_manager(C), wm_job);
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
static Vector<ObjectBakeData> collect_nodes_to_bake(Main &bmain,
Scene &scene,
const Span<Object *> objects)
{
Vector<ObjectBakeData> objects_to_bake;
for (Object *object : objects) {
if (!BKE_id_is_editable(&bmain, &object->id)) {
continue;
}
ObjectBakeData bake_data;
bake_data.object = object;
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type != eModifierType_Nodes) {
continue;
}
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
if (!nmd->node_group) {
continue;
}
if (!nmd->runtime->cache) {
continue;
}
ModifierBakeData modifier_bake_data;
modifier_bake_data.nmd = nmd;
for (auto item : nmd->runtime->cache->cache_by_id.items()) {
item.value->reset();
}
for (const bNestedNodeRef &nested_node_ref : nmd->node_group->nested_node_refs_span()) {
NodeBakeData node_bake_data;
node_bake_data.id = nested_node_ref.id;
node_bake_data.blob_sharing = std::make_unique<bake::BlobSharing>();
std::optional<bake::BakePath> path = bake::get_node_bake_path(
bmain, *object, *nmd, nested_node_ref.id);
if (!path) {
continue;
}
std::optional<IndexRange> frame_range = bake::get_node_bake_frame_range(
scene, *object, *nmd, nested_node_ref.id);
if (!frame_range) {
continue;
}
node_bake_data.path = std::move(*path);
node_bake_data.frame_start = frame_range->first();
node_bake_data.frame_end = frame_range->last();
modifier_bake_data.nodes.append(std::move(node_bake_data));
}
if (modifier_bake_data.nodes.is_empty()) {
continue;
}
bake_data.modifiers.append(std::move(modifier_bake_data));
}
if (bake_data.modifiers.is_empty()) {
continue;
}
objects_to_bake.append(std::move(bake_data));
}
return objects_to_bake;
}
static int bake_simulation_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
Main *bmain = CTX_data_main(C);
Vector<Object *> objects;
if (RNA_boolean_get(op->ptr, "selected")) {
CTX_DATA_BEGIN (C, Object *, object, selected_objects) {
objects.append(object);
}
CTX_DATA_END;
}
else {
if (Object *object = CTX_data_active_object(C)) {
objects.append(object);
}
}
Vector<ObjectBakeData> objects_to_bake = collect_nodes_to_bake(*bmain, *scene, objects);
return start_bake_job(C, std::move(objects_to_bake), op);
}
struct PathStringHash {
uint64_t operator()(const StringRef s) const
{
/* Normalize the paths so we can compare them. */
DynamicStackBuffer<256> norm_buf(s.size() + 1, 8);
memcpy(norm_buf.buffer(), s.data(), s.size() + 1);
char *norm = static_cast<char *>(norm_buf.buffer());
BLI_path_slash_native(norm);
/* Strip ending slash. */
BLI_path_slash_rstrip(norm);
BLI_path_normalize(norm);
return get_default_hash(norm);
}
};
struct PathStringEquality {
bool operator()(const StringRef a, const StringRef b) const
{
return BLI_path_cmp_normalized(a.data(), b.data()) == 0;
}
};
static bool bake_directory_has_data(const StringRefNull absolute_bake_dir)
{
char meta_dir[FILE_MAX];
BLI_path_join(meta_dir, sizeof(meta_dir), absolute_bake_dir.c_str(), "meta");
char blobs_dir[FILE_MAX];
BLI_path_join(blobs_dir, sizeof(blobs_dir), absolute_bake_dir.c_str(), "blobs");
if (!BLI_is_dir(meta_dir) || !BLI_is_dir(blobs_dir)) {
return false;
}
return true;
}
static void bake_simulation_validate_paths(bContext *C,
wmOperator *op,
const Span<Object *> objects)
{
Main *bmain = CTX_data_main(C);
for (Object *object : objects) {
if (!BKE_id_is_editable(bmain, &object->id)) {
continue;
}
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type != eModifierType_Nodes) {
continue;
}
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
if (StringRef(nmd->simulation_bake_directory).is_empty()) {
BKE_reportf(op->reports,
RPT_INFO,
"Bake directory of object %s, modifier %s is empty, setting default path",
object->id.name + 2,
md->name);
nmd->simulation_bake_directory = BLI_strdup(
bake::get_default_modifier_bake_directory(*bmain, *object, *nmd).c_str());
}
}
}
}
/* Map for counting path references. */
using PathUsersMap = Map<std::string,
int,
default_inline_buffer_capacity(sizeof(std::string)),
DefaultProbingStrategy,
PathStringHash,
PathStringEquality>;
static PathUsersMap bake_simulation_get_path_users(bContext *C, const Span<Object *> objects)
{
Main *bmain = CTX_data_main(C);
PathUsersMap path_users;
for (const Object *object : objects) {
const char *base_path = ID_BLEND_PATH(bmain, &object->id);
LISTBASE_FOREACH (const ModifierData *, md, &object->modifiers) {
if (md->type != eModifierType_Nodes) {
continue;
}
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
if (StringRef(nmd->simulation_bake_directory).is_empty()) {
continue;
}
char absolute_bake_dir[FILE_MAX];
STRNCPY(absolute_bake_dir, nmd->simulation_bake_directory);
BLI_path_abs(absolute_bake_dir, base_path);
path_users.add_or_modify(
absolute_bake_dir, [](int *value) { *value = 1; }, [](int *value) { ++(*value); });
}
}
return path_users;
}
static int bake_simulation_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
Vector<Object *> objects;
if (RNA_boolean_get(op->ptr, "selected")) {
CTX_DATA_BEGIN (C, Object *, object, selected_objects) {
objects.append(object);
}
CTX_DATA_END;
}
else {
if (Object *object = CTX_data_active_object(C)) {
objects.append(object);
}
}
/* Set empty paths to default. */
bake_simulation_validate_paths(C, op, objects);
PathUsersMap path_users = bake_simulation_get_path_users(C, objects);
bool has_path_conflict = false;
bool has_existing_bake_data = false;
for (const auto &item : path_users.items()) {
/* Check if multiple caches are writing to the same bake directory. */
if (item.value > 1) {
BKE_reportf(op->reports,
RPT_ERROR,
"Path conflict: %d caches set to path %s",
item.value,
item.key.data());
has_path_conflict = true;
}
/* Check if path exists and contains bake data already. */
if (bake_directory_has_data(item.key.data())) {
has_existing_bake_data = true;
}
}
if (has_path_conflict) {
UI_popup_menu_reports(C, op->reports);
return OPERATOR_CANCELLED;
}
if (has_existing_bake_data) {
return WM_operator_confirm_message(C, op, "Overwrite existing bake data");
}
return bake_simulation_exec(C, op);
}
static int bake_simulation_modal(bContext *C, wmOperator * /*op*/, const wmEvent * /*event*/)
{
if (!WM_jobs_test(CTX_wm_manager(C), CTX_data_scene(C), WM_JOB_TYPE_BAKE_SIMULATION_NODES)) {
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
}
return OPERATOR_PASS_THROUGH;
}
static void try_delete_bake(
bContext *C, Object &object, NodesModifierData &nmd, const int bake_id, ReportList *reports)
{
Main *bmain = CTX_data_main(C);
if (!nmd.runtime->cache) {
return;
}
bake::ModifierCache &modifier_cache = *nmd.runtime->cache;
std::lock_guard lock{modifier_cache.mutex};
if (!modifier_cache.cache_by_id.contains(bake_id)) {
return;
}
bake::NodeCache &node_cache = *modifier_cache.cache_by_id.lookup(bake_id);
node_cache.reset();
const std::optional<bake::BakePath> bake_path = bake::get_node_bake_path(
*bmain, object, nmd, bake_id);
if (!bake_path) {
return;
}
const char *meta_dir = bake_path->meta_dir.c_str();
if (BLI_exists(meta_dir)) {
if (BLI_delete(meta_dir, true, true)) {
BKE_reportf(reports, RPT_ERROR, "Failed to remove metadata directory %s", meta_dir);
}
}
const char *blobs_dir = bake_path->blobs_dir.c_str();
if (BLI_exists(blobs_dir)) {
if (BLI_delete(blobs_dir, true, true)) {
BKE_reportf(reports, RPT_ERROR, "Failed to remove blobs directory %s", blobs_dir);
}
}
if (bake_path->bake_dir.has_value()) {
const char *zone_bake_dir = bake_path->bake_dir->c_str();
/* Try to delete zone bake directory if it is empty. */
BLI_delete(zone_bake_dir, true, false);
}
if (const std::optional<std::string> modifier_bake_dir = bake::get_modifier_bake_path(
*bmain, object, nmd))
{
/* Try to delete modifier bake directory if it is empty. */
BLI_delete(modifier_bake_dir->c_str(), true, false);
}
}
static int delete_baked_simulation_exec(bContext *C, wmOperator *op)
{
Vector<Object *> objects;
if (RNA_boolean_get(op->ptr, "selected")) {
CTX_DATA_BEGIN (C, Object *, object, selected_objects) {
objects.append(object);
}
CTX_DATA_END;
}
else {
if (Object *object = CTX_data_active_object(C)) {
objects.append(object);
}
}
if (objects.is_empty()) {
return OPERATOR_CANCELLED;
}
for (Object *object : objects) {
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Nodes) {
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) {
try_delete_bake(C, *object, *nmd, bake.id, op->reports);
}
}
}
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
}
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, nullptr);
return OPERATOR_FINISHED;
}
static int bake_single_simulation_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Object *object = reinterpret_cast<Object *>(
WM_operator_properties_id_lookup_from_name_or_session_uuid(bmain, op->ptr, ID_OB));
if (object == nullptr) {
return OPERATOR_CANCELLED;
}
char *modifier_name = RNA_string_get_alloc(op->ptr, "modifier_name", nullptr, 0, nullptr);
if (modifier_name == nullptr) {
return OPERATOR_CANCELLED;
}
BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(modifier_name); });
ModifierData *md = BKE_modifiers_findby_name(object, modifier_name);
if (md == nullptr) {
return OPERATOR_CANCELLED;
}
NodesModifierData &nmd = *reinterpret_cast<NodesModifierData *>(md);
if (StringRef(nmd.simulation_bake_directory).is_empty()) {
const std::string directory = bake::get_default_modifier_bake_directory(*bmain, *object, nmd);
nmd.simulation_bake_directory = BLI_strdup(directory.c_str());
}
const int bake_id = RNA_int_get(op->ptr, "bake_id");
const std::optional<bake::BakePath> bake_path = bake::get_node_bake_path(
*bmain, *object, nmd, bake_id);
if (!bake_path.has_value()) {
return OPERATOR_CANCELLED;
}
const std::optional<IndexRange> frame_range = bake::get_node_bake_frame_range(
*scene, *object, nmd, bake_id);
if (!frame_range.has_value()) {
return OPERATOR_CANCELLED;
}
if (frame_range->is_empty()) {
return OPERATOR_CANCELLED;
}
NodeBakeData node_bake_data;
node_bake_data.id = bake_id;
node_bake_data.path = std::move(*bake_path);
node_bake_data.frame_start = frame_range->first();
node_bake_data.frame_end = frame_range->last();
node_bake_data.blob_sharing = std::make_unique<bake::BlobSharing>();
ModifierBakeData modifier_bake_data;
modifier_bake_data.nmd = &nmd;
modifier_bake_data.nodes.append(std::move(node_bake_data));
ObjectBakeData object_bake_data;
object_bake_data.object = object;
object_bake_data.modifiers.append(std::move(modifier_bake_data));
Vector<ObjectBakeData> objects_to_bake;
objects_to_bake.append(std::move(object_bake_data));
return start_bake_job(C, std::move(objects_to_bake), op);
}
static int bake_single_simulation_modal(bContext *C,
wmOperator * /*op*/,
const wmEvent * /*event*/)
{
if (!WM_jobs_test(CTX_wm_manager(C), CTX_data_scene(C), WM_JOB_TYPE_BAKE_SIMULATION_NODES)) {
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
}
return OPERATOR_PASS_THROUGH;
}
static int delete_baked_simulation_single_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Object *object = reinterpret_cast<Object *>(
WM_operator_properties_id_lookup_from_name_or_session_uuid(bmain, op->ptr, ID_OB));
if (object == nullptr) {
return OPERATOR_CANCELLED;
}
char *modifier_name = RNA_string_get_alloc(op->ptr, "modifier_name", nullptr, 0, nullptr);
if (modifier_name == nullptr) {
return OPERATOR_CANCELLED;
}
BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(modifier_name); });
ModifierData *md = BKE_modifiers_findby_name(object, modifier_name);
if (md == nullptr) {
return OPERATOR_CANCELLED;
}
NodesModifierData &nmd = *reinterpret_cast<NodesModifierData *>(md);
const int bake_id = RNA_int_get(op->ptr, "bake_id");
try_delete_bake(C, *object, nmd, bake_id, op->reports);
return OPERATOR_FINISHED;
}
} // namespace blender::ed::object::bake_simulation
void OBJECT_OT_simulation_nodes_cache_calculate_to_frame(wmOperatorType *ot)
{
using namespace blender::ed::object::bake_simulation;
ot->name = "Calculate Simulation to Frame";
ot->description =
"Calculate simulations in geometry nodes modifiers from the start to current frame";
ot->idname = __func__;
ot->invoke = calculate_to_frame_invoke;
ot->modal = calculate_to_frame_modal;
ot->poll = calculate_to_frame_poll;
RNA_def_boolean(ot->srna,
"selected",
false,
"Selected",
"Calculate all selected objects instead of just the active object");
}
void OBJECT_OT_simulation_nodes_cache_bake(wmOperatorType *ot)
{
using namespace blender::ed::object::bake_simulation;
ot->name = "Bake Simulation";
ot->description = "Bake simulations in geometry nodes modifiers";
ot->idname = __func__;
ot->exec = bake_simulation_exec;
ot->invoke = bake_simulation_invoke;
ot->modal = bake_simulation_modal;
ot->poll = bake_simulation_poll;
RNA_def_boolean(ot->srna, "selected", false, "Selected", "Bake cache on all selected objects");
}
void OBJECT_OT_simulation_nodes_cache_delete(wmOperatorType *ot)
{
using namespace blender::ed::object::bake_simulation;
ot->name = "Delete Cached Simulation";
ot->description = "Delete cached/baked simulations in geometry nodes modifiers";
ot->idname = __func__;
ot->exec = delete_baked_simulation_exec;
ot->poll = ED_operator_object_active;
RNA_def_boolean(ot->srna, "selected", false, "Selected", "Delete cache on all selected objects");
}
void OBJECT_OT_simulation_nodes_cache_bake_single(wmOperatorType *ot)
{
using namespace blender::ed::object::bake_simulation;
ot->name = "Bake Single Simulation Zone";
ot->description = "Bake a single simulation zone";
ot->idname = "OBJECT_OT_simulation_nodes_cache_bake_single";
ot->exec = bake_single_simulation_exec;
ot->modal = bake_single_simulation_modal;
WM_operator_properties_id_lookup(ot, false);
RNA_def_string(ot->srna,
"modifier_name",
nullptr,
0,
"Modifier Name",
"Name of the modifier that contains the node to bake");
RNA_def_int(ot->srna,
"bake_id",
0,
0,
INT32_MAX,
"Bake ID",
"Nested node id of the node to bake",
0,
INT32_MAX);
}
void OBJECT_OT_simulation_nodes_cache_delete_single(wmOperatorType *ot)
{
using namespace blender::ed::object::bake_simulation;
ot->name = "Delete Single Cached Simulation";
ot->description = "Delete simulation data of a single simulation zone";
ot->idname = "OBJECT_OT_simulation_nodes_cache_delete_single";
ot->exec = delete_baked_simulation_single_exec;
WM_operator_properties_id_lookup(ot, false);
RNA_def_string(ot->srna,
"modifier_name",
nullptr,
0,
"Modifier Name",
"Name of the modifier that contains the node");
RNA_def_int(ot->srna,
"bake_id",
0,
0,
INT32_MAX,
"Bake ID",
"Nested node id of the bake to delete",
0,
INT32_MAX);
}