WIP: Experiment: Geometry Nodes: support baking individual simulations #112179

Closed
Jacques Lucke wants to merge 51 commits from JacquesLucke/blender:simulation-bake-individual into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
36 changed files with 1282 additions and 293 deletions

View File

@ -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,

View File

@ -48,6 +48,9 @@ class TIME_HT_editor_buttons:
row.operator("screen.keyframe_jump", text="", icon='NEXT_KEYFRAME').next = True
row.operator("screen.frame_jump", text="", icon='FF').end = True
row = layout.row(align=True)
row.operator("screen.animation_play_with_preroll", text="Play with Preroll")
layout.separator_spacer()
row = layout.row()

View File

@ -84,6 +84,10 @@ std::optional<BakePath> get_node_bake_path(const Main &bmain,
const Object &object,
const NodesModifierData &nmd,
int node_id);
std::optional<IndexRange> get_node_bake_frame_range(const Scene &scene,
const Object &object,
const NodesModifierData &nmd,
int node_id);
std::optional<std::string> get_modifier_bake_path(const Main &bmain,
const Object &object,
const NodesModifierData &nmd);

View File

@ -71,17 +71,52 @@ std::optional<bake::BakePath> 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<std::string> 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<IndexRange> 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);
}
/**

View File

@ -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<const bNodeTree *>(node->id);
if (group == nullptr) {
return nullptr;
}
return group->find_nested_node(ref->path.id_in_node);
}

View File

@ -1232,5 +1232,10 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
}
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
scene->simulation_frame_start = scene->r.sfra;
scene->simulation_frame_end = scene->r.efra;
}
}
}

View File

@ -4,6 +4,8 @@
#pragma once
#include <optional>
#include "BLI_vector_set.hh"
#include "ED_node_c.hh"
@ -33,4 +35,9 @@ 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<int32_t> find_nested_node_id_in_root(const SpaceNode &snode, const bNode &node);
} // namespace blender::ed::space_node

View File

@ -308,7 +308,8 @@ void ED_screen_exit(bContext *C, wmWindow *window, bScreen *screen);
* redraws: uses defines from `stime->redraws`
* \param enable: 1 - forward on, -1 - backwards on, 0 - off.
*/
void ED_screen_animation_timer(bContext *C, int redraws, int sync, int enable);
void ED_screen_animation_timer(
bContext *C, int redraws, int sync, int enable, bool fix_start_frame);
void ED_screen_animation_timer_update(bScreen *screen, int redraws);
void ED_screen_restore_temp_type(bContext *C, ScrArea *area);
ScrArea *ED_screen_full_newspace(bContext *C, ScrArea *area, int type);
@ -445,7 +446,7 @@ void ED_update_for_newframe(Main *bmain, Depsgraph *depsgraph);
/**
* Toggle operator.
*/
int ED_screen_animation_play(bContext *C, int sync, int mode);
int ED_screen_animation_play(bContext *C, int sync, int mode, bool fix_start_frame);
/**
* Find window that owns the animation timer.
*/

View File

