GPencil: New support for Asset Manager #104413

Closed
Antonio Vazquez wants to merge 10 commits from antoniov/blender:temp-asset-lite into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
13 changed files with 979 additions and 16 deletions

View File

@ -5138,6 +5138,11 @@ class VIEW3D_MT_edit_gpencil(Menu):
layout.separator()
# Assets
layout.menu("VIEW3D_MT_edit_gpencil_asset", text="Create Asset")
layout.separator()
# Cut, Copy, Paste
layout.operator("gpencil.duplicate_move", text="Duplicate")
layout.operator("gpencil.stroke_split", text="Split")
@ -5228,6 +5233,14 @@ class VIEW3D_MT_edit_gpencil_point(Menu):
layout.menu("VIEW3D_MT_gpencil_vertex_group")
Review

I think you can use layout.operator_enum() here.

I think you can use `layout.operator_enum()` here.
class VIEW3D_MT_edit_gpencil_asset(Menu):
bl_label = "Create Asset"
def draw(self, _context):
layout = self.layout
layout.operator_enum("gpencil.asset_create", "source")
class VIEW3D_MT_weight_gpencil(Menu):
bl_label = "Weights"
@ -7401,6 +7414,11 @@ class VIEW3D_MT_gpencil_edit_context_menu(Menu):
col.menu("VIEW3D_MT_mirror", text="Mirror")
col.menu("GPENCIL_MT_snap", text="Snap")
# Assets
col.separator()
col.operator_menu_enum("gpencil.asset_create", "source", text="Create Asset")
col.separator()
# Duplicate operators
@ -7453,6 +7471,11 @@ class VIEW3D_MT_gpencil_edit_context_menu(Menu):
col.menu("VIEW3D_MT_mirror", text="Mirror")
col.menu("VIEW3D_MT_snap", text="Snap")
# Assets
col.separator()
col.operator_menu_enum("gpencil.asset_create", "source", text="Create Asset")
col.separator()
# Duplicate operators
@ -8138,6 +8161,7 @@ classes = (
VIEW3D_MT_edit_gpencil,
VIEW3D_MT_edit_gpencil_stroke,
VIEW3D_MT_edit_gpencil_point,
VIEW3D_MT_edit_gpencil_asset,
VIEW3D_MT_edit_gpencil_delete,
VIEW3D_MT_edit_gpencil_showhide,
VIEW3D_MT_weight_gpencil,

View File

@ -328,6 +328,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.

View File

@ -60,7 +60,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;
@ -108,6 +108,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)
@ -177,6 +184,8 @@ static void greasepencil_blend_write(BlendWriter *writer, ID *id, const void *id
}
}
}
BKE_previewimg_blend_write(writer, gpd->preview);
}
}
@ -191,6 +200,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;
@ -493,6 +506,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)
@ -2213,7 +2228,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);
Review

Is this necessary for this patch or a general fix?

Is this necessary for this patch or a general fix?
/* 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;
}
}
@ -3059,4 +3074,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;
}
}
}
}
/** \} */

View File

@ -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)

View File

@ -16,7 +16,8 @@ struct ID;
bool ED_asset_type_id_is_non_experimental(const struct ID *id);
#define ED_ASSET_TYPE_IDS_NON_EXPERIMENTAL_FLAGS \
(FILTER_ID_MA | FILTER_ID_GR | FILTER_ID_OB | FILTER_ID_AC | FILTER_ID_WO | FILTER_ID_NT)
(FILTER_ID_MA | FILTER_ID_GR | FILTER_ID_OB | FILTER_ID_AC | FILTER_ID_WO | FILTER_ID_NT | \
FILTER_ID_GD)
/**
* Check if the asset type for \a id (which doesn't need to be an asset right now) can be an asset,
@ -39,7 +40,7 @@ int64_t ED_asset_types_supported_as_filter_flags(void);
* Should start with a consonant, so usages can prefix it with "a" (not "an").
*/
#define ED_ASSET_TYPE_IDS_NON_EXPERIMENTAL_UI_STRING \
"Material, Collection, Object, Pose Action, Node Group or World"
"Material, Collection, Object, Pose Action, Node Group or World, Grease Pencil"
#ifdef __cplusplus
}

