Grease Pencil: Make brush asset pinned materials local

Keeping these as linked datablocks to the brush does not match the idea
that assets should generally be appended, and leads to some confusing
situations with linked materials on objects. Now use either a local
material with matching weak library reference or make a local copy if
it does not exist yet.

This also add weak library references to the materials in the 2D Animation
template, so they will be reused.

A problem is that weak library references include a full path to assets
blend files, including the Blender version for the essentials assets files.
This means weak library references do not work across platforms and
Blender versions.

Another known limitation is that if the (linked) Brush Asset material is
edited, and there is already a local copy of it, this local copy will
remain unchanged and will be used by future strokes as well.

Ref #131186

Pull Request: https://projects.blender.org/blender/blender/pulls/134226
This commit is contained in:
Brecht Van Lommel
2025-02-07 12:28:56 +01:00
parent 1602658461
commit c650977534
6 changed files with 106 additions and 7 deletions

View File

@@ -68,4 +68,9 @@ bool asset_edit_id_save(Main &global_main, const ID &id, ReportList &reports);
ID *asset_edit_id_revert(Main &global_main, ID &id, ReportList &reports);
bool asset_edit_id_delete(Main &global_main, ID &id, ReportList &reports);
/** Find a local copy of the asset. */
ID *asset_edit_id_find_local(Main &global_main, ID &id);
/** Ensure a local copy of the asset exists. */
ID *asset_edit_id_ensure_local(Main &global_main, ID &id);
} // namespace blender::bke

View File

@@ -476,6 +476,21 @@ void BKE_main_library_weak_reference_remove_item(
const char *library_id_name,
ID *old_id) ATTR_NONNULL();
/**
* Find local ID with weak library reference matching library and ID name.
* For cases where creating a full MainLibraryWeakReferenceMap is unnecessary.
*/
ID *BKE_main_library_weak_reference_find(Main *bmain,
const char *library_filepath,
const char *library_id_name);
/**
* Add library weak reference to ID, referencing the specified library and ID name.
* For cases where creating a full MainLibraryWeakReferenceMap is unnecessary.*/
void BKE_main_library_weak_reference_add(ID *id,
const char *library_filepath,
const char *library_id_name);
/* *** Generic utils to loop over whole Main database. *** */
#define FOREACH_MAIN_LISTBASE_ID_BEGIN(_lb, _id) \

View File

@@ -10,6 +10,7 @@
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "DNA_ID.h"
#include "DNA_asset_types.h"
#include "DNA_space_types.h"
@@ -386,4 +387,36 @@ bool asset_edit_id_is_writable(const ID &id)
return asset_edit_id_is_editable(id) && (id.lib->runtime.tag & LIBRARY_ASSET_FILE_WRITABLE);
}
ID *asset_edit_id_find_local(Main &global_main, ID &id)
{
if (!asset_edit_id_is_editable(id)) {
return &id;
}
/* Make filepath relative to match weak ref, it might not be if Library datablock is new. */
char lib_filepath[FILE_MAX];
STRNCPY(lib_filepath, id.lib->filepath);
BLI_path_rel(lib_filepath, BKE_main_blendfile_path(&global_main));
return BKE_main_library_weak_reference_find(&global_main, lib_filepath, id.name);
}
ID *asset_edit_id_ensure_local(Main &global_main, ID &id)
{
ID *local_id = asset_edit_id_find_local(global_main, id);
if (local_id) {
return local_id;
}
/* Make local and create weak library reference for reuse. */
BKE_lib_id_make_local(&global_main,
&id,
LIB_ID_MAKELOCAL_FORCE_COPY | LIB_ID_MAKELOCAL_INDIRECT |
LIB_ID_MAKELOCAL_ASSET_DATA_CLEAR);
BLI_assert(id.newid != nullptr);
BKE_main_library_weak_reference_add(id.newid, id.lib->filepath, id.name);
return id.newid;
}
} // namespace blender::bke

View File