@ -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"
@ -222,15 +223,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<bake::BlobSharing> blob_sharing;
};
struct ModifierBakeData {
NodesModifierData *nmd;
Vector<ZoneBakeData> zones;
Vector<NodeBakeData> nodes;
};
struct ObjectBakeData {
@ -243,7 +246,7 @@ struct BakeSimulationJob {
Main *bmain;
Depsgraph *depsgraph;
Scene *scene;
Vector<Object *> objects;
Vector<ObjectBakeData> objects;
};
static void bake_simulation_job_startjob(void *customdata,
@ -256,9 +259,169 @@ static void bake_simulation_job_startjob(void *customdata,
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 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 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;
for (Object *object : job.objects) {
if (!BKE_id_is_editable(job.bmain, &object->id)) {
for (Object *object : objects) {
if (!BKE_id_is_editable(bmain, &object->id)) {
continue;
}
@ -281,162 +444,38 @@ static void bake_simulation_job_startjob(void *customdata,
}
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<bake::BlobSharing>();
if (std::optional<bake::BakePath> 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));
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));
}
*progress = 0.0f;
*do_update = true;
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 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)
{
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 : objects_to_bake) {
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)) {
continue;
}
const bake::NodeCache &node_cache = *modifier_cache.cache_by_id.lookup(
zone_bake_data.zone_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 = zone_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, *zone_bake_data.blob_sharing, meta_file);
}
}
}
*progress += progress_per_frame;
*do_update = true;
}
for (ObjectBakeData &object_bake_data : objects_to_bake) {
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<bake::NodeCache> &node_cache = nmd.runtime->cache->cache_by_id.lookup(
zone_bake_data.zone_id))
{
/* 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 bake_simulation_exec(bContext *C, 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<BakeSimulationJob>(__func__);
job->wm = wm;
job->bmain = bmain;
job->depsgraph = depsgraph;
job->scene = scene;
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),
"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;
return start_bake_job(C, std::move(objects_to_bake), op);
}
struct PathStringHash {
@ -601,10 +640,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::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 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(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) {
@ -626,42 +707,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<NodesModifierData *>(md);
if (!nmd->runtime->cache) {
continue;
}
for (auto item : nmd->runtime->cache->cache_by_id.items()) {
item.value->reset();
const std::optional<bake::BakePath> 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<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);
for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) {
try_delete_bake(C, *object, *nmd, bake.id, op->reports);
}
}
}
@ -674,6 +721,102 @@ static int delete_baked_simulation_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static int geometry_nodes_bake_node_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 geometry_nodes_bake_node_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 geometry_nodes_delete_bake_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)
@ -725,3 +868,55 @@ 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_geometry_nodes_bake_node(wmOperatorType *ot)
{
using namespace blender::ed::object::bake_simulation;
ot->name = "Bake Single Geometry Node";
ot->description = "Bake a single geometry node";
ot->idname = "OBJECT_OT_geometry_nodes_bake_node";
ot->exec = geometry_nodes_bake_node_exec;
ot->modal = geometry_nodes_bake_node_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", "ID of the node to bake", 0, INT32_MAX);
}
void OBJECT_OT_geometry_nodes_delete_bake(wmOperatorType *ot)
{
using namespace blender::ed::object::bake_simulation;
ot->name = "Delete Baked Data";
ot->description = "Delete baked data of a single geometry node";
ot->idname = "OBJECT_OT_geometry_nodes_delete_bake";
ot->exec = geometry_nodes_delete_bake_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);
}

View File

@ -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_geometry_nodes_bake_node(wmOperatorType *ot);
void OBJECT_OT_geometry_nodes_delete_bake(wmOperatorType *ot);
/* `object_random.cc` */

View File

@ -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_geometry_nodes_bake_node);
WM_operatortype_append(OBJECT_OT_geometry_nodes_delete_bake);
WM_operatortype_append(OBJECT_OT_drop_named_material);
WM_operatortype_append(OBJECT_OT_drop_geometry_nodes);
WM_operatortype_append(OBJECT_OT_unlink_data);

View File

