Refactoring: Corrections and unifications in mathematics vfont gizmos #107193

Closed
Iliya Katushenock wants to merge 19 commits from mod_moder/blender:tmp_fix_text_cursor_transform into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
91 changed files with 1068 additions and 654 deletions
Showing only changes of commit 07b51b0799 - Show all commits

View File

@ -224,10 +224,13 @@ ccl_device_intersect bool scene_intersect_local(KernelGlobals kg,
}
int blas_index = metal_ancillaries->blas_userID_to_index_lookUp[local_object];
// transform the ray into object's local space
Transform itfm = kernel_data_fetch(objects, local_object).itfm;
r.origin = transform_point(&itfm, r.origin);
r.direction = transform_direction(&itfm, r.direction);
if (!(kernel_data_fetch(object_flag, local_object) & SD_OBJECT_TRANSFORM_APPLIED)) {
// transform the ray into object's local space
Transform itfm = kernel_data_fetch(objects, local_object).itfm;
r.origin = transform_point(&itfm, r.origin);
r.direction = transform_direction(&itfm, r.direction);
}
intersection = metalrt_intersect.intersect(
r,

View File

@ -550,7 +550,7 @@ static void log_kernel_features(const uint features)
<< "\n";
VLOG_INFO << "Use Shader Raytrace " << string_from_bool(features & KERNEL_FEATURE_NODE_RAYTRACE)
<< "\n";
VLOG_INFO << "Use MNEE" << string_from_bool(features & KERNEL_FEATURE_MNEE) << "\n";
VLOG_INFO << "Use MNEE " << string_from_bool(features & KERNEL_FEATURE_MNEE) << "\n";
VLOG_INFO << "Use Transparent " << string_from_bool(features & KERNEL_FEATURE_TRANSPARENT)
<< "\n";
VLOG_INFO << "Use Denoising " << string_from_bool(features & KERNEL_FEATURE_DENOISING) << "\n";

View File

@ -761,7 +761,7 @@ GHOST_IContext *GHOST_SystemCocoa::createOffscreenContext(GHOST_GLSettings glSet
#ifdef WITH_VULKAN_BACKEND
if (glSettings.context_type == GHOST_kDrawingContextTypeVulkan) {
const bool debug_context = (glSettings.flags & GHOST_glDebugContext) != 0;
GHOST_Context *context = new GHOST_ContextVK(false, NULL, 1, 0, debug_context);
GHOST_Context *context = new GHOST_ContextVK(false, NULL, 1, 2, debug_context);
if (!context->initializeDrawingContext()) {
delete context;
return NULL;

View File

@ -6298,7 +6298,7 @@ GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings glS
wl_surface,
display_->wl_display,
1,
0,
2,
debug_context);
if (!context->initializeDrawingContext()) {

View File

@ -267,7 +267,7 @@ GHOST_IContext *GHOST_SystemWin32::createOffscreenContext(GHOST_GLSettings glSet
#ifdef WITH_VULKAN_BACKEND
/* Vulkan does not need a window. */
if (glSettings.context_type == GHOST_kDrawingContextTypeVulkan) {
context = new GHOST_ContextVK(false, (HWND)0, 1, 0, debug_context);
context = new GHOST_ContextVK(false, (HWND)0, 1, 2, debug_context);
if (!context->initializeDrawingContext()) {
delete context;

View File

@ -417,7 +417,7 @@ GHOST_IContext *GHOST_SystemX11::createOffscreenContext(GHOST_GLSettings glSetti
#ifdef WITH_VULKAN_BACKEND
if (glSettings.context_type == GHOST_kDrawingContextTypeVulkan) {
context = new GHOST_ContextVK(
false, GHOST_kVulkanPlatformX11, 0, m_display, NULL, NULL, 1, 0, debug_context);
false, GHOST_kVulkanPlatformX11, 0, m_display, NULL, NULL, 1, 2, debug_context);
if (!context->initializeDrawingContext()) {
delete context;

View File

@ -815,7 +815,7 @@ GHOST_Context *GHOST_WindowCocoa::newDrawingContext(GHOST_TDrawingContextType ty
{
#ifdef WITH_VULKAN_BACKEND
if (type == GHOST_kDrawingContextTypeVulkan) {
GHOST_Context *context = new GHOST_ContextVK(m_wantStereoVisual, m_metalLayer, 1, 0, true);
GHOST_Context *context = new GHOST_ContextVK(m_wantStereoVisual, m_metalLayer, 1, 2, true);
if (!context->initializeDrawingContext()) {
delete context;

View File

@ -1744,7 +1744,7 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType
window_->wl_surface,
system_->wl_display(),
1,
0,
2,
true);
break;
#endif

View File

@ -637,7 +637,7 @@ GHOST_Context *GHOST_WindowWin32::newDrawingContext(GHOST_TDrawingContextType ty
#ifdef WITH_VULKAN_BACKEND
else if (type == GHOST_kDrawingContextTypeVulkan) {
GHOST_Context *context = new GHOST_ContextVK(false, m_hWnd, 1, 0, m_debug_context);
GHOST_Context *context = new GHOST_ContextVK(false, m_hWnd, 1, 2, m_debug_context);
if (context->initializeDrawingContext()) {
return context;

View File

@ -1235,7 +1235,7 @@ GHOST_Context *GHOST_WindowX11::newDrawingContext(GHOST_TDrawingContextType type
NULL,
NULL,
1,
0,
2,
m_is_debug_context);
if (!context->initializeDrawingContext()) {

View File

@ -571,4 +571,8 @@ class KeyframesCo:
keyframe_points.foreach_set("co", co_buffer)
keyframe_points.foreach_set("interpolation", ipo_buffer)
fcurve.update()
# TODO: in Blender 4.0 the next lines can be replaced with one call to `fcurve.update()`.
# See https://projects.blender.org/blender/blender/issues/107126 for more info.
keyframe_points.sort()
keyframe_points.deduplicate()
keyframe_points.handles_recalc()

View File

@ -23,7 +23,7 @@ std::string normalize_directory_path(StringRef directory)
directory.data(),
/* + 1 for null terminator. */
std::min(directory.size() + 1, int64_t(sizeof(dir_normalized))));
BLI_path_normalize_dir(nullptr, dir_normalized, sizeof(dir_normalized));
BLI_path_normalize_dir(dir_normalized, sizeof(dir_normalized));
return std::string(dir_normalized);
}
@ -34,7 +34,7 @@ std::string normalize_path(StringRefNull path, int64_t max_len)
char *buf = BLI_strdupn(path.c_str(), len);
BLI_path_slash_native(buf);
BLI_path_normalize(nullptr, buf);
BLI_path_normalize(buf);
std::string normalized_path = buf;
MEM_freeN(buf);

View File

@ -416,6 +416,7 @@ int BKE_fcurve_active_keyframe_index(const struct FCurve *fcu);
* Move the indexed keyframe to the given value,
* and move the handles with it to ensure the slope remains the same.
*/
void BKE_fcurve_keyframe_move_time_with_handles(BezTriple *keyframe, const float new_time);
void BKE_fcurve_keyframe_move_value_with_handles(struct BezTriple *keyframe, float new_value);
/* .............. */
@ -472,6 +473,14 @@ bool BKE_fcurve_bezt_subdivide_handles(struct BezTriple *bezt,
struct BezTriple *next,
float *r_pdelta);
/**
* Resize the FCurve 'bezt' array to fit the given length.
*
* \param new_totvert new number of elements in the FCurve's `bezt` array.
* Constraint: `0 <= new_totvert <= fcu->totvert`
*/
void BKE_fcurve_bezt_shrink(struct FCurve *fcu, int new_totvert);
/**
* Delete a keyframe from an F-curve at a specific index.
*/
@ -499,6 +508,21 @@ void BKE_fcurve_merge_duplicate_keys(struct FCurve *fcu,
const int sel_flag,
const bool use_handle);
/**
* Ensure the FCurve is a proper function, such that every X-coordinate of the
* timeline has only one value of the FCurve. In other words, removes duplicate
* keyframes.
*
* Contrary to #BKE_fcurve_merge_duplicate_keys, which is intended for
* interactive use, and where selection matters, this is a simpler deduplication
* where the last duplicate "wins".
*
* Assumes the keys are sorted (see #sort_time_fcurve).
*
* After deduplication, call `BKE_fcurve_handles_recalc(fcu);`
*/
void BKE_fcurve_deduplicate_keys(struct FCurve *fcu);
/* -------- Curve Sanity -------- */
/**

View File

@ -148,6 +148,28 @@ enum {
IDWALK_INCLUDE_UI = (1 << 2),
/** Do not process ID pointers inside embedded IDs. Needed by depsgraph processing e.g. */
IDWALK_IGNORE_EMBEDDED_ID = (1 << 3),
/**
* Do not access original processed pointer's data, only process its address value.
*
* This is required in cases where to current address may not be valid anymore (e.g. during
* readfile process). A few ID pointers (like e.g. the `LayerCollection.collection` one) are by
* default accessed to check things (e.g. whether they are pointing to an embedded ID or a
* regular one).
*
* \note Access to owning embedded ID pointers (e.g. `Scene.master_collection`) is not affected
* here, these are presumed always valid.
*
* \note This flag is mutually exclusive with `IDWALK_READONLY` and `IDWALK_RECURSE`, since by
* definition the only thing doable in readonly case is accessing current ID pointer, and this is
* also required for recursion.
*
* \note After remapping, code may access the newly set ID pointer, which is always presumed
* valid.
*
* \warning Use only with great caution, this flag will modify the handling of some ID pointers
* (especially when it comes to detecting `IDWALK_CB_EMBEDDED_NOT_OWNING` usages).
*/
IDWALK_NO_ORIG_POINTERS_ACCESS = (1 << 5),
/**
* Also process internal ID pointers like `ID.newid` or `ID.orig_id`.

View File

@ -88,6 +88,13 @@ enum {
/** Do NOT tag IDs which had some of their ID pointers updated for update in the depsgraph, or ID
* type specific updates, like e.g. with node trees. */
ID_REMAP_SKIP_UPDATE_TAGGING = 1 << 19,
/**
* Do not attempt to access original ID pointers (triggers usages of
* `IDWALK_NO_ORIG_POINTERS_ACCESS` too).
*
* Use when original ID pointers values are (probably) not valid, e.g. dureing readfile process.
*/
ID_REMAP_NO_ORIG_POINTERS_ACCESS = 1 << 20,
};
typedef enum eIDRemapType {

View File

@ -11,8 +11,6 @@
# include <mutex>
# include "MEM_guardedalloc.h"
# include "BLI_array.hh"
# include "BLI_bit_vector.hh"
# include "BLI_bounds_types.hh"
@ -22,7 +20,6 @@
# include "BLI_span.hh"
# include "BLI_vector.hh"
# include "DNA_customdata_types.h"
# include "DNA_meshdata_types.h"
struct BVHCache;
@ -175,7 +172,7 @@ struct MeshRuntime {
SharedCache<LooseEdgeCache> loose_edges_cache;
/** Cache of data about vertices not used by edges. See #Mesh::loose_verts(). */
SharedCache<LooseVertCache> loose_verts_cache;
/** Cache of data about vertices not used by faces. See #Mesh::loose_verts(). */
/** Cache of data about vertices not used by faces. See #Mesh::verts_no_face(). */
SharedCache<LooseVertCache> verts_no_face_cache;
/**

View File

@ -386,7 +386,7 @@ static bool get_path_local_ex(char *targetpath,
char osx_resourses[FILE_MAX + 4 + 9];
BLI_path_join(osx_resourses, sizeof(osx_resourses), g_app.program_dirname, "..", "Resources");
/* Remove the '/../' added above. */
BLI_path_normalize(NULL, osx_resourses);
BLI_path_normalize(osx_resourses);
path_base = osx_resourses;
#endif
return test_path(targetpath,
@ -871,7 +871,7 @@ static void where_am_i(char *fullname, const size_t maxlen, const char *name)
BLI_path_program_search(fullname, maxlen, name);
}
/* Remove "/./" and "/../" so string comparisons can be used on the path. */
BLI_path_normalize(NULL, fullname);
BLI_path_normalize(fullname);
# if defined(DEBUG)
if (!STREQ(name, fullname)) {
@ -890,7 +890,7 @@ void BKE_appdir_program_path_init(const char *argv0)
* which must point to the Python module for data-files to be detected. */
STRNCPY(g_app.program_filepath, argv0);
BLI_path_abs_from_cwd(g_app.program_filepath, sizeof(g_app.program_filepath));
BLI_path_normalize(NULL, g_app.program_filepath);
BLI_path_normalize(g_app.program_filepath);
if (g_app.program_dirname[0] == '\0') {
/* First time initializing, the file binary path isn't valid from a Python module.

View File

@ -441,7 +441,7 @@ static bool relative_rebase_foreach_path_cb(BPathForeachPathData *bpath_data,
return false;
}
BLI_path_normalize(NULL, filepath);
BLI_path_normalize(filepath);
/* This may fail, if so it's fine to leave absolute since the path is still valid. */
BLI_path_rel(filepath, data->basedir_dst);

View File

@ -168,6 +168,7 @@ static void collection_free_data(ID *id)
static void collection_foreach_id(ID *id, LibraryForeachIDData *data)
{
Collection *collection = (Collection *)id;
const int data_flags = BKE_lib_query_foreachid_process_flags_get(data);
BKE_LIB_FOREACHID_PROCESS_ID(
data, collection->runtime.owner_id, IDWALK_CB_LOOPBACK | IDWALK_CB_NEVER_SELF);
@ -197,6 +198,7 @@ static void collection_foreach_id(ID *id, LibraryForeachIDData *data)
/* XXX This is very weak. The whole idea of keeping pointers to private IDs is very bad
* anyway... */
const int cb_flag = ((parent->collection != NULL &&
(data_flags & IDWALK_NO_ORIG_POINTERS_ACCESS) == 0 &&
(parent->collection->id.flag & LIB_EMBEDDED_DATA) != 0) ?
IDWALK_CB_EMBEDDED_NOT_OWNING :
IDWALK_CB_NOP);

View File

@ -883,6 +883,14 @@ int BKE_fcurve_active_keyframe_index(const FCurve *fcu)
/** \} */
void BKE_fcurve_keyframe_move_time_with_handles(BezTriple *keyframe, const float new_time)
{
const float time_delta = new_time - keyframe->vec[1][0];
keyframe->vec[0][0] += time_delta;
keyframe->vec[1][0] = new_time;
keyframe->vec[2][0] += time_delta;
}
void BKE_fcurve_keyframe_move_value_with_handles(struct BezTriple *keyframe, const float new_value)
{
const float value_delta = new_value - keyframe->vec[1][1];
@ -1659,6 +1667,24 @@ bool BKE_fcurve_bezt_subdivide_handles(struct BezTriple *bezt,
return true;
}
void BKE_fcurve_bezt_shrink(struct FCurve *fcu, const int new_totvert)
{
BLI_assert(new_totvert >= 0);
BLI_assert(new_totvert <= fcu->totvert);
/* No early return when new_totvert == fcu->totvert. There is no way to know the intention of the
* caller, nor the history of the FCurve so far, so `fcu->bezt` may actually have allocated space
* for more than `fcu->totvert` keys. */
if (new_totvert == 0) {
fcurve_bezt_free(fcu);
return;
}
fcu->bezt = MEM_reallocN(fcu->bezt, new_totvert * sizeof(*(fcu->bezt)));
fcu->totvert = new_totvert;
}
void BKE_fcurve_delete_key(FCurve *fcu, int index)
{
/* sanity check */
@ -1849,6 +1875,52 @@ void BKE_fcurve_merge_duplicate_keys(FCurve *fcu, const int sel_flag, const bool
BLI_freelistN(&retained_keys);
}
void BKE_fcurve_deduplicate_keys(FCurve *fcu)
{
BLI_assert_msg(fcu->bezt, "this function only works with regular (non-sampled) FCurves");
if (fcu->totvert < 2 || fcu->bezt == NULL) {
return;
}
int prev_bezt_index = 0;
for (int i = 1; i < fcu->totvert; i++) {
BezTriple *bezt = &fcu->bezt[i];
BezTriple *prev_bezt = &fcu->bezt[prev_bezt_index];
const float bezt_x = bezt->vec[1][0];
const float prev_x = prev_bezt->vec[1][0];
if (bezt_x - prev_x <= BEZT_BINARYSEARCH_THRESH) {
/* Replace 'prev_bezt', as it has the same X-coord as 'bezt' and the last one wins. */
*prev_bezt = *bezt;
if (floor(bezt_x) == bezt_x) {
/* Keep the 'bezt_x' coordinate, as being on a frame is more desirable
* than being ever so slightly off. */
}
else {
/* Move the retained key to the old X-coordinate, to 'anchor' the X-coordinate used for
* subsequente comparisons. Without this, the reference X-coordinate would keep moving
* forward in time, potentially merging in more keys than desired. */
BKE_fcurve_keyframe_move_time_with_handles(prev_bezt, prev_x);
}
continue;
}
/* Next iteration should look at the current element. However, because of the deletions, that
* may not be at index 'i'; after this increment, `prev_bezt_index` points at where the current
* element should go. */
prev_bezt_index++;
if (prev_bezt_index != i) {
/* This bezt should be kept, so copy it to its new location in the array. */
fcu->bezt[prev_bezt_index] = *bezt;
}
}
BKE_fcurve_bezt_shrink(fcu, prev_bezt_index + 1);
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -10,6 +10,8 @@
#include "DNA_anim_types.h"
#include "BLI_math_vector_types.hh"
namespace blender::bke::tests {
/* Epsilon for floating point comparisons. */
@ -346,6 +348,37 @@ TEST(BKE_fcurve, BKE_fcurve_keyframe_move_value_with_handles)
BKE_fcurve_free(fcu);
}
TEST(BKE_fcurve, BKE_fcurve_keyframe_move_time_with_handles)
{
FCurve *fcu = BKE_fcurve_create();
insert_vert_fcurve(fcu, 1.0f, 7.5f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
insert_vert_fcurve(fcu, 8.0f, 15.0f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
insert_vert_fcurve(fcu, 14.0f, 8.2f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[0][0], 5.2671194f);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[0][1], 15.0f);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[1][0], 8.0f);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[1][1], 15.0f);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[2][0], 10.342469f);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[2][1], 15.0f);
BKE_fcurve_keyframe_move_time_with_handles(&fcu->bezt[1], 47.0f);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[0][0], 44.2671194f) << "Left handle time should be updated";
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[0][1], 15.0f) << "Left handle should not move in value";
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[1][0], 47.0f) << "Frame time should have been updated";
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[1][1], 15.0f) << "Frame should not move in value";
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[2][0], 49.342469f) << "Right handle time should be updated";
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[2][1], 15.0f) << "Right handle should not move in value";
BKE_fcurve_free(fcu);
}
TEST(BKE_fcurve, BKE_fcurve_calc_range)
{
FCurve *fcu = BKE_fcurve_create();
@ -546,4 +579,127 @@ TEST(BKE_fcurve, BKE_fcurve_calc_bounds)
BKE_fcurve_free(fcu);
}
static void set_key(FCurve *fcu, const int index, const float x, const float y)
{
fcu->bezt[index].vec[0][0] = x - 0.5f;
fcu->bezt[index].vec[1][0] = x;
fcu->bezt[index].vec[2][0] = x + 0.5f;
fcu->bezt[index].vec[0][1] = y;
fcu->bezt[index].vec[1][1] = y;
fcu->bezt[index].vec[2][1] = y;
}
static FCurve *testcurve_with_duplicates()
{
/* Create a curve with some duplicate keys. The first ones are all with Y=1, the later repeats
* increase Y-coordinates on every repeat. */
FCurve *fcu = BKE_fcurve_create();
ED_keyframes_add(fcu, 10); /* Avoid `insert_vert_fcurve`, that deduplicates the keys. */
set_key(fcu, 0, 1.0f, 1.0f);
set_key(fcu, 1, 327.16f, 1.0f);
set_key(fcu, 2, 7.0f, 1.0f);
set_key(fcu, 3, 47.0f, 1.0f);
set_key(fcu, 4, 7.0f, 2.0f);
set_key(fcu, 5, 47.0f, 2.0f);
set_key(fcu, 6, 47.0f + BEZT_BINARYSEARCH_THRESH, 3.0f);
set_key(fcu, 7, 7.0f, 3.0f);
set_key(fcu, 8, 3.0f, 1.0f);
set_key(fcu, 9, 2.0f, 1.0f);
return fcu;
}
TEST(BKE_fcurve, sort_time_fcurve_stability)
{
FCurve *fcu = testcurve_with_duplicates();
ASSERT_EQ(fcu->totvert, 10);
sort_time_fcurve(fcu);
/* The sorting should be stable, i.e. retain the original order when the
* X-coordinates are identical. */
ASSERT_EQ(fcu->totvert, 10) << "sorting should not influence number of keys";
EXPECT_V2_NEAR(fcu->bezt[0].vec[1], float2(1.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[1].vec[1], float2(2.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[2].vec[1], float2(3.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[3].vec[1], float2(7.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[4].vec[1], float2(7.0f, 2.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[5].vec[1], float2(7.0f, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[6].vec[1], float2(47.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[7].vec[1], float2(47.0f, 2.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[8].vec[1], float2(47.0f + BEZT_BINARYSEARCH_THRESH, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[9].vec[1], float2(327.16f, 1.0f), 1e-3);
BKE_fcurve_free(fcu);
}
TEST(BKE_fcurve, BKE_fcurve_deduplicate_keys)
{
FCurve *fcu = testcurve_with_duplicates();
ASSERT_EQ(fcu->totvert, 10);
sort_time_fcurve(fcu);
BKE_fcurve_deduplicate_keys(fcu);
ASSERT_GE(fcu->totvert, 6); /* Protect against out-of-bounds access. */
EXPECT_EQ(fcu->totvert, 6); /* The actual expected value. */
EXPECT_V2_NEAR(fcu->bezt[0].vec[1], float2(1.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[1].vec[1], float2(2.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[2].vec[1], float2(3.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[3].vec[1], float2(7.0f, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[4].vec[1], float2(47.0f, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[5].vec[1], float2(327.16f, 1.0f), 1e-3);
BKE_fcurve_free(fcu);
}
TEST(BKE_fcurve, BKE_fcurve_deduplicate_keys_edge_cases)
{
FCurve *fcu = testcurve_with_duplicates();
ASSERT_EQ(fcu->totvert, 10);
/* Update the 2nd and 2nd-to-last keys to test the edge cases. */
set_key(fcu, 0, 1, 1);
set_key(fcu, 1, 1, 2);
set_key(fcu, 8, 327.16f, 1);
set_key(fcu, 9, 327.16f, 2);
sort_time_fcurve(fcu);
BKE_fcurve_deduplicate_keys(fcu);
ASSERT_EQ(fcu->totvert, 4);
EXPECT_V2_NEAR(fcu->bezt[0].vec[1], float2(1.0f, 2.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[1].vec[1], float2(7.0f, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[2].vec[1], float2(47.0f, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[3].vec[1], float2(327.16f, 2.0f), 1e-3);
BKE_fcurve_free(fcu);
}
TEST(BKE_fcurve, BKE_fcurve_deduplicate_keys_prefer_whole_frames)
{
FCurve *fcu = testcurve_with_duplicates();
ASSERT_EQ(fcu->totvert, 10);
/* Update the first key around 47.0 to be slightly before the frame. This gives us three keys on
* 47-epsilon, 47, and 47+epsilon. The keys at index 5 and 6 already have this value, so the
* `set_key` calls are unnecessary, but this way this test has a more local overview of the
* situation under test. */
set_key(fcu, 3, 47.0f - BEZT_BINARYSEARCH_THRESH, 1.0f);
set_key(fcu, 5, 47.0f, 2.0f);
set_key(fcu, 6, 47.0f + BEZT_BINARYSEARCH_THRESH, 3.0f);
sort_time_fcurve(fcu);
BKE_fcurve_deduplicate_keys(fcu);
ASSERT_EQ(fcu->totvert, 6);
EXPECT_V2_NEAR(fcu->bezt[0].vec[1], float2(1.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[1].vec[1], float2(2.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[2].vec[1], float2(3.0f, 1.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[3].vec[1], float2(7.0f, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[4].vec[1], float2(47.0f, 3.0f), 1e-3);
EXPECT_V2_NEAR(fcu->bezt[5].vec[1], float2(327.16f, 1.0f), 1e-3);
BKE_fcurve_free(fcu);
}
} // namespace blender::bke::tests

View File

@ -137,7 +137,7 @@ static bool lib_id_library_local_paths_callback(BPathForeachPathData *bpath_data
/* Path was relative and is now absolute. Remap.
* Important BLI_path_normalize runs before the path is made relative
* because it won't work for paths that start with "//../" */
BLI_path_normalize(base_new, filepath);
BLI_path_normalize(filepath);
BLI_path_rel(filepath, base_new);
BLI_strncpy(r_path_dst, filepath, FILE_MAX);
return true;
@ -874,7 +874,12 @@ static void id_embedded_swap(ID **embedded_id_a,
struct IDRemapper *remapper_id_b)
{
if (embedded_id_a != NULL && *embedded_id_a != NULL) {
BLI_assert(embedded_id_b != NULL && *embedded_id_b != NULL);
BLI_assert(embedded_id_b != NULL);
if (*embedded_id_b == NULL) {
/* Cannot swap anything if one of the embedded IDs is NULL. */
return;
}
/* Do not remap internal references to itself here, since embedded IDs pointers also need to be
* potentially remapped in owner ID's data, which will also handle embedded IDs data. */

View File

@ -201,6 +201,12 @@ static bool library_foreach_ID_link(Main *bmain,
LibraryForeachIDData data = {.bmain = bmain};
BLI_assert(inherit_data == NULL || data.bmain == inherit_data->bmain);
/* `IDWALK_NO_ORIG_POINTERS_ACCESS` is mutually exclusive with both `IDWALK_READONLY` and
* `IDWALK_RECURSE`. */
BLI_assert((flag & (IDWALK_NO_ORIG_POINTERS_ACCESS | IDWALK_READONLY)) !=
(IDWALK_NO_ORIG_POINTERS_ACCESS | IDWALK_READONLY));
BLI_assert((flag & (IDWALK_NO_ORIG_POINTERS_ACCESS | IDWALK_RECURSE)) !=
(IDWALK_NO_ORIG_POINTERS_ACCESS | IDWALK_RECURSE));
if (flag & IDWALK_RECURSE) {
/* For now, recursion implies read-only, and no internal pointers. */

View File

@ -483,7 +483,9 @@ static void libblock_remap_data(Main *bmain,
(((remap_flags & ID_REMAP_FORCE_INTERNAL_RUNTIME_POINTERS) != 0 ?
IDWALK_DO_INTERNAL_RUNTIME_POINTERS :
IDWALK_NOP) |
((remap_flags & ID_REMAP_FORCE_UI_POINTERS) != 0 ? IDWALK_INCLUDE_UI : IDWALK_NOP));
((remap_flags & ID_REMAP_FORCE_UI_POINTERS) != 0 ? IDWALK_INCLUDE_UI : IDWALK_NOP) |
((remap_flags & ID_REMAP_NO_ORIG_POINTERS_ACCESS) != 0 ? IDWALK_NO_ORIG_POINTERS_ACCESS :
IDWALK_NOP));
id_remap_data.id_remapper = id_remapper;
id_remap_data.type = remap_type;

View File

@ -115,14 +115,14 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int
/* This is a direct copy of a main mesh, so for now it has the same topology. */
mesh_dst->runtime->deformed_only = true;
}
/* This option is set for run-time meshes that have been copied from the current objects mode.
/* This option is set for run-time meshes that have been copied from the current object's mode.
* Currently this is used for edit-mesh although it could be used for sculpt or other
* kinds of data specific to an objects mode.
* kinds of data specific to an object's mode.
*
* The flag signals that the mesh hasn't been modified from the data that generated it,
* allowing us to use the object-mode data for drawing.
*
* While this could be the callers responsibility, keep here since it's
* While this could be the caller's responsibility, keep here since it's
* highly unlikely we want to create a duplicate and not use it for drawing. */
mesh_dst->runtime->is_original_bmesh = false;

View File

@ -136,10 +136,10 @@ static void bit_vector_with_reset_bits_or_empty(const Span<int> indices_to_reset
*/
static void try_tag_verts_no_face_none(const Mesh &mesh)
{
if (mesh.runtime->loose_edges_cache.is_cached() || mesh.loose_edges().count > 0) {
if (!mesh.runtime->loose_edges_cache.is_cached() || mesh.loose_edges().count > 0) {
return;
}
if (mesh.runtime->loose_verts_cache.is_cached() || mesh.loose_verts().count > 0) {
if (!mesh.runtime->loose_verts_cache.is_cached() || mesh.loose_verts().count > 0) {
return;
}
mesh.runtime->verts_no_face_cache.ensure([&](LooseVertCache &r_data) {

View File

@ -795,7 +795,7 @@ static void foreach_vertex_of_loose_edge(const SubdivForeachContext *foreach_con
static void store_edge(MultiresReshapeSmoothContext *reshape_smooth_context,
const int subdiv_v1,
const int subdiv_v2,
const char crease)
const float crease)
{
/* This is a bit overhead to use atomics in such a simple function called from many threads,
* but this allows to save quite measurable amount of memory. */
@ -805,7 +805,7 @@ static void store_edge(MultiresReshapeSmoothContext *reshape_smooth_context,
Edge *edge = &reshape_smooth_context->geometry.edges[edge_index];
edge->v1 = subdiv_v1;
edge->v2 = subdiv_v2;
edge->sharpness = BKE_subdiv_crease_to_sharpness_char(crease);
edge->sharpness = BKE_subdiv_crease_to_sharpness_f(crease);
}
static void foreach_edge(const SubdivForeachContext *foreach_context,
@ -821,7 +821,7 @@ static void foreach_edge(const SubdivForeachContext *foreach_context,
if (reshape_smooth_context->smoothing_type == MULTIRES_SUBDIVIDE_LINEAR) {
if (!is_loose) {
store_edge(reshape_smooth_context, subdiv_v1, subdiv_v2, char(255));
store_edge(reshape_smooth_context, subdiv_v1, subdiv_v2, 1.0f);
}
return;
}
@ -837,8 +837,8 @@ static void foreach_edge(const SubdivForeachContext *foreach_context,
return;
}
/* Edges without crease are to be ignored as well. */
const char crease = get_effective_crease(reshape_smooth_context, coarse_edge_index);
if (crease == 0) {
const float crease = get_effective_crease(reshape_smooth_context, coarse_edge_index);
if (crease == 0.0f) {
return;
}
store_edge(reshape_smooth_context, subdiv_v1, subdiv_v2, crease);

View File

@ -2430,29 +2430,7 @@ bNode *node_copy_with_mapping(bNodeTree *dst_tree,
return node_dst;
}
static void for_each_node_group_instance(Main &bmain,
const bNodeTree &node_group,
const Span<int> tree_types_to_lookup,
const FunctionRef<void(bNode &)> func)
{
LISTBASE_FOREACH (bNodeTree *, other_group, &bmain.nodetrees) {
if (!tree_types_to_lookup.contains(other_group->type)) {
continue;
}
if (other_group == &node_group) {
continue;
}
other_group->ensure_topology_cache();
for (bNode *node : other_group->group_nodes()) {
if (node->id == &node_group.id) {
func(*node);
}
}
}
}
void node_socket_move_default_value(Main &bmain,
void node_socket_move_default_value(Main & /*bmain*/,
bNodeTree &tree,
bNodeSocket &src,
bNodeSocket &dst)
@ -2490,14 +2468,6 @@ void node_socket_move_default_value(Main &bmain,
dst_values.append(&dst_node.id);
break;
}
case NODE_GROUP_INPUT: {
for_each_node_group_instance(bmain, tree, {NTREE_GEOMETRY}, [&](bNode &node_group) {
bNodeSocket &socket = node_group.input_by_identifier(dst.identifier);
Image **tmp_dst_value = &socket.default_value_typed<bNodeSocketValueImage>()->value;
dst_values.append(reinterpret_cast<ID **>(tmp_dst_value));
});
break;
}
default: {
break;
}

View File

@ -758,10 +758,10 @@ static void scene_foreach_toolsettings(LibraryForeachIDData *data,
static void scene_foreach_layer_collection(LibraryForeachIDData *data, ListBase *lb)
{
const int data_flags = BKE_lib_query_foreachid_process_flags_get(data);
LISTBASE_FOREACH (LayerCollection *, lc, lb) {
/* XXX This is very weak. The whole idea of keeping pointers to private IDs is very bad
* anyway... */
const int cb_flag = (lc->collection != nullptr &&
(data_flags & IDWALK_NO_ORIG_POINTERS_ACCESS) == 0 &&
(lc->collection->id.flag & LIB_EMBEDDED_DATA) != 0) ?
IDWALK_CB_EMBEDDED_NOT_OWNING :
IDWALK_CB_NOP;

View File

@ -85,8 +85,9 @@ static void screen_foreach_id_dopesheet(LibraryForeachIDData *data, bDopeSheet *
void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area)
{
const bool is_readonly = (BKE_lib_query_foreachid_process_flags_get(data) & IDWALK_READONLY) !=
0;
const int data_flags = BKE_lib_query_foreachid_process_flags_get(data);
const bool is_readonly = (data_flags & IDWALK_READONLY) != 0;
const bool allow_pointer_access = (data_flags & IDWALK_NO_ORIG_POINTERS_ACCESS) == 0;
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, area->full, IDWALK_CB_NOP);
@ -190,7 +191,7 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
while ((tselem = BLI_mempool_iterstep(&iter))) {
/* Do not try to restore non-ID pointers (drivers/sequence/etc.). */
if (TSE_IS_REAL_ID(tselem)) {
const int cb_flag = (tselem->id != NULL &&
const int cb_flag = (tselem->id != NULL && allow_pointer_access &&
(tselem->id->flag & LIB_EMBEDDED_DATA) != 0) ?
IDWALK_CB_EMBEDDED_NOT_OWNING :
IDWALK_CB_NOP;
@ -209,7 +210,7 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
}
case SPACE_NODE: {
SpaceNode *snode = (SpaceNode *)sl;
const bool is_embedded_nodetree = snode->id != NULL &&
const bool is_embedded_nodetree = snode->id != NULL && allow_pointer_access &&
ntreeFromID(snode->id) == snode->nodetree;
BKE_LIB_FOREACHID_PROCESS_ID(data, snode->id, IDWALK_CB_NOP);
@ -227,8 +228,11 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
/* Embedded ID pointers are not remapped (besides exceptions), ensure it still matches
* actual data. Note that `snode->id` was already processed (and therefore potentially
* remapped) above.*/
if (!is_readonly && path != NULL) {
path->nodetree = snode->nodetree = (snode->id == NULL) ? NULL : ntreeFromID(snode->id);
if (!is_readonly) {
snode->nodetree = (snode->id == NULL) ? NULL : ntreeFromID(snode->id);
if (path != NULL) {
path->nodetree = snode->nodetree;
}
}
}
else {
@ -238,10 +242,18 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
}
}
/* Both `snode->id` and `snode->nodetree` have been remapped now, sotheir data can be
* accessed. */
BLI_assert(snode->id == NULL || snode->nodetree == NULL ||
(snode->nodetree->id.flag & LIB_EMBEDDED_DATA) == 0 ||
snode->nodetree == ntreeFromID(snode->id));
if (path != NULL) {
for (path = path->next; path != NULL; path = path->next) {
BLI_assert(path->nodetree != NULL);
BLI_assert((path->nodetree->id.flag & LIB_EMBEDDED_DATA) == 0);
if ((data_flags & IDWALK_NO_ORIG_POINTERS_ACCESS) == 0) {
BLI_assert((path->nodetree->id.flag & LIB_EMBEDDED_DATA) == 0);
}
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, path->nodetree, IDWALK_CB_USER_ONE);
@ -270,13 +282,16 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area
snode->edittree = NULL;
}
}
/* NOTE: It is disputable whether this should be called here, especially when editing it is
* allowed? But for now, for sake of consistency, do it in any case. */
if (is_embedded_nodetree && snode->edittree == snode->nodetree) {
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, snode->edittree, IDWALK_CB_EMBEDDED_NOT_OWNING);
}
else {
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, snode->edittree, IDWALK_CB_NOP);
/* Only process this pointer in readonly case, otherwise could lead to a bad
* double-remapping e.g. */
if (is_embedded_nodetree && snode->edittree == snode->nodetree) {
BKE_LIB_FOREACHID_PROCESS_IDSUPER(
data, snode->edittree, IDWALK_CB_EMBEDDED_NOT_OWNING);
}
else {
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, snode->edittree, IDWALK_CB_NOP);
}
}
break;
}

View File

@ -161,7 +161,7 @@ static float get_edge_sharpness(const OpenSubdiv_Converter *converter, int manif
return 10.0f;
}
#endif
if (!storage->settings.use_creases || storage->cd_edge_crease == nullptr) {
if (storage->cd_edge_crease == nullptr) {
return 0.0f;
}
const int edge_index = storage->manifold_edge_index_reverse[manifold_edge_index];
@ -187,7 +187,7 @@ static bool is_infinite_sharp_vertex(const OpenSubdiv_Converter *converter,
static float get_vertex_sharpness(const OpenSubdiv_Converter *converter, int manifold_vertex_index)
{
ConverterStorage *storage = static_cast<ConverterStorage *>(converter->user_data);
if (!storage->settings.use_creases || storage->cd_vertex_crease == nullptr) {
if (storage->cd_vertex_crease == nullptr) {
return 0.0f;
}
const int vertex_index = storage->manifold_vertex_index_reverse[manifold_vertex_index];
@ -214,16 +214,15 @@ static void precalc_uv_layer(const OpenSubdiv_Converter *converter, const int la
storage->loop_uv_indices = static_cast<int *>(
MEM_malloc_arrayN(mesh->totloop, sizeof(int), "loop uv vertex index"));
}
UvVertMap *uv_vert_map = BKE_mesh_uv_vert_map_create(
storage->polys,
(const bool *)CustomData_get_layer_named(&mesh->pdata, CD_PROP_BOOL, ".hide_poly"),
(const bool *)CustomData_get_layer_named(&mesh->pdata, CD_PROP_BOOL, ".select_poly"),
storage->corner_verts.data(),
mloopuv,
num_vert,
limit,
false,
true);
UvVertMap *uv_vert_map = BKE_mesh_uv_vert_map_create(storage->polys,
nullptr,
nullptr,
storage->corner_verts.data(),
mloopuv,
num_vert,
limit,
false,
true);
/* NOTE: First UV vertex is supposed to be always marked as separate. */
storage->num_uv_coordinates = -1;
for (int vertex_index = 0; vertex_index < num_vert; vertex_index++) {
@ -395,10 +394,12 @@ static void init_user_data(OpenSubdiv_Converter *converter,
user_data->polys = mesh->polys();
user_data->corner_verts = mesh->corner_verts();
user_data->corner_edges = mesh->corner_edges();
user_data->cd_vertex_crease = static_cast<const float *>(
CustomData_get_layer(&mesh->vdata, CD_CREASE));
user_data->cd_edge_crease = static_cast<const float *>(
CustomData_get_layer(&mesh->edata, CD_CREASE));
if (settings->use_creases) {
user_data->cd_vertex_crease = static_cast<const float *>(
CustomData_get_layer(&mesh->vdata, CD_CREASE));
user_data->cd_edge_crease = static_cast<const float *>(
CustomData_get_layer(&mesh->edata, CD_CREASE));
}
user_data->loop_uv_indices = nullptr;
initialize_manifold_indices(user_data);
converter->user_data = user_data;

View File

@ -91,9 +91,3 @@ BLI_INLINE float BKE_subdiv_crease_to_sharpness_f(float edge_crease)
{
return edge_crease * edge_crease * 10.0f;
}
BLI_INLINE float BKE_subdiv_crease_to_sharpness_char(char edge_crease)
{
const float edge_crease_f = edge_crease / 255.0f;
return BKE_subdiv_crease_to_sharpness_f(edge_crease_f);
}

View File

@ -1192,11 +1192,19 @@ Mesh *BKE_subdiv_to_mesh(Subdiv *subdiv,
BitVector<> &bit_vector = result->runtime->subsurf_optimal_display_edges;
bit_vector.clear();
bit_vector.resize(subdiv_context.subdiv_display_edges.size());
threading::parallel_for_aligned(span.index_range(), 4096, 64, [&](const IndexRange range) {
for (const int i : range) {
bit_vector[i].set(span[i]);
}
});
threading::parallel_for_aligned(
span.index_range(), 4096, bits::BitsPerInt, [&](const IndexRange range) {
for (const int i : range) {
bit_vector[i].set(span[i]);
}
});
}
if (coarse_mesh->verts_no_face().count == 0) {
result->tag_loose_verts_none();
}
if (coarse_mesh->loose_edges().count == 0) {
result->loose_edges_tag_none();
}
if (subdiv->settings.is_simple) {

View File

@ -170,12 +170,9 @@ Subdiv *BKE_subsurf_modifier_subdiv_descriptor_ensure(SubsurfRuntimeData *runtim
return runtime_data->subdiv_gpu = BKE_subdiv_update_from_mesh(
runtime_data->subdiv_gpu, &runtime_data->settings, mesh);
}
else {
runtime_data->used_cpu = 2;
return runtime_data->subdiv_cpu = BKE_subdiv_update_from_mesh(
runtime_data->subdiv_cpu, &runtime_data->settings, mesh);
}
runtime_data->used_cpu = 2;
return runtime_data->subdiv_cpu = BKE_subdiv_update_from_mesh(
runtime_data->subdiv_cpu, &runtime_data->settings, mesh);
}
int BKE_subsurf_modifier_eval_required_mode(bool is_final_render, bool is_edit_mode)

View File

@ -625,7 +625,7 @@ fn::GField DataTypeConversions::try_convert(fn::GField field, const CPPType &to_
}
const mf::MultiFunction &fn = *this->get_conversion_multi_function(
mf::DataType::ForSingle(from_type), mf::DataType::ForSingle(to_type));
return {std::make_shared<fn::FieldOperation>(fn, Vector<fn::GField>{std::move(field)})};
return {fn::FieldOperation::Create(fn, {std::move(field)})};
}
} // namespace blender::bke

View File

@ -15,6 +15,14 @@ namespace blender::array_utils {
* grain-size.
*/
void copy(const GVArray &src, GMutableSpan dst, int64_t grain_size = 4096);
template<typename T>
inline void copy(const VArray<T> &src, MutableSpan<T> dst, const int64_t grain_size = 4096)
{
BLI_assert(src.size() == dst.size());
threading::parallel_for(src.index_range(), grain_size, [&](const IndexRange range) {
src.materialize_to_uninitialized(range, dst);
});
}
/**
* Fill the destination span by copying all values from the `src` array. Threaded based on
@ -28,6 +36,7 @@ inline void copy(const Span<T> src, MutableSpan<T> dst, const int64_t grain_size
dst.slice(range).copy_from(src.slice(range));
});
}
/**
* Fill the destination span by copying masked values from the `src` array. Threaded based on
* grain-size.

View File

@ -323,19 +323,18 @@ void BLI_path_sequence_encode(
/**
* Remove redundant characters from \a path and optionally make absolute.
*
* \param relabase: The path this is relative to, or ignored when NULL.
* \param path: Can be any input, and this function converts it to a regular full path.
* Also removes garbage from directory paths, like `/../` or double slashes etc.
*
* \note \a path isn't protected for max string names.
*/
void BLI_path_normalize(const char *relabase, char *path) ATTR_NONNULL(2);
void BLI_path_normalize(char *path) ATTR_NONNULL(1);
/**
* Cleanup file-path ensuring a trailing slash.
*
* \note Same as #BLI_path_normalize but adds a trailing slash.
*/
void BLI_path_normalize_dir(const char *relabase, char *dir, size_t dir_maxlen) ATTR_NONNULL(2);
void BLI_path_normalize_dir(char *dir, size_t dir_maxlen) ATTR_NONNULL(1);
/**
* Make given name safe to be used in paths.

View File

@ -112,40 +112,55 @@ void BLI_path_sequence_encode(
BLI_sprintf(string, "%s%.*d%s", head, numlen, MAX2(0, pic), tail);
}
void BLI_path_normalize(const char *relabase, char *path)
void BLI_path_normalize(char *path)
{
const char *path_orig = path;
int path_len;
ptrdiff_t a;
char *start, *eind;
if (relabase) {
BLI_path_abs(path, relabase);
}
else {
if (path[0] == '/' && path[1] == '/') {
if (path[2] == '\0') {
return; /* Path is `//` - can't clean it. */
}
path = path + 2; /* Leave the initial `//` untouched. */
path_len = strlen(path);
if (path[0] == '/' && path[1] == '/') {
path = path + 2; /* Leave the initial `//` untouched. */
path_len -= 2;
/* Strip leading slashes, as they will interfere with the absolute/relative check
* (besides being redundant). */
int i = 0;
while (path[i] == SEP) {
i++;
}
if (i != 0) {
memmove(path, path + i, (path_len - i) + 1);
path_len -= i;
}
BLI_assert(path_len == strlen(path));
}
#ifdef WIN32
/* Skip to the first slash of the drive or UNC path,
* so additional slashes are treated as doubles. */
{
const int path_unc_len = BLI_path_unc_prefix_len(path);
if (path_orig == path) {
int path_unc_len = BLI_path_unc_prefix_len(path);
if (path_unc_len) {
BLI_assert(path_unc_len > 1 && path[path_unc_len - 1] == SEP);
path += path_unc_len - 1;
path_unc_len -= 1;
BLI_assert(path_unc_len > 0 && path[path_unc_len] == SEP);
path += path_unc_len;
path_len -= path_unc_len;
}
else if (isalpha(path[0]) && path[1] == ':') {
else if (isalpha(path[0]) && (path[1] == ':')) {
path += 2;
}
if (path[0] == '\0') {
return;
path_len -= 2;
}
}
#endif /* WIN32 */
/* Works on WIN32 as well, because the drive component is skipped. */
const bool is_relative = path[0] && (path[0] != SEP);
/* NOTE(@ideasman42):
* `memmove(start, eind, strlen(eind) + 1);`
* is the same as
@ -157,7 +172,6 @@ void BLI_path_normalize(const char *relabase, char *path)
* - `/./` -> `/`.
* - `//` -> `/`.
* Performed until no more replacements can be made. */
int path_len = strlen(path);
if (path_len > 1) {
for (int i = path_len - 1; i > 0; i--) {
/* Calculate the redundant slash span (if any). */
@ -187,21 +201,50 @@ void BLI_path_normalize(const char *relabase, char *path)
}
}
/* Remove redundant `./` prefix, while it could be kept, it confuses the loop below. */
if (is_relative) {
if ((path_len > 2) && (path[0] == '.') && (path[1] == SEP)) {
memmove(path, path + 2, (path_len - 2) + 1);
path_len -= 2;
}
}
const ptrdiff_t a_start = is_relative ? 0 : 1;
start = path;
while ((start = strstr(start, SEP_STR ".." SEP_STR))) {
a = start - path - 1;
if (a > 0) {
while ((start = strstr(start, SEP_STR ".."))) {
if (!ELEM(start[3], SEP, '\0')) {
start += 3;
continue;
}
a = (start - path) - 1;
if (a >= a_start) {
/* `<prefix>/<parent>/../<postfix> => <prefix>/<postfix>`. */
eind = start + (4 - 1) /* `strlen("/../") - 1` */; /* Strip "/.." and keep last "/". */
const size_t eind_len = path_len - (eind - path);
BLI_assert(eind_len == strlen(eind));
while (a > 0 && path[a] != SEP) { /* Find start of `<parent>`. */
eind = start + (4 - 1) /* `strlen("/../") - 1` */; /* Strip "/.." and keep the char after. */
while (a > 0 && path[a] != SEP) { /* Find start of `<parent>`. */
a--;
}
start = path + a;
memmove(start, eind, eind_len + 1);
path_len -= (eind - start);
BLI_assert(strlen(path) == path_len);
if (is_relative && (a == 0) && *eind) {
/* When the path does not start with a slash, don't copy the first `/` to the destination
* as it will make a relative path into an absolute path. */
eind += 1;
}
const size_t eind_len = path_len - (eind - path);
BLI_assert(eind_len == strlen(eind));
/* Only remove the parent if it's not also a `..`. */
if (is_relative && STRPREFIX(path + ((path[a] == SEP) ? a + 1 : a), ".." SEP_STR)) {
start += 3 /* `strlen("/..")` */;
}
else {
start = path + a;
BLI_assert(start < eind);
memmove(start, eind, eind_len + 1);
path_len -= (eind - start);
BLI_assert(strlen(path) == path_len);
BLI_assert(!is_relative || (path[0] != SEP));
}
}
else {
/* Support for odd paths: eg `/../home/me` --> `/home/me`
@ -218,20 +261,27 @@ void BLI_path_normalize(const char *relabase, char *path)
memmove(start, eind, eind_len + 1);
path_len -= 3;
BLI_assert(strlen(path) == path_len);
BLI_assert(!is_relative || (path[0] != SEP));
}
}
if (is_relative && path_len == 0 && (path == path_orig)) {
path[0] = '.';
path[1] = '\0';
path_len += 1;
}
BLI_assert(strlen(path) == path_len);
}
void BLI_path_normalize_dir(const char *relabase, char *dir, size_t dir_maxlen)
void BLI_path_normalize_dir(char *dir, size_t dir_maxlen)
{
/* Would just create an unexpected "/" path, just early exit entirely. */
if (dir[0] == '\0') {
return;
}
BLI_path_normalize(relabase, dir);
BLI_path_normalize(dir);
BLI_path_slash_ensure(dir, dir_maxlen);
}
@ -523,8 +573,8 @@ void BLI_path_rel(char *file, const char *relfile)
BLI_str_replace_char(file + BLI_path_unc_prefix_len(file), '\\', '/');
/* Remove `/./` which confuse the following slash counting. */
BLI_path_normalize(NULL, file);
BLI_path_normalize(NULL, temp);
BLI_path_normalize(file);
BLI_path_normalize(temp);
/* The last slash in the file indicates where the path part ends. */
lslash = BLI_path_slash_rfind(temp);
@ -925,7 +975,7 @@ bool BLI_path_abs(char *path, const char *basepath)
BLI_strncpy(base, basepath, sizeof(base));
/* File component is ignored, so don't bother with the trailing slash. */
BLI_path_normalize(NULL, base);
BLI_path_normalize(base);
lslash = BLI_path_slash_rfind(base);
BLI_str_replace_char(base + BLI_path_unc_prefix_len(base), '\\', '/');
@ -957,7 +1007,7 @@ bool BLI_path_abs(char *path, const char *basepath)
#endif
/* Ensure this is after correcting for path switch. */
BLI_path_normalize(NULL, path);
BLI_path_normalize(path);
return wasrelative;
}
@ -1657,8 +1707,8 @@ bool BLI_path_contains(const char *container_path, const char *containee_path)
BLI_path_slash_native(container_native);
BLI_path_slash_native(containee_native);
BLI_path_normalize(NULL, container_native);
BLI_path_normalize(NULL, containee_native);
BLI_path_normalize(container_native);
BLI_path_normalize(containee_native);
#ifdef WIN32
BLI_str_tolower_ascii(container_native, PATH_MAX);
@ -1761,8 +1811,8 @@ int BLI_path_cmp_normalized(const char *p1, const char *p2)
BLI_path_slash_native(norm_p1);
BLI_path_slash_native(norm_p2);
BLI_path_normalize(NULL, norm_p1);
BLI_path_normalize(NULL, norm_p2);
BLI_path_normalize(norm_p1);
BLI_path_normalize(norm_p2);
return BLI_path_cmp(norm_p1, norm_p2);
}

View File

@ -42,26 +42,38 @@ static char *str_replace_char_strdup(const char *str, char src, char dst)
/** \name Tests for: #BLI_path_normalize
* \{ */
#define NORMALIZE_WITH_BASEDIR(input, input_base, output_expect) \
#define NORMALIZE(input, output_expect) \
{ \
char path[FILE_MAX] = input; \
const char *input_base_test = input_base; \
if (SEP == '\\') { \
str_replace_char_with_relative_exception(path, '/', '\\'); \
input_base_test = str_replace_char_strdup(input_base_test, '/', '\\'); \
} \
BLI_path_normalize(input_base_test, path); \
BLI_path_normalize(path); \
if (SEP == '\\') { \
BLI_str_replace_char(path, '\\', '/'); \
if (input_base_test) { \
free((void *)input_base_test); \
} \
} \
EXPECT_STREQ(path, output_expect); \
} \
((void)0)
#define NORMALIZE(input, output_expect) NORMALIZE_WITH_BASEDIR(input, nullptr, output_expect)
/* #BLI_path_normalize: do nothing. */
TEST(path_util, Normalize_Nop)
{
NORMALIZE(".", ".");
NORMALIZE("./", "./");
NORMALIZE("/", "/");
NORMALIZE("//", "//");
NORMALIZE("//a", "//a");
}
TEST(path_util, Normalize_NopRelative)
{
NORMALIZE("..", "..");
NORMALIZE("../", "../");
NORMALIZE("../", "../");
NORMALIZE("../..", "../..");
NORMALIZE("../../", "../../");
}
/* #BLI_path_normalize: "/./" -> "/" */
TEST(path_util, Normalize_Dot)
@ -83,17 +95,16 @@ TEST(path_util, Normalize_DoubleSlash)
NORMALIZE("//", "//"); /* Exception, double forward slash. */
NORMALIZE(".//", "./");
NORMALIZE("a////", "a/");
NORMALIZE("./a////", "./a/");
NORMALIZE("./a////", "a/");
}
/* #BLI_path_normalize: "foo/bar/../" -> "foo/" */
TEST(path_util, Normalize_Parent)
{
NORMALIZE("/a/b/c/../../../", "/");
NORMALIZE("/a/../a/b/../b/c/../c/", "/a/b/c/");
NORMALIZE_WITH_BASEDIR("//../", "/a/b/c/", "/a/b/");
}
/* #BLI_path_normalize: with too many "/../", match Python's behavior. */
TEST(path_util, Normalize_Unbalanced)
TEST(path_util, Normalize_UnbalancedAbsolute)
{
NORMALIZE("/../", "/");
NORMALIZE("/../a", "/a");
@ -102,7 +113,39 @@ TEST(path_util, Normalize_Unbalanced)
NORMALIZE("/a/b/c/../../../d", "/d");
}
#undef NORMALIZE_WITH_BASEDIR
/* #BLI_path_normalize: with relative paths that result in leading "../". */
TEST(path_util, Normalize_UnbalancedRelative)
{
NORMALIZE("./a/b/c/../../../", ".");
NORMALIZE("a/b/c/../../../", ".");
NORMALIZE("//a/b/c/../../../", "//");
NORMALIZE("./a/../../../", "../../");
NORMALIZE("a/../../../", "../../");
NORMALIZE("///a/../../../", "//../../");
NORMALIZE("//./a/../../../", "//../../");
NORMALIZE("../a/../../../", "../../../");
NORMALIZE("a/b/../c/../../d/../../../e/../../../../f", "../../../../../f");
NORMALIZE(".../.../a/.../b/../c/../../d/../../../e/../../../.../../f", "../f");
}
TEST(path_util, Normalize_UnbalancedRelativeTrailing)
{
NORMALIZE("./a/b/c/../../..", ".");
NORMALIZE("a/b/c/../../..", ".");
NORMALIZE("//a/b/c/../../..", "//");
NORMALIZE("./a/../../..", "../..");
NORMALIZE("a/../../..", "../..");
NORMALIZE("///a/../../..", "//../..");
NORMALIZE("//./a/../../..", "//../..");
NORMALIZE("../a/../../..", "../../..");
}
#undef NORMALIZE
/** \} */

View File

@ -516,7 +516,8 @@ static Main *blo_find_main(FileData *fd, const char *filepath, const char *relab
char name1[FILE_MAX];
BLI_strncpy(name1, filepath, sizeof(name1));
BLI_path_normalize(relabase, name1);
BLI_path_abs(name1, relabase);
BLI_path_normalize(name1);
// printf("blo_find_main: relabase %s\n", relabase);
// printf("blo_find_main: original in %s\n", filepath);
@ -2695,7 +2696,8 @@ static void direct_link_library(FileData *fd, Library *lib, Main *main)
/* Make sure we have full path in lib->filepath_abs */
BLI_strncpy(lib->filepath_abs, lib->filepath, sizeof(lib->filepath));
BLI_path_normalize(fd->relabase, lib->filepath_abs);
BLI_path_abs(lib->filepath_abs, fd->relabase);
BLI_path_normalize(lib->filepath_abs);
// printf("direct_link_library: filepath %s\n", lib->filepath);
// printf("direct_link_library: filepath_abs %s\n", lib->filepath_abs);
@ -3115,8 +3117,8 @@ static void read_libblock_undo_restore_at_old_address(FileData *fd, Main *main,
id,
id_old,
true,
ID_REMAP_SKIP_NEVER_NULL_USAGE | ID_REMAP_SKIP_UPDATE_TAGGING |
ID_REMAP_SKIP_USER_REFCOUNT);
(ID_REMAP_NO_ORIG_POINTERS_ACCESS | ID_REMAP_SKIP_NEVER_NULL_USAGE |
ID_REMAP_SKIP_UPDATE_TAGGING | ID_REMAP_SKIP_USER_REFCOUNT));
/* Special temporary usage of this pointer, necessary for the `undo_preserve` call after
* lib-linking to restore some data that should never be affected by undo, e.g. the 3D cursor of

View File

@ -1474,13 +1474,13 @@ bool BLO_write_file(Main *mainvar,
/* Normalize the paths in case there is some subtle difference (so they can be compared). */
if (relbase_valid) {
BLI_split_dir_part(mainvar->filepath, dir_src, sizeof(dir_src));
BLI_path_normalize(nullptr, dir_src);
BLI_path_normalize(dir_src);
}
else {
dir_src[0] = '\0';
}
BLI_split_dir_part(filepath, dir_dst, sizeof(dir_dst));
BLI_path_normalize(nullptr, dir_dst);
BLI_path_normalize(dir_dst);
/* Only for relative, not relative-all, as this means making existing paths relative. */
if (remap_mode == BLO_WRITE_PATH_REMAP_RELATIVE) {

View File

@ -1659,6 +1659,29 @@ int insert_keyframe(Main *bmain,
return ret;
}
void ED_keyframes_add(FCurve *fcu, int num_keys_to_add)
{
BLI_assert_msg(num_keys_to_add >= 0, "cannot remove keyframes with this function");
if (num_keys_to_add == 0) {
return;
}
fcu->bezt = MEM_recallocN(fcu->bezt, sizeof(BezTriple) * (fcu->totvert + num_keys_to_add));
BezTriple *bezt = fcu->bezt + fcu->totvert; /* Pointer to the first new one. '*/
fcu->totvert += num_keys_to_add;
/* Iterate over the new keys to update their settings. */
while (num_keys_to_add--) {
/* Defaults, ignoring user-preference gives predictable results for API. */
bezt->f1 = bezt->f2 = bezt->f3 = SELECT;
bezt->ipo = BEZT_IPO_BEZ;
bezt->h1 = bezt->h2 = HD_AUTO_ANIM;
bezt++;
}
}
/* ************************************************** */
/* KEYFRAME DELETION */

View File

@ -123,6 +123,17 @@ int insert_vert_fcurve(struct FCurve *fcu,
eBezTriple_KeyframeType keyframe_type,
eInsertKeyFlags flag);
/**
* Add the given number of keyframes to the FCurve. Their coordinates are
* uninitialized, so the curve should not be used without further attention.
*
* The newly created keys are selected, existing keys are not touched.
*
* This can be used to allocate all the keys at once, and then update them
* afterwards.
*/
void ED_keyframes_add(struct FCurve *fcu, int num_keys_to_add);
/* -------- */
/**

View File

@ -198,11 +198,12 @@ static FileSelect file_select_do(bContext *C, int selected_idx, bool do_diropen)
}
else if (file->redirection_path) {
BLI_strncpy(params->dir, file->redirection_path, sizeof(params->dir));
BLI_path_normalize_dir(BKE_main_blendfile_path(bmain), params->dir, sizeof(params->dir));
BLI_path_slash_ensure(params->dir, sizeof(params->dir));
BLI_path_abs(params->dir, BKE_main_blendfile_path(bmain));
BLI_path_normalize_dir(params->dir, sizeof(params->dir));
}
else {
BLI_path_normalize_dir(BKE_main_blendfile_path(bmain), params->dir, sizeof(params->dir));
BLI_path_abs(params->dir, BKE_main_blendfile_path(bmain));
BLI_path_normalize_dir(params->dir, sizeof(params->dir));
BLI_path_append_dir(params->dir, sizeof(params->dir), file->relpath);
}
@ -1095,7 +1096,8 @@ static int bookmark_select_exec(bContext *C, wmOperator *op)
RNA_property_string_get(op->ptr, prop, entry);
BLI_strncpy(params->dir, entry, sizeof(params->dir));
BLI_path_normalize_dir(BKE_main_blendfile_path(bmain), params->dir, sizeof(params->dir));
BLI_path_abs(params->dir, BKE_main_blendfile_path(bmain));
BLI_path_normalize_dir(params->dir, sizeof(params->dir));
ED_file_change_dir(C);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
@ -2058,7 +2060,8 @@ static bool file_execute(bContext *C, SpaceFile *sfile)
BLI_path_parent_dir(params->dir);
}
else {
BLI_path_normalize(BKE_main_blendfile_path(bmain), params->dir);
BLI_path_abs(params->dir, BKE_main_blendfile_path(bmain));
BLI_path_normalize(params->dir);
BLI_path_append_dir(params->dir, sizeof(params->dir), file->relpath);
}
ED_file_change_dir(C);
@ -2222,7 +2225,8 @@ static int file_parent_exec(bContext *C, wmOperator *UNUSED(unused))
if (params) {
if (BLI_path_parent_dir(params->dir)) {
BLI_path_normalize_dir(BKE_main_blendfile_path(bmain), params->dir, sizeof(params->dir));
BLI_path_abs(params->dir, BKE_main_blendfile_path(bmain));
BLI_path_normalize_dir(params->dir, sizeof(params->dir));
ED_file_change_dir(C);
if (params->recursion_level > 1) {
/* Disable 'dirtree' recursion when going up in tree. */
@ -2805,7 +2809,8 @@ void file_directory_enter_handle(bContext *C, void *UNUSED(arg_unused), void *UN
}
}
BLI_path_normalize_dir(BKE_main_blendfile_path(bmain), params->dir, sizeof(params->dir));
BLI_path_abs(params->dir, BKE_main_blendfile_path(bmain));
BLI_path_normalize_dir(params->dir, sizeof(params->dir));
if (filelist_is_dir(sfile->files, params->dir)) {
if (!STREQ(params->dir, old_dir)) { /* Avoids flickering when nothing's changed. */
@ -2892,7 +2897,8 @@ void file_filename_enter_handle(bContext *C, void *UNUSED(arg_unused), void *arg
/* if directory, open it and empty filename field */
if (filelist_is_dir(sfile->files, filepath)) {
BLI_path_normalize_dir(BKE_main_blendfile_path(bmain), filepath, sizeof(filepath));
BLI_path_abs(filepath, BKE_main_blendfile_path(bmain));
BLI_path_normalize_dir(filepath, sizeof(filepath));
BLI_strncpy(params->dir, filepath, sizeof(params->dir));
params->file[0] = '\0';
ED_file_change_dir(C);

View File

@ -1984,7 +1984,8 @@ void filelist_setdir(FileList *filelist, char *r_dir)
const bool allow_invalid = filelist->asset_library_ref != nullptr;
BLI_assert(strlen(r_dir) < FILE_MAX_LIBEXTRA);
BLI_path_normalize_dir(BKE_main_blendfile_path_from_global(), r_dir, FILE_MAX_LIBEXTRA);
BLI_path_abs(r_dir, BKE_main_blendfile_path_from_global());
BLI_path_normalize_dir(r_dir, FILE_MAX_LIBEXTRA);
const bool is_valid_path = filelist->check_dir_fn(filelist, r_dir, !allow_invalid);
BLI_assert(is_valid_path || allow_invalid);
UNUSED_VARS_NDEBUG(is_valid_path);
@ -2916,7 +2917,7 @@ struct TodoDir {
struct FileListReadJob {
ThreadMutex lock;
char main_name[FILE_MAX];
char main_filepath[FILE_MAX];
Main *current_main;
FileList *filelist;
@ -2981,7 +2982,7 @@ static int filelist_readjob_list_dir(FileListReadJob *job_params,
ListBase *entries,
const char *filter_glob,
const bool do_lib,
const char *main_name,
const char *main_filepath,
const bool skip_currpar)
{
direntry *files;
@ -3045,7 +3046,7 @@ static int filelist_readjob_list_dir(FileListReadJob *job_params,
/* If we are considering .blend files as libraries, promote them to directory status. */
entry->typeflag = FILE_TYPE_BLENDER;
/* prevent current file being used as acceptable dir */
if (BLI_path_cmp(main_name, target) != 0) {
if (BLI_path_cmp(main_filepath, target) != 0) {
entry->typeflag |= FILE_TYPE_DIR;
}
}
@ -3586,7 +3587,8 @@ static void filelist_readjob_recursive_dir_add_items(const bool do_lib,
BLI_strncpy(dir, filelist->filelist.root, sizeof(dir));
BLI_strncpy(filter_glob, filelist->filter_data.filter_glob, sizeof(filter_glob));
BLI_path_normalize_dir(job_params->main_name, dir, sizeof(dir));
BLI_path_abs(dir, job_params->main_filepath);
BLI_path_normalize_dir(dir, sizeof(dir));
td_dir->dir = BLI_strdup(dir);
/* Init the file indexer. */
@ -3617,7 +3619,8 @@ static void filelist_readjob_recursive_dir_add_items(const bool do_lib,
* Note that in the end, this means we 'cache' valid relative subdir once here,
* this is actually better. */
BLI_strncpy(rel_subdir, subdir, sizeof(rel_subdir));
BLI_path_normalize_dir(root, rel_subdir, sizeof(rel_subdir));
BLI_path_abs(rel_subdir, root);
BLI_path_normalize_dir(rel_subdir, sizeof(rel_subdir));
BLI_path_rel(rel_subdir, root);
/* Update the current relative base path within the filelist root. */
@ -3649,8 +3652,13 @@ static void filelist_readjob_recursive_dir_add_items(const bool do_lib,
}
if (!is_lib && BLI_is_dir(subdir)) {
entries_num = filelist_readjob_list_dir(
job_params, subdir, &entries, filter_glob, do_lib, job_params->main_name, skip_currpar);
entries_num = filelist_readjob_list_dir(job_params,
subdir,
&entries,
filter_glob,
do_lib,
job_params->main_filepath,
skip_currpar);
}
LISTBASE_FOREACH (FileListInternEntry *, entry, &entries) {
@ -3663,7 +3671,8 @@ static void filelist_readjob_recursive_dir_add_items(const bool do_lib,
/* We have a directory we want to list, add it to todo list!
* Using #BLI_path_join works but isn't needed as `root` has a trailing slash. */
BLI_string_join(dir, sizeof(dir), root, entry->relpath);
BLI_path_normalize_dir(job_params->main_name, dir, sizeof(dir));
BLI_path_abs(dir, job_params->main_filepath);
BLI_path_normalize_dir(dir, sizeof(dir));
td_dir = static_cast<TodoDir *>(BLI_stack_push_r(todo_dirs));
td_dir->level = recursion_level + 1;
td_dir->dir = BLI_strdup(dir);
@ -4088,7 +4097,7 @@ void filelist_readjob_start(FileList *filelist, const int space_notifier, const
flrj = MEM_cnew<FileListReadJob>(__func__);
flrj->filelist = filelist;
flrj->current_main = bmain;
BLI_strncpy(flrj->main_name, BKE_main_blendfile_path(bmain), sizeof(flrj->main_name));
BLI_strncpy(flrj->main_filepath, BKE_main_blendfile_path(bmain), sizeof(flrj->main_filepath));
if ((filelist->flags & FL_FORCE_RESET_MAIN_FILES) && !(filelist->flags & FL_FORCE_RESET)) {
flrj->only_main_data = true;
}

View File

@ -203,8 +203,8 @@ static FileSelectParams *fileselect_ensure_updated_file_params(SpaceFile *sfile)
}
if (params->dir[0]) {
BLI_path_normalize_dir(blendfile_path, params->dir, sizeof(params->dir));
BLI_path_abs(params->dir, blendfile_path);
BLI_path_normalize_dir(params->dir, sizeof(params->dir));
}
params->flag = 0;

View File

@ -571,7 +571,7 @@ static Mesh *subdivide_edit_mesh(const Object *object,
mesh_settings.resolution = (1 << smd->levels) + 1;
mesh_settings.use_optimal_display = (smd->flags & eSubsurfModifierFlag_ControlEdges);
Subdiv *subdiv = BKE_subdiv_update_from_mesh(nullptr, &settings, me_from_em);
Subdiv *subdiv = BKE_subdiv_new_from_mesh(&settings, me_from_em);
Mesh *result = BKE_subdiv_to_mesh(subdiv, &mesh_settings, me_from_em);
BKE_id_free(nullptr, me_from_em);
BKE_subdiv_free(subdiv);

View File

@ -331,7 +331,7 @@ int Controller::LoadMesh(Render *re, ViewLayer *view_layer, Depsgraph *depsgraph
soc string basename((const char *)qfi.fileName().toAscii().data());
char cleaned[FILE_MAX];
BLI_strncpy(cleaned, iFileName, FILE_MAX);
BLI_path_normalize(NULL, cleaned);
BLI_path_normalize(cleaned);
string basename = string(cleaned);
#endif

View File

@ -32,7 +32,7 @@ void getPathName(const string &path, const string &base, vector<string> &pathnam
dir = path.substr(pos, sep - pos);
BLI_strncpy(cleaned, dir.c_str(), FILE_MAX);
BLI_path_normalize(nullptr, cleaned);
BLI_path_normalize(cleaned);
res = string(cleaned);
if (!base.empty()) {

View File

@ -522,7 +522,7 @@ Field<bool> invert_boolean_field(const Field<bool> &field)
{
static auto not_fn = mf::build::SI1_SO<bool, bool>(
"Not", [](bool a) { return !a; }, mf::build::exec_presets::AllSpanOrSingle());
auto not_op = std::make_shared<FieldOperation>(FieldOperation(not_fn, {field}));
auto not_op = FieldOperation::Create(not_fn, {field});
return Field<bool>(not_op);
}

View File

@ -11,10 +11,8 @@ namespace blender::fn::tests {
TEST(field, ConstantFunction)
{
/* TODO: Figure out how to not use another "FieldOperation(" inside of std::make_shared. */
GField constant_field{std::make_shared<FieldOperation>(
FieldOperation(std::make_unique<mf::CustomMF_Constant<int>>(10), {})),
0};
GField constant_field{
FieldOperation::Create(std::make_unique<mf::CustomMF_Constant<int>>(10), {}), 0};
Array<int> result(4);
@ -103,8 +101,7 @@ TEST(field, InputAndFunction)
GField index_field{std::make_shared<IndexFieldInput>()};
auto add_fn = mf::build::SI2_SO<int, int, int>("add", [](int a, int b) { return a + b; });
GField output_field{
std::make_shared<FieldOperation>(FieldOperation(add_fn, {index_field, index_field})), 0};
GField output_field{FieldOperation::Create(add_fn, {index_field, index_field}), 0};
Array<int> result(10);
@ -126,11 +123,10 @@ TEST(field, TwoFunctions)
GField index_field{std::make_shared<IndexFieldInput>()};
auto add_fn = mf::build::SI2_SO<int, int, int>("add", [](int a, int b) { return a + b; });
GField add_field{
std::make_shared<FieldOperation>(FieldOperation(add_fn, {index_field, index_field})), 0};
GField add_field{FieldOperation::Create(add_fn, {index_field, index_field}), 0};
auto add_10_fn = mf::build::SI1_SO<int, int>("add_10", [](int a) { return a + 10; });
GField result_field{std::make_shared<FieldOperation>(FieldOperation(add_10_fn, {add_field})), 0};
GField result_field{FieldOperation::Create(add_10_fn, {add_field}), 0};
Array<int> result(10);
@ -181,8 +177,8 @@ TEST(field, FunctionTwoOutputs)
GField index_field_1{std::make_shared<IndexFieldInput>()};
GField index_field_2{std::make_shared<IndexFieldInput>()};
std::shared_ptr<FieldOperation> fn = std::make_shared<FieldOperation>(
FieldOperation(std::make_unique<TwoOutputFunction>(), {index_field_1, index_field_2}));
std::shared_ptr<FieldOperation> fn = FieldOperation::Create(
std::make_unique<TwoOutputFunction>(), {index_field_1, index_field_2});
GField result_field_1{fn, 0};
GField result_field_2{fn, 1};
@ -212,8 +208,8 @@ TEST(field, TwoFunctionsTwoOutputs)
{
GField index_field{std::make_shared<IndexFieldInput>()};
std::shared_ptr<FieldOperation> fn = std::make_shared<FieldOperation>(
FieldOperation(std::make_unique<TwoOutputFunction>(), {index_field, index_field}));
std::shared_ptr<FieldOperation> fn = FieldOperation::Create(
std::make_unique<TwoOutputFunction>(), {index_field, index_field});
Array<int64_t> mask_indices = {2, 4, 6, 8};
IndexMask mask = mask_indices.as_span();
@ -222,8 +218,7 @@ TEST(field, TwoFunctionsTwoOutputs)
Field<int> intermediate_field{fn, 1};
auto add_10_fn = mf::build::SI1_SO<int, int>("add_10", [](int a) { return a + 10; });
Field<int> result_field_2{
std::make_shared<FieldOperation>(FieldOperation(add_10_fn, {intermediate_field})), 0};
Field<int> result_field_2{FieldOperation::Create(add_10_fn, {intermediate_field}), 0};
FieldContext field_context;
FieldEvaluator field_evaluator{field_context, &mask};
@ -245,8 +240,8 @@ TEST(field, TwoFunctionsTwoOutputs)
TEST(field, SameFieldTwice)
{
GField constant_field{
std::make_shared<FieldOperation>(std::make_unique<mf::CustomMF_Constant<int>>(10)), 0};
GField constant_field{FieldOperation::Create(std::make_unique<mf::CustomMF_Constant<int>>(10)),
0};
FieldContext field_context;
IndexMask mask{IndexRange(2)};
@ -266,7 +261,7 @@ TEST(field, SameFieldTwice)
TEST(field, IgnoredOutput)
{
static mf::tests::OptionalOutputsFunction fn;
Field<int> field{std::make_shared<FieldOperation>(fn), 0};
Field<int> field{FieldOperation::Create(fn), 0};
FieldContext field_context;
FieldEvaluator field_evaluator{field_context, 10};

View File

@ -21,10 +21,7 @@ static fn::Field<int> get_count_input_max_one(const fn::Field<int> &count_field)
"Clamp Above One",
[](int value) { return std::max(1, value); },
mf::build::exec_presets::AllSpanOrSingle());
auto clamp_op = std::make_shared<fn::FieldOperation>(
fn::FieldOperation(max_one_fn, {count_field}));
return fn::Field<int>(std::move(clamp_op));
return fn::Field<int>(fn::FieldOperation::Create(max_one_fn, {count_field}));
}
static fn::Field<int> get_count_input_from_length(const fn::Field<float> &length_field)
@ -39,9 +36,9 @@ static fn::Field<int> get_count_input_from_length(const fn::Field<float> &length
},
mf::build::exec_presets::AllSpanOrSingle());
auto get_count_op = std::make_shared<fn::FieldOperation>(fn::FieldOperation(
auto get_count_op = fn::FieldOperation::Create(
get_count_fn,
{fn::Field<float>(std::make_shared<bke::CurveLengthFieldInput>()), length_field}));
{fn::Field<float>(std::make_shared<bke::CurveLengthFieldInput>()), length_field});
return fn::Field<int>(std::move(get_count_op));
}

View File

@ -151,13 +151,36 @@ shaderc::Compiler &VKBackend::get_shaderc_compiler()
return shaderc_compiler_;
}
void VKBackend::capabilities_init(VKContext & /*context*/)
void VKBackend::capabilities_init(VKContext &context)
{
const VkPhysicalDeviceLimits limits = context.physical_device_limits_get();
/* Reset all capabilities from previous context. */
GCaps = {};
GCaps.compute_shader_support = true;
GCaps.shader_storage_buffer_objects_support = true;
GCaps.shader_image_load_store_support = true;
GCaps.max_texture_size = max_ii(limits.maxImageDimension1D, limits.maxImageDimension2D);
GCaps.max_texture_3d_size = limits.maxImageDimension3D;
GCaps.max_texture_layers = limits.maxImageArrayLayers;
GCaps.max_textures = limits.maxDescriptorSetSampledImages;
GCaps.max_textures_vert = limits.maxPerStageDescriptorSampledImages;
GCaps.max_textures_geom = limits.maxPerStageDescriptorSampledImages;
GCaps.max_textures_frag = limits.maxPerStageDescriptorSampledImages;
GCaps.max_samplers = limits.maxSamplerAllocationCount;
for (int i = 0; i < 3; i++) {
GCaps.max_work_group_count[i] = limits.maxComputeWorkGroupCount[i];
GCaps.max_work_group_size[i] = limits.maxComputeWorkGroupSize[i];
}
GCaps.max_uniforms_vert = limits.maxPerStageDescriptorUniformBuffers;
GCaps.max_uniforms_frag = limits.maxPerStageDescriptorUniformBuffers;
GCaps.max_batch_indices = limits.maxDrawIndirectCount;
GCaps.max_batch_vertices = limits.maxDrawIndexedIndexValue;
GCaps.max_vertex_attribs = limits.maxVertexInputAttributes;
GCaps.max_varying_floats = limits.maxVertexOutputComponents;
GCaps.max_shader_storage_buffer_bindings = limits.maxPerStageDescriptorStorageBuffers;
GCaps.max_compute_shader_storage_blocks = limits.maxPerStageDescriptorStorageBuffers;
}
} // namespace blender::gpu

View File

@ -39,10 +39,7 @@ VKContext::VKContext(void *ghost_window, void *ghost_context)
/* Initialize the memory allocator. */
VmaAllocatorCreateInfo info = {};
/* Should use same vulkan version as GHOST (1.2), but set to 1.0 as 1.2 requires
* correct extensions and functions to be found by VMA, which isn't working as expected and
* requires more research. To continue development we lower the API to version 1.0. */
info.vulkanApiVersion = VK_API_VERSION_1_0;
info.vulkanApiVersion = VK_API_VERSION_1_2;
info.physicalDevice = vk_physical_device_;
info.device = vk_device_;
info.instance = vk_instance_;

View File

@ -93,7 +93,7 @@ void ImagesExporter::export_UV_Image(Image *image, bool use_copies)
/* make absolute source path */
BLI_strncpy(source_path, image->filepath, sizeof(source_path));
BLI_path_abs(source_path, ID_BLEND_PATH_FROM_GLOBAL(&image->id));
BLI_path_normalize(nullptr, source_path);
BLI_path_normalize(source_path);
if (use_copies) {

View File

@ -16,7 +16,7 @@ std::string path_reference(StringRefNull filepath,
char filepath_abs[PATH_MAX];
BLI_strncpy(filepath_abs, filepath.c_str(), PATH_MAX);
BLI_path_abs(filepath_abs, base_src.c_str());
BLI_path_normalize(nullptr, filepath_abs);
BLI_path_normalize(filepath_abs);
/* Figure out final mode to be used. */
if (mode == PATH_REFERENCE_MATCH) {

View File

@ -82,7 +82,7 @@ static std::string copy_asset_to_directory(const char *src_path,
char dest_file_path[FILE_MAX];
BLI_path_join(dest_file_path, sizeof(dest_file_path), dest_dir_path, base_name.c_str());
BLI_path_normalize(NULL, dest_file_path);
BLI_path_normalize(dest_file_path);
if (name_collision_mode == USD_TEX_NAME_COLLISION_USE_EXISTING && BLI_is_file(dest_file_path)) {
return dest_file_path;
@ -278,7 +278,8 @@ std::string import_asset(const char *src,
}
}
BLI_path_normalize(basepath, dest_dir_path);
BLI_path_abs(dest_dir_path, basepath);
BLI_path_normalize(dest_dir_path);
if (!BLI_dir_create_recursive(dest_dir_path)) {
WM_reportf(

View File

@ -444,7 +444,7 @@ static void get_absolute_path(Image *ima, char *r_path)
/* Make absolute source path. */
BLI_strncpy(r_path, ima->filepath, FILE_MAX);
BLI_path_abs(r_path, ID_BLEND_PATH_FROM_GLOBAL(&ima->id));
BLI_path_normalize(nullptr, r_path);
BLI_path_normalize(r_path);
}
static pxr::TfToken get_node_tex_image_color_space(bNode *node)

View File

@ -666,7 +666,7 @@ void MTLWriter::write_materials(const char *blen_filepath,
char blen_filedir[PATH_MAX];
BLI_split_dir_part(blen_filepath, blen_filedir, PATH_MAX);
BLI_path_slash_native(blen_filedir);
BLI_path_normalize(nullptr, blen_filedir);
BLI_path_normalize(blen_filedir);
std::sort(mtlmaterials_.begin(),
mtlmaterials_.end(),

View File

@ -292,7 +292,7 @@ void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, co
BLI_strncpy(dest_dir, export_params.file_base_for_tests, PATH_MAX);
}
BLI_path_slash_native(dest_dir);
BLI_path_normalize(nullptr, dest_dir);
BLI_path_normalize(dest_dir);
mtl_writer->write_materials(export_params.blen_filepath,
export_params.path_mode,
dest_dir,

View File

@ -692,6 +692,7 @@ static void rna_tag_animation_update(Main *bmain, ID *id)
static void rna_FCurve_update_data_ex(ID *id, FCurve *fcu, Main *bmain)
{
sort_time_fcurve(fcu);
/* TODO: Blender 4.0 should call BKE_fcurve_deduplicate_keys(fcu) here. */
BKE_fcurve_handles_recalc(fcu);
rna_tag_animation_update(bmain, id);
@ -1071,24 +1072,12 @@ static BezTriple *rna_FKeyframe_points_insert(
static void rna_FKeyframe_points_add(ID *id, FCurve *fcu, Main *bmain, int tot)
{
if (tot > 0) {
BezTriple *bezt;
fcu->bezt = MEM_recallocN(fcu->bezt, sizeof(BezTriple) * (fcu->totvert + tot));
bezt = fcu->bezt + fcu->totvert;
fcu->totvert += tot;
while (tot--) {
/* Defaults, ignoring user-preference gives predictable results for API. */
bezt->f1 = bezt->f2 = bezt->f3 = SELECT;
bezt->ipo = BEZT_IPO_BEZ;
bezt->h1 = bezt->h2 = HD_AUTO_ANIM;
bezt++;
}
rna_tag_animation_update(bmain, id);
if (tot <= 0) {
return;
}
ED_keyframes_add(fcu, tot);
rna_tag_animation_update(bmain, id);
}
static void rna_FKeyframe_points_remove(
@ -1118,6 +1107,24 @@ static void rna_FKeyframe_points_clear(ID *id, FCurve *fcu, Main *bmain)
rna_tag_animation_update(bmain, id);
}
static void rna_FKeyframe_points_sort(ID *id, FCurve *fcu, Main *bmain)
{
sort_time_fcurve(fcu);
rna_tag_animation_update(bmain, id);
}
static void rna_FKeyframe_points_deduplicate(ID *id, FCurve *fcu, Main *bmain)
{
BKE_fcurve_deduplicate_keys(fcu);
rna_tag_animation_update(bmain, id);
}
static void rna_FKeyframe_points_handles_recalc(ID *id, FCurve *fcu, Main *bmain)
{
BKE_fcurve_handles_recalc(fcu);
rna_tag_animation_update(bmain, id);
}
static FCM_EnvelopeData *rna_FModifierEnvelope_points_add(
ID *id, FModifier *fmod, Main *bmain, ReportList *reports, float frame)
{
@ -2414,6 +2421,22 @@ static void rna_def_fcurve_keyframe_points(BlenderRNA *brna, PropertyRNA *cprop)
func = RNA_def_function(srna, "clear", "rna_FKeyframe_points_clear");
RNA_def_function_ui_description(func, "Remove all keyframes from an F-Curve");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
func = RNA_def_function(srna, "sort", "rna_FKeyframe_points_sort");
RNA_def_function_ui_description(func, "Ensure all keyframe points are chronologically sorted");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
func = RNA_def_function(srna, "deduplicate", "rna_FKeyframe_points_deduplicate");
RNA_def_function_ui_description(
func,
"Ensure there are no duplicate keys. Assumes that the points have already been sorted");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
func = RNA_def_function(srna, "handles_recalc", "rna_FKeyframe_points_handles_recalc");
RNA_def_function_ui_description(func,
"Update handles after modifications to the keyframe points, to "
"update things like auto-clamping");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN);
}
static void rna_def_fcurve(BlenderRNA *brna)

View File

@ -42,23 +42,6 @@ using fn::ValueOrField;
using geo_eval_log::NamedAttributeUsage;
using geo_eval_log::NodeWarningType;
/**
* An anonymous attribute created by a node.
*/
class NodeAnonymousAttributeID : public AnonymousAttributeID {
std::string long_name_;
std::string socket_name_;
public:
NodeAnonymousAttributeID(const Object &object,
const ComputeContext &compute_context,
const bNode &bnode,
const StringRef identifier,
const StringRef name);
std::string user_name() const override;
};
class GeoNodeExecParams {
private:
const bNode &node_;
@ -66,18 +49,22 @@ class GeoNodeExecParams {
const lf::Context &lf_context_;
const Span<int> lf_input_for_output_bsocket_usage_;
const Span<int> lf_input_for_attribute_propagation_to_output_;
const FunctionRef<AnonymousAttributeIDPtr(int)> get_output_attribute_id_;
public:
GeoNodeExecParams(const bNode &node,
lf::Params &params,
const lf::Context &lf_context,
const Span<int> lf_input_for_output_bsocket_usage,
const Span<int> lf_input_for_attribute_propagation_to_output)
const Span<int> lf_input_for_attribute_propagation_to_output,
const FunctionRef<AnonymousAttributeIDPtr(int)> get_output_attribute_id)
: node_(node),
params_(params),
lf_context_(lf_context),
lf_input_for_output_bsocket_usage_(lf_input_for_output_bsocket_usage),
lf_input_for_attribute_propagation_to_output_(lf_input_for_attribute_propagation_to_output)
lf_input_for_attribute_propagation_to_output_(
lf_input_for_attribute_propagation_to_output),
get_output_attribute_id_(get_output_attribute_id)
{
}
@ -240,8 +227,6 @@ class GeoNodeExecParams {
*/
void error_message_add(const NodeWarningType type, StringRef message) const;
std::string attribute_producer_name() const;
void set_default_remaining_outputs();
void used_named_attribute(StringRef attribute_name, NamedAttributeUsage usage);
@ -268,14 +253,7 @@ class GeoNodeExecParams {
return {};
}
const bNodeSocket &output_socket = node_.output_by_identifier(output_identifier);
const GeoNodesLFUserData &user_data = *this->user_data();
const ComputeContext &compute_context = *user_data.compute_context;
return MEM_new<NodeAnonymousAttributeID>(__func__,
*user_data.modifier_data->self_object,
compute_context,
node_,
output_identifier,
output_socket.name);
return get_output_attribute_id_(output_socket.index());
}
/**

View File

@ -322,7 +322,9 @@ class SocketDeclarationBuilder : public BaseSocketDeclarationBuilder {
/**
* For inputs this means that the input field is evaluated on all geometry inputs. For outputs
* it means that this contains an anonymous attribute reference that is available on all geometry
* outputs.
* outputs. This sockets value does not have to be output manually in the node. It's done
* automatically by #LazyFunctionForGeometryNode. This allows outputting this field even if the
* geometry output does not have to be computed.
*/
Self &field_on_all()
{

View File

@ -27,8 +27,16 @@ NODE_STORAGE_FUNCS(NodeBoxMask)
static void cmp_node_boxmask_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Float>(N_("Mask")).default_value(0.0f).min(0.0f).max(1.0f);
b.add_input<decl::Float>(N_("Value")).default_value(1.0f).min(0.0f).max(1.0f);
b.add_input<decl::Float>(N_("Mask"))
.default_value(0.0f)
.min(0.0f)
.max(1.0f)
.compositor_domain_priority(0);
b.add_input<decl::Float>(N_("Value"))
.default_value(1.0f)
.min(0.0f)
.max(1.0f)
.compositor_domain_priority(1);
b.add_output<decl::Float>(N_("Mask"));
}

View File

@ -27,8 +27,16 @@ NODE_STORAGE_FUNCS(NodeEllipseMask)
static void cmp_node_ellipsemask_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Float>(N_("Mask")).default_value(0.0f).min(0.0f).max(1.0f);
b.add_input<decl::Float>(N_("Value")).default_value(1.0f).min(0.0f).max(1.0f);
b.add_input<decl::Float>(N_("Mask"))
.default_value(0.0f)
.min(0.0f)
.max(1.0f)
.compositor_domain_priority(0);
b.add_input<decl::Float>(N_("Value"))
.default_value(1.0f)
.min(0.0f)
.max(1.0f)
.compositor_domain_priority(1);
b.add_output<decl::Float>(N_("Mask"));
}

View File

@ -174,8 +174,6 @@ static void node_geo_exec(GeoNodeExecParams params)
break;
}
const CPPType &type = field.cpp_type();
/* Run on the instances component separately to only affect the top level of instances. */
if (domain == ATTR_DOMAIN_INSTANCE) {
if (geometry_set.has_instances()) {
@ -198,34 +196,6 @@ static void node_geo_exec(GeoNodeExecParams params)
});
}
GField output_field{std::make_shared<bke::AnonymousAttributeFieldInput>(
std::move(attribute_id), type, params.attribute_producer_name())};
switch (data_type) {
case CD_PROP_FLOAT: {
params.set_output(output_identifier, Field<float>(output_field));
break;
}
case CD_PROP_FLOAT3: {
params.set_output(output_identifier, Field<float3>(output_field));
break;
}
case CD_PROP_COLOR: {
params.set_output(output_identifier, Field<ColorGeometry4f>(output_field));
break;
}
case CD_PROP_BOOL: {
params.set_output(output_identifier, Field<bool>(output_field));
break;
}
case CD_PROP_INT32: {
params.set_output(output_identifier, Field<int>(output_field));
break;
}
default:
break;
}
params.set_output("Geometry", geometry_set);
}

View File

@ -160,11 +160,6 @@ static void node_geo_exec(GeoNodeExecParams params)
selection.span[i] = true;
}
selection.finish();
params.set_output(
"Intersecting Edges",
AnonymousAttributeFieldInput::Create<bool>(
std::move(attribute_outputs.intersecting_edges_id), params.attribute_producer_name()));
}
params.set_output("Mesh", GeometrySet::create_with_mesh(result));

View File

@ -81,9 +81,6 @@ static void node_geo_exec(GeoNodeExecParams params)
if (AnonymousAttributeIDPtr outer_points_id = params.get_output_anonymous_attribute_id_if_needed(
"Outer Points")) {
create_selection_output(output.get_component_for_write<CurveComponent>(), outer_points_id);
params.set_output("Outer Points",
AnonymousAttributeFieldInput::Create<bool>(
std::move(outer_points_id), params.attribute_producer_name()));
}
params.set_output("Curve", std::move(output));
}

View File

@ -200,21 +200,6 @@ static void node_geo_exec(GeoNodeExecParams params)
}
params.set_output("Points", std::move(geometry_set));
if (tangent_anonymous_id) {
params.set_output("Tangent",
AnonymousAttributeFieldInput::Create<float3>(
std::move(tangent_anonymous_id), params.attribute_producer_name()));
}
if (normal_anonymous_id) {
params.set_output("Normal",
AnonymousAttributeFieldInput::Create<float3>(
std::move(normal_anonymous_id), params.attribute_producer_name()));
}
if (rotation_anonymous_id) {
params.set_output("Rotation",
AnonymousAttributeFieldInput::Create<float3>(
std::move(rotation_anonymous_id), params.attribute_producer_name()));
}
}
} // namespace blender::nodes::node_geo_curve_to_points_cc

View File

@ -590,19 +590,6 @@ static void node_geo_exec(GeoNodeExecParams params)
});
params.set_output("Points", std::move(geometry_set));
if (attribute_outputs.normal_id) {
params.set_output(
"Normal",
AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.normal_id),
params.attribute_producer_name()));
}
if (attribute_outputs.rotation_id) {
params.set_output(
"Rotation",
AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.rotation_id),
params.attribute_producer_name()));
}
}
} // namespace blender::nodes::node_geo_distribute_points_on_faces_cc

View File

@ -1098,12 +1098,6 @@ static void node_geo_exec(GeoNodeExecParams params)
return;
}
if (attribute_outputs.duplicate_index) {
params.set_output(
"Duplicate Index",
AnonymousAttributeFieldInput::Create<int>(std::move(attribute_outputs.duplicate_index),
params.attribute_producer_name()));
}
params.set_output("Geometry", std::move(geometry_set));
}

View File

@ -1343,9 +1343,8 @@ static void node_geo_exec(GeoNodeExecParams params)
"Scale",
[](const float3 &offset, const float scale) { return offset * scale; },
mf::build::exec_presets::AllSpanOrSingle());
std::shared_ptr<FieldOperation> multiply_op = std::make_shared<FieldOperation>(
FieldOperation(multiply_fn, {std::move(offset_field), std::move(scale_field)}));
const Field<float3> final_offset{std::move(multiply_op)};
const Field<float3> final_offset{
FieldOperation::Create(multiply_fn, {std::move(offset_field), std::move(scale_field)})};
AttributeOutputs attribute_outputs;
attribute_outputs.top_id = params.get_output_anonymous_attribute_id_if_needed("Top");
@ -1377,16 +1376,6 @@ static void node_geo_exec(GeoNodeExecParams params)
});
params.set_output("Mesh", std::move(geometry_set));
if (attribute_outputs.top_id) {
params.set_output("Top",
AnonymousAttributeFieldInput::Create<bool>(
std::move(attribute_outputs.top_id), params.attribute_producer_name()));
}
if (attribute_outputs.side_id) {
params.set_output("Side",
AnonymousAttributeFieldInput::Create<bool>(
std::move(attribute_outputs.side_id), params.attribute_producer_name()));
}
}
} // namespace blender::nodes::node_geo_extrude_mesh_cc

View File

@ -415,8 +415,7 @@ static void node_geo_exec(GeoNodeExecParams params)
Field<float3> vector_field = params.extract_input<Field<float3>>("Vector");
auto image_op = std::make_shared<FieldOperation>(
FieldOperation(std::move(image_fn), {std::move(vector_field)}));
auto image_op = FieldOperation::Create(std::move(image_fn), {std::move(vector_field)});
params.set_output("Color", Field<ColorGeometry4f>(image_op, 0));
params.set_output("Alpha", Field<float>(image_op, 1));

View File

@ -836,16 +836,6 @@ static void node_geo_exec(GeoNodeExecParams params)
}
params.set_output("Curves", std::move(new_curves));
if (index_attribute_id) {
params.set_output("Closest Index",
AnonymousAttributeFieldInput::Create<int>(std::move(index_attribute_id),
params.attribute_producer_name()));
}
if (weight_attribute_id) {
params.set_output("Closest Weight",
AnonymousAttributeFieldInput::Create<float>(
std::move(weight_attribute_id), params.attribute_producer_name()));
}
}
} // namespace blender::nodes::node_geo_interpolate_curves_cc

View File

@ -851,29 +851,6 @@ static void node_geo_exec(GeoNodeExecParams params)
/* Transform the mesh so that the base of the cone is at the origin. */
BKE_mesh_translate(mesh, float3(0.0f, 0.0f, depth * 0.5f), false);
if (attribute_outputs.top_id) {
params.set_output("Top",
AnonymousAttributeFieldInput::Create<bool>(
std::move(attribute_outputs.top_id), params.attribute_producer_name()));
}
if (attribute_outputs.bottom_id) {
params.set_output(
"Bottom",
AnonymousAttributeFieldInput::Create<bool>(std::move(attribute_outputs.bottom_id),
params.attribute_producer_name()));
}
if (attribute_outputs.side_id) {
params.set_output("Side",
AnonymousAttributeFieldInput::Create<bool>(
std::move(attribute_outputs.side_id), params.attribute_producer_name()));
}
if (attribute_outputs.uv_map_id) {
params.set_output(
"UV Map",
AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.uv_map_id),
params.attribute_producer_name()));
}
params.set_output("Mesh", GeometrySet::create_with_mesh(mesh));
}

View File

@ -112,12 +112,6 @@ static void node_geo_exec(GeoNodeExecParams params)
Mesh *mesh = create_cube_mesh(size, verts_x, verts_y, verts_z, uv_map_id.get());
params.set_output("Mesh", GeometrySet::create_with_mesh(mesh));
if (uv_map_id) {
params.set_output("UV Map",
AnonymousAttributeFieldInput::Create<float3>(
std::move(uv_map_id), params.attribute_producer_name()));
}
}
} // namespace blender::nodes::node_geo_mesh_primitive_cube_cc

View File

@ -122,29 +122,6 @@ static void node_geo_exec(GeoNodeExecParams params)
fill,
attribute_outputs);
if (attribute_outputs.top_id) {
params.set_output("Top",
AnonymousAttributeFieldInput::Create<bool>(
std::move(attribute_outputs.top_id), params.attribute_producer_name()));
}
if (attribute_outputs.bottom_id) {
params.set_output(
"Bottom",
AnonymousAttributeFieldInput::Create<bool>(std::move(attribute_outputs.bottom_id),
params.attribute_producer_name()));
}
if (attribute_outputs.side_id) {
params.set_output("Side",
AnonymousAttributeFieldInput::Create<bool>(
std::move(attribute_outputs.side_id), params.attribute_producer_name()));
}
if (attribute_outputs.uv_map_id) {
params.set_output(
"UV Map",
AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.uv_map_id),
params.attribute_producer_name()));
}
params.set_output("Mesh", GeometrySet::create_with_mesh(mesh));
}

View File

@ -205,12 +205,6 @@ static void node_geo_exec(GeoNodeExecParams params)
BKE_id_material_eval_ensure_default_slot(&mesh->id);
params.set_output("Mesh", GeometrySet::create_with_mesh(mesh));
if (uv_map_id) {
params.set_output("UV Map",
AnonymousAttributeFieldInput::Create<float3>(
std::move(uv_map_id), params.attribute_producer_name()));
}
}
} // namespace blender::nodes::node_geo_mesh_primitive_grid_cc

View File

@ -115,12 +115,6 @@ static void node_geo_exec(GeoNodeExecParams params)
Mesh *mesh = create_ico_sphere_mesh(subdivisions, radius, uv_map_id.get());
params.set_output("Mesh", GeometrySet::create_with_mesh(mesh));
if (uv_map_id) {
params.set_output("UV Map",
AnonymousAttributeFieldInput::Create<float3>(
std::move(uv_map_id), params.attribute_producer_name()));
}
}
} // namespace blender::nodes::node_geo_mesh_primitive_ico_sphere_cc

View File

@ -363,11 +363,6 @@ static void node_geo_exec(GeoNodeExecParams params)
Mesh *mesh = create_uv_sphere_mesh(radius, segments_num, rings_num, uv_map_id.get());
params.set_output("Mesh", GeometrySet::create_with_mesh(mesh));
if (uv_map_id) {
params.set_output("UV Map",
AnonymousAttributeFieldInput::Create<float3>(
std::move(uv_map_id), params.attribute_producer_name()));
}
}
} // namespace blender::nodes::node_geo_mesh_primitive_uv_sphere_cc

View File

@ -18,14 +18,8 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Geometry>(N_("Mesh")).propagate_all();
}
static void geometry_set_mesh_subdivide(GeometrySet &geometry_set, const int level)
static Mesh *simple_subdivide_mesh(const Mesh &mesh, const int level)
{
if (!geometry_set.has_mesh()) {
return;
}
const Mesh *mesh_in = geometry_set.get_mesh_for_read();
/* Initialize mesh settings. */
SubdivToMeshSettings mesh_settings;
mesh_settings.resolution = (1 << level) + 1;
@ -42,43 +36,38 @@ static void geometry_set_mesh_subdivide(GeometrySet &geometry_set, const int lev
subdiv_settings.fvar_linear_interpolation = BKE_subdiv_fvar_interpolation_from_uv_smooth(0);
/* Apply subdivision from mesh. */
Subdiv *subdiv = BKE_subdiv_update_from_mesh(nullptr, &subdiv_settings, mesh_in);
/* In case of bad topology, skip to input mesh. */
if (subdiv == nullptr) {
return;
Subdiv *subdiv = BKE_subdiv_new_from_mesh(&subdiv_settings, &mesh);
if (!subdiv) {
return nullptr;
}
Mesh *mesh_out = BKE_subdiv_to_mesh(subdiv, &mesh_settings, mesh_in);
MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>();
mesh_component.replace(mesh_out);
Mesh *result = BKE_subdiv_to_mesh(subdiv, &mesh_settings, &mesh);
BKE_subdiv_free(subdiv);
return result;
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh");
#ifndef WITH_OPENSUBDIV
params.error_message_add(NodeWarningType::Error,
TIP_("Disabled, Blender was compiled without OpenSubdiv"));
params.set_default_remaining_outputs();
return;
#endif
#ifdef WITH_OPENSUBDIV
/* See CCGSUBSURF_LEVEL_MAX for max limit. */
const int subdiv_level = clamp_i(params.extract_input<int>("Level"), 0, 11);
if (subdiv_level == 0) {
const int level = clamp_i(params.extract_input<int>("Level"), 0, 11);
if (level == 0) {
params.set_output("Mesh", std::move(geometry_set));
return;
}
geometry_set.modify_geometry_sets(
[&](GeometrySet &geometry_set) { geometry_set_mesh_subdivide(geometry_set, subdiv_level); });
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
if (const Mesh *mesh = geometry_set.get_mesh_for_read()) {
geometry_set.replace_mesh(simple_subdivide_mesh(*mesh, level));
}
});
#else
params.error_message_add(NodeWarningType::Error,
TIP_("Disabled, Blender was compiled without OpenSubdiv"));
#endif
params.set_output("Mesh", std::move(geometry_set));
}

View File

@ -47,9 +47,9 @@ static void node_init(bNodeTree * /*tree*/, bNode *node)
}
static void geometry_set_mesh_to_points(GeometrySet &geometry_set,
Field<float3> &position_field,
Field<float> &radius_field,
Field<bool> &selection_field,
const Field<float3> &position_field,
const Field<float> &radius_field,
const Field<bool> &selection_field,
const eAttrDomain domain,
const AnonymousAttributePropagationInfo &propagation_info)
{
@ -151,9 +151,7 @@ static void node_geo_exec(GeoNodeExecParams params)
__func__,
[](float value) { return std::max(0.0f, value); },
mf::build::exec_presets::AllSpanOrSingle());
auto max_zero_op = std::make_shared<FieldOperation>(
FieldOperation(max_zero_fn, {std::move(radius)}));
Field<float> positive_radius(std::move(max_zero_op), 0);
const Field<float> positive_radius(FieldOperation::Create(max_zero_fn, {std::move(radius)}), 0);
const NodeGeometryMeshToPoints &storage = node_storage(params.node());
const GeometryNodeMeshToPointsMode mode = (GeometryNodeMeshToPointsMode)storage.mode;

View File

@ -198,10 +198,10 @@ class ProximityFunction : public mf::MultiFunction {
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set_target = params.extract_input<GeometrySet>("Target");
geometry_set_target.ensure_owns_direct_data();
GeometrySet target = params.extract_input<GeometrySet>("Target");
target.ensure_owns_direct_data();
if (!geometry_set_target.has_mesh() && !geometry_set_target.has_pointcloud()) {
if (!target.has_mesh() && !target.has_pointcloud()) {
params.set_default_remaining_outputs();
return;
}
@ -210,9 +210,8 @@ static void node_geo_exec(GeoNodeExecParams params)
Field<float3> position_field = params.extract_input<Field<float3>>("Source Position");
auto proximity_fn = std::make_unique<ProximityFunction>(
std::move(geometry_set_target), GeometryNodeProximityTargetType(storage.target_element));
auto proximity_op = std::make_shared<FieldOperation>(
FieldOperation(std::move(proximity_fn), {std::move(position_field)}));
std::move(target), GeometryNodeProximityTargetType(storage.target_element));
auto proximity_op = FieldOperation::Create(std::move(proximity_fn), {std::move(position_field)});
params.set_output("Position", Field<float3>(proximity_op, 0));
params.set_output("Distance", Field<float>(proximity_op, 1));

View File

@ -407,9 +407,9 @@ static void node_geo_exec(GeoNodeExecParams params)
Field<float> length_field = params.extract_input<Field<float>>("Ray Length");
auto fn = std::make_unique<RaycastFunction>(std::move(target), std::move(field), mapping);
auto op = std::make_shared<FieldOperation>(FieldOperation(
auto op = FieldOperation::Create(
std::move(fn),
{std::move(position_field), std::move(direction_field), std::move(length_field)}));
{std::move(position_field), std::move(direction_field), std::move(length_field)});
params.set_output("Is Hit", Field<bool>(op, 0));
params.set_output("Hit Position", Field<float3>(op, 1));

View File

@ -348,9 +348,6 @@ static void create_attributes(GeoNodeExecParams &params,
*line_id, ATTR_DOMAIN_INSTANCE);
line_attribute.span.copy_from(layout.line_numbers);
line_attribute.finish();
params.set_output("Line",
AnonymousAttributeFieldInput::Create<int>(std::move(line_id),
params.attribute_producer_name()));
}
if (AnonymousAttributeIDPtr pivot_id = params.get_output_anonymous_attribute_id_if_needed(
@ -363,9 +360,6 @@ static void create_attributes(GeoNodeExecParams &params,
}
pivot_attribute.finish();
params.set_output("Pivot Point",
AnonymousAttributeFieldInput::Create<float3>(
std::move(pivot_id), params.attribute_producer_name()));
}
}

View File

@ -1,12 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_array_utils.hh"
#include "BLI_task.hh"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "BKE_attribute.hh"
#include "BKE_lib_id.h"
#include "BKE_mesh.hh"
#include "BKE_subdiv.h"
#include "BKE_subdiv_mesh.hh"
@ -54,136 +54,136 @@ static void node_init(bNodeTree * /*tree*/, bNode *node)
}
#ifdef WITH_OPENSUBDIV
static void materialize_and_clamp_creases(const VArray<float> &crease_varray,
MutableSpan<float> creases)
static void write_vert_creases(Mesh &mesh, const VArray<float> &creases)
{
threading::parallel_for(creases.index_range(), 1024, [&](IndexRange range) {
crease_varray.materialize(range, creases);
for (const int i : range) {
creases[i] = std::clamp(creases[i], 0.0f, 1.0f);
}
});
CustomData_free_layers(&mesh.vdata, CD_CREASE, mesh.totvert);
float *layer = static_cast<float *>(
CustomData_add_layer(&mesh.vdata, CD_CREASE, CD_CONSTRUCT, mesh.totvert));
array_utils::copy(creases, {layer, mesh.totvert});
}
static void write_vertex_creases(Mesh &mesh, const VArray<float> &crease_varray)
static void write_edge_creases(Mesh &mesh, const VArray<float> &creases)
{
float *crease;
if (CustomData_has_layer(&mesh.vdata, CD_CREASE)) {
crease = static_cast<float *>(
CustomData_get_layer_for_write(&mesh.vdata, CD_CREASE, mesh.totvert));
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
attributes.remove("crease");
attributes.add<float>("crease", ATTR_DOMAIN_EDGE, bke::AttributeInitVArray(creases));
}
static bool varray_is_single_zero(const VArray<float> &varray)
{
if (const std::optional<float> value = varray.get_if_single()) {
return *value == 0.0f;
}
else {
crease = static_cast<float *>(
CustomData_add_layer(&mesh.vdata, CD_CREASE, CD_CONSTRUCT, mesh.totvert));
return false;
}
static fn::Field<float> clamp_crease(fn::Field<float> crease_field)
{
static auto clamp_fn = mf::build::SI1_SO<float, float>(
"Clamp",
[](float value) { return std::clamp(value, 0.0f, 1.0f); },
mf::build::exec_presets::AllSpanOrSingle());
return fn::Field<float>(fn::FieldOperation::Create(clamp_fn, {std::move(crease_field)}));
}
static Mesh *mesh_subsurf_calc(const Mesh *mesh,
const int level,
const Field<float> &vert_crease_field,
const Field<float> &edge_crease_field,
const int boundary_smooth,
const int uv_smooth)
{
const bke::MeshFieldContext point_context{*mesh, ATTR_DOMAIN_POINT};
FieldEvaluator point_evaluator(point_context, mesh->totvert);
point_evaluator.add(clamp_crease(vert_crease_field));
point_evaluator.evaluate();
const bke::MeshFieldContext edge_context{*mesh, ATTR_DOMAIN_EDGE};
FieldEvaluator edge_evaluator(edge_context, mesh->totedge);
edge_evaluator.add(clamp_crease(edge_crease_field));
edge_evaluator.evaluate();
const VArray<float> vert_creases = point_evaluator.get_evaluated<float>(0);
const VArray<float> edge_creases = edge_evaluator.get_evaluated<float>(0);
const bool use_creases = !varray_is_single_zero(vert_creases) ||
!varray_is_single_zero(edge_creases);
Mesh *mesh_copy = nullptr;
if (use_creases) {
/* Due to the "BKE_subdiv" API, the crease layers must be on the input mesh. But in this node
* they are provided as separate inputs, not as custom data layers. When needed, retrieve the
* mesh with write access and store the new crease values there. */
mesh_copy = BKE_mesh_copy_for_eval(mesh);
write_vert_creases(*mesh_copy, vert_creases);
write_edge_creases(*mesh_copy, edge_creases);
mesh = mesh_copy;
}
materialize_and_clamp_creases(crease_varray, {crease, mesh.totvert});
SubdivToMeshSettings mesh_settings;
mesh_settings.resolution = (1 << level) + 1;
mesh_settings.use_optimal_display = false;
SubdivSettings subdiv_settings;
subdiv_settings.is_simple = false;
subdiv_settings.is_adaptive = false;
subdiv_settings.use_creases = use_creases;
subdiv_settings.level = level;
subdiv_settings.vtx_boundary_interpolation = BKE_subdiv_vtx_boundary_interpolation_from_subsurf(
boundary_smooth);
subdiv_settings.fvar_linear_interpolation = BKE_subdiv_fvar_interpolation_from_uv_smooth(
uv_smooth);
Subdiv *subdiv = BKE_subdiv_new_from_mesh(&subdiv_settings, mesh);
if (!subdiv) {
return nullptr;
}
Mesh *result = BKE_subdiv_to_mesh(subdiv, &mesh_settings, mesh);
BKE_subdiv_free(subdiv);
if (use_creases) {
/* Remove the layer in case it was created by the node from the field input. The fact
* that this node uses #CD_CREASE to input creases to the subdivision code is meant to be
* an implementation detail ideally. */
CustomData_free_layers(&result->edata, CD_CREASE, result->totedge);
}
if (mesh_copy) {
BKE_id_free(nullptr, mesh_copy);
}
return result;
}
static void write_edge_creases(Mesh &mesh, const VArray<float> &crease_varray)
{
bke::SpanAttributeWriter<float> attribute =
mesh.attributes_for_write().lookup_or_add_for_write_only_span<float>("crease",
ATTR_DOMAIN_EDGE);
materialize_and_clamp_creases(crease_varray, attribute.span);
attribute.finish();
}
static bool varray_is_nonzero(const VArray<float> &varray)
{
return !(varray.is_single() && varray.get_internal_single() == 0.0f);
}
#endif
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh");
#ifndef WITH_OPENSUBDIV
params.error_message_add(NodeWarningType::Error,
TIP_("Disabled, Blender was compiled without OpenSubdiv"));
#else
Field<float> edge_crease_field = params.extract_input<Field<float>>("Edge Crease");
Field<float> vertex_crease_field = params.extract_input<Field<float>>("Vertex Crease");
#ifdef WITH_OPENSUBDIV
const Field<float> vert_crease = params.extract_input<Field<float>>("Vertex Crease");
const Field<float> edge_crease = params.extract_input<Field<float>>("Edge Crease");
const NodeGeometrySubdivisionSurface &storage = node_storage(params.node());
const int uv_smooth = storage.uv_smooth;
const int boundary_smooth = storage.boundary_smooth;
const int subdiv_level = clamp_i(params.extract_input<int>("Level"), 0, 30);
/* Only process subdivision if level is greater than 0. */
if (subdiv_level == 0) {
const int level = std::clamp(params.extract_input<int>("Level"), 0, 11);
if (level == 0) {
params.set_output("Mesh", std::move(geometry_set));
return;
}
geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
const Mesh *mesh = geometry_set.get_mesh_for_read();
if (!mesh) {
return;
if (const Mesh *mesh = geometry_set.get_mesh_for_read()) {
geometry_set.replace_mesh(
mesh_subsurf_calc(mesh, level, vert_crease, edge_crease, boundary_smooth, uv_smooth));
}
if (mesh->totvert == 0 || mesh->totedge == 0) {
return;
}
bke::MeshFieldContext point_context{*mesh, ATTR_DOMAIN_POINT};
FieldEvaluator point_evaluator(point_context, mesh->totvert);
point_evaluator.add(vertex_crease_field);
point_evaluator.evaluate();
const VArray<float> vertex_creases = point_evaluator.get_evaluated<float>(0);
bke::MeshFieldContext edge_context{*mesh, ATTR_DOMAIN_EDGE};
FieldEvaluator edge_evaluator(edge_context, mesh->totedge);
edge_evaluator.add(edge_crease_field);
edge_evaluator.evaluate();
const VArray<float> edge_creases = edge_evaluator.get_evaluated<float>(0);
const bool use_creases = varray_is_nonzero(vertex_creases) || varray_is_nonzero(edge_creases);
if (use_creases) {
/* Due to the "BKE_subdiv" API, the crease layers must be on the input mesh. But in this node
* they are provided as separate inputs, not as custom data layers. When needed, retrieve the
* mesh with write access and store the new crease values there. */
Mesh &mesh_for_write = *geometry_set.get_mesh_for_write();
write_vertex_creases(mesh_for_write, vertex_creases);
write_edge_creases(mesh_for_write, edge_creases);
mesh = &mesh_for_write;
}
/* Initialize mesh settings. */
SubdivToMeshSettings mesh_settings;
mesh_settings.resolution = (1 << subdiv_level) + 1;
mesh_settings.use_optimal_display = false;
/* Initialize subdivision settings. */
SubdivSettings subdiv_settings;
subdiv_settings.is_simple = false;
subdiv_settings.is_adaptive = false;
subdiv_settings.use_creases = use_creases;
subdiv_settings.level = subdiv_level;
subdiv_settings.vtx_boundary_interpolation =
BKE_subdiv_vtx_boundary_interpolation_from_subsurf(boundary_smooth);
subdiv_settings.fvar_linear_interpolation = BKE_subdiv_fvar_interpolation_from_uv_smooth(
uv_smooth);
/* Apply subdivision to mesh. */
Subdiv *subdiv = BKE_subdiv_update_from_mesh(nullptr, &subdiv_settings, mesh);
/* In case of bad topology, skip to input mesh. */
if (subdiv == nullptr) {
return;
}
Mesh *result = BKE_subdiv_to_mesh(subdiv, &mesh_settings, mesh);
BKE_subdiv_free(subdiv);
if (use_creases) {
/* Remove the layer in case it was created by the node from the field input. The fact
* that this node uses #CD_CREASE to input creases to the subdivision code is meant to be
* an implementation detail ideally. */
CustomData_free_layers(&result->edata, CD_CREASE, result->totedge);
}
geometry_set.replace_mesh(result);
});
#else
params.error_message_add(NodeWarningType::Error,
TIP_("Disabled, Blender was compiled without OpenSubdiv"));
#endif
params.set_output("Mesh", std::move(geometry_set));
}

View File

@ -21,6 +21,7 @@
#include "BLI_cpp_types.hh"
#include "BLI_dot_export.hh"
#include "BLI_hash.h"
#include "BLI_hash_md5.h"
#include "BLI_lazy_threading.hh"
#include "BLI_map.hh"
@ -102,6 +103,43 @@ static void lazy_function_interface_from_node(const bNode &node,
}
}
/**
* An anonymous attribute created by a node.
*/
class NodeAnonymousAttributeID : public AnonymousAttributeID {
std::string long_name_;
std::string socket_name_;
public:
NodeAnonymousAttributeID(const Object &object,
const ComputeContext &compute_context,
const bNode &bnode,
const StringRef identifier,
const StringRef name)
: socket_name_(name)
{
const ComputeContextHash &hash = compute_context.hash();
{
std::stringstream ss;
ss << hash << "_" << object.id.name << "_" << bnode.identifier << "_" << identifier;
long_name_ = ss.str();
}
{
uint64_t hash_result[2];
BLI_hash_md5_buffer(long_name_.data(), long_name_.size(), hash_result);
std::stringstream ss;
ss << ".a_" << std::hex << hash_result[0] << hash_result[1];
name_ = ss.str();
BLI_assert(name_.size() < MAX_CUSTOMDATA_LAYER_NAME);
}
}
std::string user_name() const override
{
return socket_name_;
}
};
/**
* Used for most normal geometry nodes like Subdivision Surface and Set Position.
*/
@ -117,6 +155,25 @@ class LazyFunctionForGeometryNode : public LazyFunction {
* propagated to the output.
*/
const Span<int> lf_input_for_attribute_propagation_to_output_;
/**
* Maps #bNodeSocket::index_in_tree to input/output indices of the current lazy-function.
*/
const Span<int> lf_index_by_bsocket_;
/**
* A bool for every output bsocket. If true, the socket just outputs a field containing an
* anonymous attribute id. If only such outputs are requested by other nodes, the node itself
* does not have to execute.
*/
Vector<bool> is_attribute_output_bsocket_;
struct OutputAttributeID {
int bsocket_index;
AnonymousAttributeIDPtr attribute_id;
};
struct Storage {
Vector<OutputAttributeID, 1> attributes;
};
public:
LazyFunctionForGeometryNode(const bNode &node,
@ -126,7 +183,9 @@ class LazyFunctionForGeometryNode : public LazyFunction {
: node_(node),
lf_input_for_output_bsocket_usage_(r_lf_input_for_output_bsocket_usage),
lf_input_for_attribute_propagation_to_output_(
r_lf_input_for_attribute_propagation_to_output)
r_lf_input_for_attribute_propagation_to_output),
lf_index_by_bsocket_(r_lf_index_by_bsocket),
is_attribute_output_bsocket_(node.output_sockets().size(), false)
{
BLI_assert(node.typeinfo->geometry_node_execute != nullptr);
debug_name_ = node.name;
@ -137,6 +196,16 @@ class LazyFunctionForGeometryNode : public LazyFunction {
if (relations == nullptr) {
return;
}
if (!relations->available_relations.is_empty()) {
/* Inputs are only used when an output is used that is not just outputting an anonymous
* attribute field. */
for (lf::Input &input : inputs_) {
input.usage = lf::ValueUsage::Maybe;
}
for (const aal::AvailableRelation &relation : relations->available_relations) {
is_attribute_output_bsocket_[relation.field_output] = true;
}
}
Vector<const bNodeSocket *> handled_field_outputs;
for (const aal::AvailableRelation &relation : relations->available_relations) {
const bNodeSocket &output_bsocket = node.output_socket(relation.field_output);
@ -160,16 +229,90 @@ class LazyFunctionForGeometryNode : public LazyFunction {
}
}
void *init_storage(LinearAllocator<> &allocator) const override
{
return allocator.construct<Storage>().release();
}
void destruct_storage(void *storage) const override
{
Storage *s = static_cast<Storage *>(storage);
std::destroy_at(s);
}
void execute_impl(lf::Params &params, const lf::Context &context) const override
{
Storage *storage = static_cast<Storage *>(context.storage);
GeoNodesLFUserData *user_data = dynamic_cast<GeoNodesLFUserData *>(context.user_data);
BLI_assert(user_data != nullptr);
/* Lazily create the required anonymous attribute ids. */
auto get_output_attribute_id = [&](const int output_bsocket_index) -> AnonymousAttributeIDPtr {
for (const OutputAttributeID &node_output_attribute : storage->attributes) {
if (node_output_attribute.bsocket_index == output_bsocket_index) {
return node_output_attribute.attribute_id;
}
}
const bNodeSocket &bsocket = node_.output_socket(output_bsocket_index);
AnonymousAttributeIDPtr attribute_id = MEM_new<NodeAnonymousAttributeID>(
__func__,
*user_data->modifier_data->self_object,
*user_data->compute_context,
node_,
bsocket.identifier,
bsocket.name);
storage->attributes.append({output_bsocket_index, attribute_id});
return attribute_id;
};
bool used_non_attribute_output_exists = false;
for (const int output_bsocket_index : node_.output_sockets().index_range()) {
const bNodeSocket &output_bsocket = node_.output_socket(output_bsocket_index);
const int lf_index = lf_index_by_bsocket_[output_bsocket.index_in_tree()];
if (lf_index == -1) {
continue;
}
const lf::ValueUsage output_usage = params.get_output_usage(lf_index);
if (output_usage == lf::ValueUsage::Unused) {
continue;
}
if (is_attribute_output_bsocket_[output_bsocket_index]) {
if (params.output_was_set(lf_index)) {
continue;
}
this->output_anonymous_attribute_field(
params, lf_index, get_output_attribute_id(output_bsocket_index));
}
else {
if (output_usage == lf::ValueUsage::Used) {
used_non_attribute_output_exists = true;
}
}
}
if (!used_non_attribute_output_exists) {
/* Only attribute outputs are used currently, no need to evaluate the full node and its
* inputs. */
return;
}
bool missing_input = false;
for (const int lf_index : inputs_.index_range()) {
if (params.try_get_input_data_ptr_or_request(lf_index) == nullptr) {
missing_input = true;
}
}
if (missing_input) {
/* Wait until all inputs are available. */
return;
}
GeoNodeExecParams geo_params{node_,
params,
context,
lf_input_for_output_bsocket_usage_,
lf_input_for_attribute_propagation_to_output_};
lf_input_for_attribute_propagation_to_output_,
get_output_attribute_id};
geo_eval_log::TimePoint start_time = geo_eval_log::Clock::now();
node_.typeinfo->geometry_node_execute(geo_params);
@ -182,6 +325,24 @@ class LazyFunctionForGeometryNode : public LazyFunction {
}
}
/**
* Output the given anonymous attribute id as a field.
*/
void output_anonymous_attribute_field(lf::Params &params,
const int lf_index,
AnonymousAttributeIDPtr attribute_id) const
{
const ValueOrFieldCPPType &value_or_field_cpp_type = *ValueOrFieldCPPType::get_from_self(
*outputs_[lf_index].type);
GField output_field{
std::make_shared<AnonymousAttributeFieldInput>(std::move(attribute_id),
value_or_field_cpp_type.value,
node_.label_or_name() + TIP_(" node"))};
void *r_value = params.get_output_data_ptr(lf_index);
value_or_field_cpp_type.construct_from_field(r_value, std::move(output_field));
params.output_set(lf_index);
}
std::string input_name(const int index) const override
{
for (const bNodeSocket *bsocket : node_.output_sockets()) {
@ -341,10 +502,10 @@ static void execute_multi_function_on_value_or_field(
/* Construct the new field node. */
std::shared_ptr<fn::FieldOperation> operation;
if (owned_fn) {
operation = std::make_shared<fn::FieldOperation>(owned_fn, std::move(input_fields));
operation = fn::FieldOperation::Create(owned_fn, std::move(input_fields));
}
else {
operation = std::make_shared<fn::FieldOperation>(fn, std::move(input_fields));
operation = fn::FieldOperation::Create(fn, std::move(input_fields));
}
/* Store the new fields in the output. */

View File

@ -9,40 +9,10 @@
#include "NOD_geometry_exec.hh"
#include "BLI_hash_md5.h"
#include "node_geometry_util.hh"
namespace blender::nodes {
NodeAnonymousAttributeID::NodeAnonymousAttributeID(const Object &object,
const ComputeContext &compute_context,
const bNode &bnode,
const StringRef identifier,
const StringRef name)
: socket_name_(name)
{
const ComputeContextHash &hash = compute_context.hash();
{
std::stringstream ss;
ss << hash << "_" << object.id.name << "_" << bnode.identifier << "_" << identifier;
long_name_ = ss.str();
}
{
uint64_t hash_result[2];
BLI_hash_md5_buffer(long_name_.data(), long_name_.size(), hash_result);
std::stringstream ss;
ss << ".a_" << std::hex << hash_result[0] << hash_result[1];
name_ = ss.str();
BLI_assert(name_.size() < MAX_CUSTOMDATA_LAYER_NAME);
}
}
std::string NodeAnonymousAttributeID::user_name() const
{
return socket_name_;
}
void GeoNodeExecParams::error_message_add(const NodeWarningType type,
const StringRef message) const
{
@ -153,11 +123,6 @@ const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name
return nullptr;
}
std::string GeoNodeExecParams::attribute_producer_name() const
{
return node_.label_or_name() + TIP_(" node");
}
void GeoNodeExecParams::set_default_remaining_outputs()
{
params_.set_default_remaining_outputs();

View File

@ -75,7 +75,7 @@ static PyObject *bpy_rna_region_as_string(PyObject *self, PyObject *args, PyObje
return NULL;
}
if (PyDict_GET_SIZE(kwds) > 0) {
if (kwds && PyDict_GET_SIZE(kwds) > 0) {
txt_sel_set(text, region.curl, region.curc, region.sell, region.selc);
}
@ -140,7 +140,7 @@ static PyObject *bpy_rna_region_from_string(PyObject *self, PyObject *args, PyOb
return NULL;
}
if (PyDict_GET_SIZE(kwds) > 0) {
if (kwds && PyDict_GET_SIZE(kwds) > 0) {
txt_sel_set(text, region.curl, region.curc, region.sell, region.selc);
}

View File

@ -2619,6 +2619,8 @@ void *WM_opengl_context_create(void)
BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());
GHOST_GLSettings glSettings = {0};
const eGPUBackendType gpu_backend = GPU_backend_type_selection_get();
glSettings.context_type = wm_ghost_drawing_context_type(gpu_backend);
if (G.debug & G_DEBUG_GPU) {
glSettings.flags |= GHOST_glDebugContext;
}

View File

@ -2060,7 +2060,7 @@ static int arg_handle_load_file(int UNUSED(argc), const char **argv, void *data)
BLI_strncpy(filepath, argv[0], sizeof(filepath));
BLI_path_slash_native(filepath);
BLI_path_abs_from_cwd(filepath, sizeof(filepath));
BLI_path_normalize(NULL, filepath);
BLI_path_normalize(filepath);
/* load the file */
BKE_reports_init(&reports, RPT_PRINT);