ID-Remap, step two: add some user-level tools.
This commit adds operators and Outliner menu entries to reload or relocate a library, and to delete or replace a datablock. RNA ID API is also extended to allow ID deletion and remapping from python. Review task: D2027 (https://developer.blender.org/D2027). Reviewed by campbellbarton, thanks a bunch.
This commit is contained in:
parent
fb1f7fad78
commit
7547c6a250
Notes:
blender-bot
2023-05-26 09:41:56 +02:00
Referenced by issue #108285, Remap Users operator doesn't work on Objects
|
@ -100,7 +100,8 @@ struct ID *BLO_library_link_named_part(struct Main *mainl, BlendHandle **bh, con
|
|||
struct ID *BLO_library_link_named_part_ex(
|
||||
struct Main *mainl, BlendHandle **bh,
|
||||
const short idcode, const char *name, const short flag,
|
||||
struct Scene *scene, struct View3D *v3d);
|
||||
struct Scene *scene, struct View3D *v3d,
|
||||
const bool use_placeholders, const bool force_indirect);
|
||||
void BLO_library_link_end(struct Main *mainl, BlendHandle **bh, short flag, struct Scene *scene, struct View3D *v3d);
|
||||
|
||||
void BLO_library_link_copypaste(struct Main *mainl, BlendHandle *bh);
|
||||
|
|
|
@ -113,6 +113,7 @@
|
|||
|
||||
#include "BKE_action.h"
|
||||
#include "BKE_armature.h"
|
||||
#include "BKE_blender_version.h"
|
||||
#include "BKE_brush.h"
|
||||
#include "BKE_cloth.h"
|
||||
#include "BKE_constraint.h"
|
||||
|
@ -8287,6 +8288,9 @@ static void do_versions(FileData *fd, Library *lib, Main *main)
|
|||
blo_do_versions_260(fd, lib, main);
|
||||
blo_do_versions_270(fd, lib, main);
|
||||
|
||||
main->versionfile = BLENDER_VERSION;
|
||||
main->subversionfile = BLENDER_SUBVERSION;
|
||||
|
||||
/* WATCH IT!!!: pointers from libdata have not been converted yet here! */
|
||||
/* WATCH IT 2!: Userdef struct init see do_versions_userdef() above! */
|
||||
|
||||
|
@ -9707,13 +9711,13 @@ static void give_base_to_groups(
|
|||
}
|
||||
}
|
||||
|
||||
static ID *create_placeholder(Main *mainvar, const char *idname, const short tag)
|
||||
static ID *create_placeholder(Main *mainvar, const short idcode, const char *idname, const short tag)
|
||||
{
|
||||
const short idcode = GS(idname);
|
||||
ListBase *lb = which_libbase(mainvar, idcode);
|
||||
ID *ph_id = BKE_libblock_alloc_notest(idcode);
|
||||
|
||||
memcpy(ph_id->name, idname, sizeof(ph_id->name));
|
||||
*((short *)ph_id->name) = idcode;
|
||||
BLI_strncpy(ph_id->name + 2, idname, sizeof(ph_id->name) - 2);
|
||||
BKE_libblock_init_empty(ph_id);
|
||||
ph_id->lib = mainvar->curlib;
|
||||
ph_id->tag = tag | LIB_TAG_MISSING;
|
||||
|
@ -9728,7 +9732,9 @@ static ID *create_placeholder(Main *mainvar, const char *idname, const short tag
|
|||
|
||||
/* returns true if the item was found
|
||||
* but it may already have already been appended/linked */
|
||||
static ID *link_named_part(Main *mainl, FileData *fd, const short idcode, const char *name)
|
||||
static ID *link_named_part(
|
||||
Main *mainl, FileData *fd, const short idcode, const char *name,
|
||||
const bool use_placeholders, const bool force_indirect)
|
||||
{
|
||||
BHead *bhead = find_bhead_from_code_name(fd, idcode, name);
|
||||
ID *id;
|
||||
|
@ -9739,7 +9745,7 @@ static ID *link_named_part(Main *mainl, FileData *fd, const short idcode, const
|
|||
id = is_yet_read(fd, mainl, bhead);
|
||||
if (id == NULL) {
|
||||
/* not read yet */
|
||||
read_libblock(fd, mainl, bhead, LIB_TAG_TESTEXT, &id);
|
||||
read_libblock(fd, mainl, bhead, force_indirect ? LIB_TAG_TESTIND : LIB_TAG_TESTEXT, &id);
|
||||
|
||||
if (id) {
|
||||
/* sort by name in list */
|
||||
|
@ -9752,18 +9758,22 @@ static ID *link_named_part(Main *mainl, FileData *fd, const short idcode, const
|
|||
if (G.debug)
|
||||
printf("append: already linked\n");
|
||||
oldnewmap_insert(fd->libmap, bhead->old, id, bhead->code);
|
||||
if (id->tag & LIB_TAG_INDIRECT) {
|
||||
if (!force_indirect && (id->tag & LIB_TAG_INDIRECT)) {
|
||||
id->tag &= ~LIB_TAG_INDIRECT;
|
||||
id->tag |= LIB_TAG_EXTERN;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (use_placeholders) {
|
||||
/* XXX flag part is weak! */
|
||||
id = create_placeholder(mainl, idcode, name, force_indirect ? LIB_TAG_INDIRECT : LIB_TAG_EXTERN);
|
||||
}
|
||||
else {
|
||||
id = NULL;
|
||||
}
|
||||
|
||||
/* if we found the id but the id is NULL, this is really bad */
|
||||
BLI_assert((bhead != NULL) == (id != NULL));
|
||||
BLI_assert(!((bhead != NULL) && (id == NULL)));
|
||||
|
||||
return id;
|
||||
}
|
||||
|
@ -9835,9 +9845,9 @@ void BLO_library_link_copypaste(Main *mainl, BlendHandle *bh)
|
|||
|
||||
static ID *link_named_part_ex(
|
||||
Main *mainl, FileData *fd, const short idcode, const char *name, const short flag,
|
||||
Scene *scene, View3D *v3d)
|
||||
Scene *scene, View3D *v3d, const bool use_placeholders, const bool force_indirect)
|
||||
{
|
||||
ID *id = link_named_part(mainl, fd, idcode, name);
|
||||
ID *id = link_named_part(mainl, fd, idcode, name, use_placeholders, force_indirect);
|
||||
|
||||
if (id && (GS(id->name) == ID_OB)) { /* loose object: give a base */
|
||||
link_object_postprocess(id, scene, v3d, flag);
|
||||
|
@ -9863,7 +9873,7 @@ static ID *link_named_part_ex(
|
|||
ID *BLO_library_link_named_part(Main *mainl, BlendHandle **bh, const short idcode, const char *name)
|
||||
{
|
||||
FileData *fd = (FileData*)(*bh);
|
||||
return link_named_part(mainl, fd, idcode, name);
|
||||
return link_named_part(mainl, fd, idcode, name, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -9877,15 +9887,18 @@ ID *BLO_library_link_named_part(Main *mainl, BlendHandle **bh, const short idcod
|
|||
* \param flag Options for linking, used for instantiating.
|
||||
* \param scene The scene in which to instantiate objects/groups (if NULL, no instantiation is done).
|
||||
* \param v3d The active View3D (only to define active layers for instantiated objects & groups, can be NULL).
|
||||
* \param use_placeholders If true, generate a placeholder (empty ID) if not found in current lib file.
|
||||
* \param force_indirect If true, force loaded ID to be tagged as LIB_TAG_INDIRECT (used in reload context only).
|
||||
* \return the linked ID when found.
|
||||
*/
|
||||
ID *BLO_library_link_named_part_ex(
|
||||
Main *mainl, BlendHandle **bh,
|
||||
const short idcode, const char *name, const short flag,
|
||||
Scene *scene, View3D *v3d)
|
||||
Scene *scene, View3D *v3d,
|
||||
const bool use_placeholders, const bool force_indirect)
|
||||
{
|
||||
FileData *fd = (FileData*)(*bh);
|
||||
return link_named_part_ex(mainl, fd, idcode, name, flag, scene, v3d);
|
||||
return link_named_part_ex(mainl, fd, idcode, name, flag, scene, v3d, use_placeholders, force_indirect);
|
||||
}
|
||||
|
||||
static void link_id_part(ReportList *reports, FileData *fd, Main *mainvar, ID *id, ID **r_id)
|
||||
|
@ -9925,7 +9938,7 @@ static void link_id_part(ReportList *reports, FileData *fd, Main *mainvar, ID *i
|
|||
|
||||
/* Generate a placeholder for this ID (simplified version of read_libblock actually...). */
|
||||
if (r_id) {
|
||||
*r_id = is_valid ? create_placeholder(mainvar, id->name, id->tag) : NULL;
|
||||
*r_id = is_valid ? create_placeholder(mainvar, GS(id->name), id->name + 2, id->tag) : NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,17 +29,23 @@
|
|||
* \ingroup spoutliner
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_anim_types.h"
|
||||
#include "DNA_group_types.h"
|
||||
#include "DNA_ID.h"
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_material_types.h"
|
||||
|
||||
#include "BLI_blenlib.h"
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_path_util.h"
|
||||
#include "BLI_mempool.h"
|
||||
#include "BLI_stack.h"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
|
@ -47,7 +53,9 @@
|
|||
#include "BKE_context.h"
|
||||
#include "BKE_depsgraph.h"
|
||||
#include "BKE_global.h"
|
||||
#include "BKE_idcode.h"
|
||||
#include "BKE_library.h"
|
||||
#include "BKE_library_remap.h"
|
||||
#include "BKE_main.h"
|
||||
#include "BKE_outliner_treehash.h"
|
||||
#include "BKE_report.h"
|
||||
|
@ -55,6 +63,8 @@
|
|||
#include "BKE_material.h"
|
||||
#include "BKE_group.h"
|
||||
|
||||
#include "../blenloader/BLO_readfile.h"
|
||||
|
||||
#include "ED_object.h"
|
||||
#include "ED_outliner.h"
|
||||
#include "ED_screen.h"
|
||||
|
@ -70,6 +80,9 @@
|
|||
|
||||
#include "RNA_access.h"
|
||||
#include "RNA_define.h"
|
||||
#include "RNA_enum_types.h"
|
||||
|
||||
#include "GPU_material.h"
|
||||
|
||||
#include "outliner_intern.h"
|
||||
|
||||
|
@ -291,64 +304,48 @@ void OUTLINER_OT_item_rename(wmOperatorType *ot)
|
|||
ot->poll = ED_operator_outliner_active;
|
||||
}
|
||||
|
||||
/* Library delete --------------------------------------------------- */
|
||||
/* ID delete --------------------------------------------------- */
|
||||
|
||||
static void lib_delete(bContext *C, TreeElement *te, TreeStoreElem *tselem, ReportList *reports)
|
||||
static void id_delete(bContext *C, TreeElement *te, TreeStoreElem *tselem)
|
||||
{
|
||||
Library *lib = (Library *)tselem->id;
|
||||
ListBase *lbarray[MAX_LIBARRAY];
|
||||
int a;
|
||||
Main *bmain = CTX_data_main(C);
|
||||
ID *id = tselem->id;
|
||||
|
||||
BLI_assert(te->idcode == ID_LI && lib != NULL);
|
||||
BLI_assert(te->idcode != 0 && id != NULL);
|
||||
BLI_assert(te->idcode != ID_LI || ((Library *)id)->parent == NULL);
|
||||
UNUSED_VARS_NDEBUG(te);
|
||||
|
||||
/* We simply set all ID from given lib (including lib itself) to zero user count.
|
||||
* It is not possible to do a proper cleanup without a save/reload in current master. */
|
||||
a = set_listbasepointers(CTX_data_main(C), lbarray);
|
||||
while (a--) {
|
||||
ListBase *lb = lbarray[a];
|
||||
ID *id;
|
||||
BKE_libblock_delete(bmain, id);
|
||||
|
||||
for (id = lb->first; id; id = id->next) {
|
||||
if (id->lib == lib) {
|
||||
id_fake_user_clear(id);
|
||||
id->us = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BKE_reportf(reports, RPT_WARNING,
|
||||
"Please save and reload .blend file to complete deletion of '%s' library",
|
||||
((Library *)tselem->id)->filepath);
|
||||
WM_event_add_notifier(C, NC_WINDOW, NULL);
|
||||
}
|
||||
|
||||
void lib_delete_cb(
|
||||
bContext *C, Scene *UNUSED(scene), TreeElement *te,
|
||||
TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
|
||||
void id_delete_cb(
|
||||
bContext *C, Scene *UNUSED(scene), TreeElement *te, TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem,
|
||||
void *UNUSED(user_data))
|
||||
{
|
||||
lib_delete(C, te, tselem, CTX_wm_reports(C));
|
||||
id_delete(C, te, tselem);
|
||||
}
|
||||
|
||||
static int outliner_lib_delete_invoke_do(bContext *C, ReportList *reports, TreeElement *te, const float mval[2])
|
||||
static int outliner_id_delete_invoke_do(bContext *C, ReportList *reports, TreeElement *te, const float mval[2])
|
||||
{
|
||||
if (mval[1] > te->ys && mval[1] < te->ys + UI_UNIT_Y) {
|
||||
TreeStoreElem *tselem = TREESTORE(te);
|
||||
|
||||
if (te->idcode == ID_LI && tselem->id) {
|
||||
if (((Library *)tselem->id)->parent) {
|
||||
if (te->idcode != 0 && tselem->id) {
|
||||
if (te->idcode == ID_LI && ((Library *)tselem->id)->parent) {
|
||||
BKE_reportf(reports, RPT_ERROR_INVALID_INPUT,
|
||||
"Cannot delete indirectly linked library '%s'", ((Library *)tselem->id)->filepath);
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
lib_delete(C, te, tselem, reports);
|
||||
id_delete(C, te, tselem);
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (te = te->subtree.first; te; te = te->next) {
|
||||
int ret;
|
||||
if ((ret = outliner_lib_delete_invoke_do(C, reports, te, mval))) {
|
||||
if ((ret = outliner_id_delete_invoke_do(C, reports, te, mval))) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
@ -357,7 +354,7 @@ static int outliner_lib_delete_invoke_do(bContext *C, ReportList *reports, TreeE
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int outliner_lib_delete_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
static int outliner_id_delete_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
ARegion *ar = CTX_wm_region(C);
|
||||
SpaceOops *soops = CTX_wm_space_outliner(C);
|
||||
|
@ -371,7 +368,7 @@ static int outliner_lib_delete_invoke(bContext *C, wmOperator *op, const wmEvent
|
|||
for (te = soops->tree.first; te; te = te->next) {
|
||||
int ret;
|
||||
|
||||
if ((ret = outliner_lib_delete_invoke_do(C, op->reports, te, fmval))) {
|
||||
if ((ret = outliner_id_delete_invoke_do(C, op->reports, te, fmval))) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
@ -379,16 +376,315 @@ static int outliner_lib_delete_invoke(bContext *C, wmOperator *op, const wmEvent
|
|||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
void OUTLINER_OT_lib_delete(wmOperatorType *ot)
|
||||
void OUTLINER_OT_id_delete(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Delete Library";
|
||||
ot->idname = "OUTLINER_OT_lib_delete";
|
||||
ot->description = "Delete the library under cursor (needs a save/reload)";
|
||||
ot->name = "Delete Datablock";
|
||||
ot->idname = "OUTLINER_OT_id_delete";
|
||||
ot->description = "Delete the ID under cursor";
|
||||
|
||||
ot->invoke = outliner_lib_delete_invoke;
|
||||
ot->invoke = outliner_id_delete_invoke;
|
||||
ot->poll = ED_operator_outliner_active;
|
||||
}
|
||||
|
||||
/* ID remap --------------------------------------------------- */
|
||||
|
||||
static int outliner_id_remap_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
SpaceOops *soops = CTX_wm_space_outliner(C);
|
||||
|
||||
const short id_type = (short)RNA_enum_get(op->ptr, "id_type");
|
||||
ID *old_id = BLI_findlink(which_libbase(CTX_data_main(C), id_type), RNA_enum_get(op->ptr, "old_id"));
|
||||
ID *new_id = BLI_findlink(which_libbase(CTX_data_main(C), id_type), RNA_enum_get(op->ptr, "new_id"));
|
||||
|
||||
/* check for invalid states */
|
||||
if (soops == NULL)
|
||||
return OPERATOR_CANCELLED;
|
||||
|
||||
if (!(old_id && (old_id != new_id) && (GS(old_id->name) == GS(new_id->name)))) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
BKE_libblock_remap(bmain, old_id, new_id,
|
||||
ID_REMAP_SKIP_INDIRECT_USAGE | ID_REMAP_SKIP_NEVER_NULL_USAGE);
|
||||
|
||||
BKE_main_lib_objects_recalc_all(bmain);
|
||||
|
||||
/* recreate dependency graph to include new objects */
|
||||
DAG_scene_relations_rebuild(bmain, scene);
|
||||
|
||||
/* free gpu materials, some materials depend on existing objects, such as lamps so freeing correctly refreshes */
|
||||
GPU_materials_free();
|
||||
|
||||
WM_event_add_notifier(C, NC_WINDOW, NULL);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static bool outliner_id_remap_find_tree_element(bContext *C, wmOperator *op, ListBase *tree, const float y)
|
||||
{
|
||||
TreeElement *te;
|
||||
|
||||
for (te = tree->first; te; te = te->next) {
|
||||
if (y > te->ys && y < te->ys + UI_UNIT_Y) {
|
||||
TreeStoreElem *tselem = TREESTORE(te);
|
||||
|
||||
if (tselem->type == 0 && tselem->id) {
|
||||
printf("found id %s (%p)!\n", tselem->id->name, tselem->id);
|
||||
|
||||
RNA_enum_set(op->ptr, "id_type", GS(tselem->id->name));
|
||||
RNA_enum_set_identifier(C, op->ptr, "new_id", tselem->id->name + 2);
|
||||
RNA_enum_set_identifier(C, op->ptr, "old_id", tselem->id->name + 2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (outliner_id_remap_find_tree_element(C, op, &te->subtree, y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int outliner_id_remap_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
SpaceOops *soops = CTX_wm_space_outliner(C);
|
||||
ARegion *ar = CTX_wm_region(C);
|
||||
float fmval[2];
|
||||
|
||||
if (!RNA_property_is_set(op->ptr, RNA_struct_find_property(op->ptr, "id_type"))) {
|
||||
UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
|
||||
|
||||
outliner_id_remap_find_tree_element(C, op, &soops->tree, fmval[1]);
|
||||
}
|
||||
|
||||
return WM_operator_props_dialog_popup(C, op, 200, 100);
|
||||
}
|
||||
|
||||
static EnumPropertyItem *outliner_id_itemf(bContext *C, PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free)
|
||||
{
|
||||
EnumPropertyItem item_tmp = {0}, *item = NULL;
|
||||
int totitem = 0;
|
||||
int i = 0;
|
||||
|
||||
short id_type = (short)RNA_enum_get(ptr, "id_type");
|
||||
ID *id = which_libbase(CTX_data_main(C), id_type)->first;
|
||||
|
||||
for (; id; id = id->next) {
|
||||
item_tmp.identifier = item_tmp.name = id->name + 2;
|
||||
item_tmp.value = i++;
|
||||
RNA_enum_item_add(&item, &totitem, &item_tmp);
|
||||
}
|
||||
|
||||
RNA_enum_item_end(&item, &totitem);
|
||||
*r_free = true;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void OUTLINER_OT_id_remap(wmOperatorType *ot)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
||||
/* identifiers */
|
||||
ot->name = "Outliner ID data Remap";
|
||||
ot->idname = "OUTLINER_OT_id_remap";
|
||||
ot->description = "";
|
||||
|
||||
/* callbacks */
|
||||
ot->invoke = outliner_id_remap_invoke;
|
||||
ot->exec = outliner_id_remap_exec;
|
||||
ot->poll = ED_operator_outliner_active;
|
||||
|
||||
ot->flag = 0;
|
||||
|
||||
RNA_def_enum(ot->srna, "id_type", rna_enum_id_type_items, ID_OB, "ID Type", "");
|
||||
|
||||
prop = RNA_def_enum(ot->srna, "old_id", DummyRNA_NULL_items, 0, "Old ID", "Old ID to replace");
|
||||
RNA_def_property_enum_funcs_runtime(prop, NULL, NULL, outliner_id_itemf);
|
||||
RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE | PROP_HIDDEN);
|
||||
|
||||
ot->prop = RNA_def_enum(ot->srna, "new_id", DummyRNA_NULL_items, 0,
|
||||
"New ID", "New ID to remap all selected IDs' users to");
|
||||
RNA_def_property_enum_funcs_runtime(ot->prop, NULL, NULL, outliner_id_itemf);
|
||||
RNA_def_property_flag(ot->prop, PROP_ENUM_NO_TRANSLATE);
|
||||
}
|
||||
|
||||
void id_remap_cb(
|
||||
bContext *C, Scene *UNUSED(scene), TreeElement *UNUSED(te),
|
||||
TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
|
||||
{
|
||||
wmOperatorType *ot = WM_operatortype_find("OUTLINER_OT_id_remap", false);
|
||||
PointerRNA op_props;
|
||||
|
||||
BLI_assert(tselem->id != NULL);
|
||||
|
||||
WM_operator_properties_create_ptr(&op_props, ot);
|
||||
|
||||
RNA_enum_set(&op_props, "id_type", GS(tselem->id->name));
|
||||
RNA_enum_set_identifier(C, &op_props, "old_id", tselem->id->name + 2);
|
||||
|
||||
WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &op_props);
|
||||
|
||||
WM_operator_properties_free(&op_props);
|
||||
}
|
||||
|
||||
/* Library relocate/reload --------------------------------------------------- */
|
||||
|
||||
static int lib_relocate(
|
||||
bContext *C, TreeElement *te, TreeStoreElem *tselem, wmOperatorType *ot, const bool reload)
|
||||
{
|
||||
PointerRNA op_props;
|
||||
int ret = 0;
|
||||
|
||||
BLI_assert(te->idcode == ID_LI && tselem->id != NULL);
|
||||
UNUSED_VARS_NDEBUG(te);
|
||||
|
||||
WM_operator_properties_create_ptr(&op_props, ot);
|
||||
|
||||
RNA_string_set(&op_props, "library", tselem->id->name + 2);
|
||||
|
||||
if (reload) {
|
||||
Library *lib = (Library *)tselem->id;
|
||||
char dir[FILE_MAXDIR], filename[FILE_MAX];
|
||||
|
||||
BLI_split_dirfile(lib->filepath, dir, filename, sizeof(dir), sizeof(filename));
|
||||
|
||||
printf("%s, %s\n", tselem->id->name, lib->filepath);
|
||||
|
||||
/* We assume if both paths in lib are not the same then lib->name was relative... */
|
||||
RNA_boolean_set(&op_props, "relative_path", BLI_path_cmp(lib->filepath, lib->name) != 0);
|
||||
|
||||
RNA_string_set(&op_props, "directory", dir);
|
||||
RNA_string_set(&op_props, "filename", filename);
|
||||
|
||||
ret = WM_operator_name_call_ptr(C, ot, WM_OP_EXEC_DEFAULT, &op_props);
|
||||
}
|
||||
else {
|
||||
ret = WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &op_props);
|
||||
}
|
||||
|
||||
WM_operator_properties_free(&op_props);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int outliner_lib_relocate_invoke_do(
|
||||
bContext *C, ReportList *reports, TreeElement *te, const float mval[2], const bool reload)
|
||||
{
|
||||
if (mval[1] > te->ys && mval[1] < te->ys + UI_UNIT_Y) {
|
||||
TreeStoreElem *tselem = TREESTORE(te);
|
||||
|
||||
if (te->idcode == ID_LI && tselem->id) {
|
||||
if (((Library *)tselem->id)->parent && !reload) {
|
||||
BKE_reportf(reports, RPT_ERROR_INVALID_INPUT,
|
||||
"Cannot relocate indirectly linked library '%s'", ((Library *)tselem->id)->filepath);
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
else {
|
||||
wmOperatorType *ot = WM_operatortype_find(reload ? "WM_OT_lib_reload" : "WM_OT_lib_relocate", false);
|
||||
|
||||
return lib_relocate(C, te, tselem, ot, reload);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (te = te->subtree.first; te; te = te->next) {
|
||||
int ret;
|
||||
if ((ret = outliner_lib_relocate_invoke_do(C, reports, te, mval, reload))) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int outliner_lib_relocate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
ARegion *ar = CTX_wm_region(C);
|
||||
SpaceOops *soops = CTX_wm_space_outliner(C);
|
||||
TreeElement *te;
|
||||
float fmval[2];
|
||||
|
||||
BLI_assert(ar && soops);
|
||||
|
||||
UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
|
||||
|
||||
for (te = soops->tree.first; te; te = te->next) {
|
||||
int ret;
|
||||
|
||||
if ((ret = outliner_lib_relocate_invoke_do(C, op->reports, te, fmval, false))) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
void OUTLINER_OT_lib_relocate(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Relocate Library";
|
||||
ot->idname = "OUTLINER_OT_lib_relocate";
|
||||
ot->description = "Relocate the library under cursor";
|
||||
|
||||
ot->invoke = outliner_lib_relocate_invoke;
|
||||
ot->poll = ED_operator_outliner_active;
|
||||
}
|
||||
|
||||
/* XXX This does not work with several items
|
||||
* (it is only called once in the end, due to the 'deffered' filebrowser invocation through event system...). */
|
||||
void lib_relocate_cb(
|
||||
bContext *C, Scene *UNUSED(scene), TreeElement *te,
|
||||
TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
|
||||
{
|
||||
wmOperatorType *ot = WM_operatortype_find("WM_OT_lib_relocate", false);
|
||||
|
||||
lib_relocate(C, te, tselem, ot, false);
|
||||
}
|
||||
|
||||
|
||||
static int outliner_lib_reload_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
ARegion *ar = CTX_wm_region(C);
|
||||
SpaceOops *soops = CTX_wm_space_outliner(C);
|
||||
TreeElement *te;
|
||||
float fmval[2];
|
||||
|
||||
BLI_assert(ar && soops);
|
||||
|
||||
UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fmval[0], &fmval[1]);
|
||||
|
||||
for (te = soops->tree.first; te; te = te->next) {
|
||||
int ret;
|
||||
|
||||
if ((ret = outliner_lib_relocate_invoke_do(C, op->reports, te, fmval, true))) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
void OUTLINER_OT_lib_reload(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Reload Library";
|
||||
ot->idname = "OUTLINER_OT_lib_reload";
|
||||
ot->description = "Reload the library under cursor";
|
||||
|
||||
ot->invoke = outliner_lib_reload_invoke;
|
||||
ot->poll = ED_operator_outliner_active;
|
||||
}
|
||||
|
||||
void lib_reload_cb(
|
||||
bContext *C, Scene *UNUSED(scene), TreeElement *te,
|
||||
TreeStoreElem *UNUSED(tsep), TreeStoreElem *tselem, void *UNUSED(user_data))
|
||||
{
|
||||
wmOperatorType *ot = WM_operatortype_find("WM_OT_lib_reload", false);
|
||||
|
||||
lib_relocate(C, te, tselem, ot, true);
|
||||
}
|
||||
|
||||
/* ************************************************************** */
|
||||
/* Setting Toggling Operators */
|
||||
|
||||
|
|
|
@ -179,8 +179,17 @@ void group_toggle_selectability_cb(struct bContext *C, struct Scene *scene, Tree
|
|||
void group_toggle_renderability_cb(struct bContext *C, struct Scene *scene, TreeElement *te, struct TreeStoreElem *tsep, struct TreeStoreElem *tselem, void *user_data);
|
||||
|
||||
void item_rename_cb(struct bContext *C, struct Scene *scene, TreeElement *te, struct TreeStoreElem *tsep, struct TreeStoreElem *tselem, void *user_data);
|
||||
void lib_relocate_cb(
|
||||
struct bContext *C, struct Scene *scene, struct TreeElement *te,
|
||||
struct TreeStoreElem *tsep, struct TreeStoreElem *tselem, void *user_data);
|
||||
void lib_reload_cb(
|
||||
struct bContext *C, struct Scene *scene, struct TreeElement *te,
|
||||
struct TreeStoreElem *tsep, struct TreeStoreElem *tselem, void *user_data);
|
||||
|
||||
void lib_delete_cb(
|
||||
void id_delete_cb(
|
||||
struct bContext *C, struct Scene *scene, struct TreeElement *te,
|
||||
struct TreeStoreElem *tsep, struct TreeStoreElem *tselem, void *user_data);
|
||||
void id_remap_cb(
|
||||
struct bContext *C, struct Scene *scene, struct TreeElement *te,
|
||||
struct TreeStoreElem *tsep, struct TreeStoreElem *tselem, void *user_data);
|
||||
|
||||
|
@ -190,8 +199,10 @@ TreeElement *outliner_dropzone_find(const struct SpaceOops *soops, const float f
|
|||
void OUTLINER_OT_item_activate(struct wmOperatorType *ot);
|
||||
void OUTLINER_OT_item_openclose(struct wmOperatorType *ot);
|
||||
void OUTLINER_OT_item_rename(struct wmOperatorType *ot);
|
||||
void OUTLINER_OT_lib_relocate(struct wmOperatorType *ot);
|
||||
void OUTLINER_OT_lib_reload(struct wmOperatorType *ot);
|
||||
|
||||
void OUTLINER_OT_lib_delete(struct wmOperatorType *ot);
|
||||
void OUTLINER_OT_id_delete(struct wmOperatorType *ot);
|
||||
|
||||
void OUTLINER_OT_show_one_level(struct wmOperatorType *ot);
|
||||
void OUTLINER_OT_show_active(struct wmOperatorType *ot);
|
||||
|
|
|
@ -47,13 +47,15 @@ void outliner_operatortypes(void)
|
|||
WM_operatortype_append(OUTLINER_OT_select_border);
|
||||
WM_operatortype_append(OUTLINER_OT_item_openclose);
|
||||
WM_operatortype_append(OUTLINER_OT_item_rename);
|
||||
WM_operatortype_append(OUTLINER_OT_lib_delete);
|
||||
WM_operatortype_append(OUTLINER_OT_operation);
|
||||
WM_operatortype_append(OUTLINER_OT_scene_operation);
|
||||
WM_operatortype_append(OUTLINER_OT_object_operation);
|
||||
WM_operatortype_append(OUTLINER_OT_group_operation);
|
||||
WM_operatortype_append(OUTLINER_OT_lib_operation);
|
||||
WM_operatortype_append(OUTLINER_OT_lib_relocate);
|
||||
WM_operatortype_append(OUTLINER_OT_id_operation);
|
||||
WM_operatortype_append(OUTLINER_OT_id_delete);
|
||||
WM_operatortype_append(OUTLINER_OT_id_remap);
|
||||
WM_operatortype_append(OUTLINER_OT_data_operation);
|
||||
WM_operatortype_append(OUTLINER_OT_animdata_operation);
|
||||
WM_operatortype_append(OUTLINER_OT_action_set);
|
||||
|
|
|
@ -850,6 +850,7 @@ enum {
|
|||
OL_OP_SELECT_HIERARCHY,
|
||||
OL_OP_DELETE,
|
||||
OL_OP_DELETE_HIERARCHY,
|
||||
OL_OP_REMAP,
|
||||
OL_OP_LOCALIZED, /* disabled, see below */
|
||||
OL_OP_TOGVIS,
|
||||
OL_OP_TOGSEL,
|
||||
|
@ -863,6 +864,8 @@ static EnumPropertyItem prop_object_op_types[] = {
|
|||
{OL_OP_SELECT_HIERARCHY, "SELECT_HIERARCHY", 0, "Select Hierarchy", ""},
|
||||
{OL_OP_DELETE, "DELETE", 0, "Delete", ""},
|
||||
{OL_OP_DELETE_HIERARCHY, "DELETE_HIERARCHY", 0, "Delete Hierarchy", ""},
|
||||
{OL_OP_REMAP, "REMAP", 0, "Remap Users",
|
||||
"Make all users of selected datablocks to use instead a new chosen one"},
|
||||
{OL_OP_TOGVIS, "TOGVIS", 0, "Toggle Visible", ""},
|
||||
{OL_OP_TOGSEL, "TOGSEL", 0, "Toggle Selectable", ""},
|
||||
{OL_OP_TOGREN, "TOGREN", 0, "Toggle Renderable", ""},
|
||||
|
@ -932,6 +935,10 @@ static int outliner_object_operation_exec(bContext *C, wmOperator *op)
|
|||
str = "Delete Object Hierarchy";
|
||||
WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
|
||||
}
|
||||
else if (event == OL_OP_REMAP) {
|
||||
outliner_do_libdata_operation(C, scene, soops, &soops->tree, id_remap_cb, NULL);
|
||||
str = "Remap ID";
|
||||
}
|
||||
else if (event == OL_OP_LOCALIZED) { /* disabled, see above enum (ton) */
|
||||
outliner_do_object_operation(C, scene, soops, &soops->tree, id_local_cb);
|
||||
str = "Localized Objects";
|
||||
|
@ -989,6 +996,8 @@ typedef enum eOutliner_PropGroupOps {
|
|||
OL_GROUPOP_UNLINK = 1,
|
||||
OL_GROUPOP_LOCAL,
|
||||
OL_GROUPOP_LINK,
|
||||
OL_GROUPOP_DELETE,
|
||||
OL_GROUPOP_REMAP,
|
||||
OL_GROUPOP_INSTANCE,
|
||||
OL_GROUPOP_TOGVIS,
|
||||
OL_GROUPOP_TOGSEL,
|
||||
|
@ -1000,6 +1009,9 @@ static EnumPropertyItem prop_group_op_types[] = {
|
|||
{OL_GROUPOP_UNLINK, "UNLINK", 0, "Unlink Group", ""},
|
||||
{OL_GROUPOP_LOCAL, "LOCAL", 0, "Make Local Group", ""},
|
||||
{OL_GROUPOP_LINK, "LINK", 0, "Link Group Objects to Scene", ""},
|
||||
{OL_GROUPOP_DELETE, "DELETE", 0, "Delete Group", "WARNING: no undo"},
|
||||
{OL_GROUPOP_REMAP, "REMAP", 0, "Remap Users",
|
||||
"Make all users of selected datablocks to use instead current (clicked) one"},
|
||||
{OL_GROUPOP_INSTANCE, "INSTANCE", 0, "Instance Groups in Scene", ""},
|
||||
{OL_GROUPOP_TOGVIS, "TOGVIS", 0, "Toggle Visible Group", ""},
|
||||
{OL_GROUPOP_TOGSEL, "TOGSEL", 0, "Toggle Selectable", ""},
|
||||
|
@ -1032,6 +1044,14 @@ static int outliner_group_operation_exec(bContext *C, wmOperator *op)
|
|||
break;
|
||||
case OL_GROUPOP_INSTANCE:
|
||||
outliner_do_libdata_operation(C, scene, soops, &soops->tree, group_instance_cb, NULL);
|
||||
/* works without this except if you try render right after, see: 22027 */
|
||||
DAG_relations_tag_update(CTX_data_main(C));
|
||||
break;
|
||||
case OL_GROUPOP_DELETE:
|
||||
WM_operator_name_call(C, "OUTLINER_OT_id_delete", WM_OP_INVOKE_REGION_WIN, NULL);
|
||||
break;
|
||||
case OL_GROUPOP_REMAP:
|
||||
outliner_do_libdata_operation(C, scene, soops, &soops->tree, id_remap_cb, NULL);
|
||||
break;
|
||||
case OL_GROUPOP_TOGVIS:
|
||||
outliner_do_libdata_operation(C, scene, soops, &soops->tree, group_toggle_visibility_cb, NULL);
|
||||
|
@ -1049,11 +1069,6 @@ static int outliner_group_operation_exec(bContext *C, wmOperator *op)
|
|||
BLI_assert(0);
|
||||
}
|
||||
|
||||
if (event == 3) { /* instance */
|
||||
/* works without this except if you try render right after, see: 22027 */
|
||||
DAG_relations_tag_update(CTX_data_main(C));
|
||||
}
|
||||
|
||||
ED_undo_push(C, prop_group_op_types[event - 1].name);
|
||||
WM_event_add_notifier(C, NC_GROUP, NULL);
|
||||
|
||||
|
@ -1086,6 +1101,8 @@ typedef enum eOutlinerIdOpTypes {
|
|||
OUTLINER_IDOP_UNLINK,
|
||||
OUTLINER_IDOP_LOCAL,
|
||||
OUTLINER_IDOP_SINGLE,
|
||||
OUTLINER_IDOP_DELETE,
|
||||
OUTLINER_IDOP_REMAP,
|
||||
|
||||
OUTLINER_IDOP_FAKE_ADD,
|
||||
OUTLINER_IDOP_FAKE_CLEAR,
|
||||
|
@ -1099,6 +1116,9 @@ static EnumPropertyItem prop_id_op_types[] = {
|
|||
{OUTLINER_IDOP_UNLINK, "UNLINK", 0, "Unlink", ""},
|
||||
{OUTLINER_IDOP_LOCAL, "LOCAL", 0, "Make Local", ""},
|
||||
{OUTLINER_IDOP_SINGLE, "SINGLE", 0, "Make Single User", ""},
|
||||
{OUTLINER_IDOP_DELETE, "DELETE", 0, "Delete", "WARNING: no undo"},
|
||||
{OUTLINER_IDOP_REMAP, "REMAP", 0, "Remap Users",
|
||||
"Make all users of selected datablocks to use instead current (clicked) one"},
|
||||
{OUTLINER_IDOP_FAKE_ADD, "ADD_FAKE", 0, "Add Fake User",
|
||||
"Ensure datablock gets saved even if it isn't in use (e.g. for motion and material libraries)"},
|
||||
{OUTLINER_IDOP_FAKE_CLEAR, "CLEAR_FAKE", 0, "Clear Fake User", ""},
|
||||
|
@ -1188,6 +1208,20 @@ static int outliner_id_operation_exec(bContext *C, wmOperator *op)
|
|||
}
|
||||
break;
|
||||
}
|
||||
case OUTLINER_IDOP_DELETE:
|
||||
{
|
||||
if (idlevel > 0) {
|
||||
outliner_do_libdata_operation(C, scene, soops, &soops->tree, id_delete_cb, NULL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OUTLINER_IDOP_REMAP:
|
||||
{
|
||||
if (idlevel > 0) {
|
||||
outliner_do_libdata_operation(C, scene, soops, &soops->tree, id_remap_cb, NULL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OUTLINER_IDOP_FAKE_ADD:
|
||||
{
|
||||
/* set fake user */
|
||||
|
@ -1259,13 +1293,16 @@ typedef enum eOutlinerLibOpTypes {
|
|||
|
||||
OL_LIB_RENAME,
|
||||
OL_LIB_DELETE,
|
||||
OL_LIB_RELOCATE,
|
||||
OL_LIB_RELOAD,
|
||||
} eOutlinerLibOpTypes;
|
||||
|
||||
static EnumPropertyItem outliner_lib_op_type_items[] = {
|
||||
{OL_LIB_RENAME, "RENAME", 0, "Rename", ""},
|
||||
{OL_LIB_DELETE, "DELETE", 0, "Delete", "Delete this library and all its item from Blender (needs a save/reload)"},
|
||||
{OL_LIB_DELETE, "DELETE", 0, "Delete", "Delete this library and all its item from Blender - WARNING: no undo"},
|
||||
{OL_LIB_RELOCATE, "RELOCATE", 0, "Relocate", "Select a new path for this library, and reload all its data"},
|
||||
{OL_LIB_RELOAD, "RELOAD", 0, "Reload", "Reload all data from this library"},
|
||||
{0, NULL, 0, NULL, NULL}
|
||||
|
||||
};
|
||||
|
||||
static int outliner_lib_operation_exec(bContext *C, wmOperator *op)
|
||||
|
@ -1295,13 +1332,19 @@ static int outliner_lib_operation_exec(bContext *C, wmOperator *op)
|
|||
}
|
||||
case OL_LIB_DELETE:
|
||||
{
|
||||
/* delete */
|
||||
outliner_do_libdata_operation(C, scene, soops, &soops->tree, lib_delete_cb, NULL);
|
||||
|
||||
WM_event_add_notifier(C, NC_ID | NA_EDITED, NULL);
|
||||
/* Note: no undo possible here really, not 100% sure why...
|
||||
* Probably because of library optimizations in undo/redo process? */
|
||||
/* ED_undo_push(C, "Rename"); */
|
||||
outliner_do_libdata_operation(C, scene, soops, &soops->tree, id_delete_cb, NULL);
|
||||
break;
|
||||
}
|
||||
case OL_LIB_RELOCATE:
|
||||
{
|
||||
/* rename */
|
||||
outliner_do_libdata_operation(C, scene, soops, &soops->tree, lib_relocate_cb, NULL);
|
||||
break;
|
||||
}
|
||||
case OL_LIB_RELOAD:
|
||||
{
|
||||
/* rename */
|
||||
outliner_do_libdata_operation(C, scene, soops, &soops->tree, lib_reload_cb, NULL);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -87,10 +87,13 @@ EnumPropertyItem rna_enum_id_type_items[] = {
|
|||
|
||||
#include "DNA_anim_types.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
|
||||
#include "BKE_font.h"
|
||||
#include "BKE_idprop.h"
|
||||
#include "BKE_library.h"
|
||||
#include "BKE_library_query.h"
|
||||
#include "BKE_library_remap.h"
|
||||
#include "BKE_animsys.h"
|
||||
#include "BKE_material.h"
|
||||
#include "BKE_depsgraph.h"
|
||||
|
@ -333,6 +336,19 @@ static void rna_ID_user_clear(ID *id)
|
|||
id->us = 0; /* don't save */
|
||||
}
|
||||
|
||||
static void rna_ID_delete(ID *id, Main *bmain)
|
||||
{
|
||||
BKE_libblock_delete(bmain, id);
|
||||
}
|
||||
|
||||
static void rna_ID_user_remap(ID *id, Main *bmain, ID *new_id)
|
||||
{
|
||||
if (GS(id->name) == GS(new_id->name)) {
|
||||
/* For now, do not allow remapping data in linked data from here... */
|
||||
BKE_libblock_remap(bmain, id, new_id, ID_REMAP_SKIP_INDIRECT_USAGE | ID_REMAP_SKIP_NEVER_NULL_USAGE);
|
||||
}
|
||||
}
|
||||
|
||||
static AnimData * rna_ID_animation_data_create(ID *id, Main *bmain)
|
||||
{
|
||||
AnimData *adt = BKE_animdata_add_id(id);
|
||||
|
@ -973,10 +989,20 @@ static void rna_def_ID(BlenderRNA *brna)
|
|||
parm = RNA_def_pointer(func, "id", "ID", "", "New copy of the ID");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
func = RNA_def_function(srna, "destroy", "rna_ID_delete");
|
||||
RNA_def_function_flag(func, FUNC_USE_MAIN);
|
||||
RNA_def_function_ui_description(func, "Delete this ID from Blender (WARNING: no undo, do not use it after calling this!)");
|
||||
|
||||
func = RNA_def_function(srna, "user_clear", "rna_ID_user_clear");
|
||||
RNA_def_function_ui_description(func, "Clear the user count of a data-block so its not saved, "
|
||||
"on reload the data will be removed");
|
||||
|
||||
func = RNA_def_function(srna, "user_remap", "rna_ID_user_remap");
|
||||
RNA_def_function_ui_description(func, "Replace all usage in the .blend file of this ID by new given one");
|
||||
RNA_def_function_flag(func, FUNC_USE_MAIN);
|
||||
parm = RNA_def_pointer(func, "new_id", "ID", "", "New ID to use");
|
||||
RNA_def_property_flag(parm, PROP_NEVER_NULL);
|
||||
|
||||
func = RNA_def_function(srna, "user_of_id", "BKE_library_ID_use_ID");
|
||||
RNA_def_function_ui_description(func, "Count the number of times that ID uses/references given one");
|
||||
parm = RNA_def_pointer(func, "id", "ID", "", "ID to count usages");
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
#include "BKE_context.h"
|
||||
#include "BKE_depsgraph.h"
|
||||
#include "BKE_library.h"
|
||||
#include "BKE_library_remap.h"
|
||||
#include "BKE_global.h"
|
||||
#include "BKE_main.h"
|
||||
#include "BKE_report.h"
|
||||
|
@ -211,7 +212,8 @@ static WMLinkAppendDataItem *wm_link_append_data_item_add(
|
|||
}
|
||||
|
||||
static void wm_link_do(
|
||||
WMLinkAppendData *lapp_data, ReportList *reports, Main *bmain, Scene *scene, View3D *v3d)
|
||||
WMLinkAppendData *lapp_data, ReportList *reports, Main *bmain, Scene *scene, View3D *v3d,
|
||||
const bool use_placeholders, const bool force_indirect)
|
||||
{
|
||||
Main *mainl;
|
||||
BlendHandle *bh;
|
||||
|
@ -258,7 +260,9 @@ static void wm_link_do(
|
|||
continue;
|
||||
}
|
||||
|
||||
new_id = BLO_library_link_named_part_ex(mainl, &bh, item->idcode, item->name, flag, scene, v3d);
|
||||
new_id = BLO_library_link_named_part_ex(
|
||||
mainl, &bh, item->idcode, item->name, flag, scene, v3d, use_placeholders, force_indirect);
|
||||
|
||||
if (new_id) {
|
||||
/* If the link is sucessful, clear item's libs 'todo' flags.
|
||||
* This avoids trying to link same item with other libraries to come. */
|
||||
|
@ -401,7 +405,7 @@ static int wm_link_append_exec(bContext *C, wmOperator *op)
|
|||
/* XXX We'd need re-entrant locking on Main for this to work... */
|
||||
/* BKE_main_lock(bmain); */
|
||||
|
||||
wm_link_do(lapp_data, op->reports, bmain, scene, CTX_wm_view3d(C));
|
||||
wm_link_do(lapp_data, op->reports, bmain, scene, CTX_wm_view3d(C), false, false);
|
||||
|
||||
/* BKE_main_unlock(bmain); */
|
||||
|
||||
|
@ -518,3 +522,393 @@ void WM_OT_append(wmOperatorType *ot)
|
|||
RNA_def_boolean(ot->srna, "use_recursive", true, "Localize All",
|
||||
"Localize all appended data, including those indirectly linked from other libraries");
|
||||
}
|
||||
|
||||
/** \name Reload/relocate libraries.
|
||||
*
|
||||
* \{ */
|
||||
|
||||
static int wm_lib_relocate_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
|
||||
{
|
||||
Library *lib;
|
||||
char lib_name[MAX_NAME];
|
||||
|
||||
RNA_string_get(op->ptr, "library", lib_name);
|
||||
lib = (Library *)BKE_libblock_find_name_ex(CTX_data_main(C), ID_LI, lib_name);
|
||||
|
||||
if (lib) {
|
||||
if (lib->parent) {
|
||||
BKE_reportf(op->reports, RPT_ERROR_INVALID_INPUT,
|
||||
"Cannot relocate indirectly linked library '%s'", lib->filepath);
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
RNA_string_set(op->ptr, "filepath", lib->filepath);
|
||||
|
||||
WM_event_add_fileselect(C, op);
|
||||
|
||||
return OPERATOR_RUNNING_MODAL;
|
||||
}
|
||||
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
/* Note that IDs listed in lapp_data items *must* have been removed from bmain by caller. */
|
||||
static void lib_relocate_do(Main *bmain, WMLinkAppendData *lapp_data, ReportList *reports, const bool do_reload)
|
||||
{
|
||||
ListBase *lbarray[MAX_LIBARRAY];
|
||||
int lba_idx;
|
||||
|
||||
LinkNode *itemlink;
|
||||
int item_idx;
|
||||
|
||||
BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true);
|
||||
|
||||
/* We do not want any instanciation here! */
|
||||
wm_link_do(lapp_data, reports, bmain, NULL, NULL, do_reload, do_reload);
|
||||
|
||||
BKE_main_lock(bmain);
|
||||
|
||||
/* We add back old id to bmain.
|
||||
* We need to do this in a first, separated loop, otherwise some of those may not be handled by
|
||||
* ID remapping, which means they would still reference old data to be deleted... */
|
||||
for (item_idx = 0, itemlink = lapp_data->items.list; itemlink; item_idx++, itemlink = itemlink->next) {
|
||||
WMLinkAppendDataItem *item = itemlink->link;
|
||||
ID *old_id = item->customdata;
|
||||
|
||||
BLI_assert(old_id);
|
||||
BLI_addtail(which_libbase(bmain, GS(old_id->name)), old_id);
|
||||
}
|
||||
|
||||
/* Note that in reload case, we also want to replace indirect usages. */
|
||||
const short remap_flags = ID_REMAP_SKIP_NEVER_NULL_USAGE | (do_reload ? 0 : ID_REMAP_SKIP_INDIRECT_USAGE);
|
||||
for (item_idx = 0, itemlink = lapp_data->items.list; itemlink; item_idx++, itemlink = itemlink->next) {
|
||||
WMLinkAppendDataItem *item = itemlink->link;
|
||||
ID *old_id = item->customdata;
|
||||
ID *new_id = item->new_id;
|
||||
|
||||
BLI_assert(old_id);
|
||||
if (do_reload) {
|
||||
/* Since we asked for placeholders in case of missing IDs, we expect to always get a valid one. */
|
||||
BLI_assert(new_id);
|
||||
}
|
||||
if (new_id) {
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("before remap, old_id users: %d, new_id users: %d\n", old_id->us, new_id->us);
|
||||
#endif
|
||||
BKE_libblock_remap_locked(bmain, old_id, new_id, remap_flags);
|
||||
|
||||
if (old_id->flag & LIB_FAKEUSER) {
|
||||
id_fake_user_clear(old_id);
|
||||
id_fake_user_set(new_id);
|
||||
}
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("after remap, old_id users: %d, new_id users: %d\n", old_id->us, new_id->us);
|
||||
#endif
|
||||
|
||||
/* In some cases, new_id might become direct link, remove parent of library in this case. */
|
||||
if (new_id->lib->parent && (new_id->tag & LIB_TAG_INDIRECT) == 0) {
|
||||
if (do_reload) {
|
||||
BLI_assert(0); /* Should not happen in 'pure' reload case... */
|
||||
}
|
||||
new_id->lib->parent = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (old_id->us > 0 && new_id && old_id->lib == new_id->lib) {
|
||||
/* Note that this *should* not happen - but better be safe than sorry in this area, at least until we are
|
||||
* 100% sure this cannot ever happen.
|
||||
* Also, we can safely assume names were unique so far, so just replacing '.' by '~' should work,
|
||||
* but this does not totally rules out the possibility of name collision. */
|
||||
size_t len = strlen(old_id->name);
|
||||
size_t dot_pos;
|
||||
bool has_num = false;
|
||||
|
||||
for (dot_pos = len; dot_pos--;) {
|
||||
char c = old_id->name[dot_pos];
|
||||
if (c == '.') {
|
||||
break;
|
||||
}
|
||||
else if (c < '0' || c > '9') {
|
||||
has_num = false;
|
||||
break;
|
||||
}
|
||||
has_num = true;
|
||||
}
|
||||
|
||||
if (has_num) {
|
||||
old_id->name[dot_pos] = '~';
|
||||
}
|
||||
else {
|
||||
len = MIN2(len, MAX_ID_NAME - 7);
|
||||
BLI_strncpy(&old_id->name[len], "~000", 7);
|
||||
}
|
||||
|
||||
id_sort_by_name(which_libbase(bmain, GS(old_id->name)), old_id);
|
||||
|
||||
BKE_reportf(reports, RPT_WARNING,
|
||||
"Lib Reload: Replacing all references to old datablock '%s' by reloaded one failed, "
|
||||
"old one (%d remaining users) had to be kept and was renamed to '%s'",
|
||||
new_id->name, old_id->us, old_id->name);
|
||||
}
|
||||
}
|
||||
|
||||
BKE_main_unlock(bmain);
|
||||
|
||||
for (item_idx = 0, itemlink = lapp_data->items.list; itemlink; item_idx++, itemlink = itemlink->next) {
|
||||
WMLinkAppendDataItem *item = itemlink->link;
|
||||
ID *old_id = item->customdata;
|
||||
|
||||
if (old_id->us == 0) {
|
||||
BKE_libblock_free(bmain, old_id);
|
||||
}
|
||||
}
|
||||
|
||||
/* Some datablocks can get reloaded/replaced 'silently' because they are not linkable (shape keys e.g.),
|
||||
* so we need another loop here to clear old ones if possible. */
|
||||
lba_idx = set_listbasepointers(bmain, lbarray);
|
||||
while (lba_idx--) {
|
||||
ID *id, *id_next;
|
||||
for (id = lbarray[lba_idx]->first; id; id = id_next) {
|
||||
id_next = id->next;
|
||||
/* XXX That check may be a bit to generic/permissive? */
|
||||
if (id->lib && (id->flag & LIB_TAG_PRE_EXISTING) && id->us == 0) {
|
||||
BKE_libblock_free(bmain, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Get rid of no more used libraries... */
|
||||
BKE_main_id_tag_idcode(bmain, ID_LI, LIB_TAG_DOIT, true);
|
||||
lba_idx = set_listbasepointers(bmain, lbarray);
|
||||
while (lba_idx--) {
|
||||
ID *id;
|
||||
for (id = lbarray[lba_idx]->first; id; id = id->next) {
|
||||
if (id->lib) {
|
||||
id->lib->id.tag &= ~LIB_TAG_DOIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
Library *lib, *lib_next;
|
||||
for (lib = which_libbase(bmain, ID_LI)->first; lib; lib = lib_next) {
|
||||
lib_next = lib->id.next;
|
||||
if (lib->id.tag & LIB_TAG_DOIT) {
|
||||
id_us_clear_real(&lib->id);
|
||||
if (lib->id.us == 0) {
|
||||
BKE_libblock_free(bmain, (ID *)lib);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int wm_lib_relocate_exec_do(bContext *C, wmOperator *op, bool do_reload)
|
||||
{
|
||||
Library *lib;
|
||||
char lib_name[MAX_NAME];
|
||||
|
||||
RNA_string_get(op->ptr, "library", lib_name);
|
||||
lib = (Library *)BKE_libblock_find_name_ex(CTX_data_main(C), ID_LI, lib_name);
|
||||
|
||||
if (lib) {
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
PropertyRNA *prop;
|
||||
WMLinkAppendData *lapp_data;
|
||||
|
||||
ListBase *lbarray[MAX_LIBARRAY];
|
||||
int lba_idx;
|
||||
|
||||
char path[FILE_MAX], root[FILE_MAXDIR], libname[FILE_MAX], relname[FILE_MAX];
|
||||
short flag = 0;
|
||||
|
||||
if (RNA_boolean_get(op->ptr, "relative_path")) {
|
||||
flag |= FILE_RELPATH;
|
||||
}
|
||||
|
||||
if (lib->parent && !do_reload) {
|
||||
BKE_reportf(op->reports, RPT_ERROR_INVALID_INPUT,
|
||||
"Cannot relocate indirectly linked library '%s'", lib->filepath);
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
RNA_string_get(op->ptr, "directory", root);
|
||||
RNA_string_get(op->ptr, "filename", libname);
|
||||
|
||||
if (!BLO_has_bfile_extension(libname)) {
|
||||
BKE_report(op->reports, RPT_ERROR, "Not a library");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
BLI_join_dirfile(path, sizeof(path), root, libname);
|
||||
|
||||
if (!BLI_exists(path)) {
|
||||
BKE_reportf(op->reports, RPT_ERROR_INVALID_INPUT,
|
||||
"Trying to reload or relocate library '%s' to invalid path '%s'", lib->id.name, path);
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
if (BLI_path_cmp(lib->filepath, path) == 0) {
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("We are supposed to reload '%s' lib (%d)...\n", lib->filepath, lib->id.us);
|
||||
#endif
|
||||
|
||||
do_reload = true;
|
||||
|
||||
lapp_data = wm_link_append_data_new(flag);
|
||||
wm_link_append_data_library_add(lapp_data, path);
|
||||
}
|
||||
else {
|
||||
int totfiles = 0;
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("We are supposed to relocate '%s' lib to new '%s' one...\n", lib->filepath, libname);
|
||||
#endif
|
||||
|
||||
/* Check if something is indicated for relocate. */
|
||||
prop = RNA_struct_find_property(op->ptr, "files");
|
||||
if (prop) {
|
||||
totfiles = RNA_property_collection_length(op->ptr, prop);
|
||||
if (totfiles == 0) {
|
||||
if (!libname[0]) {
|
||||
BKE_report(op->reports, RPT_ERROR, "Nothing indicated");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lapp_data = wm_link_append_data_new(flag);
|
||||
|
||||
if (totfiles) {
|
||||
RNA_BEGIN (op->ptr, itemptr, "files")
|
||||
{
|
||||
RNA_string_get(&itemptr, "name", relname);
|
||||
|
||||
BLI_join_dirfile(path, sizeof(path), root, relname);
|
||||
|
||||
if (BLI_path_cmp(path, lib->filepath) == 0 || !BLO_has_bfile_extension(relname)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("\t candidate new lib to reload datablocks from: %s\n", path);
|
||||
#endif
|
||||
wm_link_append_data_library_add(lapp_data, path);
|
||||
}
|
||||
RNA_END;
|
||||
}
|
||||
else {
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("\t candidate new lib to reload datablocks from: %s\n", path);
|
||||
#endif
|
||||
wm_link_append_data_library_add(lapp_data, path);
|
||||
}
|
||||
}
|
||||
|
||||
lba_idx = set_listbasepointers(bmain, lbarray);
|
||||
while (lba_idx--) {
|
||||
ID *id = lbarray[lba_idx]->first;
|
||||
const short idcode = id ? GS(id->name) : 0;
|
||||
|
||||
if (!id || !BKE_idcode_is_linkable(idcode)) {
|
||||
/* No need to reload non-linkable datatypes, those will get relinked with their 'users ID'. */
|
||||
continue;
|
||||
}
|
||||
|
||||
for (; id; id = id->next) {
|
||||
if (id->lib == lib) {
|
||||
WMLinkAppendDataItem *item;
|
||||
|
||||
/* We remove it from current Main, and add it to items to link... */
|
||||
/* Note that non-linkable IDs (like e.g. shapekeys) are also explicitely linked here... */
|
||||
BLI_remlink(lbarray[lba_idx], id);
|
||||
item = wm_link_append_data_item_add(lapp_data, id->name + 2, idcode, id);
|
||||
BLI_BITMAP_SET_ALL(item->libraries, true, lapp_data->num_libraries);
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("\tdatablock to seek for: %s\n", id->name);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lib_relocate_do(bmain, lapp_data, op->reports, do_reload);
|
||||
|
||||
wm_link_append_data_free(lapp_data);
|
||||
|
||||
BKE_main_lib_objects_recalc_all(bmain);
|
||||
IMB_colormanagement_check_file_config(bmain);
|
||||
|
||||
/* important we unset, otherwise these object wont
|
||||
* link into other scenes from this blend file */
|
||||
BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false);
|
||||
|
||||
/* recreate dependency graph to include new objects */
|
||||
DAG_scene_relations_rebuild(bmain, scene);
|
||||
|
||||
/* free gpu materials, some materials depend on existing objects, such as lamps so freeing correctly refreshes */
|
||||
GPU_materials_free();
|
||||
|
||||
/* XXX TODO: align G.lib with other directory storage (like last opened image etc...) */
|
||||
BLI_strncpy(G.lib, root, FILE_MAX);
|
||||
|
||||
WM_event_add_notifier(C, NC_WINDOW, NULL);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
static int wm_lib_relocate_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
return wm_lib_relocate_exec_do(C, op, false);
|
||||
}
|
||||
|
||||
void WM_OT_lib_relocate(wmOperatorType *ot)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
||||
ot->name = "Relocate Library";
|
||||
ot->idname = "WM_OT_lib_relocate";
|
||||
ot->description = "Relocate the given library to one or several others";
|
||||
|
||||
ot->invoke = wm_lib_relocate_invoke;
|
||||
ot->exec = wm_lib_relocate_exec;
|
||||
|
||||
ot->flag |= OPTYPE_UNDO;
|
||||
|
||||
prop = RNA_def_string(ot->srna, "library", NULL, MAX_NAME, "Library", "Library to relocate");
|
||||
RNA_def_property_flag(prop, PROP_HIDDEN);
|
||||
|
||||
WM_operator_properties_filesel(
|
||||
ot, FILE_TYPE_FOLDER | FILE_TYPE_BLENDER, FILE_BLENDER, FILE_OPENFILE,
|
||||
WM_FILESEL_FILEPATH | WM_FILESEL_DIRECTORY | WM_FILESEL_FILENAME | WM_FILESEL_FILES | WM_FILESEL_RELPATH,
|
||||
FILE_DEFAULTDISPLAY, FILE_SORT_ALPHA);
|
||||
}
|
||||
|
||||
static int wm_lib_reload_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
return wm_lib_relocate_exec_do(C, op, true);
|
||||
}
|
||||
|
||||
void WM_OT_lib_reload(wmOperatorType *ot)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
||||
ot->name = "Reload Library";
|
||||
ot->idname = "WM_OT_lib_reload";
|
||||
ot->description = "Reload the given library";
|
||||
|
||||
ot->exec = wm_lib_reload_exec;
|
||||
|
||||
ot->flag |= OPTYPE_UNDO;
|
||||
|
||||
prop = RNA_def_string(ot->srna, "library", NULL, MAX_NAME, "Library", "Library to reload");
|
||||
RNA_def_property_flag(prop, PROP_HIDDEN);
|
||||
|
||||
WM_operator_properties_filesel(
|
||||
ot, FILE_TYPE_FOLDER | FILE_TYPE_BLENDER, FILE_BLENDER, FILE_OPENFILE,
|
||||
WM_FILESEL_FILEPATH | WM_FILESEL_DIRECTORY | WM_FILESEL_FILENAME | WM_FILESEL_RELPATH,
|
||||
FILE_DEFAULTDISPLAY, FILE_SORT_ALPHA);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -4127,6 +4127,8 @@ void wm_operatortype_init(void)
|
|||
WM_operatortype_append(WM_OT_revert_mainfile);
|
||||
WM_operatortype_append(WM_OT_link);
|
||||
WM_operatortype_append(WM_OT_append);
|
||||
WM_operatortype_append(WM_OT_lib_relocate);
|
||||
WM_operatortype_append(WM_OT_lib_reload);
|
||||
WM_operatortype_append(WM_OT_recover_last_session);
|
||||
WM_operatortype_append(WM_OT_recover_auto_save);
|
||||
WM_operatortype_append(WM_OT_save_as_mainfile);
|
||||
|
|
|
@ -59,5 +59,8 @@ void WM_OT_save_mainfile(struct wmOperatorType *ot);
|
|||
void WM_OT_link(struct wmOperatorType *ot);
|
||||
void WM_OT_append(struct wmOperatorType *ot);
|
||||
|
||||
void WM_OT_lib_relocate(struct wmOperatorType *ot);
|
||||
void WM_OT_lib_reload(struct wmOperatorType *ot);
|
||||
|
||||
#endif /* __WM_FILES_H__ */
|
||||
|
||||
|
|
Loading…
Reference in New Issue