@ -1017,7 +1017,7 @@ static int screen_render_invoke(bContext *C, wmOperator *op, const wmEvent *even
/* cancel animation playback */
if (ED_screen_animation_playing(CTX_wm_manager(C))) {
ED_screen_animation_play(C, 0, 0);
ED_screen_animation_play(C, 0, 0, true);
}
/* handle UI stuff */

View File

@ -15,6 +15,7 @@ set(INC
../../gpu
../../imbuf
../../makesrna
../../modifiers
../../sequencer
../../windowmanager
# RNA_prototypes.h

View File

@ -1679,7 +1679,8 @@ ScrArea *ED_screen_temp_space_open(bContext *C,
return area;
}
void ED_screen_animation_timer(bContext *C, int redraws, int sync, int enable)
void ED_screen_animation_timer(
bContext *C, int redraws, int sync, int enable, const bool fix_start_frame)
{
bScreen *screen = CTX_wm_screen(C);
wmWindowManager *wm = CTX_wm_manager(C);
@ -1699,24 +1700,26 @@ void ED_screen_animation_timer(bContext *C, int redraws, int sync, int enable)
screen->animtimer = WM_event_timer_add(wm, win, TIMER0, (1.0 / FPS));
sad->region = CTX_wm_region(C);
/* If start-frame is larger than current frame, we put current-frame on start-frame.
* NOTE(ton): first frame then is not drawn! */
if (PRVRANGEON) {
if (scene->r.psfra > scene->r.cfra) {
sad->sfra = scene->r.cfra;
scene->r.cfra = scene->r.psfra;
if (fix_start_frame) {
/* If start-frame is larger than current frame, we put current-frame on start-frame.
* NOTE(ton): first frame then is not drawn! */
if (PRVRANGEON) {
if (scene->r.psfra > scene->r.cfra) {
sad->sfra = scene->r.cfra;
scene->r.cfra = scene->r.psfra;
}
else {
sad->sfra = scene->r.cfra;
}
}
else {
sad->sfra = scene->r.cfra;
}
}
else {
if (scene->r.sfra > scene->r.cfra) {
sad->sfra = scene->r.cfra;
scene->r.cfra = scene->r.sfra;
}
else {
sad->sfra = scene->r.cfra;
if (scene->r.sfra > scene->r.cfra) {
sad->sfra = scene->r.cfra;
scene->r.cfra = scene->r.sfra;
}
else {
sad->sfra = scene->r.cfra;
}
}
}
sad->redraws = redraws;

View File

@ -26,21 +26,25 @@
#include "DNA_mask_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meta_types.h"
#include "DNA_modifier_types.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_userdef_types.h"
#include "DNA_workspace_types.h"
#include "BKE_bake_geometry_nodes_modifier.hh"
#include "BKE_callbacks.h"
#include "BKE_context.h"
#include "BKE_editmesh.h"
#include "BKE_fcurve.h"
#include "BKE_global.h"
#include "BKE_icons.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_mask.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_report.h"
#include "BKE_scene.h"
@ -81,6 +85,8 @@
#include "GPU_capabilities.h"
#include "MOD_nodes.hh"
#include "screen_intern.h" /* own module include */
#define KM_MODAL_CANCEL 1
@ -4952,7 +4958,7 @@ bScreen *ED_screen_animation_no_scrub(const wmWindowManager *wm)
return nullptr;
}
int ED_screen_animation_play(bContext *C, int sync, int mode)
int ED_screen_animation_play(bContext *C, int sync, int mode, const bool fix_start_frame)
{
bScreen *screen = CTX_wm_screen(C);
Scene *scene = CTX_data_scene(C);
@ -4962,7 +4968,7 @@ int ED_screen_animation_play(bContext *C, int sync, int mode)
if (ED_screen_animation_playing(CTX_wm_manager(C))) {
/* stop playback now */
ED_screen_animation_timer(C, 0, 0, 0);
ED_screen_animation_timer(C, 0, 0, 0, false);
ED_scene_fps_average_clear(scene);
BKE_sound_stop_scene(scene_eval);
@ -4984,7 +4990,7 @@ int ED_screen_animation_play(bContext *C, int sync, int mode)
BKE_sound_play_scene(scene_eval);
}
ED_screen_animation_timer(C, screen->redraws_flag, sync, mode);
ED_screen_animation_timer(C, screen->redraws_flag, sync, mode, fix_start_frame);
ED_scene_fps_average_clear(scene);
if (screen->animtimer) {
@ -5007,7 +5013,7 @@ static int screen_animation_play_exec(bContext *C, wmOperator *op)
sync = RNA_boolean_get(op->ptr, "sync");
}
return ED_screen_animation_play(C, sync, mode);
return ED_screen_animation_play(C, sync, mode, true);
}
static void SCREEN_OT_animation_play(wmOperatorType *ot)
@ -5033,6 +5039,94 @@ static void SCREEN_OT_animation_play(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Animation Playback with Preroll Operator
* \{ */
static int screen_animation_play_with_preroll_exec(bContext *C, wmOperator * /*op*/)
{
namespace bake = blender::bke::bake;
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
std::optional<int> playback_start_frame;
FOREACH_OBJECT_BEGIN (scene, view_layer, ob) {
LISTBASE_FOREACH (const ModifierData *, md, &ob->modifiers) {
if (md->type != eModifierType_Nodes) {
continue;
}
const NodesModifierData &nmd = *reinterpret_cast<const NodesModifierData *>(md);
if (!nmd.runtime->cache) {
continue;
}
const bake::ModifierCache &modifier_cache = *nmd.runtime->cache;
std::lock_guard lock{modifier_cache.mutex};
for (const NodesModifierBake &bake : blender::Span(nmd.bakes, nmd.bakes_num)) {
const std::unique_ptr<bake::NodeCache> *node_cache_ptr =
modifier_cache.cache_by_id.lookup_ptr(bake.id);
const bool cache_is_invalid = (node_cache_ptr == nullptr) ||
(*node_cache_ptr)->cache_status ==
bake::CacheStatus::Invalid ||
(*node_cache_ptr)->frame_caches.is_empty();
if (!cache_is_invalid) {
continue;
}
const std::optional<blender::IndexRange> frame_range = bake::get_node_bake_frame_range(
*scene, *ob, nmd, bake.id);
if (!frame_range.has_value()) {
continue;
}
const int sim_start_frame = frame_range->start();
if (playback_start_frame) {
if (sim_start_frame < *playback_start_frame) {
playback_start_frame = sim_start_frame;
}
}
else {
playback_start_frame = sim_start_frame;
}
}
}
}
FOREACH_OBJECT_END;
const bool is_playing = ED_screen_animation_playing(CTX_wm_manager(C));
if (playback_start_frame.has_value()) {
if (wmTimer *animtimer = CTX_wm_screen(C)->animtimer) {
ScreenAnimData *sad = static_cast<ScreenAnimData *>(animtimer->customdata);
sad->flag |= ANIMPLAY_FLAG_USE_NEXT_FRAME;
sad->nextfra = *playback_start_frame;
}
else {
scene->r.cfra = *playback_start_frame;
DEG_id_tag_update(&scene->id, ID_RECALC_FRAME_CHANGE);
WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
}
}
if (!is_playing) {
return ED_screen_animation_play(C, ANIMPLAY_FLAG_NO_SYNC, 1, false);
}
return OPERATOR_FINISHED;
}
static void SCREEN_OT_animation_play_with_preroll(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Play Animation with Preroll";
ot->description = "Play animation starting at the earliest invalidated simulation";
ot->idname = "SCREEN_OT_animation_play_with_preroll";
/* api callbacks */
ot->exec = screen_animation_play_with_preroll_exec;
ot->poll = ED_operator_screenactive_norender;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Animation Cancel Operator
* \{ */
@ -5055,7 +5149,7 @@ static int screen_animation_cancel_exec(bContext *C, wmOperator *op)
}
/* call the other "toggling" operator to clean up now */
ED_screen_animation_play(C, 0, 0);
ED_screen_animation_play(C, 0, 0, false);
}
return OPERATOR_PASS_THROUGH;
@ -5891,6 +5985,7 @@ void ED_operatortypes_screen()
WM_operatortype_append(SCREEN_OT_animation_step);
WM_operatortype_append(SCREEN_OT_animation_play);
WM_operatortype_append(SCREEN_OT_animation_play_with_preroll);
WM_operatortype_append(SCREEN_OT_animation_cancel);
/* New/delete. */

View File

@ -748,56 +748,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<SimulationRange> 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<int> 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<int> 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<int> 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<blender::bke::bake::FrameCache> &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)
@ -832,6 +888,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<SimulationRange> simulation_ranges;
bool all_simulations_baked = true;
LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) {
if (md->type != eModifierType_Nodes) {
continue;
@ -846,10 +904,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<blender::bke::bake::NodeCache> &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);

View File

@ -241,6 +241,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<int32_t> find_nested_node_id_in_root(const SpaceNode &snode, const bNode &query_node)
{
BLI_assert(snode.edittree->runtime->nodes_by_id.contains(const_cast<bNode *>(&query_node)));
std::optional<int32_t> 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;
}
/* ******************** default callbacks for node space ***************** */
static SpaceLink *node_create(const ScrArea * /*area*/, const Scene * /*scene*/)

View File

@ -38,6 +38,7 @@ set(SRC
space_outliner.cc
tree/common.cc
tree/tree_display.cc
tree/tree_display_bakes.cc
tree/tree_display_data.cc
tree/tree_display_libraries.cc
tree/tree_display_orphaned.cc

View File

@ -3993,7 +3993,8 @@ void draw_outliner(const bContext *C)
SO_LIBRARIES,
SO_OVERRIDES_LIBRARY,
SO_DATA_API,
SO_ID_ORPHANS) &&
SO_ID_ORPHANS,
SO_BAKES) &&
space_outliner->flag & SO_SYNC_SELECT)
{
outliner_sync_selection(C, space_outliner);

View File

@ -360,7 +360,8 @@ void ED_outliner_select_sync_from_outliner(bContext *C, SpaceOutliner *space_out
SO_LIBRARIES,
SO_OVERRIDES_LIBRARY,
SO_DATA_API,
SO_ID_ORPHANS))
SO_ID_ORPHANS,
SO_BAKES))
{
return;
}

View File

@ -313,6 +313,7 @@ float outliner_right_columns_width(const SpaceOutliner *space_outliner)
case SO_DATA_API:
case SO_SEQUENCE:
case SO_LIBRARIES:
case SO_BAKES:
return 0.0f;
case SO_OVERRIDES_LIBRARY:
switch ((eSpaceOutliner_LibOverrideViewMode)space_outliner->lib_override_view_mode) {

View File

@ -41,6 +41,8 @@ std::unique_ptr<AbstractTreeDisplay> AbstractTreeDisplay::create_from_display_mo
break;
case SO_VIEW_LAYER:
return std::make_unique<TreeDisplayViewLayer>(space_outliner);
case SO_BAKES:
return std::make_unique<TreeDisplayBakes>(space_outliner);
}
BLI_assert_unreachable();

View File

@ -287,4 +287,14 @@ class TreeDisplayDataAPI final : public AbstractTreeDisplay {
bool is_lazy_built() const override;
};
/* -------------------------------------------------------------------- */
/* Data API Tree-Display */
class TreeDisplayBakes final : public AbstractTreeDisplay {
public:
TreeDisplayBakes(SpaceOutliner &space_outliner);
ListBase build_tree(const TreeSourceData &source_data) override;
};
} // namespace blender::ed::outliner

