Merge branch 'blender2.7'
authorBastien Montagne <montagne29@wanadoo.fr>
Wed, 22 May 2019 21:34:07 +0000 (23:34 +0200)
committerBastien Montagne <montagne29@wanadoo.fr>
Wed, 22 May 2019 21:36:02 +0000 (23:36 +0200)
Conflicts:
source/blender/blenkernel/intern/library.c
source/blender/blenloader/intern/readfile.c
source/blender/editors/screen/screen_edit.c

1  2 
source/blender/blenkernel/BKE_library.h
source/blender/blenkernel/intern/blendfile.c
source/blender/blenkernel/intern/library.c

@@@ -218,17 -180,12 +218,19 @@@ void BKE_main_id_flag_all(struct Main *
  
  void BKE_main_id_clear_newpoins(struct Main *bmain);
  
+ void BLE_main_id_refcount_recompute(struct Main *bmain, const bool do_linked_only);
  void BKE_main_lib_objects_recalc_all(struct Main *bmain);
  
 -/* (MAX_ID_NAME - 2) + 3 */
 -void BKE_id_ui_prefix(char name[66 + 1], const struct ID *id);
 +/* Only for repairing files via versioning, avoid for general use. */
 +void BKE_main_id_repair_duplicate_names_listbase(struct ListBase *lb);
 +
 +#define MAX_ID_FULL_NAME (64 + 64 + 3 + 1)         /* 64 is MAX_ID_NAME - 2 */
 +#define MAX_ID_FULL_NAME_UI (MAX_ID_FULL_NAME + 3) /* Adds 'keycode' two letters at begining. */
 +void BKE_id_full_name_get(char name[MAX_ID_FULL_NAME], const struct ID *id);
 +void BKE_id_full_name_ui_prefix_get(char name[MAX_ID_FULL_NAME_UI], const struct ID *id);
 +
 +char *BKE_id_to_unique_string_key(const struct ID *id);
  
  void BKE_library_free(struct Library *lib);
  
@@@ -126,253 -105,233 +126,263 @@@ static void setup_app_userdef(BlendFile
   * \param bfd: Blend file data, freed by this function on exit.
   * \param filepath: File path or identifier.
   */
 -static void setup_app_data(
 -        bContext *C, BlendFileData *bfd,
 -        const char *filepath,
 -        const bool is_startup,
 -        ReportList *reports)
 +static void setup_app_data(bContext *C,
 +                           BlendFileData *bfd,
 +                           const char *filepath,
 +                           const bool is_startup,
 +                           ReportList *reports)
  {
 -      Main *bmain = G_MAIN;
 -      Scene *curscene = NULL;
 -      const bool recover = (G.fileflags & G_FILE_RECOVER) != 0;
 -      enum {
 -              LOAD_UI = 1,
 -              LOAD_UI_OFF,
 -              LOAD_UNDO,
 -      } mode;
 -
 -      /* may happen with library files - UNDO file should never have NULL cursccene... */
 -      if (ELEM(NULL, bfd->curscreen, bfd->curscene)) {
 -              BKE_report(reports, RPT_WARNING, "Library file, loading empty scene");
 -              mode = LOAD_UI_OFF;
 -      }
 -      else if (BLI_listbase_is_empty(&bfd->main->screen)) {
 -              mode = LOAD_UNDO;
 -      }
 -      else if ((G.fileflags & G_FILE_NO_UI) && (is_startup == false)) {
 -              mode = LOAD_UI_OFF;
 -      }
 -      else {
 -              mode = LOAD_UI;
 -      }
 -
 -      /* Free all render results, without this stale data gets displayed after loading files */
 -      if (mode != LOAD_UNDO) {
 -              RE_FreeAllRenderResults();
 -      }
 -
 -      /* Only make filepaths compatible when loading for real (not undo) */
 -      if (mode != LOAD_UNDO) {
 -              clean_paths(bfd->main);
 -      }
 -
 -      /* XXX here the complex windowmanager matching */
 -
 -      /* no load screens? */
 -      if (mode != LOAD_UI) {
 -              /* Logic for 'track_undo_scene' is to keep using the scene which the active screen has,
 -               * as long as the scene associated with the undo operation is visible in one of the open windows.
 -               *
 -               * - 'curscreen->scene' - scene the user is currently looking at.
 -               * - 'bfd->curscene' - scene undo-step was created in.
 -               *
 -               * This means users can have 2+ windows open and undo in both without screens switching.
 -               * But if they close one of the screens,
 -               * undo will ensure that the scene being operated on will be activated
 -               * (otherwise we'd be undoing on an off-screen scene which isn't acceptable).
 -               * see: T43424
 -               */
 -              bScreen *curscreen = NULL;
 -              bool track_undo_scene;
 -
 -              /* comes from readfile.c */
 -              SWAP(ListBase, bmain->wm, bfd->main->wm);
 -              SWAP(ListBase, bmain->screen, bfd->main->screen);
 -
 -              /* we re-use current screen */
 -              curscreen = CTX_wm_screen(C);
 -              /* but use new Scene pointer */
 -              curscene = bfd->curscene;
 -
 -              track_undo_scene = (mode == LOAD_UNDO && curscreen && curscene && bfd->main->wm.first);
 -
 -              if (curscene == NULL) {
 -                      curscene = bfd->main->scene.first;
 -              }
 -              /* empty file, we add a scene to make Blender work */
 -              if (curscene == NULL) {
 -                      curscene = BKE_scene_add(bfd->main, "Empty");
 -              }
 -
 -              if (track_undo_scene) {
 -                      /* keep the old (free'd) scene, let 'blo_lib_link_screen_restore'
 -                       * replace it with 'curscene' if its needed */
 -              }
 -              else {
 -                      /* and we enforce curscene to be in current screen */
 -                      if (curscreen) {
 -                              /* can run in bgmode */
 -                              curscreen->scene = curscene;
 -                      }
 -              }
 -
 -              /* BKE_blender_globals_clear will free G_MAIN, here we can still restore pointers */
 -              blo_lib_link_screen_restore(bfd->main, curscreen, curscene);
 -              /* curscreen might not be set when loading without ui (see T44217) so only re-assign if available */
 -              if (curscreen) {
 -                      curscene = curscreen->scene;
 -              }
 -
 -              if (track_undo_scene) {
 -                      wmWindowManager *wm = bfd->main->wm.first;
 -                      if (wm_scene_is_visible(wm, bfd->curscene) == false) {
 -                              curscene = bfd->curscene;
 -                              curscreen->scene = curscene;
 -                              BKE_screen_view3d_scene_sync(curscreen);
 -                      }
 -              }
 -      }
 -
 -      /* free G_MAIN Main database */
 -//    CTX_wm_manager_set(C, NULL);
 -      BKE_blender_globals_clear();
 -
 -      /* clear old property update cache, in case some old references are left dangling */
 -      RNA_property_update_cache_free();
 -
 -      bmain = G_MAIN = bfd->main;
 -
 -      CTX_data_main_set(C, bmain);
 -
 -      if (bfd->user) {
 -              /* only here free userdef themes... */
 -              BKE_blender_userdef_data_set_and_free(bfd->user);
 -              bfd->user = NULL;
 -
 -              /* Security issue: any blend file could include a USER block.
 -               *
 -               * Currently we load prefs from BLENDER_STARTUP_FILE and later on load BLENDER_USERPREF_FILE,
 -               * to load the preferences defined in the users home dir.
 -               *
 -               * This means we will never accidentally (or maliciously)
 -               * enable scripts auto-execution by loading a '.blend' file.
 -               */
 -              U.flag |= USER_SCRIPT_AUTOEXEC_DISABLE;
 -      }
 -
 -      /* case G_FILE_NO_UI or no screens in file */
 -      if (mode != LOAD_UI) {
 -              /* leave entire context further unaltered? */
 -              CTX_data_scene_set(C, curscene);
 -      }
 -      else {
 -              CTX_wm_manager_set(C, bmain->wm.first);
 -              CTX_wm_screen_set(C, bfd->curscreen);
 -              CTX_data_scene_set(C, bfd->curscene);
 -              CTX_wm_area_set(C, NULL);
 -              CTX_wm_region_set(C, NULL);
 -              CTX_wm_menu_set(C, NULL);
 -              curscene = bfd->curscene;
 -      }
 -
 -      /* Keep state from preferences. */
 -      const int fileflags_skip = G_FILE_FLAGS_RUNTIME;
 -      G.fileflags = (G.fileflags & fileflags_skip) | (bfd->fileflags & ~fileflags_skip);
 -
 -      /* this can happen when active scene was lib-linked, and doesn't exist anymore */
 -      if (CTX_data_scene(C) == NULL) {
 -              /* in case we don't even have a local scene, add one */
 -              if (!bmain->scene.first)
 -                      BKE_scene_add(bmain, "Empty");
 -
 -              CTX_data_scene_set(C, bmain->scene.first);
 -              CTX_wm_screen(C)->scene = CTX_data_scene(C);
 -              curscene = CTX_data_scene(C);
 -      }
 -
 -      BLI_assert(curscene == CTX_data_scene(C));
 -
 -
 -      /* special cases, override loaded flags: */
 -      if (G.f != bfd->globalf) {
 -              const int flags_keep = (G_SWAP_EXCHANGE | G_SCRIPT_AUTOEXEC | G_SCRIPT_OVERRIDE_PREF);
 -              bfd->globalf = (bfd->globalf & ~flags_keep) | (G.f & flags_keep);
 -      }
 -
 -
 -      G.f = bfd->globalf;
 +  Main *bmain = G_MAIN;
 +  Scene *curscene = NULL;
 +  const bool recover = (G.fileflags & G_FILE_RECOVER) != 0;
 +  enum {
 +    LOAD_UI = 1,
 +    LOAD_UI_OFF,
 +    LOAD_UNDO,
 +  } mode;
 +
 +  /* may happen with library files - UNDO file should never have NULL cursccene... */
 +  if (ELEM(NULL, bfd->curscreen, bfd->curscene)) {
 +    BKE_report(reports, RPT_WARNING, "Library file, loading empty scene");
 +    mode = LOAD_UI_OFF;
 +  }
 +  else if (BLI_listbase_is_empty(&bfd->main->screens)) {
 +    mode = LOAD_UNDO;
 +  }
 +  else if ((G.fileflags & G_FILE_NO_UI) && (is_startup == false)) {
 +    mode = LOAD_UI_OFF;
 +  }
 +  else {
 +    mode = LOAD_UI;
 +  }
 +
 +  /* Free all render results, without this stale data gets displayed after loading files */
 +  if (mode != LOAD_UNDO) {
 +    RE_FreeAllRenderResults();
 +  }
 +
 +  /* Only make filepaths compatible when loading for real (not undo) */
 +  if (mode != LOAD_UNDO) {
 +    clean_paths(bfd->main);
 +  }
 +
 +  /* XXX here the complex windowmanager matching */
 +
 +  /* no load screens? */
 +  if (mode != LOAD_UI) {
 +    /* Logic for 'track_undo_scene' is to keep using the scene which the active screen has,
 +     * as long as the scene associated with the undo operation is visible
 +     * in one of the open windows.
 +     *
 +     * - 'curscreen->scene' - scene the user is currently looking at.
 +     * - 'bfd->curscene' - scene undo-step was created in.
 +     *
 +     * This means users can have 2+ windows open and undo in both without screens switching.
 +     * But if they close one of the screens,
 +     * undo will ensure that the scene being operated on will be activated
 +     * (otherwise we'd be undoing on an off-screen scene which isn't acceptable).
 +     * see: T43424
 +     */
 +    wmWindow *win;
 +    bScreen *curscreen = NULL;
 +    ViewLayer *cur_view_layer;
 +    bool track_undo_scene;
 +
 +    /* comes from readfile.c */
 +    SWAP(ListBase, bmain->wm, bfd->main->wm);
 +    SWAP(ListBase, bmain->workspaces, bfd->main->workspaces);
 +    SWAP(ListBase, bmain->screens, bfd->main->screens);
 +
 +    /* we re-use current window and screen */
 +    win = CTX_wm_window(C);
 +    curscreen = CTX_wm_screen(C);
 +    /* but use Scene pointer from new file */
 +    curscene = bfd->curscene;
 +    cur_view_layer = bfd->cur_view_layer;
 +
 +    track_undo_scene = (mode == LOAD_UNDO && curscreen && curscene && bfd->main->wm.first);
 +
 +    if (curscene == NULL) {
 +      curscene = bfd->main->scenes.first;
 +    }
 +    /* empty file, we add a scene to make Blender work */
 +    if (curscene == NULL) {
 +      curscene = BKE_scene_add(bfd->main, "Empty");
 +    }
 +    if (cur_view_layer == NULL) {
 +      /* fallback to scene layer */
 +      cur_view_layer = BKE_view_layer_default_view(curscene);
 +    }
 +
 +    if (track_undo_scene) {
 +      /* keep the old (free'd) scene, let 'blo_lib_link_screen_restore'
 +       * replace it with 'curscene' if its needed */
 +    }
 +    /* and we enforce curscene to be in current screen */
 +    else if (win) { /* can run in bgmode */
 +      win->scene = curscene;
 +    }
 +
 +    /* BKE_blender_globals_clear will free G_MAIN, here we can still restore pointers */
 +    blo_lib_link_restore(bmain, bfd->main, CTX_wm_manager(C), curscene, cur_view_layer);
 +    if (win) {
 +      curscene = win->scene;
 +    }
 +
 +    if (track_undo_scene) {
 +      wmWindowManager *wm = bfd->main->wm.first;
 +      if (wm_scene_is_visible(wm, bfd->curscene) == false) {
 +        curscene = bfd->curscene;
 +        win->scene = curscene;
 +        BKE_screen_view3d_scene_sync(curscreen, curscene);
 +      }
 +    }
 +
 +    /* We need to tag this here because events may be handled immediately after.
 +     * only the current screen is important because we wont have to handle
 +     * events from multiple screens at once.*/
 +    if (curscreen) {
 +      BKE_screen_gizmo_tag_refresh(curscreen);
 +    }
 +  }
 +
 +  /* free G_MAIN Main database */
 +  //  CTX_wm_manager_set(C, NULL);
 +  BKE_blender_globals_clear();
 +
 +  /* clear old property update cache, in case some old references are left dangling */
 +  RNA_property_update_cache_free();
 +
 +  bmain = G_MAIN = bfd->main;
 +  bfd->main = NULL;
 +
 +  CTX_data_main_set(C, bmain);
 +
 +  /* case G_FILE_NO_UI or no screens in file */
 +  if (mode != LOAD_UI) {
 +    /* leave entire context further unaltered? */
 +    CTX_data_scene_set(C, curscene);
 +  }
 +  else {
 +    CTX_wm_manager_set(C, bmain->wm.first);
 +    CTX_wm_screen_set(C, bfd->curscreen);
 +    CTX_data_scene_set(C, bfd->curscene);
 +    CTX_wm_area_set(C, NULL);
 +    CTX_wm_region_set(C, NULL);
 +    CTX_wm_menu_set(C, NULL);
 +    curscene = bfd->curscene;
 +  }
 +
 +  /* Keep state from preferences. */
 +  const int fileflags_keep = G_FILE_FLAG_ALL_RUNTIME;
 +  G.fileflags = (G.fileflags & fileflags_keep) | (bfd->fileflags & ~fileflags_keep);
 +
 +  /* this can happen when active scene was lib-linked, and doesn't exist anymore */
 +  if (CTX_data_scene(C) == NULL) {
 +    wmWindow *win = CTX_wm_window(C);
 +
 +    /* in case we don't even have a local scene, add one */
 +    if (!bmain->scenes.first) {
 +      BKE_scene_add(bmain, "Empty");
 +    }
 +
 +    CTX_data_scene_set(C, bmain->scenes.first);
 +    win->scene = CTX_data_scene(C);
 +    curscene = CTX_data_scene(C);
 +  }
 +
 +  BLI_assert(curscene == CTX_data_scene(C));
 +
 +  /* special cases, override loaded flags: */
 +  if (G.f != bfd->globalf) {
 +    const int flags_keep = G_FLAG_ALL_RUNTIME;
 +    bfd->globalf &= G_FLAG_ALL_READFILE;
 +    bfd->globalf = (bfd->globalf & ~flags_keep) | (G.f & flags_keep);
 +  }
 +
 +  G.f = bfd->globalf;
  
  #ifdef WITH_PYTHON
 -      /* let python know about new main */
 -      BPY_context_update(C);
 +  /* let python know about new main */
 +  BPY_context_update(C);
  #endif
  
 -      /* FIXME: this version patching should really be part of the file-reading code,
 -       * but we still get too many unrelated data-corruption crashes otherwise... */
 -      if (bmain->versionfile < 250)
 -              do_versions_ipos_to_animato(bmain);
 -
 -      bmain->recovered = 0;
 -
 -      /* startup.blend or recovered startup */
 -      if (is_startup) {
 -              bmain->name[0] = '\0';
 -      }
 -      else if (recover && G.relbase_valid) {
 -              /* in case of autosave or quit.blend, use original filename instead
 -               * use relbase_valid to make sure the file is saved, else we get <memory2> in the filename */
 -              filepath = bfd->filename;
 -              bmain->recovered = 1;
 -
 -              /* these are the same at times, should never copy to the same location */
 -              if (bmain->name != filepath)
 -                      BLI_strncpy(bmain->name, filepath, FILE_MAX);
 -      }
 -
 -      /* baseflags, groups, make depsgraph, etc */
 -      /* first handle case if other windows have different scenes visible */
 -      if (mode == LOAD_UI) {
 -              wmWindowManager *wm = bmain->wm.first;
 -
 -              if (wm) {
 -                      wmWindow *win;
 -
 -                      for (win = wm->windows.first; win; win = win->next) {
 -                              if (win->screen && win->screen->scene) /* zealous check... */
 -                                      if (win->screen->scene != curscene)
 -                                              BKE_scene_set_background(bmain, win->screen->scene);
 -                      }
 -              }
 -      }
 -      BKE_scene_set_background(bmain, curscene);
 -
 -      if (mode != LOAD_UNDO) {
 -              RE_FreeAllPersistentData();
 -              IMB_colormanagement_check_file_config(bmain);
 -      }
 -
 -      MEM_freeN(bfd);
 +  /* FIXME: this version patching should really be part of the file-reading code,
 +   * but we still get too many unrelated data-corruption crashes otherwise... */
 +  if (bmain->versionfile < 250) {
 +    do_versions_ipos_to_animato(bmain);
 +  }
 +
 +  bmain->recovered = 0;
 +
 +  /* startup.blend or recovered startup */
 +  if (is_startup) {
 +    bmain->name[0] = '\0';
 +  }
 +  else if (recover && G.relbase_valid) {
 +    /* in case of autosave or quit.blend, use original filename instead
 +     * use relbase_valid to make sure the file is saved, else we get <memory2> in the filename */
 +    filepath = bfd->filename;
 +    bmain->recovered = 1;
 +
 +    /* these are the same at times, should never copy to the same location */
 +    if (bmain->name != filepath) {
 +      BLI_strncpy(bmain->name, filepath, FILE_MAX);
 +    }
 +  }
 +
 +  /* baseflags, groups, make depsgraph, etc */
 +  /* first handle case if other windows have different scenes visible */
 +  if (mode == LOAD_UI) {
 +    wmWindowManager *wm = bmain->wm.first;
 +
 +    if (wm) {
 +      for (wmWindow *win = wm->windows.first; win; win = win->next) {
 +        if (win->scene && win->scene != curscene) {
 +          BKE_scene_set_background(bmain, win->scene);
 +        }
 +      }
 +    }
 +  }
 +
 +  /* Setting scene might require having a dependency graph, with copy on write
 +   * we need to make sure we ensure scene has correct color management before
 +   * constructing dependency graph.
 +   */
 +  if (mode != LOAD_UNDO) {
 +    IMB_colormanagement_check_file_config(bmain);
 +  }
 +
 +  BKE_scene_set_background(bmain, curscene);
 +
 +  if (mode != LOAD_UNDO) {
 +    /* TODO(sergey): Can this be also move above? */
 +    RE_FreeAllPersistentData();
 +  }
++
++  if (mode == LOAD_UNDO) {
++    /* In undo/redo case, we do a whole lot of magic tricks to avoid having to re-read linked
++     * datablocks from libraries (since those are not supposed to change). Unfortunately, that
++     * means that we do not reset their user count, however we do increase that one when doing
++     * lib_link on local IDs using linked ones.
++     * There is no real way to predict amount of changes here, so we have to fully redo
++     * refcounting . */
++    BLE_main_id_refcount_recompute(bmain, true);
++  }
 +}
  
 +static void setup_app_blend_file_data(bContext *C,
 +                                      BlendFileData *bfd,
 +                                      const char *filepath,
 +                                      const struct BlendFileReadParams *params,
 +                                      ReportList *reports)
 +{
 +  if ((params->skip_flags & BLO_READ_SKIP_USERDEF) == 0) {
 +    setup_app_userdef(bfd);
 +  }
 +  if ((params->skip_flags & BLO_READ_SKIP_DATA) == 0) {
 +    setup_app_data(C, bfd, filepath, params->is_startup, reports);
 +  }
  }
  
  static int handle_subversion_warning(Main *main, ReportList *reports)
@@@ -1779,76 -1867,131 +1779,136 @@@ void id_clear_lib_data(Main *bmain, ID 
  /* next to indirect usage in read/writefile also in editobject.c scene.c */
  void BKE_main_id_clear_newpoins(Main *bmain)
  {
 -      ListBase *lbarray[MAX_LIBARRAY];
 -      ID *id;
 -      int a;
 +  ID *id;
  
 -      a = set_listbasepointers(bmain, lbarray);
 -      while (a--) {
 -              id = lbarray[a]->first;
 -              while (id) {
 -                      id->newid = NULL;
 -                      id->tag &= ~LIB_TAG_NEW;
 -                      id = id->next;
 -              }
 -      }
 +  FOREACH_MAIN_ID_BEGIN (bmain, id) {
 +    id->newid = NULL;
 +    id->tag &= ~LIB_TAG_NEW;
 +  }
 +  FOREACH_MAIN_ID_END;
  }
  
 -
 -static int id_refcount_recompute_callback(
 -        void *user_data, struct ID *UNUSED(id_self), struct ID **id_pointer, int cb_flag)
++static int id_refcount_recompute_callback(void *user_data,
++                                          struct ID *UNUSED(id_self),
++                                          struct ID **id_pointer,
++                                          int cb_flag)
+ {
 -      const bool do_linked_only = (bool)POINTER_AS_INT(user_data);
++  const bool do_linked_only = (bool)POINTER_AS_INT(user_data);
 -      if (*id_pointer == NULL) {
 -              return IDWALK_RET_NOP;
 -      }
 -      if (do_linked_only && !ID_IS_LINKED(*id_pointer)) {
 -              return IDWALK_RET_NOP;
 -      }
++  if (*id_pointer == NULL) {
++    return IDWALK_RET_NOP;
++  }
++  if (do_linked_only && !ID_IS_LINKED(*id_pointer)) {
++    return IDWALK_RET_NOP;
++  }
 -      if (cb_flag & IDWALK_CB_USER) {
 -              /* Do not touch to direct/indirect linked status here... */
 -              id_us_plus_no_lib(*id_pointer);
 -      }
 -      if (cb_flag & IDWALK_CB_USER_ONE) {
 -              id_us_ensure_real(*id_pointer);
 -      }
++  if (cb_flag & IDWALK_CB_USER) {
++    /* Do not touch to direct/indirect linked status here... */
++    id_us_plus_no_lib(*id_pointer);
++  }
++  if (cb_flag & IDWALK_CB_USER_ONE) {
++    id_us_ensure_real(*id_pointer);
++  }
 -      return IDWALK_RET_NOP;
++  return IDWALK_RET_NOP;
+ }
+ void BLE_main_id_refcount_recompute(struct Main *bmain, const bool do_linked_only)
+ {
 -      ListBase *lbarray[MAX_LIBARRAY];
 -      ID *id;
 -      int a;
 -
 -      /* Reset usercount of all affected IDs. */
 -      const int nbr_lb = a = set_listbasepointers(bmain, lbarray);
 -      while (a--) {
 -              for(id = lbarray[a]->first; id != NULL; id = id->next) {
 -                      if (!ID_IS_LINKED(id) && do_linked_only) {
 -                              continue;
 -                      }
 -                      id->us = ID_FAKE_USERS(id);
 -                      /* Note that we keep EXTRAUSER tag here, since some UI users may define it too... */
 -                      if (id->tag & LIB_TAG_EXTRAUSER) {
 -                              id->tag &= ~(LIB_TAG_EXTRAUSER | LIB_TAG_EXTRAUSER_SET);
 -                              id_us_ensure_real(id);
 -                      }
 -              }
 -      }
 -
 -      /* Go over whole Main database to re-generate proper usercounts... */
 -      a = nbr_lb;
 -      while (a--) {
 -              for(id = lbarray[a]->first; id != NULL; id = id->next) {
 -                      BKE_library_foreach_ID_link(
 -                                  bmain, id, id_refcount_recompute_callback, POINTER_FROM_INT((int)do_linked_only), IDWALK_READONLY);
 -              }
 -      }
 -}
 -
 -
 -static void library_make_local_copying_check(ID *id, GSet *loop_tags, MainIDRelations *id_relations, GSet *done_ids)
 -{
 -      if (BLI_gset_haskey(done_ids, id)) {
 -              return;  /* Already checked, nothing else to do. */
 -      }
 -
 -      MainIDRelationsEntry *entry = BLI_ghash_lookup(id_relations->id_used_to_user, id);
 -      BLI_gset_insert(loop_tags, id);
 -      for (; entry != NULL; entry = entry->next) {
 -              ID *par_id = (ID *)entry->id_pointer;  /* used_to_user stores ID pointer, not pointer to ID pointer... */
 -
 -              /* Our oh-so-beloved 'from' pointers... */
 -              if (entry->usage_flag & IDWALK_CB_LOOPBACK) {
 -                      /* We totally disregard Object->proxy_from 'usage' here, this one would only generate fake positives. */
 -                      if (GS(par_id->name) == ID_OB) {
 -                              BLI_assert(((Object *)par_id)->proxy_from == (Object *)id);
 -                              continue;
 -                      }
 -
 -                      /* Shapekeys are considered 'private' to their owner ID here, and never tagged (since they cannot be linked),
 -                       * so we have to switch effective parent to their owner. */
 -                      if (GS(par_id->name) == ID_KE) {
 -                              par_id = ((Key *)par_id)->from;
 -                      }
 -              }
 -
 -              if (par_id->lib == NULL) {
 -                      /* Local user, early out to avoid some gset querying... */
 -                      continue;
 -              }
 -              if (!BLI_gset_haskey(done_ids, par_id)) {
 -                      if (BLI_gset_haskey(loop_tags, par_id)) {
 -                              /* We are in a 'dependency loop' of IDs, this does not say us anything, skip it.
 -                               * Note that this is the situation that can lead to archipelagoes of linked data-blocks
 -                               * (since all of them have non-local users, they would all be duplicated, leading to a loop of unused
 -                               * linked data-blocks that cannot be freed since they all use each other...). */
 -                              continue;
 -                      }
 -                      /* Else, recursively check that user ID. */
 -                      library_make_local_copying_check(par_id, loop_tags, id_relations, done_ids);
 -              }
 -
 -              if (par_id->tag & LIB_TAG_DOIT) {
 -                      /* This user will be fully local in future, so far so good, nothing to do here but check next user. */
 -              }
 -              else {
 -                      /* This user won't be fully local in future, so current ID won't be either. And we are done checking it. */
 -                      id->tag &= ~LIB_TAG_DOIT;
 -                      break;
 -              }
 -      }
 -      BLI_gset_add(done_ids, id);
 -      BLI_gset_remove(loop_tags, id, NULL);
++  ListBase *lbarray[MAX_LIBARRAY];
++  ID *id;
++  int a;
++
++  /* Reset usercount of all affected IDs. */
++  const int nbr_lb = a = set_listbasepointers(bmain, lbarray);
++  while (a--) {
++    for (id = lbarray[a]->first; id != NULL; id = id->next) {
++      if (!ID_IS_LINKED(id) && do_linked_only) {
++        continue;
++      }
++      id->us = ID_FAKE_USERS(id);
++      /* Note that we keep EXTRAUSER tag here, since some UI users may define it too... */
++      if (id->tag & LIB_TAG_EXTRAUSER) {
++        id->tag &= ~(LIB_TAG_EXTRAUSER | LIB_TAG_EXTRAUSER_SET);
++        id_us_ensure_real(id);
++      }
++    }
++  }
++
++  /* Go over whole Main database to re-generate proper usercounts... */
++  a = nbr_lb;
++  while (a--) {
++    for (id = lbarray[a]->first; id != NULL; id = id->next) {
++      BKE_library_foreach_ID_link(bmain,
++                                  id,
++                                  id_refcount_recompute_callback,
++                                  POINTER_FROM_INT((int)do_linked_only),
++                                  IDWALK_READONLY);
++    }
++  }
++}
++
 +static void library_make_local_copying_check(ID *id,
 +                                             GSet *loop_tags,
 +                                             MainIDRelations *id_relations,
 +                                             GSet *done_ids)
 +{
 +  if (BLI_gset_haskey(done_ids, id)) {
 +    return; /* Already checked, nothing else to do. */
 +  }
 +
 +  MainIDRelationsEntry *entry = BLI_ghash_lookup(id_relations->id_used_to_user, id);
 +  BLI_gset_insert(loop_tags, id);
 +  for (; entry != NULL; entry = entry->next) {
 +    ID *par_id =
 +        (ID *)entry->id_pointer; /* used_to_user stores ID pointer, not pointer to ID pointer... */
 +
 +    /* Our oh-so-beloved 'from' pointers... */
 +    if (entry->usage_flag & IDWALK_CB_LOOPBACK) {
 +      /* We totally disregard Object->proxy_from 'usage' here,
 +       * this one would only generate fake positives. */
 +      if (GS(par_id->name) == ID_OB) {
 +        BLI_assert(((Object *)par_id)->proxy_from == (Object *)id);
 +        continue;
 +      }
 +
 +      /* Shapekeys are considered 'private' to their owner ID here, and never tagged
 +       * (since they cannot be linked), * so we have to switch effective parent to their owner. */
 +      if (GS(par_id->name) == ID_KE) {
 +        par_id = ((Key *)par_id)->from;
 +      }
 +    }
 +
 +    if (par_id->lib == NULL) {
 +      /* Local user, early out to avoid some gset querying... */
 +      continue;
 +    }
 +    if (!BLI_gset_haskey(done_ids, par_id)) {
 +      if (BLI_gset_haskey(loop_tags, par_id)) {
 +        /* We are in a 'dependency loop' of IDs, this does not say us anything, skip it.
 +         * Note that this is the situation that can lead to archipelagoes of linked data-blocks
 +         * (since all of them have non-local users, they would all be duplicated,
 +         * leading to a loop of unused linked data-blocks that cannot be freed since they all use
 +         * each other...). */
 +        continue;
 +      }
 +      /* Else, recursively check that user ID. */
 +      library_make_local_copying_check(par_id, loop_tags, id_relations, done_ids);
 +    }
 +
 +    if (par_id->tag & LIB_TAG_DOIT) {
 +      /* This user will be fully local in future, so far so good,
 +       * nothing to do here but check next user. */
 +    }
 +    else {
 +      /* This user won't be fully local in future, so current ID won't be either.
 +       * And we are done checking it. */
 +      id->tag &= ~LIB_TAG_DOIT;
 +      break;
 +    }
 +  }
 +  BLI_gset_add(done_ids, id);
 +  BLI_gset_remove(loop_tags, id, NULL);
  }
  
  /** Make linked datablocks local.