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
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<int32_t> find_nested_node_id_in_root(const SpaceNode &snode, const bNode &node);
|
||||
|
||||
struct ObjectAndModifier {
|
||||
const Object *object;
|
||||
const NodesModifierData *nmd;
|
||||
|
||||
@@ -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<NodesModifierData *>(md);
|
||||
if (!nmd->runtime->cache) {
|
||||
continue;
|
||||
}
|
||||
for (auto item : nmd->runtime->cache->cache_by_id.items()) {
|
||||
if (item.value->cache_status != bake::CacheStatus::Baked) {
|
||||
item.value->reset();
|
||||
}
|
||||
if (md->type != eModifierType_Nodes) {
|
||||
continue;
|
||||
}
|
||||
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
||||
if (!nmd->runtime->cache) {
|
||||
continue;
|
||||
}
|
||||
for (auto item : nmd->runtime->cache->cache_by_id.items()) {
|
||||
if (item.value->cache_status != bake::CacheStatus::Baked) {
|
||||
item.value->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<bake::BlobSharing> blob_sharing;
|
||||
};
|
||||
|
||||
struct ModifierBakeData {
|
||||
NodesModifierData *nmd;
|
||||
Vector<ZoneBakeData> zones;
|
||||
Vector<NodeBakeData> nodes;
|
||||
};
|
||||
|
||||
struct ObjectBakeData {
|
||||
@@ -243,7 +247,7 @@ struct BakeSimulationJob {
|
||||
Main *bmain;
|
||||
Depsgraph *depsgraph;
|
||||
Scene *scene;
|
||||
Vector<Object *> objects;
|
||||
Vector<ObjectBakeData> 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<ObjectBakeData> 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<NodesModifierData *>(md);
|
||||
if (!nmd->node_group) {
|
||||
continue;
|
||||
}
|
||||
if (!nmd->runtime->cache) {
|
||||
continue;
|
||||
}
|
||||
ModifierBakeData modifier_bake_data;
|
||||
modifier_bake_data.nmd = nmd;
|
||||
|
||||
for (auto item : nmd->runtime->cache->cache_by_id.items()) {
|
||||
item.value->reset();
|
||||
}
|
||||
|
||||
for (const bNestedNodeRef &nested_node_ref : nmd->node_group->nested_node_refs_span()) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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<bake::NodeCache> &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<bake::NodeCache> *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<ObjectBakeData> 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<BakeSimulationJob>(__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<ObjectBakeData> collect_nodes_to_bake(Main &bmain,
|
||||
Scene &scene,
|
||||
const Span<Object *> objects)
|
||||
{
|
||||
Vector<ObjectBakeData> objects_to_bake;
|
||||
for (Object *object : objects) {
|
||||
if (!BKE_id_is_editable(&bmain, &object->id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ObjectBakeData bake_data;
|
||||
bake_data.object = object;
|
||||
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
|
||||
if (md->type != eModifierType_Nodes) {
|
||||
continue;
|
||||
}
|
||||
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
|
||||
if (!nmd->node_group) {
|
||||
continue;
|
||||
}
|
||||
if (!nmd->runtime->cache) {
|
||||
continue;
|
||||
}
|
||||
ModifierBakeData modifier_bake_data;
|
||||
modifier_bake_data.nmd = nmd;
|
||||
|
||||
for (auto item : nmd->runtime->cache->cache_by_id.items()) {
|
||||
item.value->reset();
|
||||
}
|
||||
|
||||
for (const bNestedNodeRef &nested_node_ref : nmd->node_group->nested_node_refs_span()) {
|
||||
NodeBakeData node_bake_data;
|
||||
node_bake_data.id = nested_node_ref.id;
|
||||
node_bake_data.blob_sharing = std::make_unique<bake::BlobSharing>();
|
||||
std::optional<bake::BakePath> path = bake::get_node_bake_path(
|
||||
bmain, *object, *nmd, nested_node_ref.id);
|
||||
if (!path) {
|
||||
continue;
|
||||
}
|
||||
std::optional<IndexRange> frame_range = bake::get_node_bake_frame_range(
|
||||
scene, *object, *nmd, nested_node_ref.id);
|
||||
if (!frame_range) {
|
||||
continue;
|
||||
}
|
||||
node_bake_data.path = std::move(*path);
|
||||
node_bake_data.frame_start = frame_range->first();
|
||||
node_bake_data.frame_end = frame_range->last();
|
||||
|
||||
modifier_bake_data.nodes.append(std::move(node_bake_data));
|
||||
}
|
||||
if (modifier_bake_data.nodes.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
bake_data.modifiers.append(std::move(modifier_bake_data));
|
||||
}
|
||||
if (bake_data.modifiers.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
objects_to_bake.append(std::move(bake_data));
|
||||
}
|
||||
return objects_to_bake;
|
||||
}
|
||||
|
||||
static int bake_simulation_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
Main *bmain = CTX_data_main(C);
|
||||
|
||||
Vector<Object *> objects;
|
||||
if (RNA_boolean_get(op->ptr, "selected")) {
|
||||
CTX_DATA_BEGIN (C, Object *, object, selected_objects) {
|
||||
objects.append(object);
|
||||
}
|
||||
CTX_DATA_END;
|
||||
}
|
||||
else {
|
||||
if (Object *object = CTX_data_active_object(C)) {
|
||||
objects.append(object);
|
||||
}
|
||||
}
|
||||
|
||||
Vector<ObjectBakeData> objects_to_bake = collect_nodes_to_bake(*bmain, *scene, objects);
|
||||
return start_bake_job(C, std::move(objects_to_bake), op);
|
||||
}
|
||||
|
||||
struct PathStringHash {
|
||||
uint64_t operator()(const StringRef s) const
|
||||
{
|
||||
@@ -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::BakePath> bake_path = bake::get_node_bake_path(
|
||||
*bmain, object, nmd, bake_id);
|
||||
if (!bake_path) {
|
||||
return;
|
||||
}
|
||||
const char *meta_dir = bake_path->meta_dir.c_str();
|
||||
if (BLI_exists(meta_dir)) {
|
||||
if (BLI_delete(meta_dir, true, true)) {
|
||||
BKE_reportf(reports, RPT_ERROR, "Failed to remove metadata directory %s", meta_dir);
|
||||
}
|
||||
}
|
||||
const char *blobs_dir = bake_path->blobs_dir.c_str();
|
||||
if (BLI_exists(blobs_dir)) {
|
||||
if (BLI_delete(blobs_dir, true, true)) {
|
||||
BKE_reportf(reports, RPT_ERROR, "Failed to remove blobs directory %s", blobs_dir);
|
||||
}
|
||||
}
|
||||
if (bake_path->bake_dir.has_value()) {
|
||||
const char *zone_bake_dir = bake_path->bake_dir->c_str();
|
||||
/* Try to delete zone bake directory if it is empty. */
|
||||
BLI_delete(zone_bake_dir, true, false);
|
||||
}
|
||||
if (const std::optional<std::string> modifier_bake_dir = bake::get_modifier_bake_path(
|
||||
*bmain, object, nmd))
|
||||
{
|
||||
/* Try to delete modifier bake directory if it is empty. */
|
||||
BLI_delete(modifier_bake_dir->c_str(), true, false);
|
||||
}
|
||||
}
|
||||
|
||||
static int delete_baked_simulation_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Vector<Object *> objects;
|
||||
if (RNA_boolean_get(op->ptr, "selected")) {
|
||||
CTX_DATA_BEGIN (C, Object *, object, selected_objects) {
|
||||
@@ -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<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 +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<Object *>(
|
||||
WM_operator_properties_id_lookup_from_name_or_session_uuid(bmain, op->ptr, ID_OB));
|
||||
if (object == nullptr) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
char *modifier_name = RNA_string_get_alloc(op->ptr, "modifier_name", nullptr, 0, nullptr);
|
||||
if (modifier_name == nullptr) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(modifier_name); });
|
||||
|
||||
ModifierData *md = BKE_modifiers_findby_name(object, modifier_name);
|
||||
if (md == nullptr) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
NodesModifierData &nmd = *reinterpret_cast<NodesModifierData *>(md);
|
||||
|
||||
if (StringRef(nmd.simulation_bake_directory).is_empty()) {
|
||||
const std::string directory = bake::get_default_modifier_bake_directory(*bmain, *object, nmd);
|
||||
nmd.simulation_bake_directory = BLI_strdup(directory.c_str());
|
||||
}
|
||||
|
||||
const int bake_id = RNA_int_get(op->ptr, "bake_id");
|
||||
const std::optional<bake::BakePath> bake_path = bake::get_node_bake_path(
|
||||
*bmain, *object, nmd, bake_id);
|
||||
if (!bake_path.has_value()) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
const std::optional<IndexRange> frame_range = bake::get_node_bake_frame_range(
|
||||
*scene, *object, nmd, bake_id);
|
||||
if (!frame_range.has_value()) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
if (frame_range->is_empty()) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
NodeBakeData node_bake_data;
|
||||
node_bake_data.id = bake_id;
|
||||
node_bake_data.path = std::move(*bake_path);
|
||||
node_bake_data.frame_start = frame_range->first();
|
||||
node_bake_data.frame_end = frame_range->last();
|
||||
node_bake_data.blob_sharing = std::make_unique<bake::BlobSharing>();
|
||||
|
||||
ModifierBakeData modifier_bake_data;
|
||||
modifier_bake_data.nmd = &nmd;
|
||||
modifier_bake_data.nodes.append(std::move(node_bake_data));
|
||||
|
||||
ObjectBakeData object_bake_data;
|
||||
object_bake_data.object = object;
|
||||
object_bake_data.modifiers.append(std::move(modifier_bake_data));
|
||||
|
||||
Vector<ObjectBakeData> objects_to_bake;
|
||||
objects_to_bake.append(std::move(object_bake_data));
|
||||
return start_bake_job(C, std::move(objects_to_bake), op);
|
||||
}
|
||||
|
||||
static int bake_single_simulation_modal(bContext *C,
|
||||
wmOperator * /*op*/,
|
||||
const wmEvent * /*event*/)
|
||||
{
|
||||
if (!WM_jobs_test(CTX_wm_manager(C), CTX_data_scene(C), WM_JOB_TYPE_BAKE_SIMULATION_NODES)) {
|
||||
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
|
||||
}
|
||||
return OPERATOR_PASS_THROUGH;
|
||||
}
|
||||
|
||||
static int delete_baked_simulation_single_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Object *object = reinterpret_cast<Object *>(
|
||||
WM_operator_properties_id_lookup_from_name_or_session_uuid(bmain, op->ptr, ID_OB));
|
||||
if (object == nullptr) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
char *modifier_name = RNA_string_get_alloc(op->ptr, "modifier_name", nullptr, 0, nullptr);
|
||||
if (modifier_name == nullptr) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(modifier_name); });
|
||||
|
||||
ModifierData *md = BKE_modifiers_findby_name(object, modifier_name);
|
||||
if (md == nullptr) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
NodesModifierData &nmd = *reinterpret_cast<NodesModifierData *>(md);
|
||||
const int bake_id = RNA_int_get(op->ptr, "bake_id");
|
||||
|
||||
try_delete_bake(C, *object, nmd, bake_id, op->reports);
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
} // namespace blender::ed::object::bake_simulation
|
||||
|
||||
void OBJECT_OT_simulation_nodes_cache_calculate_to_frame(wmOperatorType *ot)
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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` */
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<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)
|
||||
@@ -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<SimulationRange> 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<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);
|
||||
|
||||
@@ -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<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;
|
||||
}
|
||||
|
||||
std::optional<ObjectAndModifier> get_modifier_for_node_editor(const SpaceNode &snode)
|
||||
{
|
||||
if (snode.id == nullptr) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -266,6 +266,8 @@
|
||||
.eevee = _DNA_DEFAULT_SceneEEVEE, \
|
||||
\
|
||||
.hydra = _DNA_DEFAULT_SceneHydra, \
|
||||
.simulation_frame_start = 1, \
|
||||
.simulation_frame_end = 250, \
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
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, NodesModifierBake *> old_bake_by_id;
|
||||
for (NodesModifierBake &bake : MutableSpan(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 = MEM_cnew_array<NodesModifierBake>(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<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(
|
||||
@@ -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<bake::NodeCache> &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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<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) {
|
||||
@@ -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<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);
|
||||
@@ -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<NodesModifierRuntime>(__func__);
|
||||
nmd->runtime->cache = std::make_shared<bake::ModifierCache>();
|
||||
}
|
||||
@@ -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<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) {
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ set(INC
|
||||
../../gpu
|
||||
../../imbuf
|
||||
../../makesrna
|
||||
../../modifiers
|
||||
../../render
|
||||
../../windowmanager
|
||||
../../../../extern/fmtlib/include
|
||||
|
||||
@@ -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<bNode *>(ptr->data);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
SpaceNode *snode = CTX_wm_space_node(C);
|
||||
if (snode == nullptr) {
|
||||
return;
|
||||
}
|
||||
std::optional<ed::space_node::ObjectAndModifier> 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<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(
|
||||
const_cast<ID *>(&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_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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user