View File

@ -0,0 +1,36 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup spoutliner
*/
#include "DNA_ID.h"
#include "DNA_space_types.h"
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
#include "BKE_main.h"
#include "../outliner_intern.hh"
#include "common.hh"
#include "tree_display.hh"
#include "tree_element.hh"
namespace blender::ed::outliner {
TreeDisplayBakes::TreeDisplayBakes(SpaceOutliner &space_outliner)
: AbstractTreeDisplay(space_outliner)
{
}
ListBase TreeDisplayBakes::build_tree(const TreeSourceData & /*source_data*/)
{
ListBase tree{};
this->add_element(&tree, nullptr, (void *)"Hello World", nullptr, TSE_GENERIC_LABEL, 0);
return tree;
}
} // namespace blender::ed::outliner

View File

@ -2325,6 +2325,21 @@ 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;
char *directory;
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 +2348,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 {

View File

@ -716,6 +716,7 @@ typedef struct bNodeTree {
const bNestedNodeRef *nested_node_ref_from_node_id_path(blender::Span<int> node_ids) const;
[[nodiscard]] bool node_id_path_from_nested_node_ref(const int32_t nested_node_id,
blender::Vector<int32_t> &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

View File

@ -266,6 +266,8 @@
.eevee = _DNA_DEFAULT_SceneEEVEE, \
\
.hydra = _DNA_DEFAULT_SceneHydra, \
.simulation_frame_start = 1, \
.simulation_frame_end = 250, \
}
/** \} */

View File

@ -2052,6 +2052,9 @@ typedef struct Scene {
/** Settings to be override by work-spaces. */
IDProperty *layer_properties;
int simulation_frame_start;
int simulation_frame_end;
struct SceneDisplay display;
struct SceneEEVEE eevee;
struct SceneGpencil grease_pencil_settings;
@ -2491,6 +2494,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. */

View File

@ -410,6 +410,7 @@ typedef enum eSpaceOutliner_Mode {
SO_ID_ORPHANS = 14,
SO_VIEW_LAYER = 15,
SO_OVERRIDES_LIBRARY = 16,
SO_BAKES = 17,
} eSpaceOutliner_Mode;
/** #SpaceOutliner.outlinevis */

View File

@ -7050,11 +7050,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, "Auto 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");
@ -7075,6 +7121,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);

View File

@ -8373,6 +8373,26 @@ 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 running");
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 running");
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);

