Fix #108495: Pasting a material crashes #108496

Closed
Campbell Barton wants to merge 4 commits from ideasman42/blender:pr-material-clipboard-as-blend into blender-v3.6-release

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
1 changed files with 79 additions and 68 deletions
Showing only changes of commit 51ad2fe920 - Show all commits

View File

@ -2787,6 +2787,41 @@ void MATERIAL_OT_copy(wmOperatorType *ot)
/** \name Material Paste Operator
* \{ */
/**
* Clear ID's as freeing the data-block doesn't handle reference counting.
*/
static int paste_material_nodetree_ids_decref(LibraryIDLinkCallbackData *cb_data)
{
if (cb_data->cb_flag & IDWALK_CB_USER) {
id_us_min(*cb_data->id_pointer);
*cb_data->id_pointer = nullptr;
}
return IDWALK_RET_NOP;
}
/**
* Re-map ID's from the clipboard to ID's in `main`, by name.
*/
static int paste_material_nodetree_ids_relink_or_clear(LibraryIDLinkCallbackData *cb_data)
{
Main *bmain = static_cast<Main *>(cb_data->user_data);
ID **id_p = cb_data->id_pointer;
if (*id_p) {
if (cb_data->cb_flag & IDWALK_CB_USER) {
id_us_min(*id_p);
}
ListBase *lb = which_libbase(bmain, GS((*id_p)->name));
ID *id_local = static_cast<ID *>(
BLI_findstring(lb, (*id_p)->name + 2, offsetof(ID, name) + 2));
*id_p = id_local;
if (id_local) {
id_us_plus(id_local);
id_lib_extern(id_local);
}
}
return IDWALK_RET_NOP;
}
static int paste_material_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
@ -2837,19 +2872,10 @@ static int paste_material_exec(bContext *C, wmOperator *op)
Material *ma_from = static_cast<Material *>(temp_bmain->materials.first);
/* Important to run this when the embedded tree if freed,
* otherwise the depsgraph holds a reference to the (now freed) `ma->nodetree`.
* Also run this when a new node-tree is set to ensure it's accounted for.
* This also applies to animation data which is likely to be stored in the depsgraph. */
bool update_relations = false;
if (ma->nodetree || ma_from->nodetree) {
update_relations = true;
}
/* Keep animation by moving local animation to the paste node-tree. */
if (ma->nodetree && ma_from->nodetree) {
BLI_assert(ma_from->nodetree->adt == nullptr);
std::swap(ma->adt, ma_from->adt);
std::swap(ma->nodetree->adt, ma_from->nodetree->adt);
}
/* Needed to update #SpaceNode::nodetree else a stale pointer is used. */
@ -2861,39 +2887,48 @@ static int paste_material_exec(bContext *C, wmOperator *op)
* freed as part of #BKE_main_free which doesn't handle user-counts. */
/* Walk over all the embedded nodes ID's (non-recursively). */
BKE_library_foreach_ID_link(
bmain,
&nodetree->id,
[](LibraryIDLinkCallbackData *cb_data) -> int {
if (cb_data->cb_flag & IDWALK_CB_USER) {
id_us_min(*cb_data->id_pointer);
*cb_data->id_pointer = nullptr;
}
return IDWALK_RET_NOP;
},
nullptr,
IDWALK_NOP);
bmain, &nodetree->id, paste_material_nodetree_ids_decref, nullptr, IDWALK_NOP);
ntreeFreeEmbeddedTree(nodetree);
MEM_freeN(nodetree);
ma->nodetree = nullptr;
}
/* Swap data-block content. */
{
/* It's important to keep: `id` & `adt` so the current material name,
* custom properties, etc are maintained and animation data isn't clobbered. */
Material ma_temp;
MEMCPY_STRUCT_AFTER(&ma_temp, ma, adt);
MEMCPY_STRUCT_AFTER(ma, ma_from, adt);
MEMCPY_STRUCT_AFTER(ma_from, &ma_temp, adt);
/* Swap data-block content, while swapping isn't always needed,
* it means memory is properly freed in the case of allocations.. */
#define SWAP_MEMBER(member) std::swap(ma->member, ma_from->member)
/* Keep these values by swapping them back, because the values in the copy buffer
* wont link to other ID's usefully. Other members such as `gpumaterial`
* wont have useful values in the clipboard file either. */
std::swap(ma->gpumaterial, ma_from->gpumaterial);
std::swap(ma->texpaintslot, ma_from->texpaintslot);
std::swap(ma->gp_style, ma_from->gp_style);
}
/* Intentionally skip:
* - Texture painting slots.
* - Preview render.
* - Grease pencil styles (we could although they reference many ID's themselves).
*/
SWAP_MEMBER(flag);
SWAP_MEMBER(r);
SWAP_MEMBER(g);
SWAP_MEMBER(b);
SWAP_MEMBER(a);
SWAP_MEMBER(specr);
SWAP_MEMBER(specg);
SWAP_MEMBER(specb);
SWAP_MEMBER(spec);
SWAP_MEMBER(roughness);
SWAP_MEMBER(metallic);
SWAP_MEMBER(use_nodes);
SWAP_MEMBER(index);
SWAP_MEMBER(nodetree);
SWAP_MEMBER(line_col);
SWAP_MEMBER(line_priority);
SWAP_MEMBER(vcol_alpha);
SWAP_MEMBER(vcol_alpha);
SWAP_MEMBER(alpha_threshold);
SWAP_MEMBER(refract_depth);
SWAP_MEMBER(blend_method);
SWAP_MEMBER(blend_shadow);
SWAP_MEMBER(blend_flag);
#undef SWAP_MEMBER
/* The node-tree from the clipboard is now assigned to the local material,
* however the ID's it references are still part of `temp_bmain`.
@ -2902,43 +2937,19 @@ static int paste_material_exec(bContext *C, wmOperator *op)
* this would be useful for pasting materials with node-groups between files. */
if (ma->nodetree) {
ma->nodetree->owner_id = &ma->id;
/* Map remote ID's to local ones. */
BKE_library_foreach_ID_link(
bmain,
&ma->nodetree->id,
[](LibraryIDLinkCallbackData *cb_data) -> int {
Main *bmain = static_cast<Main *>(cb_data->user_data);
ID **id_p = cb_data->id_pointer;
if (*id_p) {
if (cb_data->cb_flag & IDWALK_CB_USER) {
id_us_min(*id_p);
}
ListBase *lb = which_libbase(bmain, GS((*id_p)->name));
ID *id_local = static_cast<ID *>(
BLI_findstring(lb, (*id_p)->name + 2, offsetof(ID, name) + 2));
*id_p = id_local;
if (id_local) {
id_us_plus(id_local);
id_lib_extern(id_local);
}
}
return IDWALK_RET_NOP;
},
bmain,
IDWALK_NOP);
LISTBASE_FOREACH (bNode *, node, &ma->nodetree->nodes) {
if (node->id == nullptr) {
continue;
}
}
bmain, &ma->nodetree->id, paste_material_nodetree_ids_relink_or_clear, bmain, IDWALK_NOP);
}
BKE_main_free(temp_bmain);
if (update_relations) {
DEG_relations_tag_update(bmain);
}
/* Important to run this when the embedded tree if freed,
* otherwise the depsgraph holds a reference to the (now freed) `ma->nodetree`.
* Also run this when a new node-tree is set to ensure it's accounted for.
* This also applies to animation data which is likely to be stored in the depsgraph.
* Always call instead of checking when it *might* be needed. */
DEG_relations_tag_update(bmain);
DEG_id_tag_update(&ma->id, ID_RECALC_COPY_ON_WRITE);
WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, ma);