Geometry Nodes: support caching imported files
Currently, the import nodes always reimport on each evaluation. This patch adds support for caching the loaded geometries. This is integrated with `BLI_memory_cache.hh` and thus also takes the cache size limit into account. If an imported file is modified on disk, the cache is invalidated. However, Geometry Nodes will not automatically reevaluate when a file changes, so the user would have to trigger the evaluation in some other way. This is an alternative solution to #124369. The main benefits are that the cache invalidation happens automatically and that the cache system is more general and does not have to know about e.g. the different file types. Caching speeds up node setups that heavily rely on import nodes significantly. Pull Request: https://projects.blender.org/blender/blender/pulls/138425
This commit is contained in:
48
source/blender/blenlib/BLI_generic_key_string.hh
Normal file
48
source/blender/blenlib/BLI_generic_key_string.hh
Normal file
@@ -0,0 +1,48 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_generic_key.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_struct_equality_utils.hh"
|
||||
#include "BLI_utility_mixins.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
/** Utility class that to easy create a #GenericKey from a string. */
|
||||
class GenericStringKey : public GenericKey, NonMovable {
|
||||
private:
|
||||
std::string value_;
|
||||
/** This may reference the string stored in value_. */
|
||||
StringRef value_ref_;
|
||||
|
||||
public:
|
||||
GenericStringKey(StringRef value) : value_ref_(value) {}
|
||||
|
||||
uint64_t hash() const override
|
||||
{
|
||||
return get_default_hash(value_ref_);
|
||||
}
|
||||
|
||||
BLI_STRUCT_EQUALITY_OPERATORS_1(GenericStringKey, value_ref_)
|
||||
|
||||
bool equal_to(const GenericKey &other) const override
|
||||
{
|
||||
if (const auto *other_typed = dynamic_cast<const GenericStringKey *>(&other)) {
|
||||
return value_ref_ == other_typed->value_ref_;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<GenericKey> to_storable() const override
|
||||
{
|
||||
auto storable_key = std::make_unique<GenericStringKey>("");
|
||||
storable_key->value_ = value_ref_;
|
||||
storable_key->value_ref_ = storable_key->value_;
|
||||
return storable_key;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender
|
||||
34
source/blender/blenlib/BLI_memory_cache_file_load.hh
Normal file
34
source/blender/blenlib/BLI_memory_cache_file_load.hh
Normal file
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_memory_cache.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
namespace blender::memory_cache {
|
||||
|
||||
/**
|
||||
* Call the given loader function if its result has not been cached yet. The cache key is a
|
||||
* combination of loader_key and file_paths. load_fn is responsible for still producing a valid
|
||||
* cache value even if a file is not found.
|
||||
*/
|
||||
template<typename T>
|
||||
std::shared_ptr<const T> get_loaded(const GenericKey &loader_key,
|
||||
Span<StringRefNull> file_paths,
|
||||
FunctionRef<std::unique_ptr<T>()> load_fn);
|
||||
|
||||
std::shared_ptr<CachedValue> get_loaded_base(const GenericKey &loader_key,
|
||||
Span<StringRefNull> file_paths,
|
||||
FunctionRef<std::unique_ptr<CachedValue>()> load_fn);
|
||||
|
||||
template<typename T>
|
||||
inline std::shared_ptr<const T> get_loaded(const GenericKey &loader_key,
|
||||
Span<StringRefNull> file_paths,
|
||||
FunctionRef<std::unique_ptr<T>()> load_fn)
|
||||
{
|
||||
return std::dynamic_pointer_cast<const T>(get_loaded_base(loader_key, file_paths, load_fn));
|
||||
}
|
||||
|
||||
} // namespace blender::memory_cache
|
||||
@@ -116,6 +116,7 @@ set(SRC
|
||||
intern/math_vector.cc
|
||||
intern/math_vector_inline.cc
|
||||
intern/memory_cache.cc
|
||||
intern/memory_cache_file_load.cc
|
||||
intern/memory_counter.cc
|
||||
intern/memory_utils.cc
|
||||
intern/mesh_boolean.cc
|
||||
@@ -239,6 +240,7 @@ set(SRC
|
||||
BLI_function_ref.hh
|
||||
BLI_generic_array.hh
|
||||
BLI_generic_key.hh
|
||||
BLI_generic_key_string.hh
|
||||
BLI_generic_pointer.hh
|
||||
BLI_generic_span.hh
|
||||
BLI_generic_value_map.hh
|
||||
@@ -325,6 +327,7 @@ set(SRC
|
||||
BLI_memblock.h
|
||||
BLI_memiter.h
|
||||
BLI_memory_cache.hh
|
||||
BLI_memory_cache_file_load.hh
|
||||
BLI_memory_counter.hh
|
||||
BLI_memory_counter_fwd.hh
|
||||
BLI_memory_utils.h
|
||||
|
||||
143
source/blender/blenlib/intern/memory_cache_file_load.cc
Normal file
143
source/blender/blenlib/intern/memory_cache_file_load.cc
Normal file
@@ -0,0 +1,143 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
#include "BLI_fileops.hh"
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_memory_cache_file_load.hh"
|
||||
#include "BLI_task.hh"
|
||||
#include "BLI_vector.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
namespace blender::memory_cache {
|
||||
|
||||
/**
|
||||
* A key used to identify data loaded from one or more files.
|
||||
*/
|
||||
class LoadFileKey : public GenericKey {
|
||||
private:
|
||||
/** The files to load from. */
|
||||
Vector<std::string> file_paths_;
|
||||
/**
|
||||
* The key used to identify the loader. The same files might be loaded with different loaders
|
||||
* which can result in different data that needs to be cached separately.
|
||||
*/
|
||||
std::shared_ptr<const GenericKey> loader_key_;
|
||||
|
||||
public:
|
||||
LoadFileKey(Vector<std::string> file_paths, std::shared_ptr<const GenericKey> loader_key)
|
||||
: file_paths_(std::move(file_paths)), loader_key_(std::move(loader_key))
|
||||
{
|
||||
}
|
||||
|
||||
Span<std::string> file_paths() const
|
||||
{
|
||||
return file_paths_;
|
||||
}
|
||||
|
||||
uint64_t hash() const override
|
||||
{
|
||||
return get_default_hash(file_paths_, *loader_key_);
|
||||
}
|
||||
|
||||
friend bool operator==(const LoadFileKey &a, const LoadFileKey &b)
|
||||
{
|
||||
return a.file_paths_ == b.file_paths_ && *a.loader_key_ == *b.loader_key_;
|
||||
}
|
||||
|
||||
bool equal_to(const GenericKey &other) const override
|
||||
{
|
||||
if (const auto *other_typed = dynamic_cast<const LoadFileKey *>(&other)) {
|
||||
return *this == *other_typed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<GenericKey> to_storable() const override
|
||||
{
|
||||
/* Currently #LoadFileKey is always storable, i.e. it owns all the data it references. A
|
||||
* potential future optimization could be to support just referencing the paths and loader key,
|
||||
* but that causes some boilerplate now that is not worth it. */
|
||||
return std::make_unique<LoadFileKey>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
static std::optional<int64_t> get_file_modification_time(const StringRefNull path)
|
||||
{
|
||||
BLI_stat_t stat;
|
||||
if (BLI_stat(path.c_str(), &stat) == -1) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return stat.st_mtime;
|
||||
}
|
||||
|
||||
struct FileStatMap {
|
||||
std::mutex mutex;
|
||||
Map<std::string, std::optional<int64_t>> map;
|
||||
};
|
||||
|
||||
static FileStatMap &get_file_stat_map()
|
||||
{
|
||||
static FileStatMap file_stat_map;
|
||||
return file_stat_map;
|
||||
}
|
||||
|
||||
static void invalidate_outdated_caches_if_necessary(const Span<StringRefNull> file_paths)
|
||||
{
|
||||
FileStatMap &file_stat_map = get_file_stat_map();
|
||||
|
||||
/* Retrieve the file modification times before the lock because there is no need for the lock
|
||||
* yet. While not guaranteed, retrieving the modification time is often optimized by the OS so
|
||||
* that no actual access to the hard drive is necessary. */
|
||||
Array<std::optional<int64_t>> new_times(file_paths.size());
|
||||
for (const int i : file_paths.index_range()) {
|
||||
new_times[i] = get_file_modification_time(file_paths[i]);
|
||||
}
|
||||
|
||||
std::lock_guard lock{file_stat_map.mutex};
|
||||
|
||||
/* Find all paths that have changed on disk. */
|
||||
VectorSet<StringRefNull> outdated_paths;
|
||||
for (const int i : file_paths.index_range()) {
|
||||
const StringRefNull path = file_paths[i];
|
||||
const std::optional<int64_t> new_time = new_times[i];
|
||||
std::optional<int64_t> &old_time = file_stat_map.map.lookup_or_add_as(path, new_time);
|
||||
if (old_time != new_time) {
|
||||
outdated_paths.add(path);
|
||||
old_time = new_time;
|
||||
}
|
||||
}
|
||||
/* If any referenced file was changed, invalidate the caches that use it. */
|
||||
if (!outdated_paths.is_empty()) {
|
||||
/* Isolate because a mutex is locked. */
|
||||
threading::isolate_task([&]() {
|
||||
/* Invalidation is done while the mutex is locked so that other threads won't see the old
|
||||
* cached value anymore after we've detected that it's oudated. */
|
||||
memory_cache::remove_if([&](const GenericKey &other_key) {
|
||||
if (const auto *other_key_typed = dynamic_cast<const LoadFileKey *>(&other_key)) {
|
||||
const Span<std::string> other_key_paths = other_key_typed->file_paths();
|
||||
return std::any_of(
|
||||
other_key_paths.begin(), other_key_paths.end(), [&](const StringRefNull path) {
|
||||
return outdated_paths.contains(path);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<CachedValue> get_loaded_base(const GenericKey &loader_key,
|
||||
Span<StringRefNull> file_paths,
|
||||
FunctionRef<std::unique_ptr<CachedValue>()> load_fn)
|
||||
{
|
||||
invalidate_outdated_caches_if_necessary(file_paths);
|
||||
const LoadFileKey key{file_paths, loader_key.to_storable()};
|
||||
return memory_cache::get_base(key, load_fn);
|
||||
}
|
||||
|
||||
} // namespace blender::memory_cache
|
||||
@@ -50,6 +50,7 @@
|
||||
|
||||
struct SpaceNode;
|
||||
struct NodesModifierData;
|
||||
struct Report;
|
||||
|
||||
namespace blender::nodes::geo_eval_log {
|
||||
|
||||
@@ -69,6 +70,9 @@ struct NodeWarning {
|
||||
NodeWarningType type;
|
||||
std::string message;
|
||||
|
||||
NodeWarning(NodeWarningType type, StringRef message) : type(type), message(message) {}
|
||||
NodeWarning(const Report &report);
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(this->type, this->message);
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "BLI_generic_key_string.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_memory_cache_file_load.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BKE_report.hh"
|
||||
@@ -25,6 +29,17 @@ static void node_declare(NodeDeclarationBuilder &b)
|
||||
b.add_output<decl::Geometry>("Point Cloud");
|
||||
}
|
||||
|
||||
class LoadCsvCache : public memory_cache::CachedValue {
|
||||
public:
|
||||
GeometrySet geometry;
|
||||
Vector<geo_eval_log::NodeWarning> warnings;
|
||||
|
||||
void count_memory(MemoryCounter &counter) const override
|
||||
{
|
||||
this->geometry.count_memory(counter);
|
||||
}
|
||||
};
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
#ifdef WITH_IO_CSV
|
||||
@@ -47,31 +62,35 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
return;
|
||||
}
|
||||
|
||||
blender::io::csv::CSVImportParams import_params{};
|
||||
import_params.delimiter = delimiter[0];
|
||||
STRNCPY(import_params.filepath, path->c_str());
|
||||
/* Encode delimiter in key because it affects the result. */
|
||||
const std::string loader_key = fmt::format("import_csv_node_{}", delimiter[0]);
|
||||
std::shared_ptr<const LoadCsvCache> cached_value = memory_cache::get_loaded<LoadCsvCache>(
|
||||
GenericStringKey{loader_key}, {StringRefNull(*path)}, [&]() {
|
||||
blender::io::csv::CSVImportParams import_params{};
|
||||
import_params.delimiter = delimiter[0];
|
||||
STRNCPY(import_params.filepath, path->c_str());
|
||||
|
||||
ReportList reports;
|
||||
BKE_reports_init(&reports, RPT_STORE);
|
||||
BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); })
|
||||
import_params.reports = &reports;
|
||||
ReportList reports;
|
||||
BKE_reports_init(&reports, RPT_STORE);
|
||||
BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); });
|
||||
import_params.reports = &reports;
|
||||
|
||||
PointCloud *pointcloud = blender::io::csv::import_csv_as_pointcloud(import_params);
|
||||
PointCloud *pointcloud = blender::io::csv::import_csv_as_pointcloud(import_params);
|
||||
|
||||
LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) {
|
||||
NodeWarningType type;
|
||||
switch (report->type) {
|
||||
case RPT_ERROR:
|
||||
type = NodeWarningType::Error;
|
||||
break;
|
||||
default:
|
||||
type = NodeWarningType::Info;
|
||||
break;
|
||||
}
|
||||
params.error_message_add(type, TIP_(report->message));
|
||||
auto cached_value = std::make_unique<LoadCsvCache>();
|
||||
cached_value->geometry = GeometrySet::from_pointcloud(pointcloud);
|
||||
|
||||
LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) {
|
||||
cached_value->warnings.append_as(*report);
|
||||
}
|
||||
return cached_value;
|
||||
});
|
||||
|
||||
for (const geo_eval_log::NodeWarning &warning : cached_value->warnings) {
|
||||
params.error_message_add(warning.type, warning.message);
|
||||
}
|
||||
|
||||
params.set_output("Point Cloud", GeometrySet::from_pointcloud(pointcloud));
|
||||
params.set_output("Point Cloud", cached_value->geometry);
|
||||
#else
|
||||
params.error_message_add(NodeWarningType::Error,
|
||||
TIP_("Disabled, Blender was compiled without CSV I/O"));
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
#include "BLI_generic_key_string.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_memory_cache_file_load.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BKE_instances.hh"
|
||||
@@ -25,6 +27,17 @@ static void node_declare(NodeDeclarationBuilder &b)
|
||||
b.add_output<decl::Geometry>("Instances");
|
||||
}
|
||||
|
||||
class LoadObjCache : public memory_cache::CachedValue {
|
||||
public:
|
||||
GeometrySet geometry;
|
||||
Vector<geo_eval_log::NodeWarning> warnings;
|
||||
|
||||
void count_memory(MemoryCounter &counter) const override
|
||||
{
|
||||
this->geometry.count_memory(counter);
|
||||
}
|
||||
};
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
#ifdef WITH_IO_WAVEFRONT_OBJ
|
||||
@@ -35,42 +48,40 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
return;
|
||||
}
|
||||
|
||||
OBJImportParams import_params;
|
||||
STRNCPY(import_params.filepath, path->c_str());
|
||||
std::shared_ptr<const LoadObjCache> cached_value = memory_cache::get_loaded<LoadObjCache>(
|
||||
GenericStringKey{"import_obj_node"}, {StringRefNull(*path)}, [&]() {
|
||||
OBJImportParams import_params;
|
||||
STRNCPY(import_params.filepath, path->c_str());
|
||||
|
||||
ReportList reports;
|
||||
BKE_reports_init(&reports, RPT_STORE);
|
||||
BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); });
|
||||
import_params.reports = &reports;
|
||||
ReportList reports;
|
||||
BKE_reports_init(&reports, RPT_STORE);
|
||||
BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); });
|
||||
import_params.reports = &reports;
|
||||
|
||||
Vector<bke::GeometrySet> geometries;
|
||||
OBJ_import_geometries(&import_params, geometries);
|
||||
Vector<bke::GeometrySet> geometries;
|
||||
OBJ_import_geometries(&import_params, geometries);
|
||||
|
||||
LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) {
|
||||
NodeWarningType type;
|
||||
switch (report->type) {
|
||||
case RPT_ERROR:
|
||||
type = NodeWarningType::Error;
|
||||
break;
|
||||
default:
|
||||
type = NodeWarningType::Info;
|
||||
break;
|
||||
}
|
||||
params.error_message_add(type, TIP_(report->message));
|
||||
bke::Instances *instances = new bke::Instances();
|
||||
for (GeometrySet geometry : geometries) {
|
||||
const int handle = instances->add_reference(bke::InstanceReference{std::move(geometry)});
|
||||
instances->add_instance(handle, float4x4::identity());
|
||||
}
|
||||
|
||||
auto cached_value = std::make_unique<LoadObjCache>();
|
||||
cached_value->geometry = GeometrySet::from_instances(instances);
|
||||
|
||||
LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) {
|
||||
cached_value->warnings.append_as(*report);
|
||||
}
|
||||
|
||||
return cached_value;
|
||||
});
|
||||
|
||||
for (const geo_eval_log::NodeWarning &warning : cached_value->warnings) {
|
||||
params.error_message_add(warning.type, warning.message);
|
||||
}
|
||||
|
||||
if (geometries.is_empty()) {
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
|
||||
bke::Instances *instances = new bke::Instances();
|
||||
for (GeometrySet geometry : geometries) {
|
||||
const int handle = instances->add_reference(bke::InstanceReference{std::move(geometry)});
|
||||
instances->add_instance(handle, float4x4::identity());
|
||||
}
|
||||
|
||||
params.set_output("Instances", GeometrySet::from_instances(instances));
|
||||
params.set_output("Instances", cached_value->geometry);
|
||||
#else
|
||||
params.error_message_add(NodeWarningType::Error,
|
||||
TIP_("Disabled, Blender was compiled without OBJ I/O"));
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
#include "BLI_generic_key_string.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_memory_cache_file_load.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BKE_report.hh"
|
||||
@@ -24,6 +26,17 @@ static void node_declare(NodeDeclarationBuilder &b)
|
||||
b.add_output<decl::Geometry>("Mesh");
|
||||
}
|
||||
|
||||
class LoadPlyCache : public memory_cache::CachedValue {
|
||||
public:
|
||||
GeometrySet geometry;
|
||||
Vector<geo_eval_log::NodeWarning> warnings;
|
||||
|
||||
void count_memory(MemoryCounter &counter) const override
|
||||
{
|
||||
this->geometry.count_memory(counter);
|
||||
}
|
||||
};
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
#ifdef WITH_IO_PLY
|
||||
@@ -34,31 +47,33 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
return;
|
||||
}
|
||||
|
||||
PLYImportParams import_params;
|
||||
STRNCPY(import_params.filepath, path->c_str());
|
||||
import_params.import_attributes = true;
|
||||
std::shared_ptr<const LoadPlyCache> cached_value = memory_cache::get_loaded<LoadPlyCache>(
|
||||
GenericStringKey{"import_ply_node"}, {StringRefNull(*path)}, [&]() {
|
||||
PLYImportParams import_params;
|
||||
STRNCPY(import_params.filepath, path->c_str());
|
||||
import_params.import_attributes = true;
|
||||
|
||||
ReportList reports;
|
||||
BKE_reports_init(&reports, RPT_STORE);
|
||||
BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); })
|
||||
import_params.reports = &reports;
|
||||
ReportList reports;
|
||||
BKE_reports_init(&reports, RPT_STORE);
|
||||
BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); });
|
||||
import_params.reports = &reports;
|
||||
|
||||
Mesh *mesh = PLY_import_mesh(import_params);
|
||||
Mesh *mesh = PLY_import_mesh(import_params);
|
||||
|
||||
LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) {
|
||||
NodeWarningType type;
|
||||
switch (report->type) {
|
||||
case RPT_ERROR:
|
||||
type = NodeWarningType::Error;
|
||||
break;
|
||||
default:
|
||||
type = NodeWarningType::Info;
|
||||
break;
|
||||
}
|
||||
params.error_message_add(type, TIP_(report->message));
|
||||
auto cached_value = std::make_unique<LoadPlyCache>();
|
||||
cached_value->geometry = GeometrySet::from_mesh(mesh);
|
||||
|
||||
LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) {
|
||||
cached_value->warnings.append_as(*report);
|
||||
}
|
||||
return cached_value;
|
||||
});
|
||||
|
||||
for (const geo_eval_log::NodeWarning &warning : cached_value->warnings) {
|
||||
params.error_message_add(warning.type, warning.message);
|
||||
}
|
||||
|
||||
params.set_output("Mesh", GeometrySet::from_mesh(mesh));
|
||||
params.set_output("Mesh", cached_value->geometry);
|
||||
|
||||
#else
|
||||
params.error_message_add(NodeWarningType::Error,
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
#include "BLI_generic_key_string.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_memory_cache_file_load.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "DNA_mesh_types.h"
|
||||
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "IO_stl.hh"
|
||||
@@ -24,6 +28,17 @@ static void node_declare(NodeDeclarationBuilder &b)
|
||||
b.add_output<decl::Geometry>("Mesh");
|
||||
}
|
||||
|
||||
class LoadStlCache : public memory_cache::CachedValue {
|
||||
public:
|
||||
GeometrySet geometry;
|
||||
Vector<geo_eval_log::NodeWarning> warnings;
|
||||
|
||||
void count_memory(MemoryCounter &counter) const override
|
||||
{
|
||||
this->geometry.count_memory(counter);
|
||||
}
|
||||
};
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
#ifdef WITH_IO_STL
|
||||
@@ -34,33 +49,36 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
return;
|
||||
}
|
||||
|
||||
STLImportParams import_params;
|
||||
STRNCPY(import_params.filepath, path->c_str());
|
||||
std::shared_ptr<const LoadStlCache> cached_value = memory_cache::get_loaded<LoadStlCache>(
|
||||
GenericStringKey{"import_stl_node"}, {StringRefNull(*path)}, [&]() {
|
||||
STLImportParams import_params;
|
||||
STRNCPY(import_params.filepath, path->c_str());
|
||||
|
||||
import_params.forward_axis = IO_AXIS_NEGATIVE_Z;
|
||||
import_params.up_axis = IO_AXIS_Y;
|
||||
import_params.forward_axis = IO_AXIS_NEGATIVE_Z;
|
||||
import_params.up_axis = IO_AXIS_Y;
|
||||
|
||||
ReportList reports;
|
||||
BKE_reports_init(&reports, RPT_STORE);
|
||||
BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); })
|
||||
import_params.reports = &reports;
|
||||
ReportList reports;
|
||||
BKE_reports_init(&reports, RPT_STORE);
|
||||
BLI_SCOPED_DEFER([&]() { BKE_reports_free(&reports); })
|
||||
import_params.reports = &reports;
|
||||
|
||||
Mesh *mesh = STL_import_mesh(&import_params);
|
||||
Mesh *mesh = STL_import_mesh(&import_params);
|
||||
|
||||
LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) {
|
||||
NodeWarningType type;
|
||||
switch (report->type) {
|
||||
case RPT_ERROR:
|
||||
type = NodeWarningType::Error;
|
||||
break;
|
||||
default:
|
||||
type = NodeWarningType::Info;
|
||||
break;
|
||||
}
|
||||
params.error_message_add(type, TIP_(report->message));
|
||||
auto cached_value = std::make_unique<LoadStlCache>();
|
||||
cached_value->geometry = GeometrySet::from_mesh(mesh);
|
||||
|
||||
LISTBASE_FOREACH (Report *, report, &(import_params.reports)->list) {
|
||||
cached_value->warnings.append_as(*report);
|
||||
}
|
||||
|
||||
return cached_value;
|
||||
});
|
||||
|
||||
for (const geo_eval_log::NodeWarning &warning : cached_value->warnings) {
|
||||
params.error_message_add(warning.type, warning.message);
|
||||
}
|
||||
|
||||
params.set_output("Mesh", GeometrySet::from_mesh(mesh));
|
||||
params.set_output("Mesh", cached_value->geometry);
|
||||
|
||||
#else
|
||||
params.error_message_add(NodeWarningType::Error,
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_generic_key_string.hh"
|
||||
#include "BLI_memory_cache_file_load.hh"
|
||||
#include "BLI_memory_counter.hh"
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
@@ -22,6 +25,17 @@ static void node_declare(NodeDeclarationBuilder &b)
|
||||
b.add_output<decl::String>("String");
|
||||
}
|
||||
|
||||
class LoadTextCache : public memory_cache::CachedValue {
|
||||
public:
|
||||
std::string text;
|
||||
Vector<geo_eval_log::NodeWarning> warnings;
|
||||
|
||||
void count_memory(MemoryCounter &counter) const override
|
||||
{
|
||||
counter.add(this->text.size());
|
||||
}
|
||||
};
|
||||
|
||||
static void node_geo_exec(GeoNodeExecParams params)
|
||||
{
|
||||
const std::optional<std::string> path = params.ensure_absolute_path(
|
||||
@@ -31,23 +45,33 @@ static void node_geo_exec(GeoNodeExecParams params)
|
||||
return;
|
||||
}
|
||||
|
||||
size_t buffer_len;
|
||||
void *buffer = BLI_file_read_text_as_mem(path->c_str(), 0, &buffer_len);
|
||||
if (!buffer) {
|
||||
const std::string message = fmt::format(fmt::runtime(TIP_("Cannot open file: {}")), *path);
|
||||
params.error_message_add(NodeWarningType::Error, message);
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
BLI_SCOPED_DEFER([&]() { MEM_freeN(buffer); });
|
||||
if (BLI_str_utf8_invalid_byte(static_cast<const char *>(buffer), buffer_len) != -1) {
|
||||
params.error_message_add(NodeWarningType::Error,
|
||||
TIP_("File contains invalid UTF-8 characters"));
|
||||
params.set_default_remaining_outputs();
|
||||
return;
|
||||
std::shared_ptr<const LoadTextCache> cached_value = memory_cache::get_loaded<LoadTextCache>(
|
||||
GenericStringKey{"import_text_node"}, {StringRefNull(*path)}, [&]() {
|
||||
auto cached_value = std::make_unique<LoadTextCache>();
|
||||
|
||||
size_t buffer_len;
|
||||
void *buffer = BLI_file_read_text_as_mem(path->c_str(), 0, &buffer_len);
|
||||
if (!buffer) {
|
||||
const std::string message = fmt::format(fmt::runtime(TIP_("Cannot open file: {}")),
|
||||
*path);
|
||||
cached_value->warnings.append({NodeWarningType::Error, message});
|
||||
return cached_value;
|
||||
}
|
||||
BLI_SCOPED_DEFER([&]() { MEM_freeN(buffer); });
|
||||
if (BLI_str_utf8_invalid_byte(static_cast<const char *>(buffer), buffer_len) != -1) {
|
||||
cached_value->warnings.append(
|
||||
{NodeWarningType::Error, TIP_("File contains invalid UTF-8 characters")});
|
||||
return cached_value;
|
||||
}
|
||||
cached_value->text = std::string(static_cast<char *>(buffer), buffer_len);
|
||||
return cached_value;
|
||||
});
|
||||
|
||||
for (const geo_eval_log::NodeWarning &warning : cached_value->warnings) {
|
||||
params.error_message_add(warning.type, warning.message);
|
||||
}
|
||||
|
||||
params.set_output("String", std::string(static_cast<char *>(buffer), buffer_len));
|
||||
params.set_output("String", cached_value->text);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "DNA_windowmanager_types.h"
|
||||
#include "NOD_geometry_nodes_bundle.hh"
|
||||
#include "NOD_geometry_nodes_closure.hh"
|
||||
#include "NOD_geometry_nodes_log.hh"
|
||||
@@ -246,6 +247,19 @@ ClosureValueLog::ClosureValueLog(Vector<Item> inputs,
|
||||
}
|
||||
}
|
||||
|
||||
NodeWarning::NodeWarning(const Report &report)
|
||||
{
|
||||
switch (report.type) {
|
||||
case RPT_ERROR:
|
||||
this->type = NodeWarningType::Error;
|
||||
break;
|
||||
default:
|
||||
this->type = NodeWarningType::Info;
|
||||
break;
|
||||
}
|
||||
this->message = report.message;
|
||||
}
|
||||
|
||||
/* Avoid generating these in every translation unit. */
|
||||
GeoModifierLog::GeoModifierLog() = default;
|
||||
GeoModifierLog::~GeoModifierLog() = default;
|
||||
|
||||
Reference in New Issue
Block a user