View File

@ -3710,6 +3710,7 @@ static void rna_def_space_outliner(BlenderRNA *brna)
ICON_ORPHAN_DATA,
"Orphan Data",
"Display data-blocks which are unused and/or will be lost when the file is reloaded"},
{SO_BAKES, "BAKES", ICON_PHYSICS, "Bakes", "Display bake-able nodes"},
{0, nullptr, 0, nullptr, nullptr},
};

View File

@ -383,11 +383,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);
@ -411,10 +408,113 @@ 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>();
}
bake::ModifierCache &modifier_cache = *nmd.runtime->cache;
std::lock_guard lock{modifier_cache.mutex};
Map<int, std::unique_ptr<bake::NodeCache>> &old_cache_by_id = modifier_cache.cache_by_id;
Map<int, std::unique_ptr<bake::NodeCache>> new_cache_by_id;
for (const NodesModifierBake &bake : Span{nmd.bakes, nmd.bakes_num}) {
std::unique_ptr<bake::NodeCache> node_cache;
std::unique_ptr<bake::NodeCache> *old_node_cache_ptr = old_cache_by_id.lookup_ptr(bake.id);
if (old_node_cache_ptr == nullptr) {
node_cache = std::make_unique<bake::NodeCache>();
}
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<int, const NodesModifierBake *> old_bake_by_id;
for (const NodesModifierBake &bake : Span(nmd.bakes, nmd.bakes_num)) {
old_bake_by_id.add(bake.id, &bake);
}
Vector<int> 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 = static_cast<NodesModifierBake *>(
MEM_callocN(sizeof(NodesModifierBake) * new_bake_ids.size(), __func__));
for (const int i : new_bake_ids.index_range()) {
const int id = new_bake_ids[i];
const 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;
if (new_bake.directory) {
new_bake.directory = BLI_strdup(new_bake.directory);
}
}
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<NodesModifierBake *>(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(
@ -678,13 +778,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)
@ -694,8 +794,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();
@ -715,14 +814,34 @@ class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams {
}
}
}
/* Reset cached data if necessary. */
if (is_start_frame_) {
for (std::unique_ptr<bake::NodeCache> &node_cache : modifier_cache_->cache_by_id.values())
{
if (node_cache->cache_status == bake::CacheStatus::Invalid) {
node_cache->reset();
}
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<IndexRange> 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();
}
}
}
for (const std::unique_ptr<bake::NodeCache> &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;
}
}
}
@ -751,8 +870,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<bake::NodeCache>(); });
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) {
@ -793,25 +919,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<sim_input::OutputCopy>();
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);
@ -1680,6 +1812,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) {
@ -1706,6 +1843,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<NodesModifierRuntime>(__func__);
nmd->runtime->cache = std::make_shared<bake::ModifierCache>();
}
@ -1717,6 +1860,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<NodesModifierBake *>(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<NodesModifierRuntime>(__func__);
if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) {
@ -1746,6 +1899,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);
}

