Modifiers: add unique modifier identifiers

This adds a new `ModifierData.persistent_uid` integer property with the following properties:
* It's unique within the object.
* Match between the original and evaluated object.
* Stable across Blender sessions.
* Stable across renames and reorderings of modifiers.

Potential use-cases:
* Everywhere where we currently use the name as identifier. For example,
  `ModifierComputeContext` and `ModifierViewerPathElem`.
* Can be used as part of a key in `IDCacheKey` to support caches that stay
  in-tact across undo steps.
* Can be stored in the `SpaceNode` to identify the modifier whose geometry node
  tree is currently pinned (this could use the name currently, but that hasn't been
  implemented yet).

This new identifier has some overlap with `ModifierData.session_uid`, but there
are some differences:
* `session_uid` is unique within the entire Blender session (except for duplicates
  between the original and evaluated data blocks).
* `session_uid` is not stable across Blender sessions.

Especially due to the first difference, it's not immediately obvious that the new
`persistent_uid` can fulfill all use-cases of the existing `session_uid`. Nevertheless,
this seems likely and will be cleaned up separately.

Unfortunately, there is not a single place where modifiers are added to objects currently.
Therefore, there are quite a few places that need to ensure valid identifiers. I tried to catch
all the places, but it's hard to be sure. Therefore, I added an assert in `object_copy_data`
that checks if all identifiers are valid. This way, we should be notified relatively quickly if
issues are caused by invalid identifiers.

Pull Request: https://projects.blender.org/blender/blender/pulls/117347
This commit is contained in:
Jacques Lucke
2024-02-06 17:10:40 +01:00
parent a214db60c6
commit 1497005054
16 changed files with 116 additions and 3 deletions

View File