View File

@ -16,7 +16,7 @@ bool ED_asset_type_id_is_non_experimental(const ID *id)
{
/* Remember to update #ED_ASSET_TYPE_IDS_NON_EXPERIMENTAL_UI_STRING and
* #ED_ASSET_TYPE_IDS_NON_EXPERIMENTAL_FLAGS() with this! */
return ELEM(GS(id->name), ID_MA, ID_GR, ID_OB, ID_AC, ID_WO, ID_NT);
return ELEM(GS(id->name), ID_MA, ID_GR, ID_OB, ID_AC, ID_WO, ID_NT, ID_GD);
}
bool ED_asset_type_is_supported(const ID *id)

View File

@ -2,6 +2,7 @@
set(INC
../include
../../asset_system
../../blenfont
../../blenkernel
../../blenlib
@ -31,6 +32,7 @@ set(SRC
gpencil_add_monkey.c
gpencil_add_stroke.c
gpencil_armature.c
gpencil_asset.cc
gpencil_bake_animation.cc
gpencil_convert.c
gpencil_data.c

View File

@ -0,0 +1,816 @@
/* 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 "BLI_vector.hh"
#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"
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];
/* Keep a reference of the asset data inserted in the target object. */
blender::Vector<tGPDAssetStroke> asset_strokes;
} tGPDasset;
/* -------------------------------------------------------------------- */
/** \name Create Grease Pencil data block Asset operator
* \{ */
typedef enum eGP_AssetSource {
/* Active Layer. */
GP_ASSET_SOURCE_ACTIVE_LAYER = 0,
/* All Layers. */
GP_ASSET_SOURCE_ALL_LAYERS,
/* All Layers in separated assets. */
GP_ASSET_SOURCE_ALL_LAYERS_SPLIT,
/* Active Frame. */
GP_ASSET_SOURCE_ACTIVE_KEYFRAME,
/* Active Frame All Layers. */
GP_ASSET_SOURCE_ACTIVE_KEYFRAME_ALL_LAYERS,
/* Selected Frames. */
GP_ASSET_SOURCE_SELECTED_KEYFRAMES,
/* Selected Strokes. */
GP_ASSET_SOURCE_SELECTED_STROKES,
/* Selected Strokes. */
GP_ASSET_SOURCE_SELECTED_POINTS,
} eGP_AssetSource;
/* 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 wmOperator *op,
const bGPdata *gpd_src,
const bGPDlayer *gpl_filter,
const eGP_AssetSource mode,
const bool reset_origin,
const bool flatten_layers)
{
Main *bmain = CTX_data_main(C);
bool non_supported_feature = false;
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd_src);
/* Create a copy of selected data block. */
bGPdata *gpd = reinterpret_cast<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_SOURCE_ACTIVE_LAYER, GP_ASSET_SOURCE_ACTIVE_KEYFRAME)) &&
(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_SOURCE_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 != nullptr) {
gpl->parent = nullptr;
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 = static_cast<bGPDlayer_Mask *>(gpl->mask_layers.first); mask;
mask = mask_next) {
mask_next = mask->next;
BKE_gpencil_layer_mask_remove(gpl, mask);
}
gpl->mask_layers.first = nullptr;
gpl->mask_layers.last = nullptr;
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 or if multi frame edition is not enabled.
*/
if ((ELEM(mode,
GP_ASSET_SOURCE_ACTIVE_KEYFRAME,
GP_ASSET_SOURCE_ACTIVE_KEYFRAME_ALL_LAYERS) ||
!is_multiedit) &&
(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_SOURCE_SELECTED_KEYFRAMES) && ((gpf->flag & GP_FRAME_SELECT) == 0)) {
BKE_gpencil_layer_frame_delete(gpl, gpf);
continue;
}
/* Remove any unselected stroke if selected strokes mode. */
if (ELEM(mode, GP_ASSET_SOURCE_SELECTED_STROKES, GP_ASSET_SOURCE_SELECTED_POINTS)) {
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;
}
}
}
/* Remove any unselected point if selected point mode. */
if (mode == GP_ASSET_SOURCE_SELECTED_POINTS) {
LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
if (gps->flag & GP_STROKE_SELECT) {
/* Mark the points to dissolve */
bGPDspoint *pt;
int i;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
if ((pt->flag & GP_SPOINT_SELECT) == 0) {
pt->flag |= GP_SPOINT_TAG;
}
}
BKE_gpencil_stroke_delete_tagged_points(
gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, false, 0);
}
}
}
/* 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);
}
/* Check if something to do. */
bool do_export = false;
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
if (BLI_listbase_count(&gpl->frames) > 0) {
do_export = true;
break;
}
}
/* Nothing to export. */
if (!do_export) {
BKE_report(op->reports, RPT_ERROR, "No strokes were found to create the asset");
return false;
}
/* 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 = static_cast<bGPDlayer *>(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);
Object *ob = CTX_data_active_object(C);
if ((ob == nullptr) || (ob->type != OB_GPENCIL)) {
CTX_wm_operator_poll_msg_set(C, "Need a Grease Pencil object selected");
return false;
}
/* Only allowed in Grease Pencil Edit mode. */
if (mode != CTX_MODE_EDIT_GPENCIL) {
CTX_wm_operator_poll_msg_set(C, "Grease Pencil object must be in Edit mode");
return false;
}
return ED_operator_view3d_active(C);
}
static int gpencil_asset_create_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
bGPdata *gpd_src = static_cast<bGPdata *>(ob->data);
const eGP_AssetSource source = static_cast<eGP_AssetSource>(RNA_enum_get(op->ptr, "source"));
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 (source == GP_ASSET_SOURCE_ALL_LAYERS_SPLIT) {
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_src->layers) {
non_supported_feature |= gpencil_asset_create(
C, op, gpd_src, gpl, source, reset_origin, flatten_layers);
}
}
else {
non_supported_feature = gpencil_asset_create(
C, op, gpd_src, nullptr, source, 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, nullptr);
WM_main_add_notifier(NC_ASSET | NA_ADDED, nullptr);
return OPERATOR_FINISHED;
}
void GPENCIL_OT_asset_create(wmOperatorType *ot)
{
static const EnumPropertyItem source_types[] = {
JulianEisel marked this conversation as resolved
Review

sources?

`sources`?
{GP_ASSET_SOURCE_ACTIVE_LAYER,
"LAYER",
0,
"Active Layer",
"Create an asset using strokes of the active layer"},
JulianEisel marked this conversation as resolved
Review

None of the descriptions should end with a period.

None of the descriptions should end with a period.
{GP_ASSET_SOURCE_ALL_LAYERS,
"LAYERS_ALL",
0,
"All Layers",
"Create an asset using strokes from all layers"},
{GP_ASSET_SOURCE_ALL_LAYERS_SPLIT,
"LAYERS_SPLIT",
0,
"All Layers Separated",
"Create multiple grease pencil assets, one for each layer"},
JulianEisel marked this conversation as resolved
Review

This description isn't clear to me. Does this create one asset for each layer? In that case I suggest: "Create multiple grease pencil assets, one for each layer"

This description isn't clear to me. Does this create one asset for each layer? In that case I suggest: "Create multiple grease pencil assets, one for each layer"
RNA_ENUM_ITEM_SEPR,
{GP_ASSET_SOURCE_ACTIVE_KEYFRAME,
"KEYFRAME",
0,
"Active Keyframe (Active Layer)",
"Create an asset using active keyframe for active layer"},
Review

This and the following descriptions should read "Create an asset...".

In fact this and the following descriptions aren't clear to me either, they just repeat the operator name and the enum item name. I like what you did above, it actually explains what it's doing, like:
"Copy the strokes of [something something] into a new grease pencil asset".

This and the following descriptions should read "Create ***an*** asset...". In fact this and the following descriptions aren't clear to me either, they just repeat the operator name and the enum item name. I like what you did above, it actually explains what it's doing, like: *"Copy the strokes of [something something] into a new grease pencil asset"*.
{GP_ASSET_SOURCE_ACTIVE_KEYFRAME_ALL_LAYERS,
"KEYFRAME_ALL",
0,
"Active Keyframe (All Layers)",
"Create an asset using active keyframe for all layers"},
{GP_ASSET_SOURCE_SELECTED_KEYFRAMES,
"KEYFRAME_SELECTED",
0,
"Selected Keyframes",
"Create an asset using selected keyframes"},
RNA_ENUM_ITEM_SEPR,
{GP_ASSET_SOURCE_SELECTED_STROKES,
"SELECTED",
0,
"Selected Strokes",
"Create an asset using all selected strokes"},
{GP_ASSET_SOURCE_SELECTED_POINTS,
"POINT",
0,
"Selected Points",
"Create an asset using all selected points"},
{0, nullptr, 0, nullptr, nullptr},
};
/* 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, "source", source_types, GP_ASSET_SOURCE_SELECTED_STROKES, "Create From", "");
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 nullptr;
}
Material ***materials_data_ptr = BKE_id_material_array_p(id);
Material **materials_data = materials_data_ptr ? *materials_data_ptr : nullptr;
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 (tGPDAssetStroke &data : tgpa->asset_strokes) {
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);
/* Verify something to do. */
int data_len = 0;
LISTBASE_FOREACH (bGPDlayer *, gpl_asset, &gpd_asset->layers) {
if (data_len > 0) {
break;
}
LISTBASE_FOREACH (bGPDframe *, gpf_asset, &gpl_asset->frames) {
data_len += BLI_listbase_count(&gpf_asset->strokes);
if (data_len > 0) {
break;
}
}
}
/* If the asset is empty, exit. */
if (data_len == 0) {
return false;
}
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 == nullptr) {
gpl_target = BKE_gpencil_layer_duplicate(gpl_asset, false, false);
BLI_assert(gpl_target != nullptr);
gpl_target->actframe = nullptr;
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 = nullptr;
/* 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 == nullptr) && (!IS_AUTOKEY_ON(tgpa->scene))) {
gpf_target = BKE_gpencil_layer_frame_get(gpl_target, fra, GP_GETFRAME_USE_PREV);
}
if (gpf_target == nullptr) {
gpf_target = BKE_gpencil_frame_addnew(gpl_target, fra);
gpl_target->actframe = gpf_target;
BLI_assert(gpf_target != nullptr);
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 = nullptr;
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 != nullptr) ? 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. */
int matidx = is_new_mat ? (gps_target->mat_nr + 1) : -1;
tGPDAssetStroke data = {
gpl_target, gpf_target, gps_target, matidx, is_new_gpl, is_new_gpf};
tgpa->asset_strokes.append(data);
/* 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 = static_cast<tGPDasset *>(op->customdata);
if (tgpa) {
bGPdata *gpd = static_cast<bGPdata *>(tgpa->gpd);
/* Free data. */
MEM_delete(tgpa);
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
}
WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED | ND_DATA, nullptr);
/* Clear pointer. */
op->customdata = nullptr;
}
/* Allocate memory and initialize values */
static tGPDasset *gpencil_session_init_asset_import(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
ID *id = nullptr;
PropertyRNA *prop_type = RNA_struct_find_property(op->ptr, "type");
const short id_type = RNA_property_enum_get(op->ptr, prop_type);
id = WM_operator_properties_id_lookup_from_name_or_session_uuid(
bmain, op->ptr, (ID_Type)id_type);
if (id == nullptr) {
return nullptr;
}
const int object_type = BKE_object_obdata_to_type(id);
if (object_type != OB_GPENCIL) {
return nullptr;
}
tGPDasset *tgpa = MEM_new<tGPDasset>(__func__);
/* 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 = static_cast<bGPdata *>(tgpa->ob->data);
/* Asset GP data block. */
tgpa->gpd_asset = (bGPdata *)id;
return tgpa;
Review

Shouldn't be needed, the vector is default constructed cleanly.

Shouldn't be needed, the vector is default constructed cleanly.
}
/* Init: Allocate memory and set init values */
static bool gpencil_asset_import_init(bContext *C, wmOperator *op)
{
op->customdata = static_cast<tGPDasset *>(gpencil_session_init_asset_import(C, op));
if (op->customdata == nullptr) {
gpencil_asset_import_exit(C, op);
return false;
}
return true;
Review

Why is this setting customdata twice?

Why is this setting customdata twice?
Review

In fact, there doesn't seem to be a need to use customdata at all? This just makes it hard to follow where/how the data is used. It's meant to make data available throughout multiple callbacks.

Just return a tGPDasset pointer and pass it to the functions that need it, rather than doing that indirectly via op->customdata. Bonus points for using std::unique_ptr, then you're guaranteed to be leak-free :)

In fact, there doesn't seem to be a need to use `customdata` at all? This just makes it hard to follow where/how the data is used. It's meant to make data available throughout multiple callbacks. Just return a `tGPDasset` pointer and pass it to the functions that need it, rather than doing that indirectly via `op->customdata`. Bonus points for using `std::unique_ptr`, then you're guaranteed to be leak-free :)
}
/* 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 = nullptr;
/* Try to initialize context data needed. */
if (!gpencil_asset_import_init(C, op)) {
if (op->customdata) {
MEM_delete(static_cast<tGPDasset *>(op->customdata));
}
return OPERATOR_CANCELLED;
}
tgpa = static_cast<tGPDasset *>(op->customdata);
/* Save initial position of drop. */
tgpa->drop[0] = event->mval[0];
Review