View File

@ -16,6 +16,7 @@ set(INC
../../gpu
../../imbuf
../../makesrna
../../modifiers
../../render
../../windowmanager
../../../../extern/fmtlib/include

View File

@ -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,150 @@ 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<bNode *>(ptr->data);
Scene *scene = CTX_data_scene(C);
SpaceNode *snode = CTX_wm_space_node(C);
if (snode == nullptr) {
return;
}
if (snode->id == nullptr) {
return;
}
if (GS(snode->id->name) != ID_OB) {
return;
}
Object *object = reinterpret_cast<Object *>(snode->id);
/* TODO: Better handle pinned node tree. */
ModifierData *md = BKE_object_active_modifier(object);
if (md == nullptr || md->type != eModifierType_Nodes) {
return;
}
NodesModifierData &nmd = *reinterpret_cast<NodesModifierData *>(md);
if (nmd.node_group != snode->nodetree) {
return;
}
const std::optional<int32_t> 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(&object->id, &RNA_NodesModifierBake, (void *)bake);
const std::optional<IndexRange> simulation_range = bke::bake::get_node_bake_frame_range(
*scene, *object, nmd, *bake_id);
std::optional<IndexRange> 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<bke::bake::NodeCache> *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_geometry_nodes_bake_node",
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_geometry_nodes_delete_bake",
"",
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 +1039,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);
}

View File

@ -3944,7 +3944,7 @@ void wm_event_do_handlers(bContext *C)
CTX_wm_screen_set(C, screen);
CTX_data_scene_set(C, scene);
ED_screen_animation_play(C, -1, 1);
ED_screen_animation_play(C, -1, 1, true);
CTX_data_scene_set(C, scene_ctx);
CTX_wm_screen_set(C, screen_stx);