@@ -12,6 +12,7 @@
#include "BKE_action.hh"
#include "BKE_anim_data.hh"
#include "BKE_animsys.h"
#include "BKE_asset_edit.hh"
#include "BKE_bake_data_block_id.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.hh"
@@ -2465,6 +2466,11 @@ static Material *grease_pencil_object_material_ensure_from_brush_pinned(Main *bm
{
Material *ma = (brush->gpencil_settings) ? brush->gpencil_settings->material : nullptr;
if (ma) {
/* Ensure we assign a local datablock if this is an editable asset. */
ma = reinterpret_cast<Material *>(blender::bke::asset_edit_id_ensure_local(*bmain, ma->id));
}
/* check if the material is already on object material slots and add it if missing */
if (ma && BKE_object_material_index_get(ob, ma) < 0) {
/* The object's active material is what's used for the unpinned material. Do not touch it
@@ -2509,8 +2515,12 @@ Material *BKE_grease_pencil_object_material_alt_ensure_from_brush(Main *bmain,
{
Material *material_alt = (brush->gpencil_settings) ? brush->gpencil_settings->material_alt :
nullptr;
if (material_alt && BKE_object_material_slot_find_index(ob, material_alt) != -1) {
return material_alt;
if (material_alt) {
material_alt = reinterpret_cast<Material *>(
blender::bke::asset_edit_id_find_local(*bmain, material_alt->id));
if (material_alt && BKE_object_material_slot_find_index(ob, material_alt) != -1) {
return material_alt;
}
}
return BKE_grease_pencil_object_material_ensure_from_brush(bmain, ob, brush);

View File

@@ -711,14 +711,10 @@ void BKE_main_library_weak_reference_add_item(
BLI_assert(new_id->library_weak_reference == nullptr);
BLI_assert(BKE_idtype_idcode_append_is_reusable(GS(new_id->name)));
new_id->library_weak_reference = static_cast<LibraryWeakReference *>(
MEM_mallocN(sizeof(*(new_id->library_weak_reference)), __func__));
const LibWeakRefKey key{library_filepath, library_id_name};
library_weak_reference_mapping->map.add_new(key, new_id);
STRNCPY(new_id->library_weak_reference->library_filepath, library_filepath);
STRNCPY(new_id->library_weak_reference->library_id_name, library_id_name);
BKE_main_library_weak_reference_add(new_id, library_filepath, library_id_name);
}
void BKE_main_library_weak_reference_update_item(
@@ -760,6 +756,35 @@ void BKE_main_library_weak_reference_remove_item(
MEM_SAFE_FREE(old_id->library_weak_reference);
}
ID *BKE_main_library_weak_reference_find(Main *bmain,
const char *library_filepath,
const char *library_id_name)
{
ListBase *id_list = which_libbase(bmain, GS(library_id_name));
LISTBASE_FOREACH (ID *, existing_id, id_list) {
if (existing_id->library_weak_reference &&
STREQ(existing_id->library_weak_reference->library_id_name, library_id_name) &&
STREQ(existing_id->library_weak_reference->library_filepath, library_filepath))
{
return existing_id;
}
}
return nullptr;
}
void BKE_main_library_weak_reference_add(ID *local_id,
const char *library_filepath,
const char *library_id_name)
{
if (local_id->library_weak_reference == nullptr) {
local_id->library_weak_reference = MEM_cnew<LibraryWeakReference>(__func__);
}
STRNCPY(local_id->library_weak_reference->library_filepath, library_filepath);
STRNCPY(local_id->library_weak_reference->library_id_name, library_id_name);
}
BlendThumbnail *BKE_main_thumbnail_from_buffer(Main *bmain, const uint8_t *rect, const int size[2])
{
BlendThumbnail *data = nullptr;

View File

@@ -505,6 +505,17 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
}
}
/* Add library weak references to avoid duplicating materials from essentials. */
const std::optional<std::string> assets_path = BKE_appdir_folder_id(BLENDER_SYSTEM_DATAFILES,
"assets/brushes");
if (assets_path.has_value()) {
const std::string assets_blend_path = *assets_path + "/essentials_brushes-gp_draw.blend";
LISTBASE_FOREACH (Material *, material, &bmain->materials) {
BKE_main_library_weak_reference_add(
&material->id, assets_blend_path.c_str(), material->id.name);
}
}
/* Reset grease pencil paint modes. */
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
ToolSettings *ts = scene->toolsettings;