From ad169ba67a5a5087f1b6628e1529be1fa8e567b8 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 26 Sep 2023 20:30:46 +0200 Subject: [PATCH] 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 --- scripts/startup/bl_ui/properties_scene.py | 19 + .../BKE_bake_geometry_nodes_modifier.hh | 4 + .../intern/bake_geometry_nodes_modifier.cc | 47 +- .../blender/blenkernel/intern/node_runtime.cc | 21 + .../blenloader/intern/versioning_400.cc | 5 + source/blender/editors/include/ED_node.hh | 5 + .../editors/object/object_bake_simulation.cc | 463 +++++++++++++----- source/blender/editors/object/object_intern.h | 2 + source/blender/editors/object/object_ops.cc | 2 + .../editors/space_action/action_draw.cc | 157 ++++-- .../blender/editors/space_node/space_node.cc | 46 ++ source/blender/makesdna/DNA_modifier_types.h | 34 +- source/blender/makesdna/DNA_node_types.h | 1 + source/blender/makesdna/DNA_scene_defaults.h | 2 + source/blender/makesdna/DNA_scene_types.h | 8 + .../blender/makesrna/intern/rna_modifier.cc | 51 ++ source/blender/makesrna/intern/rna_scene.cc | 19 + source/blender/modifiers/intern/MOD_nodes.cc | 216 +++++++- source/blender/nodes/geometry/CMakeLists.txt | 1 + .../nodes/node_geo_simulation_output.cc | 152 ++++++ 20 files changed, 1054 insertions(+), 201 deletions(-) diff --git a/scripts/startup/bl_ui/properties_scene.py b/scripts/startup/bl_ui/properties_scene.py index f2a4fa4656c..2a980812d7a 100644 --- a/scripts/startup/bl_ui/properties_scene.py +++ b/scripts/startup/bl_ui/properties_scene.py @@ -312,6 +312,24 @@ class SCENE_PT_physics(SceneButtonsPanel, Panel): layout.prop(scene, "gravity") +class SCENE_PT_simulation(SceneButtonsPanel, Panel): + bl_label = "Simulation" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + + scene = context.scene + + col = layout.column() + col.prop(scene, "use_custom_simulation_range", text="Simulation Range") + subcol = col.column(align=True) + subcol.active = scene.use_custom_simulation_range + subcol.prop(scene, "simulation_frame_start", text="Start") + subcol.prop(scene, "simulation_frame_end", text="End") + + class SCENE_PT_rigid_body_world(SceneButtonsPanel, Panel): bl_label = "Rigid Body World" bl_options = {'DEFAULT_CLOSED'} @@ -409,6 +427,7 @@ classes = ( SCENE_PT_scene, SCENE_PT_unit, SCENE_PT_physics, + SCENE_PT_simulation, SCENE_PT_keying_sets, SCENE_PT_keying_set_paths, SCENE_PT_keyframing_settings, diff --git a/source/blender/blenkernel/BKE_bake_geometry_nodes_modifier.hh b/source/blender/blenkernel/BKE_bake_geometry_nodes_modifier.hh index 11cb6806cff..1ee3f841476 100644 --- a/source/blender/blenkernel/BKE_bake_geometry_nodes_modifier.hh +++ b/source/blender/blenkernel/BKE_bake_geometry_nodes_modifier.hh @@ -84,6 +84,10 @@ std::optional get_node_bake_path(const Main &bmain, const Object &object, const NodesModifierData &nmd, int node_id); +std::optional get_node_bake_frame_range(const Scene &scene, + const Object &object, + const NodesModifierData &nmd, + int node_id); std::optional get_modifier_bake_path(const Main &bmain, const Object &object, const NodesModifierData &nmd); diff --git a/source/blender/blenkernel/intern/bake_geometry_nodes_modifier.cc b/source/blender/blenkernel/intern/bake_geometry_nodes_modifier.cc index 2b4438cc14f..fe53d78abff 100644 --- a/source/blender/blenkernel/intern/bake_geometry_nodes_modifier.cc +++ b/source/blender/blenkernel/intern/bake_geometry_nodes_modifier.cc @@ -71,17 +71,52 @@ std::optional get_node_bake_path(const Main &bmain, const NodesModifierData &nmd, int node_id) { + const NodesModifierBake *bake = nmd.find_bake(node_id); + if (bake == nullptr) { + return std::nullopt; + } + if (bake->flag & NODES_MODIFIER_BAKE_CUSTOM_PATH) { + if (StringRef(bake->directory).is_empty()) { + return std::nullopt; + } + const char *base_path = ID_BLEND_PATH(&bmain, &object.id); + char absolute_bake_dir[FILE_MAX]; + STRNCPY(absolute_bake_dir, bake->directory); + BLI_path_abs(absolute_bake_dir, base_path); + return bake::BakePath::from_single_root(absolute_bake_dir); + } const std::optional modifier_bake_path = get_modifier_bake_path(bmain, object, nmd); if (!modifier_bake_path) { return std::nullopt; } + char bake_dir[FILE_MAX]; + BLI_path_join( + bake_dir, sizeof(bake_dir), modifier_bake_path->c_str(), std::to_string(node_id).c_str()); + return bake::BakePath::from_single_root(bake_dir); +} - char zone_bake_dir[FILE_MAX]; - BLI_path_join(zone_bake_dir, - sizeof(zone_bake_dir), - modifier_bake_path->c_str(), - std::to_string(node_id).c_str()); - return bake::BakePath::from_single_root(zone_bake_dir); +static IndexRange fix_frame_range(const int start, const int end) +{ + const int num_frames = std::max(1, end - start + 1); + return IndexRange(start, num_frames); +} + +std::optional get_node_bake_frame_range(const Scene &scene, + const Object & /*object*/, + const NodesModifierData &nmd, + int node_id) +{ + const NodesModifierBake *bake = nmd.find_bake(node_id); + if (bake == nullptr) { + return std::nullopt; + } + if (bake->flag & NODES_MODIFIER_BAKE_CUSTOM_SIMULATION_FRAME_RANGE) { + return fix_frame_range(bake->frame_start, bake->frame_end); + } + if (scene.flag & SCE_CUSTOM_SIMULATION_RANGE) { + return fix_frame_range(scene.simulation_frame_start, scene.simulation_frame_end); + } + return fix_frame_range(scene.r.sfra, scene.r.efra); } /** diff --git a/source/blender/blenkernel/intern/node_runtime.cc b/source/blender/blenkernel/intern/node_runtime.cc index 0b7e67fbdbb..b7c474f07d2 100644 --- a/source/blender/blenkernel/intern/node_runtime.cc +++ b/source/blender/blenkernel/intern/node_runtime.cc @@ -607,3 +607,24 @@ bool bNodeTree::node_id_path_from_nested_node_ref(const int32_t nested_node_id, } return group->node_id_path_from_nested_node_ref(ref->path.id_in_node, r_node_ids); } + +const bNode *bNodeTree::find_nested_node(const int32_t nested_node_id) const +{ + const bNestedNodeRef *ref = this->find_nested_node_ref(nested_node_id); + if (ref == nullptr) { + return nullptr; + } + const int32_t node_id = ref->path.node_id; + const bNode *node = this->node_by_id(node_id); + if (node == nullptr) { + return nullptr; + } + if (!node->is_group()) { + return node; + } + const bNodeTree *group = reinterpret_cast(node->id); + if (group == nullptr) { + return nullptr; + } + return group->find_nested_node(ref->path.id_in_node); +} diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 479be23f053..9936fb7a1af 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -1455,5 +1455,10 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) */ { /* Keep this block, even when empty. */ + + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + scene->simulation_frame_start = scene->r.sfra; + scene->simulation_frame_end = scene->r.efra; + } } } diff --git a/source/blender/editors/include/ED_node.hh b/source/blender/editors/include/ED_node.hh index 90ce1ac8903..5bfd03cef8f 100644 --- a/source/blender/editors/include/ED_node.hh +++ b/source/blender/editors/include/ED_node.hh @@ -38,6 +38,11 @@ void node_insert_on_link_flags_clear(bNodeTree &node_tree); */ void node_socket_draw(bNodeSocket *sock, const rcti *rect, const float color[4], float scale); +/** + * Find the nested node id of a currently visible node in the root tree. + */ +std::optional find_nested_node_id_in_root(const SpaceNode &snode, const bNode &node); + struct ObjectAndModifier { const Object *object; const NodesModifierData *nmd; diff --git a/source/blender/editors/object/object_bake_simulation.cc b/source/blender/editors/object/object_bake_simulation.cc index 768985b60bb..f38dab7d386 100644 --- a/source/blender/editors/object/object_bake_simulation.cc +++ b/source/blender/editors/object/object_bake_simulation.cc @@ -38,6 +38,7 @@ #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" @@ -97,15 +98,16 @@ static void calculate_simulation_job_startjob(void *customdata, continue; } LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { - if (md->type == eModifierType_Nodes) { - NodesModifierData *nmd = reinterpret_cast(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(); - } + if (md->type != eModifierType_Nodes) { + continue; + } + NodesModifierData *nmd = reinterpret_cast(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(); } } } @@ -222,15 +224,17 @@ static bool bake_simulation_poll(bContext *C) return true; } -struct ZoneBakeData { - int zone_id; +struct NodeBakeData { + int id; bake::BakePath path; + int frame_start; + int frame_end; std::unique_ptr blob_sharing; }; struct ModifierBakeData { NodesModifierData *nmd; - Vector zones; + Vector nodes; }; struct ObjectBakeData { @@ -243,7 +247,7 @@ struct BakeSimulationJob { Main *bmain; Depsgraph *depsgraph; Scene *scene; - Vector objects; + Vector objects; }; static void bake_simulation_job_startjob(void *customdata, @@ -256,57 +260,29 @@ static void bake_simulation_job_startjob(void *customdata, G.is_break = false; WM_set_locked_interface(job.wm, true); - Vector objects_to_bake; - for (Object *object : job.objects) { - if (!BKE_id_is_editable(job.bmain, &object->id)) { - continue; - } + int global_bake_start_frame = INT32_MAX; + int global_bake_end_frame = INT32_MIN; - ObjectBakeData bake_data; - bake_data.object = object; - LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { - if (md->type == eModifierType_Nodes) { - NodesModifierData *nmd = reinterpret_cast(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()) { - ZoneBakeData zone_bake_data; - zone_bake_data.zone_id = nested_node_ref.id; - zone_bake_data.blob_sharing = std::make_unique(); - if (std::optional path = bake::get_node_bake_path( - *job.bmain, *object, *nmd, nested_node_ref.id)) - { - zone_bake_data.path = std::move(*path); - modifier_bake_data.zones.append(std::move(zone_bake_data)); - } - } - - bake_data.modifiers.append(std::move(modifier_bake_data)); + 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); } } - objects_to_bake.append(std::move(bake_data)); } *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 = 1.0f / (float(job.scene->r.efra - job.scene->r.sfra + 1) / - frame_step_size); + const float progress_per_frame = frame_step_size / frames_to_bake; const int old_frame = job.scene->r.cfra; - for (float frame_f = job.scene->r.sfra; frame_f <= job.scene->r.efra; frame_f += frame_step_size) + for (float frame_f = global_bake_start_frame; frame_f <= global_bake_end_frame; + frame_f += frame_step_size) { const SubFrame frame{frame_f}; @@ -321,16 +297,16 @@ static void bake_simulation_job_startjob(void *customdata, const std::string frame_file_name = bake::frame_to_file_name(frame); - for (ObjectBakeData &object_bake_data : objects_to_bake) { + 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 (ZoneBakeData &zone_bake_data : modifier_bake_data.zones) { - if (!modifier_cache.cache_by_id.contains(zone_bake_data.zone_id)) { + 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( - zone_bake_data.zone_id); + node_bake_data.id); if (node_cache.frame_caches.is_empty()) { continue; } @@ -339,7 +315,7 @@ static void bake_simulation_job_startjob(void *customdata, continue; } - const bake::BakePath path = zone_bake_data.path; + const bake::BakePath path = node_bake_data.path; const std::string blob_file_name = frame_file_name + ".blob"; @@ -357,7 +333,7 @@ static void bake_simulation_job_startjob(void *customdata, 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, *zone_bake_data.blob_sharing, meta_file); + frame_cache.state, blob_writer, *node_bake_data.blob_sharing, meta_file); } } } @@ -366,15 +342,18 @@ static void bake_simulation_job_startjob(void *customdata, *do_update = true; } - for (ObjectBakeData &object_bake_data : objects_to_bake) { + for (ObjectBakeData &object_bake_data : job.objects) { for (ModifierBakeData &modifier_bake_data : object_bake_data.modifiers) { NodesModifierData &nmd = *modifier_bake_data.nmd; - for (ZoneBakeData &zone_bake_data : modifier_bake_data.zones) { - if (std::unique_ptr &node_cache = nmd.runtime->cache->cache_by_id.lookup( - zone_bake_data.zone_id)) + for (NodeBakeData &node_bake_data : modifier_bake_data.nodes) { + if (std::unique_ptr *node_cache_ptr = + nmd.runtime->cache->cache_by_id.lookup_ptr(node_bake_data.id)) { - /* Tag the caches as being baked so that they are not changed anymore. */ - node_cache->cache_status = bake::CacheStatus::Baked; + 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; + } } } } @@ -396,34 +375,18 @@ static void bake_simulation_job_endjob(void *customdata) WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, nullptr); } -static int bake_simulation_exec(bContext *C, wmOperator *op) +static int start_bake_job(bContext *C, Vector objects_to_bake, wmOperator *op) { - 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); - BakeSimulationJob *job = MEM_new(__func__); - job->wm = wm; - job->bmain = bmain; - job->depsgraph = depsgraph; - job->scene = scene; + 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); - 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, + wmJob *wm_job = WM_jobs_get(job->wm, CTX_wm_window(C), - CTX_data_scene(C), + job->scene, "Bake Simulation Nodes", WM_JOB_PROGRESS, WM_JOB_TYPE_BAKE_SIMULATION_NODES); @@ -439,6 +402,91 @@ static int bake_simulation_exec(bContext *C, wmOperator *op) return OPERATOR_RUNNING_MODAL; } +static Vector collect_nodes_to_bake(Main &bmain, + Scene &scene, + const Span objects) +{ + Vector 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(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(); + std::optional path = bake::get_node_bake_path( + bmain, *object, *nmd, nested_node_ref.id); + if (!path) { + continue; + } + std::optional 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 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 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 { @@ -601,10 +649,52 @@ static int bake_simulation_modal(bContext *C, wmOperator * /*op*/, const wmEvent return OPERATOR_PASS_THROUGH; } -static int delete_baked_simulation_exec(bContext *C, wmOperator *op) +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_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 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 objects; if (RNA_boolean_get(op->ptr, "selected")) { CTX_DATA_BEGIN (C, Object *, object, selected_objects) { @@ -626,42 +716,8 @@ static int delete_baked_simulation_exec(bContext *C, wmOperator *op) LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { if (md->type == eModifierType_Nodes) { NodesModifierData *nmd = reinterpret_cast(md); - if (!nmd->runtime->cache) { - continue; - } - for (auto item : nmd->runtime->cache->cache_by_id.items()) { - item.value->reset(); - - const std::optional bake_path = bake::get_node_bake_path( - *bmain, *object, *nmd, item.key); - if (!bake_path) { - continue; - } - - const char *meta_dir = bake_path->meta_dir.c_str(); - if (BLI_exists(meta_dir)) { - if (BLI_delete(meta_dir, true, true)) { - BKE_reportf(op->reports, RPT_ERROR, "Failed to remove meta 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( - op->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 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); + for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) { + try_delete_bake(C, *object, *nmd, bake.id, op->reports); } } } @@ -674,6 +730,102 @@ static int delete_baked_simulation_exec(bContext *C, wmOperator *op) 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( + 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(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_path = bake::get_node_bake_path( + *bmain, *object, nmd, bake_id); + if (!bake_path.has_value()) { + return OPERATOR_CANCELLED; + } + const std::optional 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(); + + 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 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( + 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(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) @@ -725,3 +877,62 @@ void OBJECT_OT_simulation_nodes_cache_delete(wmOperatorType *ot) 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); +} diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index 102b879b215..5d5c0c056f7 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -347,6 +347,8 @@ void OBJECT_OT_bake(wmOperatorType *ot); void OBJECT_OT_simulation_nodes_cache_calculate_to_frame(wmOperatorType *ot); void OBJECT_OT_simulation_nodes_cache_bake(wmOperatorType *ot); void OBJECT_OT_simulation_nodes_cache_delete(wmOperatorType *ot); +void OBJECT_OT_simulation_nodes_cache_bake_single(wmOperatorType *ot); +void OBJECT_OT_simulation_nodes_cache_delete_single(wmOperatorType *ot); /* `object_random.cc` */ diff --git a/source/blender/editors/object/object_ops.cc b/source/blender/editors/object/object_ops.cc index e079efbc843..cbb57578737 100644 --- a/source/blender/editors/object/object_ops.cc +++ b/source/blender/editors/object/object_ops.cc @@ -259,6 +259,8 @@ void ED_operatortypes_object() WM_operatortype_append(OBJECT_OT_simulation_nodes_cache_calculate_to_frame); WM_operatortype_append(OBJECT_OT_simulation_nodes_cache_bake); WM_operatortype_append(OBJECT_OT_simulation_nodes_cache_delete); + WM_operatortype_append(OBJECT_OT_simulation_nodes_cache_bake_single); + WM_operatortype_append(OBJECT_OT_simulation_nodes_cache_delete_single); WM_operatortype_append(OBJECT_OT_drop_named_material); WM_operatortype_append(OBJECT_OT_drop_geometry_nodes); WM_operatortype_append(OBJECT_OT_unlink_data); diff --git a/source/blender/editors/space_action/action_draw.cc b/source/blender/editors/space_action/action_draw.cc index 8fe16af66e2..49673c5780d 100644 --- a/source/blender/editors/space_action/action_draw.cc +++ b/source/blender/editors/space_action/action_draw.cc @@ -705,56 +705,112 @@ static void timeline_cache_draw_single(PTCacheID *pid, float y_offset, float hei GPU_matrix_pop(); } -static void timeline_cache_draw_simulation_nodes(const blender::bke::bake::ModifierCache &cache, - const float y_offset, - const float height, - const uint pos_id) +struct SimulationRange { + blender::IndexRange frames; + blender::bke::bake::CacheStatus status; +}; + +static void timeline_cache_draw_simulation_nodes( + const blender::Span simulation_ranges, + const bool all_simulations_baked, + float *y_offset, + const float line_height, + const uint pos_id) { - std::lock_guard lock{cache.mutex}; - if (cache.cache_by_id.is_empty()) { + if (simulation_ranges.is_empty()) { return; } - /* Draw the state if one of the simulation zones. This is fine for now, because there is no ui - * that allows caching zones independently. */ - const blender::bke::bake::NodeCache &node_cache = **cache.cache_by_id.values().begin(); - if (node_cache.frame_caches.is_empty()) { - return; + + bool has_bake = false; + + for (const SimulationRange &sim_range : simulation_ranges) { + switch (sim_range.status) { + case blender::bke::bake::CacheStatus::Invalid: + case blender::bke::bake::CacheStatus::Valid: + break; + case blender::bke::bake::CacheStatus::Baked: + has_bake = true; + break; + } } + blender::Set status_change_frames_set; + for (const SimulationRange &sim_range : simulation_ranges) { + status_change_frames_set.add(sim_range.frames.first()); + status_change_frames_set.add(sim_range.frames.one_after_last()); + } + blender::Vector status_change_frames; + status_change_frames.extend(status_change_frames_set.begin(), status_change_frames_set.end()); + std::sort(status_change_frames.begin(), status_change_frames.end()); + const blender::OffsetIndices frame_ranges = status_change_frames.as_span(); + GPU_matrix_push(); - GPU_matrix_translate_2f(0.0, float(V2D_SCROLL_HANDLE_HEIGHT) + y_offset); - GPU_matrix_scale_2f(1.0, height); + GPU_matrix_translate_2f(0.0, float(V2D_SCROLL_HANDLE_HEIGHT) + *y_offset); + GPU_matrix_scale_2f(1.0, line_height); - float color[4]; - UI_GetThemeColor4fv(TH_SIMULATED_FRAMES, color); - switch (node_cache.cache_status) { - case blender::bke::bake::CacheStatus::Invalid: { - color[3] = 0.4f; - break; + blender::float4 base_color; + UI_GetThemeColor4fv(TH_SIMULATED_FRAMES, base_color); + blender::float4 invalid_color = base_color; + invalid_color.w *= 0.4f; + blender::float4 valid_color = base_color; + valid_color.w *= 0.7f; + blender::float4 baked_color = base_color; + + float max_used_height = 1.0f; + for (const int range_i : frame_ranges.index_range()) { + const blender::IndexRange frame_range = frame_ranges[range_i]; + const int start_frame = frame_range.first(); + const int end_frame = frame_range.last(); + + bool has_bake_at_frame = false; + bool has_valid_at_frame = false; + bool has_invalid_at_frame = false; + for (const SimulationRange &sim_range : simulation_ranges) { + if (sim_range.frames.contains(start_frame)) { + switch (sim_range.status) { + case blender::bke::bake::CacheStatus::Invalid: + has_invalid_at_frame = true; + break; + case blender::bke::bake::CacheStatus::Valid: + has_valid_at_frame = true; + break; + case blender::bke::bake::CacheStatus::Baked: + has_bake_at_frame = true; + break; + } + } } - case blender::bke::bake::CacheStatus::Valid: { - color[3] = 0.7f; - break; + if (!(has_bake_at_frame || has_valid_at_frame || has_invalid_at_frame)) { + continue; } - case blender::bke::bake::CacheStatus::Baked: { - color[3] = 1.0f; - break; + + if (all_simulations_baked) { + immUniformColor4fv(baked_color); + immBeginAtMost(GPU_PRIM_TRIS, 6); + immRectf_fast(pos_id, start_frame, 0, end_frame + 1.0f, 1.0f); + immEnd(); + } + else { + if (has_valid_at_frame || has_invalid_at_frame) { + immUniformColor4fv(has_invalid_at_frame ? invalid_color : valid_color); + immBeginAtMost(GPU_PRIM_TRIS, 6); + const float top = has_bake ? 2.0f : 1.0f; + immRectf_fast(pos_id, start_frame, 0.0f, end_frame + 1.0f, top); + immEnd(); + max_used_height = top; + } + if (has_bake_at_frame) { + immUniformColor4fv(baked_color); + immBeginAtMost(GPU_PRIM_TRIS, 6); + immRectf_fast(pos_id, start_frame, 0, end_frame + 1.0f, 1.0f); + immEnd(); + } } } - immUniformColor4fv(color); - - immBeginAtMost(GPU_PRIM_TRIS, node_cache.frame_caches.size() * 6); - - for (const std::unique_ptr &frame_cache : - node_cache.frame_caches.as_span()) - { - const int frame = frame_cache->frame.frame(); - immRectf_fast(pos_id, frame - 0.5f, 0, frame + 0.5f, 1.0f); - } - immEnd(); - GPU_matrix_pop(); + + *y_offset += max_used_height * 2; } void timeline_draw_cache(const SpaceAction *saction, const Object *ob, const Scene *scene) @@ -789,6 +845,8 @@ void timeline_draw_cache(const SpaceAction *saction, const Object *ob, const Sce y_offset += cache_draw_height; } if (saction->cache_display & TIME_CACHE_SIMULATION_NODES) { + blender::Vector simulation_ranges; + bool all_simulations_baked = true; LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { if (md->type != eModifierType_Nodes) { continue; @@ -803,10 +861,29 @@ void timeline_draw_cache(const SpaceAction *saction, const Object *ob, const Sce if ((nmd->node_group->runtime->runtime_flag & NTREE_RUNTIME_FLAG_HAS_SIMULATION_ZONE) == 0) { continue; } - timeline_cache_draw_simulation_nodes( - *nmd->runtime->cache, y_offset, cache_draw_height, pos_id); - y_offset += cache_draw_height; + const blender::bke::bake::ModifierCache &modifier_cache = *nmd->runtime->cache; + { + std::lock_guard lock{modifier_cache.mutex}; + for (const std::unique_ptr &node_cache_ptr : + modifier_cache.cache_by_id.values()) + { + const blender::bke::bake::NodeCache &node_cache = *node_cache_ptr; + if (node_cache.frame_caches.is_empty()) { + all_simulations_baked = false; + continue; + } + if (node_cache.cache_status != blender::bke::bake::CacheStatus::Baked) { + all_simulations_baked = false; + } + const int start_frame = node_cache.frame_caches.first()->frame.frame(); + const int end_frame = node_cache.frame_caches.last()->frame.frame(); + const blender::IndexRange frame_range{start_frame, end_frame - start_frame + 1}; + simulation_ranges.append({frame_range, node_cache.cache_status}); + } + } } + timeline_cache_draw_simulation_nodes( + simulation_ranges, all_simulations_baked, &y_offset, cache_draw_height, pos_id); } GPU_blend(GPU_BLEND_NONE); diff --git a/source/blender/editors/space_node/space_node.cc b/source/blender/editors/space_node/space_node.cc index 9ea0dff3004..4354baa81ec 100644 --- a/source/blender/editors/space_node/space_node.cc +++ b/source/blender/editors/space_node/space_node.cc @@ -245,6 +245,52 @@ float2 space_node_group_offset(const SpaceNode &snode) return float2(0); } +static const bNode *group_node_by_name(const bNodeTree &ntree, StringRef name) +{ + for (const bNode *node : ntree.group_nodes()) { + if (node->name == name) { + return node; + } + } + return nullptr; +} + +std::optional find_nested_node_id_in_root(const SpaceNode &snode, const bNode &query_node) +{ + BLI_assert(snode.edittree->runtime->nodes_by_id.contains(const_cast(&query_node))); + + std::optional id_in_node; + const char *group_node_name = nullptr; + const bNode *node = &query_node; + LISTBASE_FOREACH_BACKWARD (const bNodeTreePath *, path, &snode.treepath) { + const bNodeTree *ntree = path->nodetree; + if (group_node_name) { + node = group_node_by_name(*ntree, group_node_name); + } + bool found = false; + for (const bNestedNodeRef &ref : ntree->nested_node_refs_span()) { + if (node->is_group()) { + if (ref.path.node_id == node->identifier && ref.path.id_in_node == id_in_node) { + group_node_name = path->node_name; + id_in_node = ref.id; + found = true; + break; + } + } + else if (ref.path.node_id == node->identifier) { + group_node_name = path->node_name; + id_in_node = ref.id; + found = true; + break; + } + } + if (!found) { + return std::nullopt; + } + } + return id_in_node; +} + std::optional get_modifier_for_node_editor(const SpaceNode &snode) { if (snode.id == nullptr) { diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index 2c58b7c3cba..bf00fc1e260 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -2325,6 +2325,29 @@ typedef struct NodesModifierSettings { struct IDProperty *properties; } NodesModifierSettings; +typedef struct NodesModifierBake { + /** An id that references a nested node in the node tree. Also see #bNestedNodeRef. */ + int id; + /** #NodesModifierBakeFlag. */ + uint32_t flag; + /** + * Directory where the baked data should be stored. This is only used when + * `NODES_MODIFIER_BAKE_CUSTOM_PATH` is set. + */ + char *directory; + /** + * Frame range for the simulation and baking that is used if + * `NODES_MODIFIER_BAKE_CUSTOM_SIMULATION_FRAME_RANGE` is set. + */ + int frame_start; + int frame_end; +} NodesModifierBake; + +typedef enum NodesModifierBakeFlag { + NODES_MODIFIER_BAKE_CUSTOM_SIMULATION_FRAME_RANGE = 1 << 0, + NODES_MODIFIER_BAKE_CUSTOM_PATH = 1 << 1, +} NodesModifierBakeFlag; + typedef struct NodesModifierData { ModifierData modifier; struct bNodeTree *node_group; @@ -2333,13 +2356,20 @@ typedef struct NodesModifierData { * Directory where baked simulation states are stored. This may be relative to the .blend file. */ char *simulation_bake_directory; - /** NodesModifierFlag. */ int8_t flag; - char _pad[7]; + char _pad[3]; + int bakes_num; + NodesModifierBake *bakes; + void *_pad2; NodesModifierRuntimeHandle *runtime; + +#ifdef __cplusplus + NodesModifierBake *find_bake(int id); + const NodesModifierBake *find_bake(int id) const; +#endif } NodesModifierData; typedef enum NodesModifierFlag { diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 84aad28ca45..409f330dd33 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -716,6 +716,7 @@ typedef struct bNodeTree { const bNestedNodeRef *nested_node_ref_from_node_id_path(blender::Span node_ids) const; [[nodiscard]] bool node_id_path_from_nested_node_ref(const int32_t nested_node_id, blender::Vector &r_node_ids) const; + const bNode *find_nested_node(int32_t nested_node_id) const; /** * Update a run-time cache for the node tree based on it's current state. This makes many methods diff --git a/source/blender/makesdna/DNA_scene_defaults.h b/source/blender/makesdna/DNA_scene_defaults.h index 13e2573e848..0922a00eea7 100644 --- a/source/blender/makesdna/DNA_scene_defaults.h +++ b/source/blender/makesdna/DNA_scene_defaults.h @@ -266,6 +266,8 @@ .eevee = _DNA_DEFAULT_SceneEEVEE, \ \ .hydra = _DNA_DEFAULT_SceneHydra, \ + .simulation_frame_start = 1, \ + .simulation_frame_end = 250, \ } /** \} */ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 3053a2c92ba..5468f4cde0a 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -2052,6 +2052,13 @@ typedef struct Scene { /** Settings to be override by work-spaces. */ IDProperty *layer_properties; + /** + * Frame range used for simulations in geometry nodes by default, if SCE_CUSTOM_SIMULATION_RANGE + * is set. Individual simulations can overwrite this though. + */ + int simulation_frame_start; + int simulation_frame_end; + struct SceneDisplay display; struct SceneEEVEE eevee; struct SceneGpencil grease_pencil_settings; @@ -2491,6 +2498,7 @@ enum { SCE_FRAME_DROP = 1 << 3, SCE_KEYS_NO_SELONLY = 1 << 4, SCE_READFILE_LIBLINK_NEED_SETSCENE_CHECK = 1 << 5, + SCE_CUSTOM_SIMULATION_RANGE = 1 << 6, }; /* Return flag BKE_scene_base_iter_next functions. */ diff --git a/source/blender/makesrna/intern/rna_modifier.cc b/source/blender/makesrna/intern/rna_modifier.cc index 6107e112ca9..611c90f21f1 100644 --- a/source/blender/makesrna/intern/rna_modifier.cc +++ b/source/blender/makesrna/intern/rna_modifier.cc @@ -7067,11 +7067,57 @@ static void rna_def_modifier_weightednormal(BlenderRNA *brna) RNA_define_lib_overridable(false); } +static void rna_def_modifier_nodes_bake(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "NodesModifierBake", nullptr); + RNA_def_struct_ui_text(srna, "Nodes Modifier Bake", ""); + + prop = RNA_def_property(srna, "directory", PROP_STRING, PROP_DIRPATH); + RNA_def_property_ui_text(prop, "Directory", "Location on disk where the bake data is stored"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "frame_start", PROP_INT, PROP_TIME); + RNA_def_property_ui_text(prop, "Start Frame", "Frame where the baking starts"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "frame_end", PROP_INT, PROP_TIME); + RNA_def_property_ui_text(prop, "End Frame", "Frame where the baking ends"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "use_custom_simulation_frame_range", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "flag", NODES_MODIFIER_BAKE_CUSTOM_SIMULATION_FRAME_RANGE); + RNA_def_property_ui_text( + prop, "Custom Simulation Frame Range", "Override the simulation frame range from the scene"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "use_custom_path", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", NODES_MODIFIER_BAKE_CUSTOM_PATH); + RNA_def_property_ui_text( + prop, "Custom Path", "Specify a path where the baked data should be stored manually"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); +} + +static void rna_def_modifier_nodes_bakes(BlenderRNA *brna) +{ + StructRNA *srna; + + srna = RNA_def_struct(brna, "NodesModifierBakes", nullptr); + RNA_def_struct_sdna(srna, "NodesModifierData"); + RNA_def_struct_ui_text(srna, "Bakes", "Bake data for every bake node"); +} + static void rna_def_modifier_nodes(BlenderRNA *brna) { StructRNA *srna; PropertyRNA *prop; + rna_def_modifier_nodes_bake(brna); + rna_def_modifier_nodes_bakes(brna); + srna = RNA_def_struct(brna, "NodesModifier", "Modifier"); RNA_def_struct_ui_text(srna, "Nodes Modifier", ""); RNA_def_struct_sdna(srna, "NodesModifierData"); @@ -7092,6 +7138,11 @@ static void rna_def_modifier_nodes(BlenderRNA *brna) prop, "Simulation Bake Directory", "Location on disk where the bake data is stored"); RNA_def_property_update(prop, 0, nullptr); + prop = RNA_def_property(srna, "bakes", PROP_COLLECTION, PROP_NONE); + RNA_def_property_struct_type(prop, "NodesModifierBake"); + RNA_def_property_collection_sdna(prop, nullptr, "bakes", "bakes_num"); + RNA_def_property_srna(prop, "NodesModifierBakes"); + prop = RNA_def_property(srna, "show_group_selector", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_negative_sdna( prop, nullptr, "flag", NODES_MODIFIER_HIDE_DATABLOCK_SELECTOR); diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index 2263945df39..3cb230403cb 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -8373,6 +8373,25 @@ void RNA_def_scene(BlenderRNA *brna) RNA_def_property_update(prop, NC_SCENE, nullptr); # endif + prop = RNA_def_property(srna, "use_custom_simulation_range", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", SCE_CUSTOM_SIMULATION_RANGE); + RNA_def_property_ui_text(prop, + "Custom Simulation Range", + "Use a simulation range that is different from the scene range for " + "simulation nodes that don't override the frame range themselves"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_SCENE, "rna_Scene_set_update"); + + prop = RNA_def_property(srna, "simulation_frame_start", PROP_INT, PROP_NONE); + RNA_def_property_ui_text(prop, "Simulation Frame Start", "Frame at which simulations start"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_SCENE, "rna_Scene_set_update"); + + prop = RNA_def_property(srna, "simulation_frame_end", PROP_INT, PROP_NONE); + RNA_def_property_ui_text(prop, "Simulation Frame End", "Frame at which simulations end"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_SCENE, "rna_Scene_set_update"); + prop = RNA_def_property(srna, "sync_mode", PROP_ENUM, PROP_NONE); RNA_def_property_enum_funcs(prop, "rna_Scene_sync_mode_get", "rna_Scene_sync_mode_set", nullptr); RNA_def_property_enum_items(prop, sync_mode_items); diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 5f0720154b9..45d4fd696c6 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -382,11 +382,8 @@ static bool logging_enabled(const ModifierEvalContext *ctx) return true; } -} // namespace blender - -void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd) +static void update_id_properties_from_node_group(NodesModifierData *nmd) { - using namespace blender; if (nmd->node_group == nullptr) { if (nmd->settings.properties) { IDP_FreeProperty(nmd->settings.properties); @@ -410,10 +407,112 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd) if (old_properties != nullptr) { IDP_FreeProperty(old_properties); } +} + +static void update_existing_bake_caches(NodesModifierData &nmd) +{ + if (!nmd.runtime->cache) { + if (nmd.bakes_num == 0) { + return; + } + nmd.runtime->cache = std::make_shared(); + } + bake::ModifierCache &modifier_cache = *nmd.runtime->cache; + std::lock_guard lock{modifier_cache.mutex}; + + Map> &old_cache_by_id = modifier_cache.cache_by_id; + Map> new_cache_by_id; + for (const NodesModifierBake &bake : Span{nmd.bakes, nmd.bakes_num}) { + std::unique_ptr node_cache; + std::unique_ptr *old_node_cache_ptr = old_cache_by_id.lookup_ptr(bake.id); + if (old_node_cache_ptr == nullptr) { + node_cache = std::make_unique(); + } + else { + node_cache = std::move(*old_node_cache_ptr); + } + new_cache_by_id.add(bake.id, std::move(node_cache)); + } + modifier_cache.cache_by_id = std::move(new_cache_by_id); +} + +static void update_bakes_from_node_group(NodesModifierData &nmd) +{ + Map old_bake_by_id; + for (NodesModifierBake &bake : MutableSpan(nmd.bakes, nmd.bakes_num)) { + old_bake_by_id.add(bake.id, &bake); + } + + Vector new_bake_ids; + for (const bNestedNodeRef &ref : nmd.node_group->nested_node_refs_span()) { + const bNode *node = nmd.node_group->find_nested_node(ref.id); + if (node) { + if (node->type == GEO_NODE_SIMULATION_OUTPUT) { + new_bake_ids.append(ref.id); + } + } + else if (old_bake_by_id.contains(ref.id)) { + /* Keep baked data in case linked data is missing so that it still exists when the linked + * data has been found. */ + new_bake_ids.append(ref.id); + } + } + + NodesModifierBake *new_bake_data = MEM_cnew_array(new_bake_ids.size(), + __func__); + for (const int i : new_bake_ids.index_range()) { + const int id = new_bake_ids[i]; + NodesModifierBake *old_bake = old_bake_by_id.lookup_default(id, nullptr); + NodesModifierBake &new_bake = new_bake_data[i]; + if (old_bake) { + new_bake = *old_bake; + /* The ownership of the string was moved to `new_bake`. */ + old_bake->directory = nullptr; + } + else { + new_bake.id = id; + new_bake.frame_start = 1; + new_bake.frame_end = 100; + } + } + + for (NodesModifierBake &old_bake : MutableSpan(nmd.bakes, nmd.bakes_num)) { + MEM_SAFE_FREE(old_bake.directory); + } + MEM_SAFE_FREE(nmd.bakes); + + nmd.bakes = new_bake_data; + nmd.bakes_num = new_bake_ids.size(); + + update_existing_bake_caches(nmd); +} + +} // namespace blender + +void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd) +{ + using namespace blender; + update_id_properties_from_node_group(nmd); + update_bakes_from_node_group(*nmd); DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY); } +NodesModifierBake *NodesModifierData::find_bake(const int id) +{ + return const_cast(std::as_const(*this).find_bake(id)); +} + +const NodesModifierBake *NodesModifierData::find_bake(const int id) const +{ + for (const NodesModifierBake &bake : blender::Span{this->bakes, this->bakes_num}) { + if (bake.id == id) { + return &bake; + } + } + return nullptr; +} + namespace blender { static void find_side_effect_nodes_for_viewer_path( @@ -680,13 +779,13 @@ class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams { const NodesModifierData &nmd_; const ModifierEvalContext &ctx_; const Main *bmain_; + const Scene *scene_; SubFrame current_frame_; - SubFrame start_frame_; - bool is_start_frame_; bool use_frame_cache_; bool depsgraph_is_active_; bake::ModifierCache *modifier_cache_; float fps_; + bool has_invalid_simulation_ = false; public: NodesModifierSimulationParams(NodesModifierData &nmd, const ModifierEvalContext &ctx) @@ -696,8 +795,7 @@ class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams { bmain_ = DEG_get_bmain(depsgraph); current_frame_ = DEG_get_ctime(depsgraph); const Scene *scene = DEG_get_input_scene(depsgraph); - start_frame_ = scene->r.sfra; - is_start_frame_ = current_frame_ == start_frame_; + scene_ = scene; use_frame_cache_ = ctx_.object->flag & OB_FLAG_USE_SIMULATION_CACHE; depsgraph_is_active_ = DEG_is_active(depsgraph); modifier_cache_ = nmd.runtime->cache.get(); @@ -717,14 +815,39 @@ class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams { } } } - /* Reset cached data if necessary. */ - if (is_start_frame_) { - for (std::unique_ptr &node_cache : modifier_cache_->cache_by_id.values()) - { - if (node_cache->cache_status == bake::CacheStatus::Invalid) { - node_cache->reset(); - } - } + this->reset_invalid_node_bakes(); + } + for (const std::unique_ptr &node_cache_ptr : + modifier_cache_->cache_by_id.values()) + { + const bake::NodeCache &node_cache = *node_cache_ptr; + if (node_cache.cache_status == bake::CacheStatus::Invalid) { + has_invalid_simulation_ = true; + break; + } + } + } + + void reset_invalid_node_bakes() + { + for (auto item : modifier_cache_->cache_by_id.items()) { + const int id = item.key; + bake::NodeCache &node_cache = *item.value; + if (node_cache.cache_status != bake::CacheStatus::Invalid) { + continue; + } + const std::optional sim_frame_range = bake::get_node_bake_frame_range( + *scene_, *ctx_.object, nmd_, id); + if (!sim_frame_range.has_value()) { + continue; + } + const SubFrame start_frame{int(sim_frame_range->start())}; + if (current_frame_ <= start_frame) { + node_cache.reset(); + } + if (!node_cache.frame_caches.is_empty() && + current_frame_ < node_cache.frame_caches.first()->frame) { + node_cache.reset(); } } } @@ -753,8 +876,15 @@ class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams { void init_simulation_info(const int zone_id, nodes::SimulationZoneBehavior &zone_behavior) const { - bake::NodeCache &node_cache = *modifier_cache_->cache_by_id.lookup_or_add_cb( - zone_id, []() { return std::make_unique(); }); + if (!modifier_cache_->cache_by_id.contains(zone_id)) { + /* Should have been created in #update_existing_bake_caches. */ + return; + } + bake::NodeCache &node_cache = *modifier_cache_->cache_by_id.lookup(zone_id); + const IndexRange sim_frame_range = *bake::get_node_bake_frame_range( + *scene_, *ctx_.object, nmd_, zone_id); + const SubFrame sim_start_frame{int(sim_frame_range.first())}; + const SubFrame sim_end_frame{int(sim_frame_range.last())}; /* Try load baked data. */ if (!node_cache.failed_finding_bake) { @@ -795,25 +925,31 @@ class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams { * is read-only. */ if (depsgraph_is_active_) { if (node_cache.frame_caches.is_empty()) { + if (current_frame_ < sim_start_frame || current_frame_ > sim_end_frame) { + /* Outside of simulation frame range, so ignore the simulation if there is no cache. */ + this->input_pass_through(zone_behavior); + this->output_pass_through(zone_behavior); + return; + } /* Initialize the simulation. */ - this->input_pass_through(zone_behavior); - this->output_store_frame_cache(node_cache, zone_behavior); - if (!is_start_frame_) { - /* If we initialize at a frame that is not the start frame, the simulation is not - * valid. */ + if (current_frame_ > sim_start_frame || has_invalid_simulation_) { node_cache.cache_status = bake::CacheStatus::Invalid; } + this->input_pass_through(zone_behavior); + this->output_store_frame_cache(node_cache, zone_behavior); return; } - if (frame_indices.prev && !frame_indices.current && !frame_indices.next) { + if (frame_indices.prev && !frame_indices.current && !frame_indices.next && + current_frame_ <= sim_end_frame) + { /* Read the previous frame's data and store the newly computed simulation state. */ auto &output_copy_info = zone_behavior.input.emplace(); const bake::FrameCache &prev_frame_cache = *node_cache.frame_caches[*frame_indices.prev]; - const float delta_frames = std::min( - max_delta_frames, float(current_frame_) - float(prev_frame_cache.frame)); - if (delta_frames != 1) { + const float real_delta_frames = float(current_frame_) - float(prev_frame_cache.frame); + if (real_delta_frames != 1) { node_cache.cache_status = bake::CacheStatus::Invalid; } + const float delta_frames = std::min(max_delta_frames, real_delta_frames); output_copy_info.delta_time = delta_frames / fps_; output_copy_info.state = prev_frame_cache.state; this->output_store_frame_cache(node_cache, zone_behavior); @@ -1682,6 +1818,11 @@ static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const Modi * and don't necessarily need to be written, but we can't just free them. */ IDP_BlendWrite(writer, nmd->settings.properties); + BLO_write_struct_array(writer, NodesModifierBake, nmd->bakes_num, nmd->bakes); + for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) { + BLO_write_string(writer, bake.directory); + } + if (!BLO_write_is_undo(writer)) { LISTBASE_FOREACH (IDProperty *, prop, &nmd->settings.properties->data.group) { if (prop->type == IDP_INT) { @@ -1708,6 +1849,12 @@ static void blend_read(BlendDataReader *reader, ModifierData *md) BLO_read_data_address(reader, &nmd->settings.properties); IDP_BlendDataRead(reader, &nmd->settings.properties); } + + BLO_read_data_address(reader, &nmd->bakes); + for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) { + BLO_read_data_address(reader, &bake.directory); + } + nmd->runtime = MEM_new(__func__); nmd->runtime->cache = std::make_shared(); } @@ -1719,6 +1866,16 @@ static void copy_data(const ModifierData *md, ModifierData *target, const int fl BKE_modifier_copydata_generic(md, target, flag); + if (nmd->bakes) { + tnmd->bakes = static_cast(MEM_dupallocN(nmd->bakes)); + for (const int i : IndexRange(nmd->bakes_num)) { + NodesModifierBake &bake = tnmd->bakes[i]; + if (bake.directory) { + bake.directory = BLI_strdup(bake.directory); + } + } + } + tnmd->runtime = MEM_new(__func__); if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) { @@ -1748,6 +1905,11 @@ static void free_data(ModifierData *md) nmd->settings.properties = nullptr; } + for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) { + MEM_SAFE_FREE(bake.directory); + } + MEM_SAFE_FREE(nmd->bakes); + MEM_SAFE_FREE(nmd->simulation_bake_directory); MEM_delete(nmd->runtime); } diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index b2cfcb7c853..0da46d2dda2 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -16,6 +16,7 @@ set(INC ../../gpu ../../imbuf ../../makesrna + ../../modifiers ../../render ../../windowmanager ../../../../extern/fmtlib/include diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc index 94b13c4afe9..509a1902ec1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc @@ -8,10 +8,14 @@ #include "BLI_task.hh" #include "BKE_attribute_math.hh" +#include "BKE_bake_geometry_nodes_modifier.hh" #include "BKE_bake_items_socket.hh" #include "BKE_compute_contexts.hh" +#include "BKE_context.h" #include "BKE_curves.hh" #include "BKE_instances.hh" +#include "BKE_modifier.h" +#include "BKE_object.h" #include "BKE_scene.h" #include "DEG_depsgraph_query.hh" @@ -26,10 +30,21 @@ #include "DNA_curves_types.h" #include "DNA_mesh_types.h" +#include "DNA_modifier_types.h" #include "DNA_pointcloud_types.h" +#include "DNA_space_types.h" + +#include "ED_node.hh" + +#include "RNA_access.hh" +#include "RNA_prototypes.h" + +#include "MOD_nodes.hh" #include "BLT_translation.h" +#include "WM_api.hh" + #include "node_geometry_util.hh" namespace blender::nodes { @@ -835,6 +850,142 @@ static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const b dst_node->storage = dst_storage; } +static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr) +{ + const bNode *node = static_cast(ptr->data); + Scene *scene = CTX_data_scene(C); + SpaceNode *snode = CTX_wm_space_node(C); + if (snode == nullptr) { + return; + } + std::optional object_and_modifier = + ed::space_node::get_modifier_for_node_editor(*snode); + if (!object_and_modifier.has_value()) { + return; + } + const Object &object = *object_and_modifier->object; + const NodesModifierData &nmd = *object_and_modifier->nmd; + const std::optional bake_id = ed::space_node::find_nested_node_id_in_root(*snode, + *node); + if (!bake_id.has_value()) { + return; + } + const NodesModifierBake *bake = nullptr; + for (const NodesModifierBake &iter_bake : Span{nmd.bakes, nmd.bakes_num}) { + if (iter_bake.id == *bake_id) { + bake = &iter_bake; + break; + } + } + if (bake == nullptr) { + return; + } + + PointerRNA bake_rna = RNA_pointer_create( + const_cast(&object.id), &RNA_NodesModifierBake, (void *)bake); + + const std::optional simulation_range = bke::bake::get_node_bake_frame_range( + *scene, object, nmd, *bake_id); + + std::optional baked_range; + if (nmd.runtime->cache) { + const bke::bake::ModifierCache &cache = *nmd.runtime->cache; + std::lock_guard lock{cache.mutex}; + if (const std::unique_ptr *node_cache_ptr = cache.cache_by_id.lookup_ptr( + *bake_id)) + { + const bke::bake::NodeCache &node_cache = **node_cache_ptr; + if (node_cache.cache_status == bke::bake::CacheStatus::Baked && + !node_cache.frame_caches.is_empty()) + { + const int first_frame = node_cache.frame_caches.first()->frame.frame(); + const int last_frame = node_cache.frame_caches.last()->frame.frame(); + baked_range = IndexRange(first_frame, last_frame - first_frame + 1); + } + } + } + bool is_baked = baked_range.has_value(); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + + { + uiLayout *col = uiLayoutColumn(layout, false); + uiLayout *row = uiLayoutRow(col, true); + { + char bake_label[1024] = N_("Bake"); + + PointerRNA ptr; + uiItemFullO(row, + "OBJECT_OT_simulation_nodes_cache_bake_single", + bake_label, + ICON_NONE, + nullptr, + WM_OP_INVOKE_DEFAULT, + UI_ITEM_NONE, + &ptr); + WM_operator_properties_id_lookup_set_from_id(&ptr, &object.id); + RNA_string_set(&ptr, "modifier_name", nmd.modifier.name); + RNA_int_set(&ptr, "bake_id", bake->id); + } + { + PointerRNA ptr; + uiItemFullO(row, + "OBJECT_OT_simulation_nodes_cache_delete_single", + "", + ICON_TRASH, + nullptr, + WM_OP_INVOKE_DEFAULT, + UI_ITEM_NONE, + &ptr); + WM_operator_properties_id_lookup_set_from_id(&ptr, &object.id); + RNA_string_set(&ptr, "modifier_name", nmd.modifier.name); + RNA_int_set(&ptr, "bake_id", bake->id); + } + if (is_baked) { + char baked_range_label[64]; + SNPRINTF(baked_range_label, + N_("Baked %d - %d"), + int(baked_range->first()), + int(baked_range->last())); + uiItemL(layout, baked_range_label, ICON_NONE); + } + else if (simulation_range.has_value()) { + char simulation_range_label[64]; + SNPRINTF(simulation_range_label, + N_("Frames %d - %d"), + int(simulation_range->first()), + int(simulation_range->last())); + uiItemL(layout, simulation_range_label, ICON_NONE); + } + } + { + uiLayout *settings_col = uiLayoutColumn(layout, false); + uiLayoutSetActive(settings_col, !is_baked); + { + uiLayout *col = uiLayoutColumn(settings_col, true); + uiLayoutSetActive(col, !is_baked); + uiItemR(col, &bake_rna, "use_custom_path", UI_ITEM_NONE, "Custom Path", ICON_NONE); + uiLayout *subcol = uiLayoutColumn(col, true); + uiLayoutSetActive(subcol, bake->flag & NODES_MODIFIER_BAKE_CUSTOM_PATH); + uiItemR(subcol, &bake_rna, "directory", UI_ITEM_NONE, "Path", ICON_NONE); + } + { + uiLayout *col = uiLayoutColumn(settings_col, true); + uiItemR(col, + &bake_rna, + "use_custom_simulation_frame_range", + UI_ITEM_NONE, + "Custom Range", + ICON_NONE); + uiLayout *subcol = uiLayoutColumn(col, true); + uiLayoutSetActive(subcol, bake->flag & NODES_MODIFIER_BAKE_CUSTOM_SIMULATION_FRAME_RANGE); + uiItemR(subcol, &bake_rna, "frame_start", UI_ITEM_NONE, "Start", ICON_NONE); + uiItemR(subcol, &bake_rna, "frame_end", UI_ITEM_NONE, "End", ICON_NONE); + } + } +} + static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) { NodeGeometrySimulationOutput &storage = node_storage(*node); @@ -880,6 +1031,7 @@ static void node_register() ntype.declare_dynamic = node_declare_dynamic; ntype.gather_link_search_ops = nullptr; ntype.insert_link = node_insert_link; + ntype.draw_buttons_ex = node_layout_ex; node_type_storage(&ntype, "NodeGeometrySimulationOutput", node_free_storage, node_copy_storage); nodeRegisterType(&ntype); }