@@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 19
#define BLENDER_FILE_SUBVERSION 20
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@@ -473,7 +473,20 @@ void BKE_modifiers_foreach_tex_link(Object *ob, TexWalkFunc walk, void *user_dat
ModifierData *BKE_modifiers_findby_type(const Object *ob, ModifierType type);
ModifierData *BKE_modifiers_findby_name(const Object *ob, const char *name);
ModifierData *BKE_modifiers_findby_session_uid(const Object *ob, const SessionUID *session_uid);
ModifierData *BKE_modifiers_findby_persistent_uid(const Object *ob, int persistent_uid);
void BKE_modifiers_clear_errors(Object *ob);
/**
* Updates `md.persistent_uid` so that it is a valid identifier (>=1) and is unique in the object.
*/
void BKE_modifiers_persistent_uid_init(const Object &object, ModifierData &md);
/**
* Returns true when all the modifier identifiers are positive and unique. This should generally be
* true and should only be used by asserts.
*/
bool BKE_modifiers_persistent_uids_are_valid(const Object &object);
/**
* used for buttons, to find out if the 'draw deformed in edit-mode option is there.
*

View File

@@ -115,7 +115,8 @@ bool BKE_object_copy_gpencil_modifier(Object *ob_dst, GpencilModifierData *gmd_s
* Copy the whole stack of modifiers from one object into another.
*
* \warning *Does not* clear modifier stack and related data (particle systems, soft-body,
* etc.) in `ob_dst`, if needed calling code must do it.
* etc.) in `ob_dst`, if needed calling code must do it. The caller is also responsible for
* ensuring the modifier identifiers are unique.
*
* \param do_copy_all: If true, even modifiers that should not support copying (like Hook one)
* will be duplicated.

View File

@@ -2273,6 +2273,7 @@ void BKE_main_mesh_legacy_convert_auto_smooth(Main &bmain)
else {
BLI_addtail(&object->modifiers, md);
}
BKE_modifiers_persistent_uid_init(*object, md->modifier);
md->settings.properties = bke::idprop::create_group("Nodes Modifier Settings").release();
IDProperty *angle_prop =

View File

@@ -36,6 +36,7 @@
#include "BLI_linklist.h"
#include "BLI_listbase.h"
#include "BLI_path_util.h"
#include "BLI_rand.hh"
#include "BLI_session_uid.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
@@ -276,6 +277,16 @@ ModifierData *BKE_modifiers_findby_session_uid(const Object *ob, const SessionUI
return nullptr;
}
ModifierData *BKE_modifiers_findby_persistent_uid(const Object *ob, const int persistent_uid)
{
LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) {
if (md->persistent_uid == persistent_uid) {
return md;
}
}
return nullptr;
}
void BKE_modifiers_clear_errors(Object *ob)
{
LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) {
@@ -358,6 +369,7 @@ void BKE_modifier_copydata_ex(const ModifierData *md, ModifierData *target, cons
target->mode = md->mode;
target->flag = md->flag;
target->ui_expand_flag = md->ui_expand_flag;
target->persistent_uid = md->persistent_uid;
if (mti->copy_data) {
mti->copy_data(md, target, flag);
@@ -1020,6 +1032,48 @@ void BKE_modifier_check_uids_unique_and_report(const Object *object)
BLI_gset_free(used_uids, nullptr);
}
void BKE_modifiers_persistent_uid_init(const Object &object, ModifierData &md)
{
uint64_t hash = blender::get_default_hash(blender::StringRef(md.name));
if (ID_IS_LINKED(&object)) {
hash = blender::get_default_hash(hash, blender::StringRef(object.id.lib->filepath_abs));
}
if (ID_IS_OVERRIDE_LIBRARY_REAL(&object)) {
BLI_assert(ID_IS_LINKED(object.id.override_library->reference));
hash = blender::get_default_hash(
hash, blender::StringRef(object.id.override_library->reference->lib->filepath_abs));
}
blender::RandomNumberGenerator rng{uint32_t(hash)};
while (true) {
const int new_uid = rng.get_int32();
if (new_uid <= 0) {
continue;
}
if (BKE_modifiers_findby_persistent_uid(&object, new_uid) != nullptr) {
continue;
}
md.persistent_uid = new_uid;
break;
}
}
bool BKE_modifiers_persistent_uids_are_valid(const Object &object)
{
blender::Set<int> uids;
int modifiers_num = 0;
LISTBASE_FOREACH (const ModifierData *, md, &object.modifiers) {
if (md->persistent_uid <= 0) {
return false;
}
uids.add(md->persistent_uid);
modifiers_num++;
}
if (uids.size() != modifiers_num) {
return false;
}
return true;
}
void BKE_modifier_blend_write(BlendWriter *writer, const ID *id_owner, ListBase *modbase)
{
if (modbase == nullptr) {

View File

@@ -250,6 +250,7 @@ static void object_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const in
BLI_listbase_clear(&ob_dst->greasepencil_modifiers);
/* NOTE: Also takes care of soft-body and particle systems copying. */
BKE_object_modifier_stack_copy(ob_dst, ob_src, true, flag_subdata);
BLI_assert(BKE_modifiers_persistent_uids_are_valid(*ob_dst));
BLI_listbase_clear((ListBase *)&ob_dst->drawdata);
BLI_listbase_clear(&ob_dst->pc_ids);
@@ -766,6 +767,7 @@ static void object_blend_read_data(BlendDataReader *reader, ID *id)
wmd->width = wav->width;
BLI_addtail(&ob->modifiers, wmd);
BKE_modifiers_persistent_uid_init(*ob, wmd->modifier);
BLI_remlink(&ob->effect, paf);
MEM_freeN(paf);
@@ -784,6 +786,7 @@ static void object_blend_read_data(BlendDataReader *reader, ID *id)
bmd->seed = 1;
BLI_addtail(&ob->modifiers, bmd);
BKE_modifiers_persistent_uid_init(*ob, bmd->modifier);
BLI_remlink(&ob->effect, paf);
MEM_freeN(paf);
@@ -871,6 +874,7 @@ static void object_blend_read_data(BlendDataReader *reader, ID *id)
BLI_remlink(&ob->hooks, hook);
BKE_modifier_unique_name(&ob->modifiers, (ModifierData *)hmd);
BKE_modifiers_persistent_uid_init(*ob, hmd->modifier);
MEM_freeN(hook);
}
@@ -1423,6 +1427,7 @@ bool BKE_object_copy_modifier(
BLI_addtail(&ob_dst->modifiers, md_dst);
BKE_modifier_unique_name(&ob_dst->modifiers, md_dst);
BKE_modifiers_persistent_uid_init(*ob_dst, *md_dst);
}
BKE_object_modifier_set_active(ob_dst, md_dst);

View File