MEM_delete() on a void pointer doesn't make sense, it shouldn't even compile. Committed 4126284e46 so this is a compiler error on all platforms in future.

`MEM_delete()` on a void pointer doesn't make sense, it shouldn't even compile. Committed 4126284e46 so this is a compiler error on all platforms in future.
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);
BKE_report(op->reports, RPT_WARNING, "No strokes to append");
return OPERATOR_FINISHED;
}
/* Select imported strokes. */
gpencil_asset_set_selection(tgpa, true);
/* Clean up temp data. */
Review

This tags the depsgraph and sends notifiers... stuff that should only be done when data actually changes, not when cancelling an operation without having made changes. In this case a simple freeing of data should be sufficient.

And if you use std::unique_ptr you don't even need to free the data manually ;)

This tags the depsgraph and sends notifiers... stuff that should only be done when data actually changes, not when cancelling an operation without having made changes. In this case a simple freeing of data should be sufficient. And if you use `std::unique_ptr` you don't even need to free the data manually ;)
gpencil_asset_import_exit(C, op);
Review

Is this really cancelling the operation? It seems gpencil_asset_append_strokes() only returns false if the grease pencil asset is empty. In this case I would just display an info report to give the user some feedback, and return OPERATOR_FINISHED.

Is this really cancelling the operation? It seems `gpencil_asset_append_strokes()` only returns false if the grease pencil asset is empty. In this case I would just display an info report to give the user some feedback, and return `OPERATOR_FINISHED`.
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr);
return OPERATOR_FINISHED;
}
void GPENCIL_OT_asset_import(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Grease Pencil Import Asset";
ot->idname = "GPENCIL_OT_asset_import";
ot->description = "Add strokes from a grease pencil asset to an existing grease pencil object";
/* callbacks */
ot->invoke = gpencil_asset_import_invoke;
ot->poll = gpencil_asset_edit_poll;
/* flags */
JulianEisel marked this conversation as resolved
Review

Suggest: "Add strokes from a grease pencil asset to an existing grease pencil object".

Suggest: *"Add strokes from a grease pencil asset to an existing grease pencil object"*.
ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING;
/* Properties. */
WM_operator_properties_id_lookup(ot, true);
PropertyRNA *prop = RNA_def_enum(ot->srna, "type", rna_enum_id_type_items, 0, "Type", "");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID);
RNA_def_property_flag(prop, static_cast<PropertyFlag>(PROP_SKIP_SAVE | PROP_HIDDEN));
}
/** \} */

