Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntonio Vazquez <blendergit@gmail.com>2022-06-22 16:57:54 +0300
committerAntonio Vazquez <blendergit@gmail.com>2022-06-22 17:01:38 +0300
commit350ec8e9b9651123ec53837e2b085f46f4bcbd3d (patch)
tree9069a0b42352b11a09c7ef9a98f8127a78948111 /source/blender
parent714001683888770f9d870ba73edd65f4c2757bda (diff)
GPencil: Asset Manager support (LITE)
This patch replaces D13193 and remove any transform tool in the import. Now the import must be done in Edit mode and all transformations must be done with the standard transform tools of Blender. A new operator has been added to create an asset base on one of the next option: * Active Layer. * All Layers. * All Layers Separated * Active Frame (Active Layer). * Active Frame (All Layers). * Selected Frames. * Selected Strokes. The menu uses `Create Asset`instead of `Mark Asset` because the asset is created as new datablock and not reuse existing data. To import asset, set the grease pencil object where you want import in Edit mode. Also new asset preview for Grease Pencil has been added. For testing, be sure to enable `Experimental Feature`and `Developers Extra` Differential Revision: https://developer.blender.org/D15271
Diffstat (limited to 'source/blender')
-rw-r--r--source/blender/blenkernel/BKE_gpencil.h1
-rw-r--r--source/blender/blenkernel/intern/gpencil.c35
-rw-r--r--source/blender/blenkernel/intern/icons.cc3
-rw-r--r--source/blender/editors/gpencil/CMakeLists.txt1
-rw-r--r--source/blender/editors/gpencil/gpencil_asset.c768
-rw-r--r--source/blender/editors/gpencil/gpencil_intern.h4
-rw-r--r--source/blender/editors/gpencil/gpencil_ops.c4
-rw-r--r--source/blender/editors/render/render_preview.cc123
-rw-r--r--source/blender/editors/space_view3d/space_view3d.c23
-rw-r--r--source/blender/makesdna/DNA_gpencil_types.h3
10 files changed, 960 insertions, 5 deletions
diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h
index dc7a5ab003a..b2cc4474eb2 100644
--- a/source/blender/blenkernel/BKE_gpencil.h
+++ b/source/blender/blenkernel/BKE_gpencil.h
@@ -329,6 +329,7 @@ struct bGPDcurve *BKE_gpencil_stroke_editcurve_new(int tot_curve_points);
* \return True if layer is editable
*/
bool BKE_gpencil_layer_is_editable(const struct bGPDlayer *gpl);
+void BKE_gpencil_frame_min_max(const struct bGPdata *gpd, int *r_min, int *r_max);
/* How gpencil_layer_getframe() should behave when there
* is no existing GP-Frame on the frame requested.
diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c
index f86e947910b..88075c3e375 100644
--- a/source/blender/blenkernel/intern/gpencil.c
+++ b/source/blender/blenkernel/intern/gpencil.c
@@ -62,7 +62,7 @@ static CLG_LogRef LOG = {"bke.gpencil"};
static void greasepencil_copy_data(Main *UNUSED(bmain),
ID *id_dst,
const ID *id_src,
- const int UNUSED(flag))
+ const int flag)
{
bGPdata *gpd_dst = (bGPdata *)id_dst;
const bGPdata *gpd_src = (const bGPdata *)id_src;
@@ -110,6 +110,13 @@ static void greasepencil_copy_data(Main *UNUSED(bmain),
BLI_addtail(&gpd_dst->layers, gpl_dst);
}
+
+ if (flag & LIB_ID_COPY_NO_PREVIEW) {
+ gpd_dst->preview = NULL;
+ }
+ else {
+ BKE_previewimg_id_copy(&gpd_dst->id, &gpd_src->id);
+ }
}
static void greasepencil_free_data(ID *id)
@@ -179,6 +186,8 @@ static void greasepencil_blend_write(BlendWriter *writer, ID *id, const void *id
}
}
}
+
+ BKE_previewimg_blend_write(writer, gpd->preview);
}
}
@@ -193,6 +202,10 @@ void BKE_gpencil_blend_read_data(BlendDataReader *reader, bGPdata *gpd)
BLO_read_data_address(reader, &gpd->adt);
BKE_animdata_blend_read_data(reader, gpd->adt);
+ /* Preview. */
+ BLO_read_data_address(reader, &gpd->preview);
+ BKE_previewimg_blend_read(reader, gpd->preview);
+
/* Ensure full objectmode for linked grease pencil. */
if (ID_IS_LINKED(gpd)) {
gpd->flag &= ~GP_DATA_STROKE_PAINTMODE;
@@ -495,6 +508,8 @@ void BKE_gpencil_free_data(bGPdata *gpd, bool free_all)
/* clear cache */
BKE_gpencil_batch_cache_free(gpd);
}
+ /* Preview. */
+ BKE_previewimg_free(&gpd->preview);
}
void BKE_gpencil_eval_delete(bGPdata *gpd_eval)
@@ -2197,7 +2212,7 @@ int BKE_gpencil_object_material_index_get_by_name(Object *ob, const char *name)
for (short i = 0; i < *totcol; i++) {
read_ma = BKE_object_material_get(ob, i + 1);
/* Material names are like "MAMaterial.001" */
- if (STREQ(name, &read_ma->id.name[2])) {
+ if ((read_ma) && (STREQ(name, &read_ma->id.name[2]))) {
return i;
}
}
@@ -3040,4 +3055,20 @@ void BKE_gpencil_update_on_write(bGPdata *gpd_orig, bGPdata *gpd_eval)
BKE_gpencil_free_update_cache(gpd_orig);
}
+/* Get min and max frame number for all layers. */
+void BKE_gpencil_frame_min_max(const bGPdata *gpd, int *r_min, int *r_max)
+{
+ *r_min = INT_MAX;
+ *r_max = INT_MIN;
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ if (gpf->framenum < *r_min) {
+ *r_min = gpf->framenum;
+ }
+ if (gpf->framenum > *r_max) {
+ *r_max = gpf->framenum;
+ }
+ }
+ }
+}
/** \} */
diff --git a/source/blender/blenkernel/intern/icons.cc b/source/blender/blenkernel/intern/icons.cc
index f59f5352aad..e2a1192e89d 100644
--- a/source/blender/blenkernel/intern/icons.cc
+++ b/source/blender/blenkernel/intern/icons.cc
@@ -355,6 +355,7 @@ PreviewImage **BKE_previewimg_id_get_p(const ID *id)
ID_PRV_CASE(ID_LA, Light);
ID_PRV_CASE(ID_IM, Image);
ID_PRV_CASE(ID_BR, Brush);
+ ID_PRV_CASE(ID_GD, bGPdata);
ID_PRV_CASE(ID_GR, Collection);
ID_PRV_CASE(ID_SCE, Scene);
ID_PRV_CASE(ID_SCR, bScreen);
@@ -419,7 +420,7 @@ void BKE_previewimg_id_custom_set(ID *id, const char *filepath)
bool BKE_previewimg_id_supports_jobs(const ID *id)
{
- return ELEM(GS(id->name), ID_OB, ID_MA, ID_TE, ID_LA, ID_WO, ID_IM, ID_BR, ID_GR);
+ return ELEM(GS(id->name), ID_OB, ID_MA, ID_TE, ID_LA, ID_WO, ID_IM, ID_BR, ID_GR, ID_GD);
}
void BKE_previewimg_deferred_release(PreviewImage *prv)
diff --git a/source/blender/editors/gpencil/CMakeLists.txt b/source/blender/editors/gpencil/CMakeLists.txt
index 09a3cac0d48..13624d09a2e 100644
--- a/source/blender/editors/gpencil/CMakeLists.txt
+++ b/source/blender/editors/gpencil/CMakeLists.txt
@@ -28,6 +28,7 @@ set(SRC
gpencil_add_monkey.c
gpencil_add_stroke.c
gpencil_armature.c
+ gpencil_asset.c
gpencil_bake_animation.cc
gpencil_convert.c
gpencil_data.c
diff --git a/source/blender/editors/gpencil/gpencil_asset.c b/source/blender/editors/gpencil/gpencil_asset.c
new file mode 100644
index 00000000000..c2aabb7c9df
--- /dev/null
+++ b/source/blender/editors/gpencil/gpencil_asset.c
@@ -0,0 +1,768 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation. */
+
+/** \file
+ * \ingroup edgpencil
+ */
+
+#include "BLI_blenlib.h"
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+
+#include "BLT_translation.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_material_types.h"
+
+#include "BKE_asset.h"
+#include "BKE_context.h"
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_geom.h"
+#include "BKE_lib_id.h"
+#include "BKE_main.h"
+#include "BKE_material.h"
+#include "BKE_object.h"
+#include "BKE_report.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+#include "RNA_enum_types.h"
+
+#include "GPU_immediate.h"
+
+#include "ED_asset.h"
+#include "ED_gpencil.h"
+#include "ED_keyframing.h"
+#include "ED_screen.h"
+#include "ED_space_api.h"
+
+#include "DEG_depsgraph.h"
+
+#include "gpencil_intern.h"
+
+#define ROTATION_CONTROL_GAP 15.0f
+
+typedef struct tGPDAssetStroke {
+ bGPDlayer *gpl;
+ bGPDframe *gpf;
+ bGPDstroke *gps;
+ int slot_index;
+ bool is_new_gpl;
+ bool is_new_gpf;
+} tGPDAssetStroke;
+
+/* Temporary Asset import operation data. */
+typedef struct tGPDasset {
+ struct Main *bmain;
+ struct Depsgraph *depsgraph;
+ struct Scene *scene;
+ struct ScrArea *area;
+ struct ARegion *region;
+ /** Current object. */
+ struct Object *ob;
+ /** Current GP data block. */
+ struct bGPdata *gpd;
+ /** Asset GP data block. */
+ struct bGPdata *gpd_asset;
+ /* Space Conversion Data */
+ struct GP_SpaceConversion gsc;
+
+ /** Current frame number. */
+ int cframe;
+
+ /** Drop initial position. */
+ int drop[2];
+
+ /* Data to keep a reference of the asset data inserted in the target object. */
+ /** Number of elements in data. */
+ int data_len;
+ /** Array of data with all strokes append. */
+ tGPDAssetStroke *data;
+
+} tGPDasset;
+
+/* -------------------------------------------------------------------- */
+/** \name Create Grease Pencil data block Asset operator
+ * \{ */
+
+typedef enum eGP_AssetModes {
+ /* Active Layer. */
+ GP_ASSET_MODE_ACTIVE_LAYER = 0,
+ /* All Layers. */
+ GP_ASSET_MODE_ALL_LAYERS,
+ /* All Layers in separated assets. */
+ GP_ASSET_MODE_ALL_LAYERS_SPLIT,
+ /* Active Frame. */
+ GP_ASSET_MODE_ACTIVE_FRAME,
+ /* Active Frame All Layers. */
+ GP_ASSET_MODE_ACTIVE_FRAME_ALL_LAYERS,
+ /* Selected Frames. */
+ GP_ASSET_MODE_SELECTED_FRAMES,
+ /* Selected Strokes. */
+ GP_ASSET_MODE_SELECTED_STROKES,
+} eGP_AssetModes;
+
+/* Helper: Apply layer settings. */
+static void apply_layer_settings(bGPDlayer *gpl)
+{
+ /* Apply layer attributes. */
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ gps->fill_opacity_fac *= gpl->opacity;
+ gps->vert_color_fill[3] *= gpl->opacity;
+ for (int p = 0; p < gps->totpoints; p++) {
+ bGPDspoint *pt = &gps->points[p];
+ float factor = (((float)gps->thickness * pt->pressure) + (float)gpl->line_change) /
+ ((float)gps->thickness * pt->pressure);
+ pt->pressure *= factor;
+ pt->strength *= gpl->opacity;
+
+ /* Layer transformation. */
+ mul_v3_m4v3(&pt->x, gpl->layer_mat, &pt->x);
+ zero_v3(gpl->location);
+ zero_v3(gpl->rotation);
+ copy_v3_fl(gpl->scale, 1.0f);
+ }
+ }
+ }
+
+ gpl->line_change = 0;
+ gpl->opacity = 1.0f;
+ unit_m4(gpl->layer_mat);
+ invert_m4_m4(gpl->layer_invmat, gpl->layer_mat);
+}
+
+/* Helper: Create an asset for data block.
+ * return: False if there are features non supported. */
+static bool gpencil_asset_create(const bContext *C,
+ const bGPdata *gpd_src,
+ const bGPDlayer *gpl_filter,
+ const eGP_AssetModes mode,
+ const bool reset_origin,
+ const bool flatten_layers)
+{
+ Main *bmain = CTX_data_main(C);
+ bool non_supported_feature = false;
+
+ /* Create a copy of selected data block. */
+ bGPdata *gpd = (bGPdata *)BKE_id_copy(bmain, &gpd_src->id);
+ /* Enable fake user by default. */
+ id_fake_user_set(&gpd->id);
+ /* Disable Edit mode. */
+ gpd->flag &= ~GP_DATA_STROKE_EDITMODE;
+
+ const bGPDlayer *gpl_active = BKE_gpencil_layer_active_get(gpd);
+
+ bool is_animation = false;
+
+ LISTBASE_FOREACH_MUTABLE (bGPDlayer *, gpl, &gpd->layers) {
+ /* If layer is hidden, remove. */
+ if (gpl->flag & GP_LAYER_HIDE) {
+ BKE_gpencil_layer_delete(gpd, gpl);
+ continue;
+ }
+
+ /* If Active Layer or Active Frame mode, delete non active layers. */
+ if ((ELEM(mode, GP_ASSET_MODE_ACTIVE_LAYER, GP_ASSET_MODE_ACTIVE_FRAME)) &&
+ (gpl != gpl_active)) {
+ BKE_gpencil_layer_delete(gpd, gpl);
+ continue;
+ }
+
+ /* For splitting, remove if layer is not equals to filter parameter. */
+ if (mode == GP_ASSET_MODE_ALL_LAYERS_SPLIT) {
+ if (!STREQ(gpl_filter->info, gpl->info)) {
+ BKE_gpencil_layer_delete(gpd, gpl);
+ continue;
+ }
+ }
+
+ /* Remove parenting data (feature non supported in data block). */
+ if (gpl->parent != NULL) {
+ gpl->parent = NULL;
+ gpl->parsubstr[0] = 0;
+ gpl->partype = 0;
+ non_supported_feature = true;
+ }
+
+ /* Remove masking (feature non supported in data block). */
+ if (gpl->mask_layers.first) {
+ bGPDlayer_Mask *mask_next;
+ for (bGPDlayer_Mask *mask = gpl->mask_layers.first; mask; mask = mask_next) {
+ mask_next = mask->next;
+ BKE_gpencil_layer_mask_remove(gpl, mask);
+ }
+ gpl->mask_layers.first = NULL;
+ gpl->mask_layers.last = NULL;
+
+ non_supported_feature = true;
+ }
+
+ const bGPDframe *gpf_active = gpl->actframe;
+
+ LISTBASE_FOREACH_MUTABLE (bGPDframe *, gpf, &gpl->frames) {
+ /* If Active Frame mode, delete non active frames. */
+ if ((ELEM(mode, GP_ASSET_MODE_ACTIVE_FRAME, GP_ASSET_MODE_ACTIVE_FRAME_ALL_LAYERS)) &&
+ (gpf != gpf_active)) {
+ BKE_gpencil_layer_frame_delete(gpl, gpf);
+ continue;
+ }
+
+ /* Remove if Selected frames mode and frame is not selected. */
+ if ((mode == GP_ASSET_MODE_SELECTED_FRAMES) && ((gpf->flag & GP_FRAME_SELECT) == 0)) {
+ BKE_gpencil_layer_frame_delete(gpl, gpf);
+ continue;
+ }
+
+ /* Remove any unselected stroke if selected strokes mode. */
+ if (mode == GP_ASSET_MODE_SELECTED_STROKES) {
+ LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
+ if ((gps->flag & GP_STROKE_SELECT) == 0) {
+ BLI_remlink(&gpf->strokes, gps);
+ BKE_gpencil_free_stroke(gps);
+ continue;
+ }
+ }
+ }
+ /* Unselect all strokes and points. */
+ gpd->select_last_index = 0;
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ gps->flag &= ~GP_STROKE_SELECT;
+ BKE_gpencil_stroke_select_index_reset(gps);
+ bGPDspoint *pt;
+ int i;
+ for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+ pt->flag &= ~GP_SPOINT_SELECT;
+ }
+ }
+
+ /* If Frame is empty, remove. */
+ if (BLI_listbase_count(&gpf->strokes) == 0) {
+ BKE_gpencil_layer_frame_delete(gpl, gpf);
+ }
+ }
+
+ /* If there are more than one frame in the same layer, then is an animation. */
+ is_animation |= (BLI_listbase_count(&gpl->frames) > 1);
+ }
+
+ /* Set origin to bounding box of strokes. */
+ if (reset_origin) {
+ float gpcenter[3];
+ BKE_gpencil_centroid_3d(gpd, gpcenter);
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ bGPDspoint *pt;
+ int i;
+ for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+ sub_v3_v3(&pt->x, gpcenter);
+ }
+ BKE_gpencil_stroke_boundingbox_calc(gps);
+ }
+ }
+ }
+ }
+
+ /* Flatten layers. */
+ if ((flatten_layers) && (gpd->layers.first)) {
+ /* Apply layer attributes to all layers. */
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ apply_layer_settings(gpl);
+ }
+
+ bGPDlayer *gpl_dst = gpd->layers.first;
+ LISTBASE_FOREACH_BACKWARD_MUTABLE (bGPDlayer *, gpl, &gpd->layers) {
+ if (gpl == gpl_dst) {
+ break;
+ }
+ ED_gpencil_layer_merge(gpd, gpl, gpl->prev, false);
+ }
+ strcpy(gpl_dst->info, "Asset_Layer");
+ }
+
+ int f_min, f_max;
+ BKE_gpencil_frame_min_max(gpd, &f_min, &f_max);
+
+ /* Mark as asset. */
+ if (ED_asset_mark_id(&gpd->id)) {
+ ED_asset_generate_preview(C, &gpd->id);
+ /* Retime frame number to start by 1. Must be done after generate the render preview. */
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ gpf->framenum -= f_min - 1;
+ }
+ }
+ }
+
+ return non_supported_feature;
+}
+
+static bool gpencil_asset_edit_poll(bContext *C)
+{
+ const enum eContextObjectMode mode = CTX_data_mode_enum(C);
+
+ /* Only allowed in Grease Pencil Edit mode. */
+ if (mode != CTX_MODE_EDIT_GPENCIL) {
+ return false;
+ }
+
+ Object *ob = CTX_data_active_object(C);
+ if ((ob == NULL) || (ob->type != OB_GPENCIL)) {
+ return false;
+ }
+
+ return ED_operator_view3d_active(C);
+}
+
+static int gpencil_asset_create_exec(const bContext *C, const wmOperator *op)
+{
+ Object *ob = CTX_data_active_object(C);
+ bGPdata *gpd_src = ob->data;
+
+ const eGP_AssetModes mode = RNA_enum_get(op->ptr, "mode");
+ const bool reset_origin = RNA_boolean_get(op->ptr, "reset_origin");
+ const bool flatten_layers = RNA_boolean_get(op->ptr, "flatten_layers");
+
+ bool non_supported_feature = false;
+ if (mode == GP_ASSET_MODE_ALL_LAYERS_SPLIT) {
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_src->layers) {
+ non_supported_feature |= gpencil_asset_create(
+ C, gpd_src, gpl, mode, reset_origin, flatten_layers);
+ }
+ }
+ else {
+ non_supported_feature = gpencil_asset_create(
+ C, gpd_src, NULL, mode, reset_origin, flatten_layers);
+ }
+
+ /* Warnings for non supported features in the created asset. */
+ if ((non_supported_feature) || (ob->greasepencil_modifiers.first) || (ob->shader_fx.first)) {
+ BKE_report(op->reports,
+ RPT_WARNING,
+ "Object has layer parenting, masking, modifiers or effects not supported in this "
+ "asset type. These features have been omitted in the asset.");
+ }
+
+ WM_main_add_notifier(NC_ID | NA_EDITED, NULL);
+ WM_main_add_notifier(NC_ASSET | NA_ADDED, NULL);
+
+ return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_asset_create(wmOperatorType *ot)
+{
+ static const EnumPropertyItem mode_types[] = {
+ {GP_ASSET_MODE_ACTIVE_LAYER, "LAYER", 0, "Active Layer", ""},
+ {GP_ASSET_MODE_ALL_LAYERS, "LAYERS_ALL", 0, "All Layers", ""},
+ {GP_ASSET_MODE_ALL_LAYERS_SPLIT,
+ "LAYERS_SPLIT",
+ 0,
+ "All Layers Separated",
+ "Create an asset by layer."},
+ {GP_ASSET_MODE_ACTIVE_FRAME, "FRAME", 0, "Active Frame (Active Layer)", ""},
+ {GP_ASSET_MODE_ACTIVE_FRAME_ALL_LAYERS, "FRAME_ALL", 0, "Active Frame (All Layers)", ""},
+ {GP_ASSET_MODE_SELECTED_FRAMES, "FRAME_SELECTED", 0, "Selected Frames", ""},
+ {GP_ASSET_MODE_SELECTED_STROKES, "SELECTED", 0, "Selected Strokes", ""},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ /* identifiers */
+ ot->name = "Create Grease Pencil Asset";
+ ot->idname = "GPENCIL_OT_asset_create";
+ ot->description = "Create asset from sections of the active object";
+
+ /* callbacks */
+ ot->invoke = WM_menu_invoke;
+ ot->exec = gpencil_asset_create_exec;
+ ot->poll = gpencil_asset_edit_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ /* properties */
+ ot->prop = RNA_def_enum(
+ ot->srna, "mode", mode_types, GP_ASSET_MODE_SELECTED_STROKES, "Mode", "");
+ RNA_def_boolean(ot->srna,
+ "reset_origin",
+ true,
+ "Origin to Geometry",
+ "Set origin of the asset in the center of the strokes bounding box");
+ RNA_def_boolean(
+ ot->srna, "flatten_layers", false, "Flatten Layers", "Merge all layers in only one");
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Import Grease Pencil Asset into existing data block operator
+ * \{ */
+
+/* Helper: Get a material from the data block array. */
+static Material *gpencil_asset_material_get_from_id(ID *id, const int slot_index)
+{
+ const short *tot_slots_data_ptr = BKE_id_material_len_p(id);
+ const int tot_slots_data = tot_slots_data_ptr ? *tot_slots_data_ptr : 0;
+ if (slot_index >= tot_slots_data) {
+ return NULL;
+ }
+
+ Material ***materials_data_ptr = BKE_id_material_array_p(id);
+ Material **materials_data = materials_data_ptr ? *materials_data_ptr : NULL;
+ Material *material = materials_data[slot_index];
+
+ return material;
+}
+
+/* Helper: Set the selection of the imported strokes. */
+static void gpencil_asset_set_selection(tGPDasset *tgpa, const bool enable)
+{
+
+ for (int index = 0; index < tgpa->data_len; index++) {
+ tGPDAssetStroke *data = &tgpa->data[index];
+
+ bGPDframe *gpf = data->gpf;
+ if (enable) {
+ gpf->flag |= GP_FRAME_SELECT;
+ }
+ else {
+ gpf->flag &= ~GP_FRAME_SELECT;
+ }
+
+ bGPDstroke *gps = data->gps;
+ if (enable) {
+ gps->flag |= GP_STROKE_SELECT;
+ }
+ else {
+ gps->flag &= ~GP_STROKE_SELECT;
+ }
+
+ bGPDspoint *pt;
+ int i;
+
+ for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+ if (enable) {
+ pt->flag |= GP_SPOINT_SELECT;
+ }
+ else {
+ pt->flag &= ~GP_SPOINT_SELECT;
+ }
+ }
+
+ /* Set selection index. */
+ if (enable) {
+ gps->flag |= GP_STROKE_SELECT;
+ BKE_gpencil_stroke_select_index_set(tgpa->gpd, gps);
+ }
+ else {
+ gps->flag &= ~GP_STROKE_SELECT;
+ BKE_gpencil_stroke_select_index_reset(gps);
+ }
+ }
+}
+
+/* Helper: Append all strokes from the asset in the target data block. */
+static bool gpencil_asset_append_strokes(tGPDasset *tgpa)
+{
+ bGPdata *gpd_target = tgpa->gpd;
+ bGPdata *gpd_asset = tgpa->gpd_asset;
+
+ /* Get the vector from origin to drop position. */
+ float dest_pt[3];
+ float loc2d[2];
+ copy_v2fl_v2i(loc2d, tgpa->drop);
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, loc2d, dest_pt);
+
+ float vec[3];
+ sub_v3_v3v3(vec, dest_pt, tgpa->ob->loc);
+
+ /* Count total of strokes. */
+ tgpa->data_len = 0;
+ LISTBASE_FOREACH (bGPDlayer *, gpl_asset, &gpd_asset->layers) {
+ LISTBASE_FOREACH (bGPDframe *, gpf_asset, &gpl_asset->frames) {
+ tgpa->data_len += BLI_listbase_count(&gpf_asset->strokes);
+ }
+ }
+
+ /* If the asset is empty, exit. */
+ if (tgpa->data_len == 0) {
+ return false;
+ }
+
+ /* Alloc array of strokes. */
+ tgpa->data = MEM_calloc_arrayN(tgpa->data_len, sizeof(tGPDAssetStroke), __func__);
+ int data_index = 0;
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl_asset, &gpd_asset->layers) {
+ /* Check if Layer is in target data block. */
+ bGPDlayer *gpl_target = BKE_gpencil_layer_get_by_name(gpd_target, gpl_asset->info, false);
+
+ bool is_new_gpl = false;
+ if (gpl_target == NULL) {
+ gpl_target = BKE_gpencil_layer_duplicate(gpl_asset, false, false);
+ BLI_assert(gpl_target != NULL);
+ gpl_target->actframe = NULL;
+ BLI_listbase_clear(&gpl_target->frames);
+ BLI_addtail(&gpd_target->layers, gpl_target);
+ is_new_gpl = true;
+ }
+
+ LISTBASE_FOREACH (bGPDframe *, gpf_asset, &gpl_asset->frames) {
+ /* Check if frame is in target layer. */
+ int fra = tgpa->cframe + (gpf_asset->framenum - 1);
+ bGPDframe *gpf_target = NULL;
+ /* Find a frame in same frame number. */
+ LISTBASE_FOREACH (bGPDframe *, gpf_find, &gpl_target->frames) {
+ if (gpf_find->framenum == fra) {
+ gpf_target = gpf_find;
+ break;
+ }
+ }
+
+ bool is_new_gpf = false;
+ /* Check Rec button. If button is disabled, try to use active frame.
+ * If no active keyframe, must create a new frame. */
+ if ((gpf_target == NULL) && (!IS_AUTOKEY_ON(tgpa->scene))) {
+ gpf_target = BKE_gpencil_layer_frame_get(gpl_target, fra, GP_GETFRAME_USE_PREV);
+ }
+
+ if (gpf_target == NULL) {
+ gpf_target = BKE_gpencil_frame_addnew(gpl_target, fra);
+ gpl_target->actframe = gpf_target;
+ BLI_assert(gpf_target != NULL);
+ BLI_listbase_clear(&gpf_target->strokes);
+ is_new_gpf = true;
+ }
+
+ /* Loop all strokes and duplicate. */
+ LISTBASE_FOREACH (bGPDstroke *, gps_asset, &gpf_asset->strokes) {
+ if (gps_asset->mat_nr == -1) {
+ continue;
+ }
+
+ bGPDstroke *gps_target = BKE_gpencil_stroke_duplicate(gps_asset, true, true);
+ gps_target->next = gps_target->prev = NULL;
+ gps_target->flag &= ~GP_STROKE_SELECT;
+ BLI_addtail(&gpf_target->strokes, gps_target);
+
+ /* Add the material. */
+ Material *ma_src = gpencil_asset_material_get_from_id(&tgpa->gpd_asset->id,
+ gps_asset->mat_nr);
+
+ int mat_index = (ma_src != NULL) ? BKE_gpencil_object_material_index_get_by_name(
+ tgpa->ob, ma_src->id.name + 2) :
+ -1;
+ bool is_new_mat = false;
+ if (mat_index == -1) {
+ const int totcolors = tgpa->ob->totcol;
+ mat_index = BKE_gpencil_object_material_ensure(tgpa->bmain, tgpa->ob, ma_src);
+ if (tgpa->ob->totcol > totcolors) {
+ is_new_mat = true;
+ }
+ }
+
+ gps_target->mat_nr = mat_index;
+
+ /* Apply the offset to drop position and unselect points. */
+ bGPDspoint *pt;
+ int i;
+ for (i = 0, pt = gps_target->points; i < gps_target->totpoints; i++, pt++) {
+ add_v3_v3(&pt->x, vec);
+ pt->flag &= ~GP_SPOINT_SELECT;
+ }
+
+ /* Calc stroke bounding box. */
+ BKE_gpencil_stroke_boundingbox_calc(gps_target);
+
+ /* Add the reference to the stroke. */
+ tGPDAssetStroke *data = &tgpa->data[data_index];
+ data->gpl = gpl_target;
+ data->gpf = gpf_target;
+ data->gps = gps_target;
+ data->is_new_gpl = is_new_gpl;
+ data->is_new_gpf = is_new_gpf;
+ data->slot_index = (is_new_mat) ? gps_target->mat_nr + 1 : -1;
+ data_index++;
+
+ /* Reset flags. */
+ is_new_gpl = false;
+ is_new_gpf = false;
+ }
+ }
+ }
+
+ /* Unselect any frame and stroke. */
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_target->layers) {
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ gpf->flag &= ~GP_FRAME_SELECT;
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ gps->flag &= ~GP_STROKE_SELECT;
+ bGPDspoint *pt;
+ int i;
+ for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+ pt->flag &= ~GP_SPOINT_SELECT;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+/* Exit and free memory */
+static void gpencil_asset_import_exit(bContext *C, wmOperator *op)
+{
+ tGPDasset *tgpa = op->customdata;
+
+ if (tgpa) {
+ bGPdata *gpd = tgpa->gpd;
+
+ /* Free data. */
+ MEM_SAFE_FREE(tgpa->data);
+
+ MEM_SAFE_FREE(tgpa);
+ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
+ }
+
+ WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED | ND_DATA, NULL);
+
+ /* Clear pointer. */
+ op->customdata = NULL;
+}
+
+/* Allocate memory and initialize values */
+static tGPDasset *gpencil_session_init_asset_import(bContext *C, wmOperator *op)
+{
+ Main *bmain = CTX_data_main(C);
+ ID *id = NULL;
+
+ PropertyRNA *prop_name = RNA_struct_find_property(op->ptr, "name");
+ PropertyRNA *prop_type = RNA_struct_find_property(op->ptr, "type");
+
+ /* These shouldn't fail when created by outliner dropping as it checks the ID is valid. */
+ if (!RNA_property_is_set(op->ptr, prop_name) || !RNA_property_is_set(op->ptr, prop_type)) {
+ return NULL;
+ }
+ const short id_type = RNA_property_enum_get(op->ptr, prop_type);
+ char name[MAX_ID_NAME - 2];
+ RNA_property_string_get(op->ptr, prop_name, name);
+ id = BKE_libblock_find_name(bmain, id_type, name);
+ if (id == NULL) {
+ return NULL;
+ }
+ const int object_type = BKE_object_obdata_to_type(id);
+ if (object_type != OB_GPENCIL) {
+ return NULL;
+ }
+
+ tGPDasset *tgpa = MEM_callocN(sizeof(tGPDasset), "GPencil Asset Import Data");
+
+ /* Save current settings. */
+ tgpa->bmain = CTX_data_main(C);
+ tgpa->depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ tgpa->scene = CTX_data_scene(C);
+ tgpa->area = CTX_wm_area(C);
+ tgpa->region = CTX_wm_region(C);
+ tgpa->ob = CTX_data_active_object(C);
+
+ /* Setup space conversions data. */
+ gpencil_point_conversion_init(C, &tgpa->gsc);
+
+ /* Save current frame number. */
+ tgpa->cframe = tgpa->scene->r.cfra;
+
+ /* Target GP data block. */
+ tgpa->gpd = tgpa->ob->data;
+ /* Asset GP data block. */
+ tgpa->gpd_asset = (bGPdata *)id;
+
+ tgpa->data_len = 0;
+ tgpa->data = NULL;
+
+ return tgpa;
+}
+
+/* Init: Allocate memory and set init values */
+static bool gpencil_asset_import_init(bContext *C, wmOperator *op)
+{
+ tGPDasset *tgpa;
+
+ /* check context */
+ tgpa = op->customdata = gpencil_session_init_asset_import(C, op);
+ if (tgpa == NULL) {
+ /* something wasn't set correctly in context */
+ gpencil_asset_import_exit(C, op);
+ return false;
+ }
+
+ return true;
+}
+
+/* Invoke handler: Initialize the operator. */
+static int gpencil_asset_import_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ bGPdata *gpd = CTX_data_gpencil_data(C);
+ tGPDasset *tgpa = NULL;
+
+ /* Try to initialize context data needed. */
+ if (!gpencil_asset_import_init(C, op)) {
+ if (op->customdata) {
+ MEM_freeN(op->customdata);
+ }
+ return OPERATOR_CANCELLED;
+ }
+ tgpa = op->customdata;
+
+ /* Save initial position of drop. */
+ tgpa->drop[0] = event->mval[0];
+ tgpa->drop[1] = event->mval[1];
+
+ /* Load of the strokes in the target data block. */
+ if (!gpencil_asset_append_strokes(tgpa)) {
+ gpencil_asset_import_exit(C, op);
+ return OPERATOR_CANCELLED;
+ }
+
+ /* Select imported strokes. */
+ gpencil_asset_set_selection(tgpa, true);
+ /* Clean up temp data. */
+ gpencil_asset_import_exit(C, op);
+
+ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
+
+ return OPERATOR_FINISHED;
+}
+
+void GPENCIL_OT_asset_import(wmOperatorType *ot)
+{
+
+ PropertyRNA *prop;
+
+ /* identifiers */
+ ot->name = "Grease Pencil Import Asset";
+ ot->idname = "GPENCIL_OT_asset_import";
+ ot->description = "Import Asset into existing grease pencil object";
+
+ /* callbacks */
+ ot->invoke = gpencil_asset_import_invoke;
+ ot->poll = gpencil_asset_edit_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING;
+
+ /* Properties. */
+ RNA_def_string(ot->srna, "name", "Name", MAX_ID_NAME - 2, "Name", "ID name to add");
+ prop = RNA_def_enum(ot->srna, "type", rna_enum_id_type_items, 0, "Type", "");
+ RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID);
+}
+/** \} */
diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h
index d656241c463..cccbffbd8de 100644
--- a/source/blender/editors/gpencil/gpencil_intern.h
+++ b/source/blender/editors/gpencil/gpencil_intern.h
@@ -667,6 +667,10 @@ void GPENCIL_OT_convert_old_files(struct wmOperatorType *ot);
/* armatures */
void GPENCIL_OT_generate_weights(struct wmOperatorType *ot);
+/* Assets. */
+void GPENCIL_OT_asset_create(struct wmOperatorType *ot);
+void GPENCIL_OT_asset_import(struct wmOperatorType *ot);
+
/* ****************************************************** */
/* Stroke Iteration Utilities */
diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c
index 99e28270c3e..287602d0643 100644
--- a/source/blender/editors/gpencil/gpencil_ops.c
+++ b/source/blender/editors/gpencil/gpencil_ops.c
@@ -685,6 +685,10 @@ void ED_operatortypes_gpencil(void)
/* armatures */
WM_operatortype_append(GPENCIL_OT_generate_weights);
+
+ /* Assets. */
+ WM_operatortype_append(GPENCIL_OT_asset_create);
+ WM_operatortype_append(GPENCIL_OT_asset_import);
}
void ED_operatormacros_gpencil(void)
diff --git a/source/blender/editors/render/render_preview.cc b/source/blender/editors/render/render_preview.cc
index addcedc4481..bb067d57649 100644
--- a/source/blender/editors/render/render_preview.cc
+++ b/source/blender/editors/render/render_preview.cc
@@ -30,6 +30,7 @@
#include "DNA_brush_types.h"
#include "DNA_camera_types.h"
#include "DNA_collection_types.h"
+#include "DNA_gpencil_types.h"
#include "DNA_light_types.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
@@ -47,6 +48,7 @@
#include "BKE_colortools.h"
#include "BKE_context.h"
#include "BKE_global.h"
+#include "BKE_gpencil.h"
#include "BKE_icons.h"
#include "BKE_idprop.h"
#include "BKE_image.h"
@@ -365,6 +367,7 @@ static ID *duplicate_ids(ID *id, const bool allow_failure)
case ID_MA:
case ID_TE:
case ID_LA:
+ case ID_GD:
case ID_WO: {
BLI_assert(BKE_previewimg_id_supports_jobs(id));
ID *id_copy = BKE_id_copy_ex(nullptr,
@@ -763,6 +766,8 @@ struct ObjectPreviewData {
/* Copy of the object to create the preview for. The copy is for thread safety (and to insert
* it into its own main). */
Object *object;
+ /* Datablock copy. Can be nullptr. */
+ ID *datablock;
/* Current frame. */
int cfra;
int sizex;
@@ -851,7 +856,7 @@ static void object_preview_render(IconPreview *preview, IconPreviewSize *preview
preview_data.pr_main = preview_main;
/* Act on a copy. */
preview_data.object = (Object *)preview->id_copy;
- preview_data.cfra = preview->scene->r.cfra;
+ preview_data.datablock = nullptr;
preview_data.sizex = preview_sized->sizex;
preview_data.sizey = preview_sized->sizey;
@@ -1031,6 +1036,119 @@ static void action_preview_render(IconPreview *preview, IconPreviewSize *preview
/** \} */
/* -------------------------------------------------------------------- */
+/** \name Grease Pencil Preview
+ * \{ */
+
+static Scene *gpencil_preview_scene_create(const struct ObjectPreviewData *preview_data,
+ Depsgraph **r_depsgraph)
+{
+ Scene *scene = BKE_scene_add(preview_data->pr_main, "Object preview scene");
+ /* Grease pencil needs to set the scene to the current frame or the strokes
+ * will not be visible in the preview. */
+ CFRA = preview_data->cfra;
+ ViewLayer *view_layer = (ViewLayer *)scene->view_layers.first;
+ Depsgraph *depsgraph = DEG_graph_new(
+ preview_data->pr_main, scene, view_layer, DAG_EVAL_VIEWPORT);
+
+ /* Grease pencil draw engine needs an object to draw the datablock. */
+ Object *ob_temp = BKE_object_add_for_data(preview_data->pr_main,
+ view_layer,
+ OB_GPENCIL,
+ "preview_object",
+ preview_data->datablock,
+ true);
+ BLI_assert(ob_temp != nullptr);
+ /* Copy the materials to get full color previews. */
+ const short *materials_len_p = BKE_id_material_len_p(preview_data->datablock);
+ if (materials_len_p && *materials_len_p > 0) {
+ BKE_object_materials_test(preview_data->pr_main, ob_temp, preview_data->datablock);
+ }
+
+ Object *camera_object = object_preview_camera_create(preview_data->pr_main, view_layer, ob_temp);
+
+ scene->camera = camera_object;
+ scene->r.xsch = preview_data->sizex;
+ scene->r.ysch = preview_data->sizey;
+ scene->r.size = 100;
+
+ Base *preview_base = BKE_view_layer_base_find(view_layer, ob_temp);
+ /* For 'view selected' below. */
+ preview_base->flag |= BASE_SELECTED;
+
+ DEG_graph_build_from_view_layer(depsgraph);
+ DEG_evaluate_on_refresh(depsgraph);
+
+ ED_view3d_camera_to_view_selected(preview_data->pr_main, depsgraph, scene, camera_object);
+
+ BKE_scene_graph_update_tagged(depsgraph, preview_data->pr_main);
+
+ *r_depsgraph = depsgraph;
+ return scene;
+}
+
+/* Render a grease pencil datablock. As the Draw Engine needs an object, the datablock is assigned
+ * to a temporary object, just for render. */
+static void gpencil_preview_render(IconPreview *preview, IconPreviewSize *preview_sized)
+{
+ Main *preview_main = BKE_main_new();
+ const float pixelsize_old = U.pixelsize;
+ char err_out[256] = "unknown";
+
+ BLI_assert(preview->id_copy && (preview->id_copy != preview->id));
+
+ /* Find the frame number to make preview visible. */
+ int f_min, f_max;
+ bGPdata *gpd = (bGPdata *)preview->id_copy;
+ BKE_gpencil_frame_min_max(gpd, &f_min, &f_max);
+ const int framenum = ((preview->scene->r.cfra < f_min) || (preview->scene->r.cfra > f_max)) ?
+ f_min :
+ preview->scene->r.cfra;
+
+ struct ObjectPreviewData preview_data = {};
+ preview_data.pr_main = preview_main;
+ /* Act on a copy. */
+ preview_data.object = nullptr;
+ preview_data.datablock = (ID *)preview->id_copy;
+ preview_data.cfra = framenum;
+ preview_data.sizex = preview_sized->sizex;
+ preview_data.sizey = preview_sized->sizey;
+
+ Depsgraph *depsgraph;
+ Scene *scene = gpencil_preview_scene_create(&preview_data, &depsgraph);
+
+ U.pixelsize = 2.0f;
+
+ View3DShading shading;
+ BKE_screen_view3d_shading_init(&shading);
+
+ ImBuf *ibuf = ED_view3d_draw_offscreen_imbuf_simple(
+ depsgraph,
+ DEG_get_evaluated_scene(depsgraph),
+ &shading,
+ OB_TEXTURE,
+ DEG_get_evaluated_object(depsgraph, scene->camera),
+ preview_sized->sizex,
+ preview_sized->sizey,
+ IB_rect,
+ V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS,
+ R_ALPHAPREMUL,
+ nullptr,
+ nullptr,
+ err_out);
+
+ U.pixelsize = pixelsize_old;
+
+ if (ibuf) {
+ icon_copy_rect(ibuf, preview_sized->sizex, preview_sized->sizey, preview_sized->rect);
+ IMB_freeImBuf(ibuf);
+ }
+
+ DEG_graph_free(depsgraph);
+ BKE_main_free(preview_main);
+}
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name New Shader Preview System
* \{ */
@@ -1634,6 +1752,9 @@ static void icon_preview_startjob_all_sizes(void *customdata,
case ID_AC:
action_preview_render(ip, cur_size);
continue;
+ case ID_GD:
+ gpencil_preview_render(ip, cur_size);
+ continue;
default:
/* Fall through to the same code as the `ip->id == nullptr` case. */
break;
diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c
index 9ed2fec96db..2133d0c3efa 100644
--- a/source/blender/editors/space_view3d/space_view3d.c
+++ b/source/blender/editors/space_view3d/space_view3d.c
@@ -609,12 +609,17 @@ static bool view3d_world_drop_poll(bContext *C, wmDrag *drag, const wmEvent *eve
static bool view3d_object_data_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event)
{
ID_Type id_type = view3d_drop_id_in_main_region_poll_get_id_type(C, drag, event);
- if (id_type && OB_DATA_SUPPORT_ID(id_type)) {
+ if (id_type && OB_DATA_SUPPORT_ID(id_type) && (id_type != ID_GD)) {
return true;
}
return false;
}
+static bool view3d_gpencil_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event)
+{
+ return view3d_drop_id_in_main_region_poll(C, drag, event, ID_GD);
+}
+
static char *view3d_object_data_drop_tooltip(bContext *UNUSED(C),
wmDrag *UNUSED(drag),
const int UNUSED(xy[2]),
@@ -623,6 +628,14 @@ static char *view3d_object_data_drop_tooltip(bContext *UNUSED(C),
return BLI_strdup(TIP_("Create object instance from object-data"));
}
+static char *view3d_gpencil_data_drop_tooltip(bContext *UNUSED(C),
+ wmDrag *UNUSED(drag),
+ const int UNUSED(xy[2]),
+ wmDropBox *UNUSED(drop))
+{
+ return BLI_strdup(TIP_("Add strokes to active object"));
+}
+
static bool view3d_ima_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event)
{
if (ED_region_overlap_isect_any_xy(CTX_wm_area(C), event->xy)) {
@@ -945,6 +958,14 @@ static void view3d_dropboxes(void)
view3d_id_drop_copy,
WM_drag_free_imported_drag_ID,
NULL);
+
+ /* TODO(@antoniov): Change to use a temp copy using BLO_library_temp_load_id (). */
+ WM_dropbox_add(lb,
+ "GPENCIL_OT_asset_import",
+ view3d_gpencil_drop_poll,
+ view3d_id_drop_copy_with_type,
+ WM_drag_free_imported_drag_ID,
+ view3d_gpencil_data_drop_tooltip);
}
static void view3d_widgets(void)
diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h
index a83262d7639..e8544cb9d3e 100644
--- a/source/blender/makesdna/DNA_gpencil_types.h
+++ b/source/blender/makesdna/DNA_gpencil_types.h
@@ -751,6 +751,9 @@ typedef struct bGPdata {
/* NOTE: When adding new members, make sure to add them to BKE_gpencil_data_copy_settings as
* well! */
+ /** Preview image for assets. */
+ struct PreviewImage *preview;
+
bGPdata_Runtime runtime;
} bGPdata;