@@ -3958,6 +3958,7 @@ static ModifierData *object_add_or_copy_particle_system(
psmd->psys = psys;
BLI_addtail(&ob->modifiers, md);
BKE_object_modifier_set_active(ob, md);
BKE_modifiers_persistent_uid_init(*ob, *md);
psys->totpart = 0;
psys->flag = PSYS_CURRENT;

View File

@@ -2876,6 +2876,17 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 401, 20)) {
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
int uid = 1;
LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) {
/* These identifiers are not necessarily stable for linked data. If the linked data has a
* new modifier inserted, the identifiers of other modifiers can change. */
md->persistent_uid = uid++;
}
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@@ -551,6 +551,7 @@ static int add_hook_object(const bContext *C,
BLI_insertlinkbefore(&obedit->modifiers, md, hmd);
SNPRINTF(hmd->modifier.name, "Hook-%s", ob->id.name + 2);
BKE_modifier_unique_name(&obedit->modifiers, (ModifierData *)hmd);
BKE_modifiers_persistent_uid_init(*obedit, hmd->modifier);
hmd->object = ob;
hmd->indexar = indexar;

View File

@@ -197,6 +197,7 @@ ModifierData *ED_object_modifier_add(
else {
BLI_addtail(&ob->modifiers, new_md);
}
BKE_modifiers_persistent_uid_init(*ob, *new_md);
if (name) {
STRNCPY_UTF8(new_md->name, name);
@@ -1234,6 +1235,7 @@ bool ED_object_modifier_copy(
BKE_modifier_copydata(md, nmd);
BLI_insertlinkafter(&ob->modifiers, md, nmd);
BKE_modifier_unique_name(&ob->modifiers, nmd);
BKE_modifiers_persistent_uid_init(*ob, *nmd);
BKE_object_modifier_set_active(ob, nmd);
nmd->flag |= eModifierFlag_OverrideLibrary_Local;
@@ -3024,6 +3026,7 @@ static int skin_armature_create_exec(bContext *C, wmOperator *op)
if (arm_md) {
skin_md = edit_modifier_property_get(op, ob, eModifierType_Skin);
BLI_insertlinkafter(&ob->modifiers, skin_md, arm_md);
BKE_modifiers_persistent_uid_init(*arm_ob, arm_md->modifier);
arm_md->object = arm_ob;
arm_md->deformflag = ARM_DEF_VGROUP | ARM_DEF_QUATERNION;

View File

@@ -1151,6 +1151,7 @@ static bool copy_particle_systems_to_object(const bContext *C,
psmd = (ParticleSystemModifierData *)md;
/* push on top of the stack, no use trying to reproduce old stack order */
BLI_addtail(&ob_to->modifiers, md);
BKE_modifiers_persistent_uid_init(*ob_to, *md);
SNPRINTF(md->name, "ParticleSystem %i", i);
BKE_modifier_unique_name(&ob_to->modifiers, (ModifierData *)psmd);

View File

@@ -270,6 +270,7 @@ void AbcObjectReader::addCacheModifier()
{
ModifierData *md = BKE_modifier_new(eModifierType_MeshSequenceCache);
BLI_addtail(&m_object->modifiers, md);
BKE_modifiers_persistent_uid_init(*m_object, *md);
MeshSeqCacheModifierData *mcmd = reinterpret_cast<MeshSeqCacheModifierData *>(md);

View File

@@ -28,6 +28,7 @@ void USDGeomReader::add_cache_modifier()
ModifierData *md = BKE_modifier_new(eModifierType_MeshSequenceCache);
BLI_addtail(&object_->modifiers, md);
BKE_modifiers_persistent_uid_init(*object_, *md);
MeshSeqCacheModifierData *mcmd = reinterpret_cast<MeshSeqCacheModifierData *>(md);
@@ -42,6 +43,7 @@ void USDGeomReader::add_subdiv_modifier()
{
ModifierData *md = BKE_modifier_new(eModifierType_Subsurf);
BLI_addtail(&object_->modifiers, md);
BKE_modifiers_persistent_uid_init(*object_, *md);
}
} // namespace blender::io::usd

View File

@@ -1080,6 +1080,7 @@ void import_mesh_skel_bindings(Main *bmain,
if (!BKE_modifiers_findby_type(mesh_obj, eModifierType_Armature)) {
ModifierData *md = BKE_modifier_new(eModifierType_Armature);
BLI_addtail(&mesh_obj->modifiers, md);
BKE_modifiers_persistent_uid_init(*mesh_obj, *md);
}
/* Create a deform group per joint. */

View File

@@ -136,7 +136,18 @@ typedef struct ModifierData {
* and easily conflict with the explicit mapping of bits to panels here.
*/
uint16_t layout_panel_open_flag;
char _pad[6];
char _pad[2];
/**
* Uniquely identifies the modifier within the object. This identifier is stable across Blender
* sessions. Modifiers on the original and corresponding evaluated object have matching
* identifiers. The identifier stays the same if the modifier is renamed or moved in the modifier
* stack.
*
* A valid identifier is non-negative (>= 1). Modifiers that are currently not on an object may
* have invalid identifiers. It has to be initialized with #BKE_modifiers_persistent_uid_init
* when it is added to an object.
*/
int persistent_uid;
/** MAX_NAME. */
char name[64];

View File

@@ -8523,6 +8523,13 @@ void RNA_def_modifier(BlenderRNA *brna)
"Time in seconds that the modifier took to evaluate. This is only set on evaluated objects. "
"If multiple modifiers run in parallel, execution time is not a reliable metric");
prop = RNA_def_property(srna, "persistent_uid", PROP_INT, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(
prop,
"Persistent UID",
"Uniquely identifies the modifier within the modifier stack that it is part of");
/* types */
rna_def_modifier_subsurf(brna);
rna_def_modifier_lattice(brna);