1210 lines
37 KiB
C
1210 lines
37 KiB
C
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2007 Blender Foundation.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup wm
|
|
*
|
|
* Functions for dealing with append/link operators and helpers.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <float.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "DNA_ID.h"
|
|
#include "DNA_key_types.h"
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_screen_types.h"
|
|
#include "DNA_windowmanager_types.h"
|
|
|
|
#include "BLI_bitmap.h"
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_ghash.h"
|
|
#include "BLI_linklist.h"
|
|
#include "BLI_math.h"
|
|
#include "BLI_memarena.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BLO_readfile.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_global.h"
|
|
#include "BKE_key.h"
|
|
#include "BKE_layer.h"
|
|
#include "BKE_lib_id.h"
|
|
#include "BKE_lib_override.h"
|
|
#include "BKE_lib_remap.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_report.h"
|
|
|
|
#include "BKE_idtype.h"
|
|
|
|
#include "DEG_depsgraph.h"
|
|
#include "DEG_depsgraph_build.h"
|
|
|
|
#include "IMB_colormanagement.h"
|
|
|
|
#include "ED_datafiles.h"
|
|
#include "ED_screen.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_define.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "wm_files.h"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Link/Append Operator
|
|
* \{ */
|
|
|
|
static bool wm_link_append_poll(bContext *C)
|
|
{
|
|
if (WM_operator_winactive(C)) {
|
|
/* linking changes active object which is pretty useful in general,
|
|
* but which totally confuses edit mode (i.e. it becoming not so obvious
|
|
* to leave from edit mode and invalid tools in toolbar might be displayed)
|
|
* so disable link/append when in edit mode (sergey) */
|
|
if (CTX_data_edit_object(C)) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wm_link_append_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
|
|
{
|
|
if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
|
|
if (G.lib[0] != '\0') {
|
|
RNA_string_set(op->ptr, "filepath", G.lib);
|
|
}
|
|
else if (G.relbase_valid) {
|
|
char path[FILE_MAX];
|
|
BLI_strncpy(path, BKE_main_blendfile_path_from_global(), sizeof(path));
|
|
BLI_path_parent_dir(path);
|
|
RNA_string_set(op->ptr, "filepath", path);
|
|
}
|
|
}
|
|
|
|
WM_event_add_fileselect(C, op);
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
static short wm_link_append_flag(wmOperator *op)
|
|
{
|
|
PropertyRNA *prop;
|
|
short flag = 0;
|
|
|
|
if (RNA_boolean_get(op->ptr, "autoselect")) {
|
|
flag |= FILE_AUTOSELECT;
|
|
}
|
|
if (RNA_boolean_get(op->ptr, "active_collection")) {
|
|
flag |= FILE_ACTIVE_COLLECTION;
|
|
}
|
|
if ((prop = RNA_struct_find_property(op->ptr, "relative_path")) &&
|
|
RNA_property_boolean_get(op->ptr, prop)) {
|
|
flag |= FILE_RELPATH;
|
|
}
|
|
if (RNA_boolean_get(op->ptr, "link")) {
|
|
flag |= FILE_LINK;
|
|
}
|
|
if (RNA_boolean_get(op->ptr, "instance_collections")) {
|
|
flag |= FILE_COLLECTION_INSTANCE;
|
|
}
|
|
if (RNA_boolean_get(op->ptr, "instance_object_data")) {
|
|
flag |= FILE_OBDATA_INSTANCE;
|
|
}
|
|
|
|
return flag;
|
|
}
|
|
|
|
typedef struct WMLinkAppendDataItem {
|
|
char *name;
|
|
BLI_bitmap
|
|
*libraries; /* All libs (from WMLinkAppendData.libraries) to try to load this ID from. */
|
|
short idcode;
|
|
|
|
ID *new_id;
|
|
void *customdata;
|
|
} WMLinkAppendDataItem;
|
|
|
|
typedef struct WMLinkAppendData {
|
|
LinkNodePair libraries;
|
|
LinkNodePair items;
|
|
int num_libraries;
|
|
int num_items;
|
|
/**
|
|
* Combines #eFileSel_Params_Flag from DNA_space_types.h & #eBLOLibLinkFlags from BLO_readfile.h
|
|
*/
|
|
int flag;
|
|
|
|
/* Internal 'private' data */
|
|
MemArena *memarena;
|
|
} WMLinkAppendData;
|
|
|
|
static WMLinkAppendData *wm_link_append_data_new(const int flag)
|
|
{
|
|
MemArena *ma = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
|
|
WMLinkAppendData *lapp_data = BLI_memarena_calloc(ma, sizeof(*lapp_data));
|
|
|
|
lapp_data->flag = flag;
|
|
lapp_data->memarena = ma;
|
|
|
|
return lapp_data;
|
|
}
|
|
|
|
static void wm_link_append_data_free(WMLinkAppendData *lapp_data)
|
|
{
|
|
BLI_memarena_free(lapp_data->memarena);
|
|
}
|
|
|
|
/* WARNING! *Never* call wm_link_append_data_library_add() after having added some items! */
|
|
|
|
static void wm_link_append_data_library_add(WMLinkAppendData *lapp_data, const char *libname)
|
|
{
|
|
size_t len = strlen(libname) + 1;
|
|
char *libpath = BLI_memarena_alloc(lapp_data->memarena, len);
|
|
|
|
BLI_strncpy(libpath, libname, len);
|
|
BLI_linklist_append_arena(&lapp_data->libraries, libpath, lapp_data->memarena);
|
|
lapp_data->num_libraries++;
|
|
}
|
|
|
|
static WMLinkAppendDataItem *wm_link_append_data_item_add(WMLinkAppendData *lapp_data,
|
|
const char *idname,
|
|
const short idcode,
|
|
void *customdata)
|
|
{
|
|
WMLinkAppendDataItem *item = BLI_memarena_alloc(lapp_data->memarena, sizeof(*item));
|
|
size_t len = strlen(idname) + 1;
|
|
|
|
item->name = BLI_memarena_alloc(lapp_data->memarena, len);
|
|
BLI_strncpy(item->name, idname, len);
|
|
item->idcode = idcode;
|
|
item->libraries = BLI_BITMAP_NEW_MEMARENA(lapp_data->memarena, lapp_data->num_libraries);
|
|
|
|
item->new_id = NULL;
|
|
item->customdata = customdata;
|
|
|
|
BLI_linklist_append_arena(&lapp_data->items, item, lapp_data->memarena);
|
|
lapp_data->num_items++;
|
|
|
|
return item;
|
|
}
|
|
|
|
static void wm_link_do(WMLinkAppendData *lapp_data,
|
|
ReportList *reports,
|
|
Main *bmain,
|
|
Scene *scene,
|
|
ViewLayer *view_layer,
|
|
const View3D *v3d)
|
|
{
|
|
Main *mainl;
|
|
BlendHandle *bh;
|
|
Library *lib;
|
|
|
|
const int flag = lapp_data->flag;
|
|
const int id_tag_extra = 0;
|
|
|
|
LinkNode *liblink, *itemlink;
|
|
int lib_idx, item_idx;
|
|
|
|
BLI_assert(lapp_data->num_items && lapp_data->num_libraries);
|
|
|
|
for (lib_idx = 0, liblink = lapp_data->libraries.list; liblink;
|
|
lib_idx++, liblink = liblink->next) {
|
|
char *libname = liblink->link;
|
|
|
|
if (STREQ(libname, BLO_EMBEDDED_STARTUP_BLEND)) {
|
|
bh = BLO_blendhandle_from_memory(datatoc_startup_blend, datatoc_startup_blend_size);
|
|
}
|
|
else {
|
|
bh = BLO_blendhandle_from_file(libname, reports);
|
|
}
|
|
|
|
if (bh == NULL) {
|
|
/* Unlikely since we just browsed it, but possible
|
|
* Error reports will have been made by BLO_blendhandle_from_file() */
|
|
continue;
|
|
}
|
|
|
|
/* here appending/linking starts */
|
|
struct LibraryLink_Params liblink_params;
|
|
BLO_library_link_params_init_with_context(
|
|
&liblink_params, bmain, flag, id_tag_extra, scene, view_layer, v3d);
|
|
|
|
mainl = BLO_library_link_begin(&bh, libname, &liblink_params);
|
|
lib = mainl->curlib;
|
|
BLI_assert(lib);
|
|
UNUSED_VARS_NDEBUG(lib);
|
|
|
|
if (mainl->versionfile < 250) {
|
|
BKE_reportf(reports,
|
|
RPT_WARNING,
|
|
"Linking or appending from a very old .blend file format (%d.%d), no animation "
|
|
"conversion will "
|
|
"be done! You may want to re-save your lib file with current Blender",
|
|
mainl->versionfile,
|
|
mainl->subversionfile);
|
|
}
|
|
|
|
/* For each lib file, we try to link all items belonging to that lib,
|
|
* and tag those successful to not try to load them again with the other libs. */
|
|
for (item_idx = 0, itemlink = lapp_data->items.list; itemlink;
|
|
item_idx++, itemlink = itemlink->next) {
|
|
WMLinkAppendDataItem *item = itemlink->link;
|
|
ID *new_id;
|
|
|
|
if (!BLI_BITMAP_TEST(item->libraries, lib_idx)) {
|
|
continue;
|
|
}
|
|
|
|
new_id = BLO_library_link_named_part(mainl, &bh, item->idcode, item->name, &liblink_params);
|
|
|
|
if (new_id) {
|
|
/* If the link is successful, clear item's libs 'todo' flags.
|
|
* This avoids trying to link same item with other libraries to come. */
|
|
BLI_bitmap_set_all(item->libraries, false, lapp_data->num_libraries);
|
|
item->new_id = new_id;
|
|
}
|
|
}
|
|
|
|
BLO_library_link_end(mainl, &bh, &liblink_params);
|
|
BLO_blendhandle_close(bh);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if an item defined by \a name and \a group can be appended/linked.
|
|
*
|
|
* \param reports: Optionally report an error when an item can't be appended/linked.
|
|
*/
|
|
static bool wm_link_append_item_poll(ReportList *reports,
|
|
const char *path,
|
|
const char *group,
|
|
const char *name,
|
|
const bool do_append)
|
|
{
|
|
short idcode;
|
|
|
|
if (!group || !name) {
|
|
printf("skipping %s\n", path);
|
|
return false;
|
|
}
|
|
|
|
idcode = BKE_idtype_idcode_from_name(group);
|
|
|
|
/* XXX For now, we do a nasty exception for workspace, forbid linking them.
|
|
* Not nice, ultimately should be solved! */
|
|
if (!BKE_idtype_idcode_is_linkable(idcode) && (do_append || idcode != ID_WS)) {
|
|
if (reports) {
|
|
if (do_append) {
|
|
BKE_reportf(reports,
|
|
RPT_ERROR_INVALID_INPUT,
|
|
"Can't append data-block '%s' of type '%s'",
|
|
name,
|
|
group);
|
|
}
|
|
else {
|
|
BKE_reportf(reports,
|
|
RPT_ERROR_INVALID_INPUT,
|
|
"Can't link data-block '%s' of type '%s'",
|
|
name,
|
|
group);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int wm_link_append_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
ViewLayer *view_layer = CTX_data_view_layer(C);
|
|
PropertyRNA *prop;
|
|
WMLinkAppendData *lapp_data;
|
|
char path[FILE_MAX_LIBEXTRA], root[FILE_MAXDIR], libname[FILE_MAX_LIBEXTRA], relname[FILE_MAX];
|
|
char *group, *name;
|
|
int totfiles = 0;
|
|
|
|
RNA_string_get(op->ptr, "filename", relname);
|
|
RNA_string_get(op->ptr, "directory", root);
|
|
|
|
BLI_join_dirfile(path, sizeof(path), root, relname);
|
|
|
|
/* test if we have a valid data */
|
|
if (!BLO_library_path_explode(path, libname, &group, &name)) {
|
|
BKE_reportf(op->reports, RPT_ERROR, "'%s': not a library", path);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
if (!group) {
|
|
BKE_reportf(op->reports, RPT_ERROR, "'%s': nothing indicated", path);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
if (BLI_path_cmp(BKE_main_blendfile_path(bmain), libname) == 0) {
|
|
BKE_reportf(op->reports, RPT_ERROR, "'%s': cannot use current file as library", path);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* check if something is indicated for append/link */
|
|
prop = RNA_struct_find_property(op->ptr, "files");
|
|
if (prop) {
|
|
totfiles = RNA_property_collection_length(op->ptr, prop);
|
|
if (totfiles == 0) {
|
|
if (!name) {
|
|
BKE_reportf(op->reports, RPT_ERROR, "'%s': nothing indicated", path);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
}
|
|
else if (!name) {
|
|
BKE_reportf(op->reports, RPT_ERROR, "'%s': nothing indicated", path);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
short flag = wm_link_append_flag(op);
|
|
const bool do_append = (flag & FILE_LINK) == 0;
|
|
|
|
/* sanity checks for flag */
|
|
if (scene && scene->id.lib) {
|
|
BKE_reportf(op->reports,
|
|
RPT_WARNING,
|
|
"Scene '%s' is linked, instantiation of objects is disabled",
|
|
scene->id.name + 2);
|
|
flag &= ~(FILE_COLLECTION_INSTANCE | FILE_OBDATA_INSTANCE);
|
|
scene = NULL;
|
|
}
|
|
|
|
/* We need to add nothing from #eBLOLibLinkFlags to flag here. */
|
|
|
|
/* from here down, no error returns */
|
|
|
|
if (view_layer && RNA_boolean_get(op->ptr, "autoselect")) {
|
|
BKE_view_layer_base_deselect_all(view_layer);
|
|
}
|
|
|
|
/* tag everything, all untagged data can be made local
|
|
* its also generally useful to know what is new
|
|
*
|
|
* take extra care BKE_main_id_flag_all(bmain, LIB_TAG_PRE_EXISTING, false) is called after! */
|
|
BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true);
|
|
|
|
/* We define our working data...
|
|
* Note that here, each item 'uses' one library, and only one. */
|
|
lapp_data = wm_link_append_data_new(flag);
|
|
if (totfiles != 0) {
|
|
GHash *libraries = BLI_ghash_new(BLI_ghashutil_strhash_p, BLI_ghashutil_strcmp, __func__);
|
|
int lib_idx = 0;
|
|
|
|
RNA_BEGIN (op->ptr, itemptr, "files") {
|
|
RNA_string_get(&itemptr, "name", relname);
|
|
|
|
BLI_join_dirfile(path, sizeof(path), root, relname);
|
|
|
|
if (BLO_library_path_explode(path, libname, &group, &name)) {
|
|
if (!wm_link_append_item_poll(NULL, path, group, name, do_append)) {
|
|
continue;
|
|
}
|
|
|
|
if (!BLI_ghash_haskey(libraries, libname)) {
|
|
BLI_ghash_insert(libraries, BLI_strdup(libname), POINTER_FROM_INT(lib_idx));
|
|
lib_idx++;
|
|
wm_link_append_data_library_add(lapp_data, libname);
|
|
}
|
|
}
|
|
}
|
|
RNA_END;
|
|
|
|
RNA_BEGIN (op->ptr, itemptr, "files") {
|
|
RNA_string_get(&itemptr, "name", relname);
|
|
|
|
BLI_join_dirfile(path, sizeof(path), root, relname);
|
|
|
|
if (BLO_library_path_explode(path, libname, &group, &name)) {
|
|
WMLinkAppendDataItem *item;
|
|
|
|
if (!wm_link_append_item_poll(op->reports, path, group, name, do_append)) {
|
|
continue;
|
|
}
|
|
|
|
lib_idx = POINTER_AS_INT(BLI_ghash_lookup(libraries, libname));
|
|
|
|
item = wm_link_append_data_item_add(
|
|
lapp_data, name, BKE_idtype_idcode_from_name(group), NULL);
|
|
BLI_BITMAP_ENABLE(item->libraries, lib_idx);
|
|
}
|
|
}
|
|
RNA_END;
|
|
|
|
BLI_ghash_free(libraries, MEM_freeN, NULL);
|
|
}
|
|
else {
|
|
WMLinkAppendDataItem *item;
|
|
|
|
wm_link_append_data_library_add(lapp_data, libname);
|
|
item = wm_link_append_data_item_add(lapp_data, name, BKE_idtype_idcode_from_name(group), NULL);
|
|
BLI_BITMAP_ENABLE(item->libraries, 0);
|
|
}
|
|
|
|
if (lapp_data->num_items == 0) {
|
|
/* Early out in case there is nothing to link. */
|
|
wm_link_append_data_free(lapp_data);
|
|
/* Clear pre existing tag. */
|
|
BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* 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, view_layer, CTX_wm_view3d(C));
|
|
|
|
/* BKE_main_unlock(bmain); */
|
|
|
|
/* mark all library linked objects to be updated */
|
|
BKE_main_lib_objects_recalc_all(bmain);
|
|
IMB_colormanagement_check_file_config(bmain);
|
|
|
|
/* append, rather than linking */
|
|
if (do_append) {
|
|
const bool set_fake = RNA_boolean_get(op->ptr, "set_fake");
|
|
const bool use_recursive = RNA_boolean_get(op->ptr, "use_recursive");
|
|
|
|
if (use_recursive) {
|
|
BKE_library_make_local(bmain, NULL, NULL, true, set_fake);
|
|
}
|
|
else {
|
|
LinkNode *itemlink;
|
|
GSet *done_libraries = BLI_gset_new_ex(
|
|
BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__, lapp_data->num_libraries);
|
|
|
|
for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) {
|
|
ID *new_id = ((WMLinkAppendDataItem *)(itemlink->link))->new_id;
|
|
|
|
if (new_id && !BLI_gset_haskey(done_libraries, new_id->lib)) {
|
|
BKE_library_make_local(bmain, new_id->lib, NULL, true, set_fake);
|
|
BLI_gset_insert(done_libraries, new_id->lib);
|
|
}
|
|
}
|
|
|
|
BLI_gset_free(done_libraries, NULL);
|
|
}
|
|
}
|
|
|
|
wm_link_append_data_free(lapp_data);
|
|
|
|
/* 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);
|
|
|
|
/* TODO(sergey): Use proper flag for tagging here. */
|
|
|
|
/* TODO(dalai): Temporary solution!
|
|
* Ideally we only need to tag the new objects themselves, not the scene.
|
|
* This way we'll avoid flush of collection properties
|
|
* to all objects and limit update to the particular object only.
|
|
* But afraid first we need to change collection evaluation in DEG
|
|
* according to depsgraph manifesto. */
|
|
DEG_id_tag_update(&scene->id, 0);
|
|
|
|
/* recreate dependency graph to include new objects */
|
|
DEG_relations_tag_update(bmain);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
static void wm_link_append_properties_common(wmOperatorType *ot, bool is_link)
|
|
{
|
|
PropertyRNA *prop;
|
|
|
|
/* better not save _any_ settings for this operator */
|
|
/* properties */
|
|
prop = RNA_def_boolean(
|
|
ot->srna, "link", is_link, "Link", "Link the objects or data-blocks rather than appending");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
|
|
prop = RNA_def_boolean(ot->srna, "autoselect", true, "Select", "Select new objects");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
prop = RNA_def_boolean(ot->srna,
|
|
"active_collection",
|
|
true,
|
|
"Active Collection",
|
|
"Put new objects on the active collection");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
prop = RNA_def_boolean(
|
|
ot->srna,
|
|
"instance_collections",
|
|
is_link,
|
|
"Instance Collections",
|
|
"Create instances for collections, rather than adding them directly to the scene");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
|
|
prop = RNA_def_boolean(
|
|
ot->srna,
|
|
"instance_object_data",
|
|
true,
|
|
"Instance Object Data",
|
|
"Create instances for object data which are not referenced by any objects");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
|
|
}
|
|
|
|
void WM_OT_link(wmOperatorType *ot)
|
|
{
|
|
ot->name = "Link";
|
|
ot->idname = "WM_OT_link";
|
|
ot->description = "Link from a Library .blend file";
|
|
|
|
ot->invoke = wm_link_append_invoke;
|
|
ot->exec = wm_link_append_exec;
|
|
ot->poll = wm_link_append_poll;
|
|
|
|
ot->flag |= OPTYPE_UNDO;
|
|
|
|
WM_operator_properties_filesel(ot,
|
|
FILE_TYPE_FOLDER | FILE_TYPE_BLENDER | FILE_TYPE_BLENDERLIB,
|
|
FILE_LOADLIB,
|
|
FILE_OPENFILE,
|
|
WM_FILESEL_FILEPATH | WM_FILESEL_DIRECTORY | WM_FILESEL_FILENAME |
|
|
WM_FILESEL_RELPATH | WM_FILESEL_FILES | WM_FILESEL_SHOW_PROPS,
|
|
FILE_DEFAULTDISPLAY,
|
|
FILE_SORT_DEFAULT);
|
|
|
|
wm_link_append_properties_common(ot, true);
|
|
}
|
|
|
|
void WM_OT_append(wmOperatorType *ot)
|
|
{
|
|
ot->name = "Append";
|
|
ot->idname = "WM_OT_append";
|
|
ot->description = "Append from a Library .blend file";
|
|
|
|
ot->invoke = wm_link_append_invoke;
|
|
ot->exec = wm_link_append_exec;
|
|
ot->poll = wm_link_append_poll;
|
|
|
|
ot->flag |= OPTYPE_UNDO;
|
|
|
|
WM_operator_properties_filesel(ot,
|
|
FILE_TYPE_FOLDER | FILE_TYPE_BLENDER | FILE_TYPE_BLENDERLIB,
|
|
FILE_LOADLIB,
|
|
FILE_OPENFILE,
|
|
WM_FILESEL_FILEPATH | WM_FILESEL_DIRECTORY | WM_FILESEL_FILENAME |
|
|
WM_FILESEL_FILES | WM_FILESEL_SHOW_PROPS,
|
|
FILE_DEFAULTDISPLAY,
|
|
FILE_SORT_DEFAULT);
|
|
|
|
wm_link_append_properties_common(ot, false);
|
|
RNA_def_boolean(ot->srna,
|
|
"set_fake",
|
|
false,
|
|
"Fake User",
|
|
"Set \"Fake User\" for appended items (except objects and collections)");
|
|
RNA_def_boolean(
|
|
ot->srna,
|
|
"use_recursive",
|
|
true,
|
|
"Localize All",
|
|
"Localize all appended data, including those indirectly linked from other libraries");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Append Single Data-Block & Return it
|
|
*
|
|
* Used for appending workspace from startup files.
|
|
* \{ */
|
|
|
|
ID *WM_file_append_datablock(Main *bmain,
|
|
Scene *scene,
|
|
ViewLayer *view_layer,
|
|
View3D *v3d,
|
|
const char *filepath,
|
|
const short id_code,
|
|
const char *id_name)
|
|
{
|
|
/* Tag everything so we can make local only the new datablock. */
|
|
BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true);
|
|
|
|
/* Define working data, with just the one item we want to append. */
|
|
WMLinkAppendData *lapp_data = wm_link_append_data_new(0);
|
|
|
|
wm_link_append_data_library_add(lapp_data, filepath);
|
|
WMLinkAppendDataItem *item = wm_link_append_data_item_add(lapp_data, id_name, id_code, NULL);
|
|
BLI_BITMAP_ENABLE(item->libraries, 0);
|
|
|
|
/* Link datablock. */
|
|
wm_link_do(lapp_data, NULL, bmain, scene, view_layer, v3d);
|
|
|
|
/* Get linked datablock and free working data. */
|
|
ID *id = item->new_id;
|
|
wm_link_append_data_free(lapp_data);
|
|
|
|
/* Make datablock local. */
|
|
BKE_library_make_local(bmain, NULL, NULL, true, false);
|
|
|
|
/* Clear pre existing tag. */
|
|
BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false);
|
|
|
|
return id;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Library Relocate Operator & Library Reload API
|
|
* \{ */
|
|
|
|
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(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_abs);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
RNA_string_set(op->ptr, "filepath", lib->filepath_abs);
|
|
|
|
WM_event_add_fileselect(C, op);
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
static void lib_relocate_do_remap(Main *bmain,
|
|
ID *old_id,
|
|
ID *new_id,
|
|
ReportList *reports,
|
|
const bool do_reload,
|
|
const short remap_flags)
|
|
{
|
|
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 of %s, old_id users: %d, new_id users: %d\n",
|
|
old_id->name,
|
|
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 of %s, old_id users: %d, new_id users: %d\n",
|
|
old_id->name,
|
|
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_unreachable(); /* 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;
|
|
}
|
|
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, NULL);
|
|
|
|
BKE_reportf(
|
|
reports,
|
|
RPT_WARNING,
|
|
"Lib Reload: Replacing all references to old data-block '%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);
|
|
}
|
|
}
|
|
|
|
static void lib_relocate_do(Main *bmain,
|
|
Library *library,
|
|
WMLinkAppendData *lapp_data,
|
|
ReportList *reports,
|
|
const bool do_reload)
|
|
{
|
|
ListBase *lbarray[INDEX_ID_MAX];
|
|
int lba_idx;
|
|
|
|
LinkNode *itemlink;
|
|
int item_idx;
|
|
|
|
/* Remove all IDs to be reloaded from Main. */
|
|
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_idtype_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 == library) {
|
|
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 explicitly linked here... */
|
|
BLI_remlink(lbarray[lba_idx], id);
|
|
/* Usual special code for ShapeKeys snowflakes... */
|
|
Key *old_key = BKE_key_from_id(id);
|
|
if (old_key != NULL) {
|
|
BLI_remlink(which_libbase(bmain, GS(old_key->id.name)), &old_key->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
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lapp_data->num_items == 0) {
|
|
/* Early out in case there is nothing to do. */
|
|
return;
|
|
}
|
|
|
|
BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true);
|
|
|
|
/* We do not want any instantiation here! */
|
|
wm_link_do(lapp_data, reports, bmain, NULL, NULL, NULL);
|
|
|
|
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);
|
|
|
|
/* Usual special code for ShapeKeys snowflakes... */
|
|
Key *old_key = BKE_key_from_id(old_id);
|
|
if (old_key != NULL) {
|
|
BLI_addtail(which_libbase(bmain, GS(old_key->id.name)), &old_key->id);
|
|
}
|
|
}
|
|
|
|
/* Since our (old) reloaded IDs were removed from main, the user count done for them in linking
|
|
* code is wrong, we need to redo it here after adding them back to main. */
|
|
BKE_main_id_refcount_recompute(bmain, false);
|
|
|
|
/* Note that in reload case, we also want to replace indirect usages. */
|
|
const short remap_flags = ID_REMAP_SKIP_NEVER_NULL_USAGE |
|
|
ID_REMAP_NO_INDIRECT_PROXY_DATA_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;
|
|
|
|
lib_relocate_do_remap(bmain, old_id, new_id, reports, do_reload, remap_flags);
|
|
if (new_id == NULL) {
|
|
continue;
|
|
}
|
|
/* Usual special code for ShapeKeys snowflakes... */
|
|
Key **old_key_p = BKE_key_from_id_p(old_id);
|
|
if (old_key_p == NULL) {
|
|
continue;
|
|
}
|
|
Key *old_key = *old_key_p;
|
|
Key *new_key = BKE_key_from_id(new_id);
|
|
if (old_key != NULL) {
|
|
*old_key_p = NULL;
|
|
id_us_min(&old_key->id);
|
|
lib_relocate_do_remap(bmain, &old_key->id, &new_key->id, reports, do_reload, remap_flags);
|
|
*old_key_p = old_key;
|
|
id_us_plus_no_lib(&old_key->id);
|
|
}
|
|
}
|
|
|
|
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_id_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_id_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_id_free(bmain, (ID *)lib);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Update overrides of reloaded linked data-blocks.
|
|
* Note that this will not necessarily fully update the override, it might need to be manually
|
|
* 're-generated' depending on changes in linked data. */
|
|
ID *id;
|
|
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
|
if (ID_IS_LINKED(id) || !ID_IS_OVERRIDE_LIBRARY_REAL(id) ||
|
|
(id->tag & LIB_TAG_PRE_EXISTING) == 0) {
|
|
continue;
|
|
}
|
|
if (id->override_library->reference->lib == library) {
|
|
BKE_lib_override_library_update(bmain, id);
|
|
}
|
|
}
|
|
FOREACH_MAIN_ID_END;
|
|
|
|
BKE_main_collection_sync(bmain);
|
|
|
|
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 */
|
|
DEG_relations_tag_update(bmain);
|
|
}
|
|
|
|
void WM_lib_reload(Library *lib, bContext *C, ReportList *reports)
|
|
{
|
|
if (!BLO_has_bfile_extension(lib->filepath_abs)) {
|
|
BKE_reportf(reports, RPT_ERROR, "'%s' is not a valid library filepath", lib->filepath_abs);
|
|
return;
|
|
}
|
|
|
|
if (!BLI_exists(lib->filepath_abs)) {
|
|
BKE_reportf(reports,
|
|
RPT_ERROR,
|
|
"Trying to reload library '%s' from invalid path '%s'",
|
|
lib->id.name,
|
|
lib->filepath_abs);
|
|
return;
|
|
}
|
|
|
|
WMLinkAppendData *lapp_data = wm_link_append_data_new(BLO_LIBLINK_USE_PLACEHOLDERS |
|
|
BLO_LIBLINK_FORCE_INDIRECT);
|
|
|
|
wm_link_append_data_library_add(lapp_data, lib->filepath_abs);
|
|
|
|
lib_relocate_do(CTX_data_main(C), lib, lapp_data, reports, true);
|
|
|
|
wm_link_append_data_free(lapp_data);
|
|
|
|
WM_event_add_notifier(C, NC_WINDOW, NULL);
|
|
}
|
|
|
|
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(CTX_data_main(C), ID_LI, lib_name);
|
|
|
|
if (lib) {
|
|
Main *bmain = CTX_data_main(C);
|
|
PropertyRNA *prop;
|
|
WMLinkAppendData *lapp_data;
|
|
|
|
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_abs);
|
|
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(BKE_main_blendfile_path(bmain), path) == 0) {
|
|
BKE_reportf(op->reports,
|
|
RPT_ERROR_INVALID_INPUT,
|
|
"Cannot relocate library '%s' to current blend file '%s'",
|
|
lib->id.name,
|
|
path);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (BLI_path_cmp(lib->filepath_abs, 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_abs) == 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);
|
|
}
|
|
}
|
|
|
|
if (do_reload) {
|
|
lapp_data->flag |= BLO_LIBLINK_USE_PLACEHOLDERS | BLO_LIBLINK_FORCE_INDIRECT;
|
|
}
|
|
|
|
lib_relocate_do(bmain, lib, lapp_data, op->reports, do_reload);
|
|
|
|
wm_link_append_data_free(lapp_data);
|
|
|
|
/* 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_DEFAULT);
|
|
}
|
|
|
|
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_DEFAULT);
|
|
}
|
|
|
|
/** \} */
|