Blendread: Remove all instantiation logic from `BLO_library_link_` code.
Instantiation is now fully handled by BKE_blendfile_link_append module. Note that this also allows removal of the `BLO_LIBLINK_NEEDS_ID_TAG_DOIT` flag. Part of T91414: Unify link/append between WM operators and BPY context manager API, and cleanup usages of `BKE_library_make_local`.
This commit is contained in:
parent
4fe8c62b56
commit
9f290467ca
Notes:
blender-bot
2023-02-14 12:01:57 +01:00
Referenced by issue #91414, Unify link/append between WM operators and BPY context manager API, and cleanup usages of `BKE_library_make_local`
|
@ -1324,12 +1324,6 @@ void BKE_blendfile_link(BlendfileLinkAppendContext *lapp_context, ReportList *re
|
|||
|
||||
/* here appending/linking starts */
|
||||
|
||||
/* NOTE: This is temporary hot-fix until whole code using link/append features has been moved
|
||||
* to use new BKE code. */
|
||||
/* Do not handle instantiation in linking process anymore, we do it here in
|
||||
* #loose_data_instantiate instead. */
|
||||
lapp_context->params->flag &= ~BLO_LIBLINK_NEEDS_ID_TAG_DOIT;
|
||||
|
||||
mainl = BLO_library_link_begin(&blo_handle, libname, lapp_context->params);
|
||||
lib = mainl->curlib;
|
||||
BLI_assert(lib);
|
||||
|
|
|
@ -212,14 +212,6 @@ typedef enum eBLOLibLinkFlags {
|
|||
BLO_LIBLINK_USE_PLACEHOLDERS = 1 << 16,
|
||||
/** Force loaded ID to be tagged as #LIB_TAG_INDIRECT (used in reload context only). */
|
||||
BLO_LIBLINK_FORCE_INDIRECT = 1 << 17,
|
||||
/**
|
||||
* When set, tag ID types that pass the internal check #library_link_idcode_needs_tag_check
|
||||
*
|
||||
* Currently this is only used to instantiate objects in the scene.
|
||||
* Set this from #BLO_library_link_params_init_with_context so callers
|
||||
* don't need to remember to set this flag.
|
||||
*/
|
||||
BLO_LIBLINK_NEEDS_ID_TAG_DOIT = 1 << 18,
|
||||
/** Set fake user on appended IDs. */
|
||||
BLO_LIBLINK_APPEND_SET_FAKEUSER = 1 << 19,
|
||||
/** Append (make local) also indirect dependencies of appended IDs coming from other libraries.
|
||||
|
@ -335,14 +327,6 @@ void BLO_sanitize_experimental_features_userpref_blend(struct UserDef *userdef);
|
|||
|
||||
struct BlendThumbnail *BLO_thumbnail_from_file(const char *filepath);
|
||||
|
||||
void BLO_object_instantiate_object_base_instance_init(struct Main *bmain,
|
||||
struct Collection *collection,
|
||||
struct Object *ob,
|
||||
struct ViewLayer *view_layer,
|
||||
const struct View3D *v3d,
|
||||
const int flag,
|
||||
bool set_active);
|
||||
|
||||
/* datafiles (generated theme) */
|
||||
extern const struct bTheme U_theme_default;
|
||||
extern const struct UserDef U_default;
|
||||
|
|
|
@ -195,7 +195,6 @@ static void read_libraries(FileData *basefd, ListBase *mainlist);
|
|||
static void *read_struct(FileData *fd, BHead *bh, const char *blockname);
|
||||
static BHead *find_bhead_from_code_name(FileData *fd, const short idcode, const char *name);
|
||||
static BHead *find_bhead_from_idname(FileData *fd, const char *idname);
|
||||
static bool library_link_idcode_needs_tag_check(const short idcode, const int flag);
|
||||
|
||||
typedef struct BHeadN {
|
||||
struct BHeadN *next, *prev;
|
||||
|
@ -4441,290 +4440,6 @@ void BLO_expand_main(void *fdhandle, Main *mainvar)
|
|||
/** \name Library Linking (helper functions)
|
||||
* \{ */
|
||||
|
||||
static bool object_in_any_scene(Main *bmain, Object *ob)
|
||||
{
|
||||
LISTBASE_FOREACH (Scene *, sce, &bmain->scenes) {
|
||||
if (BKE_scene_object_find(sce, ob)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool object_in_any_collection(Main *bmain, Object *ob)
|
||||
{
|
||||
LISTBASE_FOREACH (Collection *, collection, &bmain->collections) {
|
||||
if (BKE_collection_has_object(collection, ob)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
|
||||
if (scene->master_collection != NULL &&
|
||||
BKE_collection_has_object(scene->master_collection, ob)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared operations to perform on the object's base after adding it to the scene.
|
||||
*/
|
||||
static void object_base_instance_init(
|
||||
Object *ob, ViewLayer *view_layer, const View3D *v3d, const int flag, bool set_active)
|
||||
{
|
||||
Base *base = BKE_view_layer_base_find(view_layer, ob);
|
||||
|
||||
if (v3d != NULL) {
|
||||
base->local_view_bits |= v3d->local_view_uuid;
|
||||
}
|
||||
|
||||
if (flag & FILE_AUTOSELECT) {
|
||||
/* All objects that use #FILE_AUTOSELECT must be selectable (unless linking data). */
|
||||
BLI_assert((base->flag & BASE_SELECTABLE) || (flag & FILE_LINK));
|
||||
if (base->flag & BASE_SELECTABLE) {
|
||||
base->flag |= BASE_SELECTED;
|
||||
}
|
||||
}
|
||||
|
||||
if (set_active) {
|
||||
view_layer->basact = base;
|
||||
}
|
||||
|
||||
BKE_scene_object_base_flag_sync_from_base(base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exported for link/append to create objects as well.
|
||||
*/
|
||||
void BLO_object_instantiate_object_base_instance_init(Main *bmain,
|
||||
Collection *collection,
|
||||
Object *ob,
|
||||
ViewLayer *view_layer,
|
||||
const View3D *v3d,
|
||||
const int flag,
|
||||
bool set_active)
|
||||
{
|
||||
/* Auto-select and appending. */
|
||||
if ((flag & FILE_AUTOSELECT) && ((flag & FILE_LINK) == 0)) {
|
||||
/* While in general the object should not be manipulated,
|
||||
* when the user requests the object to be selected, ensure it's visible and selectable. */
|
||||
ob->visibility_flag &= ~(OB_HIDE_VIEWPORT | OB_HIDE_SELECT);
|
||||
}
|
||||
|
||||
BKE_collection_object_add(bmain, collection, ob);
|
||||
|
||||
object_base_instance_init(ob, view_layer, v3d, flag, set_active);
|
||||
}
|
||||
|
||||
static void add_loose_objects_to_scene(Main *mainvar,
|
||||
Main *bmain,
|
||||
Scene *scene,
|
||||
ViewLayer *view_layer,
|
||||
const View3D *v3d,
|
||||
Library *lib,
|
||||
const int flag)
|
||||
{
|
||||
Collection *active_collection = NULL;
|
||||
const bool do_append = (flag & FILE_LINK) == 0;
|
||||
|
||||
BLI_assert(scene);
|
||||
|
||||
/* Give all objects which are LIB_TAG_INDIRECT a base,
|
||||
* or for a collection when *lib has been set. */
|
||||
LISTBASE_FOREACH (Object *, ob, &mainvar->objects) {
|
||||
/* NOTE: Even if this is a directly linked object and is tagged for instantiation, it might
|
||||
* have already been instantiated through one of its owner collections, in which case we do not
|
||||
* want to re-instantiate it in the active collection here. */
|
||||
bool do_it = (ob->id.tag & LIB_TAG_DOIT) != 0 && !BKE_scene_object_find(scene, ob);
|
||||
if (do_it ||
|
||||
((ob->id.tag & LIB_TAG_INDIRECT) != 0 && (ob->id.tag & LIB_TAG_PRE_EXISTING) == 0)) {
|
||||
if (do_append) {
|
||||
if (ob->id.us == 0) {
|
||||
do_it = true;
|
||||
}
|
||||
else if ((ob->id.lib == lib) && !object_in_any_collection(bmain, ob)) {
|
||||
/* When appending, make sure any indirectly loaded object gets a base,
|
||||
* when they are not part of any collection yet. */
|
||||
do_it = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (do_it) {
|
||||
/* Find or add collection as needed. */
|
||||
if (active_collection == NULL) {
|
||||
if (flag & FILE_ACTIVE_COLLECTION) {
|
||||
LayerCollection *lc = BKE_layer_collection_get_active(view_layer);
|
||||
active_collection = lc->collection;
|
||||
}
|
||||
else {
|
||||
active_collection = BKE_collection_add(bmain, scene->master_collection, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
CLAMP_MIN(ob->id.us, 0);
|
||||
ob->mode = OB_MODE_OBJECT;
|
||||
|
||||
/* Do NOT make base active here! screws up GUI stuff,
|
||||
* if you want it do it at the editor level. */
|
||||
const bool set_active = false;
|
||||
BLO_object_instantiate_object_base_instance_init(
|
||||
bmain, active_collection, ob, view_layer, v3d, flag, set_active);
|
||||
|
||||
ob->id.tag &= ~LIB_TAG_INDIRECT;
|
||||
ob->id.flag &= ~LIB_INDIRECT_WEAK_LINK;
|
||||
ob->id.tag |= LIB_TAG_EXTERN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void add_loose_object_data_to_scene(Main *mainvar,
|
||||
Main *bmain,
|
||||
Scene *scene,
|
||||
ViewLayer *view_layer,
|
||||
const View3D *v3d,
|
||||
const int flag)
|
||||
{
|
||||
if ((flag & BLO_LIBLINK_OBDATA_INSTANCE) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Collection *active_collection = scene->master_collection;
|
||||
if (flag & FILE_ACTIVE_COLLECTION) {
|
||||
LayerCollection *lc = BKE_layer_collection_get_active(view_layer);
|
||||
active_collection = lc->collection;
|
||||
}
|
||||
|
||||
/* Do not re-instantiate obdata IDs that are already instantiated by an object. */
|
||||
LISTBASE_FOREACH (Object *, ob, &mainvar->objects) {
|
||||
if ((ob->id.tag & LIB_TAG_PRE_EXISTING) == 0 && ob->data != NULL) {
|
||||
ID *obdata = ob->data;
|
||||
BLI_assert(ID_REAL_USERS(obdata) > 0);
|
||||
if ((obdata->tag & LIB_TAG_PRE_EXISTING) == 0) {
|
||||
obdata->tag &= ~LIB_TAG_DOIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop over all ID types, instancing object-data for ID types that have support for it. */
|
||||
ListBase *lbarray[INDEX_ID_MAX];
|
||||
int i = set_listbasepointers(mainvar, lbarray);
|
||||
while (i--) {
|
||||
const short idcode = BKE_idtype_idcode_from_index(i);
|
||||
if (!OB_DATA_SUPPORT_ID(idcode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (ID *, id, lbarray[i]) {
|
||||
if (id->tag & LIB_TAG_DOIT) {
|
||||
const int type = BKE_object_obdata_to_type(id);
|
||||
BLI_assert(type != -1);
|
||||
Object *ob = BKE_object_add_only_object(bmain, type, id->name + 2);
|
||||
ob->data = id;
|
||||
id_us_plus(id);
|
||||
BKE_object_materials_test(bmain, ob, ob->data);
|
||||
|
||||
/* Do NOT make base active here! screws up GUI stuff,
|
||||
* if you want it do it at the editor level. */
|
||||
bool set_active = false;
|
||||
BLO_object_instantiate_object_base_instance_init(
|
||||
bmain, active_collection, ob, view_layer, v3d, flag, set_active);
|
||||
|
||||
copy_v3_v3(ob->loc, scene->cursor.location);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void add_collections_to_scene(Main *mainvar,
|
||||
Main *bmain,
|
||||
Scene *scene,
|
||||
ViewLayer *view_layer,
|
||||
const View3D *v3d,
|
||||
Library *lib,
|
||||
const int flag)
|
||||
{
|
||||
Collection *active_collection = scene->master_collection;
|
||||
if (flag & FILE_ACTIVE_COLLECTION) {
|
||||
LayerCollection *lc = BKE_layer_collection_get_active(view_layer);
|
||||
active_collection = lc->collection;
|
||||
}
|
||||
|
||||
/* Give all objects which are tagged a base. */
|
||||
LISTBASE_FOREACH (Collection *, collection, &mainvar->collections) {
|
||||
if ((flag & BLO_LIBLINK_COLLECTION_INSTANCE) && (collection->id.tag & LIB_TAG_DOIT)) {
|
||||
/* Any indirect collection should not have been tagged. */
|
||||
BLI_assert((collection->id.tag & LIB_TAG_INDIRECT) == 0);
|
||||
|
||||
/* BKE_object_add(...) messes with the selection. */
|
||||
Object *ob = BKE_object_add_only_object(bmain, OB_EMPTY, collection->id.name + 2);
|
||||
ob->type = OB_EMPTY;
|
||||
ob->empty_drawsize = U.collection_instance_empty_size;
|
||||
|
||||
const bool set_selected = (flag & FILE_AUTOSELECT) != 0;
|
||||
/* TODO: why is it OK to make this active here but not in other situations?
|
||||
* See other callers of #object_base_instance_init */
|
||||
const bool set_active = set_selected;
|
||||
BLO_object_instantiate_object_base_instance_init(
|
||||
bmain, active_collection, ob, view_layer, v3d, flag, set_active);
|
||||
|
||||
DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION);
|
||||
|
||||
/* Assign the collection. */
|
||||
ob->instance_collection = collection;
|
||||
id_us_plus(&collection->id);
|
||||
ob->transflag |= OB_DUPLICOLLECTION;
|
||||
copy_v3_v3(ob->loc, scene->cursor.location);
|
||||
}
|
||||
/* We do not want to force instantiation of indirectly linked collections,
|
||||
* not even when appending. Users can now easily instantiate collections (and their objects)
|
||||
* as needed by themselves. See T67032. */
|
||||
else if ((collection->id.tag & LIB_TAG_INDIRECT) == 0) {
|
||||
bool do_add_collection = (collection->id.tag & LIB_TAG_DOIT) != 0;
|
||||
if (!do_add_collection) {
|
||||
/* We need to check that objects in that collections are already instantiated in a scene.
|
||||
* Otherwise, it's better to add the collection to the scene's active collection, than to
|
||||
* instantiate its objects in active scene's collection directly. See T61141.
|
||||
* Note that we only check object directly into that collection,
|
||||
* not recursively into its children.
|
||||
*/
|
||||
LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) {
|
||||
Object *ob = coll_ob->ob;
|
||||
if ((ob->id.tag & (LIB_TAG_PRE_EXISTING | LIB_TAG_DOIT | LIB_TAG_INDIRECT)) == 0 &&
|
||||
(ob->id.lib == lib) && (object_in_any_scene(bmain, ob) == false)) {
|
||||
do_add_collection = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (do_add_collection) {
|
||||
/* Add collection as child of active collection. */
|
||||
BKE_collection_child_add(bmain, active_collection, collection);
|
||||
|
||||
if (flag & FILE_AUTOSELECT) {
|
||||
LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) {
|
||||
Object *ob = coll_ob->ob;
|
||||
Base *base = BKE_view_layer_base_find(view_layer, ob);
|
||||
if (base) {
|
||||
base->flag |= BASE_SELECTED;
|
||||
BKE_scene_object_base_flag_sync_from_base(base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Those are kept for safety and consistency, but should not be needed anymore? */
|
||||
collection->id.tag &= ~LIB_TAG_INDIRECT;
|
||||
collection->id.flag &= ~LIB_INDIRECT_WEAK_LINK;
|
||||
collection->id.tag |= LIB_TAG_EXTERN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* returns true if the item was found
|
||||
* but it may already have already been appended/linked */
|
||||
static ID *link_named_part(
|
||||
|
@ -4774,14 +4489,6 @@ static ID *link_named_part(
|
|||
/* if we found the id but the id is NULL, this is really bad */
|
||||
BLI_assert(!((bhead != NULL) && (id == NULL)));
|
||||
|
||||
/* Tag as loose object (or data associated with objects)
|
||||
* needing to be instantiated in #LibraryLink_Params.scene. */
|
||||
if ((id != NULL) && (flag & BLO_LIBLINK_NEEDS_ID_TAG_DOIT)) {
|
||||
if (library_link_idcode_needs_tag_check(idcode, flag)) {
|
||||
id->tag |= LIB_TAG_DOIT;
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
|
@ -4806,41 +4513,10 @@ ID *BLO_library_link_named_part(Main *mainl,
|
|||
|
||||
/* common routine to append/link something from a library */
|
||||
|
||||
/**
|
||||
* Checks if the \a idcode needs to be tagged with #LIB_TAG_DOIT when linking/appending.
|
||||
*/
|
||||
static bool library_link_idcode_needs_tag_check(const short idcode, const int flag)
|
||||
{
|
||||
if (flag & BLO_LIBLINK_NEEDS_ID_TAG_DOIT) {
|
||||
/* Always true because of #add_loose_objects_to_scene & #add_collections_to_scene. */
|
||||
if (ELEM(idcode, ID_OB, ID_GR)) {
|
||||
return true;
|
||||
}
|
||||
if (flag & BLO_LIBLINK_OBDATA_INSTANCE) {
|
||||
if (OB_DATA_SUPPORT_ID(idcode)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears #LIB_TAG_DOIT based on the result of #library_link_idcode_needs_tag_check.
|
||||
*/
|
||||
static void library_link_clear_tag(Main *mainvar, const int flag)
|
||||
{
|
||||
for (int i = 0; i < INDEX_ID_MAX; i++) {
|
||||
const short idcode = BKE_idtype_idcode_from_index(i);
|
||||
BLI_assert(idcode != -1);
|
||||
if (library_link_idcode_needs_tag_check(idcode, flag)) {
|
||||
BKE_main_id_tag_idcode(mainvar, idcode, LIB_TAG_DOIT, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Main *library_link_begin(
|
||||
Main *mainvar, FileData **fd, const char *filepath, const int flag, const int id_tag_extra)
|
||||
static Main *library_link_begin(Main *mainvar,
|
||||
FileData **fd,
|
||||
const char *filepath,
|
||||
const int id_tag_extra)
|
||||
{
|
||||
Main *mainl;
|
||||
|
||||
|
@ -4853,11 +4529,6 @@ static Main *library_link_begin(
|
|||
|
||||
(*fd)->mainlist = MEM_callocN(sizeof(ListBase), "FileData.mainlist");
|
||||
|
||||
if (flag & BLO_LIBLINK_NEEDS_ID_TAG_DOIT) {
|
||||
/* Clear for objects and collections instantiating tag. */
|
||||
library_link_clear_tag(mainvar, flag);
|
||||
}
|
||||
|
||||
/* make mains */
|
||||
blo_split_main((*fd)->mainlist, mainvar);
|
||||
|
||||
|
@ -4896,9 +4567,6 @@ void BLO_library_link_params_init_with_context(struct LibraryLink_Params *params
|
|||
{
|
||||
BLO_library_link_params_init(params, bmain, flag, id_tag_extra);
|
||||
if (scene != NULL) {
|
||||
/* Tagging is needed for instancing. */
|
||||
params->flag |= BLO_LIBLINK_NEEDS_ID_TAG_DOIT;
|
||||
|
||||
params->context.scene = scene;
|
||||
params->context.view_layer = view_layer;
|
||||
params->context.v3d = v3d;
|
||||
|
@ -4919,7 +4587,7 @@ Main *BLO_library_link_begin(BlendHandle **bh,
|
|||
const struct LibraryLink_Params *params)
|
||||
{
|
||||
FileData *fd = (FileData *)(*bh);
|
||||
return library_link_begin(params->bmain, &fd, filepath, params->flag, params->id_tag_extra);
|
||||
return library_link_begin(params->bmain, &fd, filepath, params->id_tag_extra);
|
||||
}
|
||||
|
||||
static void split_main_newid(Main *mainptr, Main *main_newid)
|
||||
|
@ -4946,19 +4614,7 @@ static void split_main_newid(Main *mainptr, Main *main_newid)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \param scene: The scene in which to instantiate objects/collections
|
||||
* (if NULL, no instantiation is done).
|
||||
* \param v3d: The active 3D viewport.
|
||||
* (only to define active layers for instantiated objects & collections, can be NULL).
|
||||
*/
|
||||
static void library_link_end(Main *mainl,
|
||||
FileData **fd,
|
||||
Main *bmain,
|
||||
const int flag,
|
||||
Scene *scene,
|
||||
ViewLayer *view_layer,
|
||||
const View3D *v3d)
|
||||
static void library_link_end(Main *mainl, FileData **fd, const int flag)
|
||||
{
|
||||
Main *mainvar;
|
||||
Library *curlib;
|
||||
|
@ -5043,22 +4699,6 @@ static void library_link_end(Main *mainl,
|
|||
/* Make all relative paths, relative to the open blend file. */
|
||||
fix_relpaths_library(BKE_main_blendfile_path(mainvar), mainvar);
|
||||
|
||||
/* Give a base to loose objects and collections.
|
||||
* Only directly linked objects & collections are instantiated by
|
||||
* #BLO_library_link_named_part & co,
|
||||
* here we handle indirect ones and other possible edge-cases. */
|
||||
if (flag & BLO_LIBLINK_NEEDS_ID_TAG_DOIT) {
|
||||
/* Should always be true. */
|
||||
if (scene != NULL) {
|
||||
add_collections_to_scene(mainvar, bmain, scene, view_layer, v3d, curlib, flag);
|
||||
add_loose_objects_to_scene(mainvar, bmain, scene, view_layer, v3d, curlib, flag);
|
||||
add_loose_object_data_to_scene(mainvar, bmain, scene, view_layer, v3d, flag);
|
||||
}
|
||||
|
||||
/* Clear objects and collections instantiating tag. */
|
||||
library_link_clear_tag(mainvar, flag);
|
||||
}
|
||||
|
||||
/* patch to prevent switch_endian happens twice */
|
||||
if ((*fd)->flags & FD_FLAGS_SWITCH_ENDIAN) {
|
||||
blo_filedata_free(*fd);
|
||||
|
@ -5078,13 +4718,7 @@ static void library_link_end(Main *mainl,
|
|||
void BLO_library_link_end(Main *mainl, BlendHandle **bh, const struct LibraryLink_Params *params)
|
||||
{
|
||||
FileData *fd = (FileData *)(*bh);
|
||||
library_link_end(mainl,
|
||||
&fd,
|
||||
params->bmain,
|
||||
params->flag,
|
||||
params->context.scene,
|
||||
params->context.view_layer,
|
||||
params->context.v3d);
|
||||
library_link_end(mainl, &fd, params->flag);
|
||||
*bh = (BlendHandle *)fd;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue