Fix #120287: Annotations GPv2 data must not be converted.

This commit ensures that no legacy GP data is shared between GP objects
and annotations, before doing the conversion, by duplicating annotation
data when required.

Conversion code can then completely ignore annotation GPv2 IDs.

Pull Request: https://projects.blender.org/blender/blender/pulls/120581
This commit is contained in:
Bastien Montagne
2024-04-12 16:27:51 +02:00
committed by Gitea
parent fdf5a05bde
commit 48baa80a05

View File

@@ -29,6 +29,7 @@
#include "BKE_node.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_object.hh"
#include "BKE_screen.hh"
#include "BLO_readfile.hh"
@@ -50,6 +51,8 @@
#include "DNA_grease_pencil_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
@@ -2407,6 +2410,161 @@ static void legacy_object_modifiers(Main & /*bmain*/, Object &object)
}
}
/**
* Ensure that both use-cases of legacy grease pencil data (as object, and as annotations) are
* fully separated and valid (have proper tag). Annotation IDs are left as-is currently, while GPv2
* data used by objects need to be converted to GPv3 data.
*
* \note GPv2 IDs not used by object nor annotations are just left in their current state - the
* ones tagged as annotations will be left untouched, the others will be converted to GPv3 data.
*
* \note De-duplication needs to assign the new, duplicated GPv2 ID to annotations, and keep the
* original one assigned to objects. That way, when the object data are converted, other potential
* references to the old GPv2 ID (drivers, custom propoerties, etc.) can be safely remapped to the
* new GPv3 ID.
*
* \warning: This makes the assumption that annotation data is not typically referenced by anything
* else than annotation pointers. If this is not true, then some data might be lost during
* conversion process.
*/
static void legacy_gpencil_sanitize_annotations(Main &bmain)
{
/* Annotations mapping, keys are existing GPv2 IDs actually used as annotations, values are
* either:
* - nullptr: this annotation is never used by any object, there was no need to duplicate it.
* - new GPv2 ID: this annotation was also used by objects, this is its new duplicated ID. */
Map<bGPdata *, bGPdata *> annotations_gpv2;
/* All legacy GPv2 data used by objects. */
Set<bGPdata *> object_gpv2;
/* Check all GP objects. */
LISTBASE_FOREACH (Object *, object, &bmain.objects) {
if (object->type != OB_GPENCIL_LEGACY) {
continue;
}
bGPdata *legacy_gpd = static_cast<bGPdata *>(object->data);
if (!legacy_gpd) {
continue;
}
if ((legacy_gpd->flag & GP_DATA_ANNOTATIONS) != 0) {
legacy_gpd->flag &= ~GP_DATA_ANNOTATIONS;
}
object_gpv2.add(legacy_gpd);
}
/* Detect all annotations currently used as such, and de-duplicate them if they are also used by
* objects. */
auto sanitize_gpv2_annotation = [&](bGPdata **legacy_gpd_p) {
bGPdata *legacy_gpd = *legacy_gpd_p;
if (!legacy_gpd) {
return;
}
bGPdata *new_annotation_gpd = annotations_gpv2.lookup_or_add(legacy_gpd, nullptr);
if (!object_gpv2.contains(legacy_gpd)) {
/* Legacy annotation GPv2 data not used by any object, just ensure that it is properly
* tagged. */
BLI_assert(!new_annotation_gpd);
if ((legacy_gpd->flag & GP_DATA_ANNOTATIONS) == 0) {
legacy_gpd->flag |= GP_DATA_ANNOTATIONS;
}
return;
}
/* Legacy GP data also used by objects. Create the duplicate of legacy GPv2 data for
* annotations, if not yet done. */
if (!new_annotation_gpd) {
new_annotation_gpd = reinterpret_cast<bGPdata *>(BKE_id_copy_in_lib(
&bmain, legacy_gpd->id.lib, &legacy_gpd->id, nullptr, LIB_ID_COPY_DEFAULT));
new_annotation_gpd->flag |= GP_DATA_ANNOTATIONS;
id_us_min(&new_annotation_gpd->id);
annotations_gpv2.add_overwrite(legacy_gpd, new_annotation_gpd);
}
/* Assign the annotation duplicate ID to the annotation pointer. */
BLI_assert(new_annotation_gpd->flag & GP_DATA_ANNOTATIONS);
id_us_min(&legacy_gpd->id);
*legacy_gpd_p = new_annotation_gpd;
id_us_plus_no_lib(&new_annotation_gpd->id);
};
LISTBASE_FOREACH (Scene *, scene, &bmain.scenes) {
sanitize_gpv2_annotation(&scene->gpd);
}
ID *id_iter;
FOREACH_MAIN_ID_BEGIN (&bmain, id_iter) {
if (bNodeTree *node_tree = ntreeFromID(id_iter)) {
sanitize_gpv2_annotation(&node_tree->gpd);
}
}
FOREACH_MAIN_ID_END;
LISTBASE_FOREACH (bNodeTree *, node_tree, &bmain.nodetrees) {
sanitize_gpv2_annotation(&node_tree->gpd);
}
LISTBASE_FOREACH (MovieClip *, movie_clip, &bmain.movieclips) {
sanitize_gpv2_annotation(&movie_clip->gpd);
LISTBASE_FOREACH (MovieTrackingObject *, mvc_tracking_object, &movie_clip->tracking.objects) {
LISTBASE_FOREACH (MovieTrackingTrack *, mvc_track, &mvc_tracking_object->tracks) {
sanitize_gpv2_annotation(&mvc_track->gpd);
}
LISTBASE_FOREACH (
MovieTrackingPlaneTrack *, mvc_plane_track, &mvc_tracking_object->plane_tracks)
{
for (int i = 0; i < mvc_plane_track->point_tracksnr; i++) {
sanitize_gpv2_annotation(&mvc_plane_track->point_tracks[i]->gpd);
}
}
}
}
LISTBASE_FOREACH (bScreen *, screen, &bmain.screens) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
LISTBASE_FOREACH (SpaceLink *, space_link, &area->spacedata) {
switch (eSpace_Type(space_link->spacetype)) {
case SPACE_SEQ: {
SpaceSeq *space_sequencer = reinterpret_cast<SpaceSeq *>(space_link);
sanitize_gpv2_annotation(&space_sequencer->gpd);
break;
}
case SPACE_IMAGE: {
SpaceImage *space_image = reinterpret_cast<SpaceImage *>(space_link);
sanitize_gpv2_annotation(&space_image->gpd);
break;
}
case SPACE_NODE: {
SpaceNode *space_node = reinterpret_cast<SpaceNode *>(space_link);
sanitize_gpv2_annotation(&space_node->gpd);
break;
}
case SPACE_EMPTY:
case SPACE_VIEW3D:
/* #View3D.gpd is deprecated and can be ignored here. */
case SPACE_GRAPH:
case SPACE_OUTLINER:
case SPACE_PROPERTIES:
case SPACE_FILE:
case SPACE_INFO:
case SPACE_TEXT:
case SPACE_ACTION:
case SPACE_NLA:
case SPACE_SCRIPT:
case SPACE_CONSOLE:
case SPACE_USERPREF:
case SPACE_CLIP:
case SPACE_TOPBAR:
case SPACE_STATUSBAR:
case SPACE_SPREADSHEET:
break;
}
}
}
}
}
static void legacy_gpencil_object_ex(
Main &bmain,
Object &object,
@@ -2460,8 +2618,11 @@ void legacy_gpencil_object(Main &bmain, Object &object)
void legacy_main(Main &bmain, BlendFileReadReport & /*reports*/)
{
/* Ensure that annotations are fully separated from object usages of legacy GPv2 data. */
legacy_gpencil_sanitize_annotations(bmain);
/* Allows to convert a legacy GPencil data only once, in case it's used by several objects. */
blender::Map<bGPdata *, GreasePencil *> legacy_to_greasepencil_data;
Map<bGPdata *, GreasePencil *> legacy_to_greasepencil_data;
LISTBASE_FOREACH (Object *, object, &bmain.objects) {
if (object->type != OB_GPENCIL_LEGACY) {
@@ -2472,11 +2633,17 @@ void legacy_main(Main &bmain, BlendFileReadReport & /*reports*/)
/* Potential other usages of legacy bGPdata IDs also need to be remapped to their matching new
* GreasePencil counterparts. */
blender::bke::id::IDRemapper gpd_remapper;
bke::id::IDRemapper gpd_remapper;
/* Allow remapping from legacy bGPdata IDs to new GreasePencil ones. */
gpd_remapper.allow_idtype_mismatch = true;
LISTBASE_FOREACH (bGPdata *, legacy_gpd, &bmain.gpencils) {
/* Annotations still use legacy `bGPdata`, these should not be converted. Call to
* #legacy_gpencil_sanitize_annotations above ensured to fully separate annotations from object
* legacy grease pencil. */
if ((legacy_gpd->flag & GP_DATA_ANNOTATIONS) != 0) {
continue;
}
GreasePencil *new_grease_pencil = legacy_to_greasepencil_data.lookup_default(legacy_gpd,
nullptr);
if (!new_grease_pencil) {