View File

@ -671,6 +671,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 */

View File

@ -687,6 +687,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)

View File

@ -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"
@ -366,6 +368,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,
@ -765,6 +768,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;
@ -806,6 +811,10 @@ static Scene *object_preview_scene_create(const struct ObjectPreviewData *previe
Depsgraph **r_depsgraph)
{
Scene *scene = BKE_scene_add(preview_data->pr_main, "Object preview scene");
const bool is_gpencil = (preview_data->datablock != nullptr) &&
Review

Such a check is super fragile. If somebody adds another case where preview_data->object is null, this will just assume we're rendering for grease pencil.

Such a check is super fragile. If somebody adds another case where `preview_data->object` is null, this will just assume we're rendering for grease pencil.
(GS(preview_data->datablock->name) == ID_GD);
Object *ob_gpencil_temp = nullptr;
/* Preview need to be in the current frame to get a thumbnail similar of what
* viewport displays. */
scene->r.cfra = preview_data->cfra;
@ -814,13 +823,37 @@ static Scene *object_preview_scene_create(const struct ObjectPreviewData *previe
Depsgraph *depsgraph = DEG_graph_new(
preview_data->pr_main, scene, view_layer, DAG_EVAL_VIEWPORT);
BLI_assert(preview_data->object != nullptr);
BLI_addtail(&preview_data->pr_main->objects, preview_data->object);
if (!is_gpencil) {
BLI_assert(preview_data->object != nullptr);
BLI_addtail(&preview_data->pr_main->objects, preview_data->object);
BKE_collection_object_add(preview_data->pr_main, scene->master_collection, preview_data->object);
BKE_collection_object_add(
preview_data->pr_main, scene->master_collection, preview_data->object);
}
else {
/* Grease pencil draw engine needs an object to draw the datablock. */
ob_gpencil_temp = BKE_object_add_for_data(preview_data->pr_main,
scene,
Review

I would create this wrapper object in duplicate_ids(), just like we do it for instance empties there. Technically it's not thread safe, but I think it's fine not to copy the grease pencil data, and just create a wrapper object. We do the same for objects (the object data isn't copied I think) and collections (the collection itself isn't copied). This is also to avoid the memory and performance costs of the copies, this can be quite complex data.

I would create this wrapper object in `duplicate_ids()`, just like we do it for instance empties there. Technically it's not thread safe, but I think it's fine not to copy the grease pencil data, and just create a wrapper object. We do the same for objects (the object data isn't copied I think) and collections (the collection itself isn't copied). This is also to avoid the memory and performance costs of the copies, this can be quite complex data.
view_layer,
OB_GPENCIL,
"preview_object",
preview_data->datablock,
true);
BLI_assert(ob_gpencil_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,
JulianEisel marked this conversation as resolved
Review

I think this material copying can be done for all previews, not just grease pencil ones. Avoids the grease pencil specific handling.

I think this material copying can be done for all previews, not just grease pencil ones. Avoids the grease pencil specific handling.
!is_gpencil ? preview_data->object : ob_gpencil_temp,
preview_data->datablock);
}
Object *camera_object = object_preview_camera_create(
preview_data->pr_main, scene, view_layer, preview_data->object);
Object *camera_object = object_preview_camera_create(preview_data->pr_main,
scene,
view_layer,
is_gpencil ? ob_gpencil_temp :
preview_data->object);
scene->camera = camera_object;
scene->r.xsch = preview_data->sizex;
@ -828,7 +861,8 @@ static Scene *object_preview_scene_create(const struct ObjectPreviewData *previe
scene->r.size = 100;
BKE_view_layer_synced_ensure(scene, view_layer);
Base *preview_base = BKE_view_layer_base_find(view_layer, preview_data->object);
Base *preview_base = BKE_view_layer_base_find(
view_layer, is_gpencil ? ob_gpencil_temp : preview_data->object);
/* For 'view selected' below. */
preview_base->flag |= BASE_SELECTED;
@ -851,14 +885,34 @@ static void object_preview_render(IconPreview *preview, IconPreviewSize *preview
BLI_assert(preview->id_copy && (preview->id_copy != preview->id));
const bool is_gpencil = GS(preview->id->name) == ID_GD;
struct ObjectPreviewData preview_data = {};
preview_data.pr_main = preview_main;
/* Act on a copy. */
preview_data.object = (Object *)preview->id_copy;
preview_data.cfra = preview->scene->r.cfra;
Review

This change means the frame isn't set anymore for non grease pencil object.

This change means the frame isn't set anymore for non grease pencil object.
if (!is_gpencil) {
preview_data.object = (Object *)preview->id_copy;
preview_data.datablock = nullptr;
}
else {
preview_data.object = nullptr;
preview_data.datablock = (ID *)preview->id_copy;
preview_data.cfra = preview->scene->r.cfra;
}
preview_data.sizex = preview_sized->sizex;
preview_data.sizey = preview_sized->sizey;
/* Grease Pencil needs to find the frame number to make preview visible. */
if (is_gpencil) {
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;
Review

I think this bit of grease pencil specific code is fine to have here. But why this logic instead of a simple CLAMP()? Or better even, C++ std::clamp().

I think this bit of grease pencil specific code is fine to have here. But why this logic instead of a simple `CLAMP()`? Or better even, C++ `std::clamp()`.
preview_data.cfra = framenum;
}
Depsgraph *depsgraph;
Scene *scene = object_preview_scene_create(&preview_data, &depsgraph);
@ -1033,7 +1087,7 @@ static void action_preview_render(IconPreview *preview, IconPreviewSize *preview
}
/** \} */
Review

Unnecessary change.

Unnecessary change.
/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
/** \name New Shader Preview System
* \{ */
@ -1638,6 +1692,9 @@ static void icon_preview_startjob_all_sizes(void *customdata,
case ID_AC:
action_preview_render(ip, cur_size);
continue;
case ID_GD:
object_preview_render(ip, cur_size);
continue;
default:
/* Fall through to the same code as the `ip->id == nullptr` case. */
break;

View File

@ -633,6 +633,11 @@ static bool view3d_object_data_drop_poll(bContext *C, wmDrag *drag, const wmEven
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 * /*C*/,
wmDrag * /*drag*/,
const int /*xy*/[2],
@ -641,6 +646,14 @@ static char *view3d_object_data_drop_tooltip(bContext * /*C*/,
return BLI_strdup(TIP_("Create object instance from object-data"));
}
static char *view3d_gpencil_data_drop_tooltip(bContext * /*C*/,
wmDrag * /*drag*/,
const int /*xy*/[2],
wmDropBox * /*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)) {
@ -1001,6 +1014,12 @@ static void view3d_dropboxes()
view3d_id_path_drop_copy,
WM_drag_free_imported_drag_ID,
nullptr);
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);
WM_dropbox_add(lb,
"OBJECT_OT_data_instance_add",
view3d_object_data_drop_poll,
@ -1012,7 +1031,7 @@ static void view3d_dropboxes()
view3d_world_drop_poll,
view3d_id_drop_copy,
WM_drag_free_imported_drag_ID,
nullptr);
NULL);
}
static void view3d_widgets()

View File

@ -759,6 +759,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;