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:
-rw-r--r--release/scripts/startup/bl_ui/space_view3d.py21
-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.c1542
-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
11 files changed, 1755 insertions, 5 deletions
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py
index 5e5e7a79035..c6c2e0fb0d7 100644
--- a/release/scripts/startup/bl_ui/space_view3d.py
+++ b/release/scripts/startup/bl_ui/space_view3d.py
@@ -934,6 +934,7 @@ class VIEW3D_MT_editor_menus(Menu):
layout.menu("VIEW3D_MT_edit_gpencil")
layout.menu("VIEW3D_MT_edit_gpencil_stroke")
layout.menu("VIEW3D_MT_edit_gpencil_point")
+ layout.menu("VIEW3D_MT_edit_gpencil_asset")
elif obj and obj.mode == 'WEIGHT_GPENCIL':
layout.menu("VIEW3D_MT_weight_gpencil")
if obj and obj.mode == 'VERTEX_GPENCIL':
@@ -5167,6 +5168,21 @@ class VIEW3D_MT_edit_gpencil_point(Menu):
layout.menu("VIEW3D_MT_gpencil_vertex_group")
+class VIEW3D_MT_edit_gpencil_asset(Menu):
+ bl_label = "Asset"
+
+ def draw(self, _context):
+ layout = self.layout
+
+ layout.operator("gpencil.asset_create", text="Active Layer").mode = 'LAYER'
+ layout.operator("gpencil.asset_create", text="All Layers").mode = 'LAYERS_ALL'
+ layout.operator("gpencil.asset_create", text="All Layers Separated").mode = 'LAYERS_SPLIT'
+ layout.operator("gpencil.asset_create", text="Active Frame (Active Layer)").mode = 'FRAME'
+ layout.operator("gpencil.asset_create", text="Active Frame (All Layers)").mode = 'FRAME_ALL'
+ layout.operator("gpencil.asset_create", text="Selected Frames").mode = 'FRAME_SELECTED'
+ layout.operator("gpencil.asset_create", text="Selected Strokes").mode = 'SELECTED'
+
+
class VIEW3D_MT_weight_gpencil(Menu):
bl_label = "Weights"
@@ -7299,6 +7315,10 @@ class VIEW3D_MT_gpencil_edit_context_menu(Menu):
col.operator("gpencil.reproject", text="Reproject")
+ # Assets
+ col.separator()
+ col.operator_menu_enum("gpencil.asset_create", "mode", text="Create Asset")
+
def draw_gpencil_layer_active(context, layout):
gpl = context.active_gpencil_layer
@@ -7826,6 +7846,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,
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..fc12d70d535
--- /dev/null
+++ b/source/blender/editors/gpencil/gpencil_asset.c
@@ -0,0 +1,1542 @@
+/* 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 wmWindow *win;
+ 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;
+ /** General Flag. */
+ int flag;
+ /** Transform mode. */
+ short mode;
+
+ /** Drop initial position. */
+ int drop[2];
+ /** Mouse last click position. */
+ int mouse[2];
+ /** Initial distance to asset center from mouse location. */
+ float initial_dist;
+ /** Asset center. */
+ float asset_center[3];
+
+ /** 2D Cage vertices. */
+ rctf rect_cage;
+ /** 2D cage center. */
+ float cage_center[2];
+ /** Normal vector for cage. */
+ float cage_normal[3];
+ /** Transform center. */
+ float transform_center[2];
+
+ /** 2D cage manipulator points *
+ *
+ * NW-R NE-R (Rotation)
+ * \ /
+ * NW----N----NE
+ * | |
+ * W E
+ * | |
+ * SW----S----SE
+ * / \
+ * SW-R SE-R
+ */
+ float manipulator[12][2];
+ /** Manipulator index (-1 means not set). */
+ int manipulator_index;
+ /** Manipulator vector used to determine the effect. */
+ float manipulator_vector[3];
+ /** Vector with the original orientation for rotation. */
+ float vinit_rotation[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;
+
+ /** Handle for drawing while operator is running. */
+ void *draw_handle_3d;
+
+} tGPDasset;
+
+typedef enum eGP_AssetFlag {
+ /* Waiting for doing something. */
+ GP_ASSET_FLAG_IDLE = (1 << 0),
+ /* Doing a transform. */
+ GP_ASSET_FLAG_TRANSFORMING = (1 << 1),
+} eGP_AssetFlag;
+
+typedef enum eGP_AssetTransformMode {
+ /* NO action. */
+ GP_ASSET_TRANSFORM_NONE = 0,
+ /* Location. */
+ GP_ASSET_TRANSFORM_LOC = 1,
+ /* Rotation. */
+ GP_ASSET_TRANSFORM_ROT = 2,
+ /* Scale. */
+ GP_ASSET_TRANSFORM_SCALE = 3,
+} eGP_AssetTransformMode;
+
+enum eGP_CageCorners {
+ CAGE_CORNER_NW = 0,
+ CAGE_CORNER_N = 1,
+ CAGE_CORNER_NE = 2,
+ CAGE_CORNER_E = 3,
+ CAGE_CORNER_SE = 4,
+ CAGE_CORNER_S = 5,
+ CAGE_CORNER_SW = 6,
+ CAGE_CORNER_W = 7,
+ CAGE_CORNER_ROT_NW = 8,
+ CAGE_CORNER_ROT_NE = 9,
+ CAGE_CORNER_ROT_SW = 10,
+ CAGE_CORNER_ROT_SE = 11,
+ CAGE_FLIP_HORZ = 12,
+ CAGE_FLIP_VERT = 13,
+};
+
+static bool gpencil_asset_generic_poll(bContext *C)
+{
+ Object *ob = CTX_data_active_object(C);
+ if ((ob == NULL) || (ob->type != OB_GPENCIL)) {
+ return false;
+ }
+
+ return ED_operator_view3d_active(C);
+}
+
+/* -------------------------------------------------------------------- */
+/** \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_create_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;
+ }
+
+ return gpencil_asset_generic_poll(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_create_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: Update all imported strokes */
+static void gpencil_asset_import_update_strokes(const bContext *C, const tGPDasset *tgpa)
+{
+ bGPdata *gpd = tgpa->gpd;
+
+ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
+}
+
+/* Helper: Compute 2D cage size in screen pixels. */
+static void gpencil_2d_cage_calc(tGPDasset *tgpa)
+{
+ /* Add some oversize. */
+ const float oversize = 5.0f;
+
+ float diff_mat[4][4];
+ float cage_min[2];
+ float cage_max[2];
+ INIT_MINMAX2(cage_min, cage_max);
+
+ for (int index = 0; index < tgpa->data_len; index++) {
+ tGPDAssetStroke *data = &tgpa->data[index];
+ bGPDstroke *gps = data->gps;
+ bGPDlayer *gpl = data->gpl;
+ BKE_gpencil_layer_transform_matrix_get(tgpa->depsgraph, tgpa->ob, gpl, diff_mat);
+
+ if (is_zero_v3(gps->boundbox_min)) {
+ BKE_gpencil_stroke_boundingbox_calc(gps);
+ }
+ float boundbox_min[2];
+ float boundbox_max[2];
+ ED_gpencil_projected_2d_bound_box(&tgpa->gsc, gps, diff_mat, boundbox_min, boundbox_max);
+ minmax_v2v2_v2(cage_min, cage_max, boundbox_min);
+ minmax_v2v2_v2(cage_min, cage_max, boundbox_max);
+ }
+
+ tgpa->rect_cage.xmin = cage_min[0] - oversize;
+ tgpa->rect_cage.ymin = cage_min[1] - oversize;
+ tgpa->rect_cage.xmax = cage_max[0] + oversize;
+ tgpa->rect_cage.ymax = cage_max[1] + oversize;
+
+ /* Cage center. */
+ tgpa->cage_center[0] = 0.5f * (tgpa->rect_cage.xmin + tgpa->rect_cage.xmax);
+ tgpa->cage_center[1] = 0.5f * (tgpa->rect_cage.ymin + tgpa->rect_cage.ymax);
+
+ /* Manipulator points */
+ tgpa->manipulator[0][0] = tgpa->rect_cage.xmin;
+ tgpa->manipulator[0][1] = tgpa->rect_cage.ymax;
+
+ tgpa->manipulator[1][0] = tgpa->rect_cage.xmin +
+ ((tgpa->rect_cage.xmax - tgpa->rect_cage.xmin) * 0.5f);
+ tgpa->manipulator[1][1] = tgpa->rect_cage.ymax;
+
+ tgpa->manipulator[2][0] = tgpa->rect_cage.xmax;
+ tgpa->manipulator[2][1] = tgpa->rect_cage.ymax;
+
+ tgpa->manipulator[3][0] = tgpa->rect_cage.xmax;
+ tgpa->manipulator[3][1] = tgpa->rect_cage.ymin +
+ ((tgpa->rect_cage.ymax - tgpa->rect_cage.ymin) * 0.5f);
+
+ tgpa->manipulator[4][0] = tgpa->rect_cage.xmax;
+ tgpa->manipulator[4][1] = tgpa->rect_cage.ymin;
+
+ tgpa->manipulator[5][0] = tgpa->rect_cage.xmin +
+ ((tgpa->rect_cage.xmax - tgpa->rect_cage.xmin) * 0.5f);
+ tgpa->manipulator[5][1] = tgpa->rect_cage.ymin;
+
+ tgpa->manipulator[6][0] = tgpa->rect_cage.xmin;
+ tgpa->manipulator[6][1] = tgpa->rect_cage.ymin;
+
+ tgpa->manipulator[7][0] = tgpa->rect_cage.xmin;
+ tgpa->manipulator[7][1] = tgpa->rect_cage.ymin +
+ ((tgpa->rect_cage.ymax - tgpa->rect_cage.ymin) * 0.5f);
+ /* Rotation points. */
+ tgpa->manipulator[CAGE_CORNER_ROT_NW][0] = tgpa->rect_cage.xmin - ROTATION_CONTROL_GAP;
+ tgpa->manipulator[CAGE_CORNER_ROT_NW][1] = tgpa->rect_cage.ymax + ROTATION_CONTROL_GAP;
+
+ tgpa->manipulator[CAGE_CORNER_ROT_NE][0] = tgpa->rect_cage.xmax + ROTATION_CONTROL_GAP;
+ tgpa->manipulator[CAGE_CORNER_ROT_NE][1] = tgpa->rect_cage.ymax + ROTATION_CONTROL_GAP;
+
+ tgpa->manipulator[CAGE_CORNER_ROT_SW][0] = tgpa->rect_cage.xmin - ROTATION_CONTROL_GAP;
+ tgpa->manipulator[CAGE_CORNER_ROT_SW][1] = tgpa->rect_cage.ymin - ROTATION_CONTROL_GAP;
+
+ tgpa->manipulator[CAGE_CORNER_ROT_SE][0] = tgpa->rect_cage.xmax + ROTATION_CONTROL_GAP;
+ tgpa->manipulator[CAGE_CORNER_ROT_SE][1] = tgpa->rect_cage.ymin - ROTATION_CONTROL_GAP;
+
+ /* Normal vector. */
+ float co1[3], co2[3], co3[3], vec1[3], vec2[3];
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, tgpa->manipulator[CAGE_CORNER_NE], co1);
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, tgpa->manipulator[CAGE_CORNER_NW], co2);
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, tgpa->manipulator[CAGE_CORNER_SW], co3);
+ sub_v3_v3v3(vec1, co2, co1);
+ sub_v3_v3v3(vec2, co3, co2);
+
+ cross_v3_v3v3(tgpa->cage_normal, vec1, vec2);
+ normalize_v3(tgpa->cage_normal);
+}
+
+/* Draw a cage to manipulate asset. */
+static void gpencil_2d_cage_draw(const tGPDasset *tgpa)
+{
+ GPUVertFormat *format = immVertexFormat();
+ const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
+
+ GPU_blend(GPU_BLEND_ALPHA);
+
+ /* Draw dash box. */
+ immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR);
+ GPU_line_width(1.0f);
+
+ float viewport_size[4];
+ GPU_viewport_size_get_f(viewport_size);
+ immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
+
+ immUniform1i("colors_len", 0); /* "simple" mode */
+ immUniform1f("dash_width", 6.0f);
+ immUniform1f("dash_factor", 0.5f);
+
+ float box_color[4];
+ UI_GetThemeColor4fv(TH_VERTEX_SELECT, box_color);
+ immUniformColor4fv(box_color);
+
+ immBegin(GPU_PRIM_LINE_LOOP, 4);
+ immVertex2f(pos, tgpa->manipulator[CAGE_CORNER_NW][0], tgpa->manipulator[CAGE_CORNER_NW][1]);
+ immVertex2f(pos, tgpa->manipulator[CAGE_CORNER_NE][0], tgpa->manipulator[CAGE_CORNER_NE][1]);
+ immVertex2f(pos, tgpa->manipulator[CAGE_CORNER_SE][0], tgpa->manipulator[CAGE_CORNER_SE][1]);
+ immVertex2f(pos, tgpa->manipulator[CAGE_CORNER_SW][0], tgpa->manipulator[CAGE_CORNER_SW][1]);
+ immEnd();
+
+ /* Rotation boxes. */
+ const float gap = 2.0f;
+ for (int i = 0; i < 4; i++) {
+ imm_draw_box_wire_2d(pos,
+ tgpa->manipulator[CAGE_CORNER_ROT_NW + i][0] - gap,
+ tgpa->manipulator[CAGE_CORNER_ROT_NW + i][1] - gap,
+ tgpa->manipulator[CAGE_CORNER_ROT_NW + i][0] + gap,
+ tgpa->manipulator[CAGE_CORNER_ROT_NW + i][1] + gap);
+ }
+
+ /* Draw a line while is doing a transform. */
+ if ((tgpa->flag & GP_ASSET_FLAG_TRANSFORMING) && (tgpa->mode == GP_ASSET_TRANSFORM_ROT)) {
+ const float line_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
+ immUniformColor4fv(line_color);
+ immBegin(GPU_PRIM_LINES, 2);
+ immVertex2f(pos, tgpa->transform_center[0], tgpa->transform_center[1]);
+ immVertex2f(pos, tgpa->mouse[0], tgpa->mouse[1]);
+ immEnd();
+ }
+
+ immUnbindProgram();
+
+ /* Draw Points. */
+ GPU_program_point_size(true);
+
+ immBindBuiltinProgram(GPU_SHADER_2D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA);
+
+ float point_color[4];
+ UI_GetThemeColor4fv(TH_SELECT, point_color);
+ immUniformColor4fv(point_color);
+
+ immUniform1f("size", UI_GetThemeValuef(TH_VERTEX_SIZE) * 1.5f * U.dpi_fac);
+
+ /* Draw only the points of the cage, but not the rotation points. */
+ immBegin(GPU_PRIM_POINTS, 8);
+ for (int i = 0; i < 8; i++) {
+ immVertex2fv(pos, tgpa->manipulator[i]);
+ }
+ immEnd();
+
+ immUnbindProgram();
+ GPU_program_point_size(false);
+
+ GPU_blend(GPU_BLEND_NONE);
+}
+
+/* Helper: Detect mouse over cage action areas. */
+static void gpencil_2d_cage_area_detect(tGPDasset *tgpa, const int mouse[2])
+{
+ const float gap = 5.0f;
+
+ /* Check if mouse is over any of the manipualtor points with a small gap. */
+ rctf rect_mouse = {
+ (float)mouse[0] - gap, (float)mouse[0] + gap, (float)mouse[1] - gap, (float)mouse[1] + gap};
+
+ tgpa->manipulator_index = -1;
+ for (int i = 0; i < 12; i++) {
+ if (BLI_rctf_isect_pt(&rect_mouse, tgpa->manipulator[i][0], tgpa->manipulator[i][1])) {
+ tgpa->manipulator_index = i;
+ tgpa->mode = (tgpa->manipulator_index < 8) ? GP_ASSET_TRANSFORM_SCALE :
+ GP_ASSET_TRANSFORM_ROT;
+
+ /* For rotation don't need to do more. */
+ if (tgpa->mode == GP_ASSET_TRANSFORM_ROT) {
+ WM_cursor_modal_set(tgpa->win, WM_CURSOR_HAND);
+ return;
+ }
+
+ switch (i) {
+ case CAGE_CORNER_NW:
+ case CAGE_CORNER_SW:
+ case CAGE_CORNER_NE:
+ case CAGE_CORNER_SE:
+ WM_cursor_modal_set(tgpa->win, WM_CURSOR_NSEW_SCROLL);
+ break;
+ case CAGE_CORNER_N:
+ case CAGE_CORNER_S:
+ WM_cursor_modal_set(tgpa->win, WM_CURSOR_NS_ARROW);
+ break;
+ case CAGE_CORNER_E:
+ case CAGE_CORNER_W:
+ WM_cursor_modal_set(tgpa->win, WM_CURSOR_EW_ARROW);
+ break;
+ default:
+ break;
+ }
+
+ /* Determine the vector of the cage effect. For corners is always full effect. */
+ if (ELEM(tgpa->manipulator_index,
+ CAGE_CORNER_NW,
+ CAGE_CORNER_NE,
+ CAGE_CORNER_SE,
+ CAGE_CORNER_SW)) {
+ zero_v3(tgpa->manipulator_vector);
+ add_v3_fl(tgpa->manipulator_vector, 1.0f);
+ return;
+ }
+
+ float co1[3], co2[3];
+ if (ELEM(tgpa->manipulator_index, CAGE_CORNER_N, CAGE_CORNER_S)) {
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, tgpa->manipulator[CAGE_CORNER_S], co1);
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, tgpa->manipulator[CAGE_CORNER_N], co2);
+ }
+ else if (ELEM(tgpa->manipulator_index, CAGE_CORNER_E, CAGE_CORNER_W)) {
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, tgpa->manipulator[CAGE_CORNER_W], co1);
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, tgpa->manipulator[CAGE_CORNER_E], co2);
+ }
+
+ sub_v3_v3v3(tgpa->manipulator_vector, co2, co1);
+ normalize_v3(tgpa->manipulator_vector);
+
+ return;
+ }
+ }
+
+ /* Check if mouse is inside cage for Location transform. */
+ if (BLI_rctf_isect_pt(&tgpa->rect_cage, (float)mouse[0], (float)mouse[1])) {
+ tgpa->mode = GP_ASSET_TRANSFORM_LOC;
+ WM_cursor_modal_set(tgpa->win, WM_CURSOR_DEFAULT);
+ return;
+ }
+
+ tgpa->mode = GP_ASSET_TRANSFORM_NONE;
+ WM_cursor_modal_set(tgpa->win, WM_CURSOR_DEFAULT);
+}
+
+/* Helper: Get the rotation matrix for the angle using an arbitrary vector as axis. */
+static void gpencil_asset_rotation_matrix_get(const float angle,
+ const float axis[3],
+ float rotation_matrix[4][4])
+{
+ const float u2 = axis[0] * axis[0];
+ const float v2 = axis[1] * axis[1];
+ const float w2 = axis[2] * axis[2];
+ const float length = (u2 + v2 + w2);
+ const float length_sqr = sqrt(length);
+ const float cos_value = cos(angle);
+ const float sin_value = sin(angle);
+
+ rotation_matrix[0][0] = (u2 + (v2 + w2) * cos_value) / length;
+ rotation_matrix[0][1] = (axis[0] * axis[1] * (1.0f - cos_value) -
+ axis[2] * length_sqr * sin_value) /
+ length;
+ rotation_matrix[0][2] = (axis[0] * axis[2] * (1.0f - cos_value) +
+ axis[1] * length_sqr * sin_value) /
+ length;
+ rotation_matrix[0][3] = 0.0;
+
+ rotation_matrix[1][0] = (axis[0] * axis[1] * (1.0f - cos_value) +
+ axis[2] * length_sqr * sin_value) /
+ length;
+ rotation_matrix[1][1] = (v2 + (u2 + w2) * cos_value) / length;
+ rotation_matrix[1][2] = (axis[1] * axis[2] * (1.0f - cos_value) -
+ axis[0] * length_sqr * sin_value) /
+ length;
+ rotation_matrix[1][3] = 0.0f;
+
+ rotation_matrix[2][0] = (axis[0] * axis[2] * (1.0f - cos_value) -
+ axis[1] * length_sqr * sin_value) /
+ length;
+ rotation_matrix[2][1] = (axis[1] * axis[2] * (1.0f - cos_value) +
+ axis[0] * length_sqr * sin_value) /
+ length;
+ rotation_matrix[2][2] = (w2 + (u2 + v2) * cos_value) / length;
+ rotation_matrix[2][3] = 0.0f;
+
+ rotation_matrix[3][0] = 0.0f;
+ rotation_matrix[3][1] = 0.0f;
+ rotation_matrix[3][2] = 0.0f;
+ rotation_matrix[3][3] = 1.0f;
+}
+
+/* Helper: Transform the stroke with mouse movements. */
+static void gpencil_asset_transform_strokes(tGPDasset *tgpa,
+ const int mouse[2],
+ const bool shift_key)
+{
+ /* Get the vector with the movement done by the mouse since last event. */
+ float origin_pt[3], dest_pt[3];
+ float mousef[2];
+ copy_v2fl_v2i(mousef, tgpa->mouse);
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, mousef, origin_pt);
+
+ copy_v2fl_v2i(mousef, mouse);
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, mousef, dest_pt);
+
+ float vec[3];
+ sub_v3_v3v3(vec, dest_pt, origin_pt);
+
+ /* Get the scale factor. */
+ sub_v2_v2v2(mousef, mousef, tgpa->transform_center);
+ float dist = len_v2(mousef);
+ float scale_factor = dist / tgpa->initial_dist;
+ float scale_vector[3];
+ mul_v3_v3fl(scale_vector, tgpa->manipulator_vector, scale_factor - 1.0f);
+ add_v3_fl(scale_vector, 1.0f);
+
+ /* Determine pivot point. */
+ float pivot[3];
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, tgpa->transform_center, pivot);
+ if ((!shift_key) || (ELEM(tgpa->manipulator_index, CAGE_FLIP_HORZ, CAGE_FLIP_VERT))) {
+ if (tgpa->manipulator_index == CAGE_CORNER_N) {
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, tgpa->manipulator[CAGE_CORNER_S], pivot);
+ }
+ else if (tgpa->manipulator_index == CAGE_CORNER_E) {
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, tgpa->manipulator[CAGE_CORNER_W], pivot);
+ }
+ else if (tgpa->manipulator_index == CAGE_CORNER_S) {
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, tgpa->manipulator[CAGE_CORNER_N], pivot);
+ }
+ else if (tgpa->manipulator_index == CAGE_CORNER_W) {
+ gpencil_point_xy_to_3d(&tgpa->gsc, tgpa->scene, tgpa->manipulator[CAGE_CORNER_E], pivot);
+ }
+ else if (ELEM(tgpa->manipulator_index, CAGE_FLIP_HORZ, CAGE_FLIP_VERT)) {
+ copy_v3_v3(pivot, tgpa->asset_center);
+ scale_factor = 1.0f;
+ zero_v3(scale_vector);
+ add_v3_fl(scale_vector, 1.0f);
+
+ if (tgpa->manipulator_index == CAGE_FLIP_HORZ) {
+ scale_vector[0] = -1.0f;
+ }
+ else {
+ scale_vector[2] = -1.0f;
+ }
+ }
+ }
+ sub_v3_v3v3(pivot, pivot, tgpa->ob->loc);
+
+ /* Create rotation matrix. */
+ float rot_matrix[4][4];
+ float vr[2];
+ copy_v2fl_v2i(vr, mouse);
+ sub_v2_v2v2(vr, vr, tgpa->transform_center);
+ normalize_v2(vr);
+ float angle = angle_signed_v2v2(tgpa->vinit_rotation, vr);
+ gpencil_asset_rotation_matrix_get(angle, tgpa->cage_normal, rot_matrix);
+
+ /* Loop all strokes and apply transformation. */
+ for (int index = 0; index < tgpa->data_len; index++) {
+ tGPDAssetStroke *data = &tgpa->data[index];
+ bGPDstroke *gps = data->gps;
+ bGPDspoint *pt;
+ int i;
+ for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
+ switch (tgpa->mode) {
+ case GP_ASSET_TRANSFORM_LOC: {
+ add_v3_v3(&pt->x, vec);
+ break;
+ }
+ case GP_ASSET_TRANSFORM_ROT: {
+ sub_v3_v3(&pt->x, pivot);
+ mul_v3_m4v3(&pt->x, rot_matrix, &pt->x);
+ add_v3_v3(&pt->x, pivot);
+ break;
+ }
+ case GP_ASSET_TRANSFORM_SCALE: {
+ /* Apply scale. */
+ sub_v3_v3(&pt->x, pivot);
+ mul_v3_v3(&pt->x, scale_vector);
+ add_v3_v3(&pt->x, pivot);
+
+ /* Thickness change only in full scale. */
+ if (ELEM(tgpa->manipulator_index,
+ CAGE_CORNER_NW,
+ CAGE_CORNER_NE,
+ CAGE_CORNER_SE,
+ CAGE_CORNER_SW)) {
+ pt->pressure *= scale_factor;
+ CLAMP_MIN(pt->pressure, 0.01f);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ /* In scale mode, recalculate stroke geometry because fill triangulation can change. */
+ if (tgpa->mode == GP_ASSET_TRANSFORM_SCALE) {
+ BKE_gpencil_stroke_geometry_update(tgpa->gpd, gps);
+ }
+ else {
+ /* Recalc stroke bounding box. */
+ BKE_gpencil_stroke_boundingbox_calc(gps);
+ }
+ }
+
+ /* In location mode move the asset center. */
+ if (tgpa->mode == GP_ASSET_TRANSFORM_LOC) {
+ add_v3_v3(tgpa->asset_center, vec);
+ }
+
+ /* Update mouse position and transform data to avoid accumulation for the next transformation. */
+ copy_v2_v2_int(tgpa->mouse, mouse);
+ tgpa->initial_dist = dist;
+ copy_v2_v2(tgpa->vinit_rotation, vr);
+}
+
+/* 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;
+ }
+ }
+ }
+ }
+
+ /* Prepare 2D cage. */
+ gpencil_2d_cage_calc(tgpa);
+ BKE_gpencil_centroid_3d(tgpa->gpd_asset, tgpa->asset_center);
+ add_v3_v3(tgpa->asset_center, vec);
+
+ return true;
+}
+
+/* Helper: Clean any temp added data when the operator is canceled. */
+static void gpencil_asset_clean_temp_data(tGPDasset *tgpa)
+{
+ /* Clean Strokes and materials. */
+ int actcol = tgpa->ob->actcol;
+ for (int index = 0; index < tgpa->data_len; index++) {
+ tGPDAssetStroke *data = &tgpa->data[index];
+ bGPDstroke *gps = data->gps;
+ bGPDframe *gpf = data->gpf;
+ BLI_remlink(&gpf->strokes, gps);
+ BKE_gpencil_free_stroke(gps);
+ if (data->slot_index >= 0) {
+ tgpa->ob->actcol = data->slot_index;
+ BKE_object_material_slot_remove(tgpa->bmain, tgpa->ob);
+ }
+ }
+ tgpa->ob->actcol = (actcol > tgpa->ob->totcol) ? tgpa->ob->totcol : actcol;
+
+ /* Clean Frames. */
+ for (int index = 0; index < tgpa->data_len; index++) {
+ tGPDAssetStroke *data = &tgpa->data[index];
+ if (data->is_new_gpf) {
+ bGPDframe *gpf = data->gpf;
+ bGPDlayer *gpl = data->gpl;
+ BLI_freelinkN(&gpl->frames, gpf);
+ }
+ }
+ /* Clean Layers. */
+ for (int index = 0; index < tgpa->data_len; index++) {
+ tGPDAssetStroke *data = &tgpa->data[index];
+ if (data->is_new_gpl) {
+ bGPDlayer *gpl = data->gpl;
+ BKE_gpencil_layer_delete(tgpa->gpd, gpl);
+ }
+ }
+}
+
+/* Helper: Draw status message while the user is running the operator. */
+static void gpencil_asset_import_status_indicators(bContext *C, const tGPDasset *tgpa)
+{
+ char status_str[UI_MAX_DRAW_STR];
+ char msg_str[UI_MAX_DRAW_STR];
+ bGPdata *gpd_asset = tgpa->gpd_asset;
+ const char *mode_txt[] = {"", "(Location)", "(Rotation)", "(Scale)"};
+
+ BLI_strncpy(msg_str, TIP_("Importing Asset"), UI_MAX_DRAW_STR);
+
+ BLI_snprintf(status_str,
+ sizeof(status_str),
+ "%s %s %s",
+ msg_str,
+ gpd_asset->id.name + 2,
+ mode_txt[tgpa->mode]);
+
+ ED_area_status_text(tgpa->area, status_str);
+ ED_workspace_status_text(C,
+ TIP_("ESC/RMB to cancel, Enter/LMB(outside cage) to confirm, Shift to "
+ "Scale uniform, F: Flip Horiz. (Shift Vert.)"));
+}
+
+/* Update screen and stroke. */
+static void gpencil_asset_import_update(bContext *C, const wmOperator *op, tGPDasset *tgpa)
+{
+ /* Update shift indicator in header. */
+ gpencil_asset_import_status_indicators(C, tgpa);
+ /* Update points position. */
+ gpencil_asset_import_update_strokes(C, tgpa);
+}
+
+/* ----------------------- */
+
+/* Exit and free memory */
+static void gpencil_asset_import_exit(bContext *C, wmOperator *op)
+{
+ tGPDasset *tgpa = op->customdata;
+
+ if (tgpa) {
+ bGPdata *gpd = tgpa->gpd;
+
+ /* Clear status message area. */
+ ED_area_status_text(tgpa->area, NULL);
+ ED_workspace_status_text(C, NULL);
+
+ /* Free data. */
+ MEM_SAFE_FREE(tgpa->data);
+
+ /* Remove drawing handler. */
+ if (tgpa->draw_handle_3d) {
+ ED_region_draw_cb_exit(tgpa->region->type, tgpa->draw_handle_3d);
+ }
+
+ 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->win = CTX_wm_window(C);
+ 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->mode = GP_ASSET_TRANSFORM_LOC;
+ tgpa->flag |= GP_ASSET_FLAG_IDLE;
+
+ /* Manipulator point is not set yet. */
+ tgpa->manipulator_index = -1;
+
+ 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;
+}
+
+/* Drawing callback for modal operator. */
+static void gpencil_asset_draw(const bContext *C, ARegion *UNUSED(region), void *arg)
+{
+ tGPDasset *tgpa = (tGPDasset *)arg;
+ /* Draw only in the region that originated operator. This is required for multi-window. */
+ ARegion *region = CTX_wm_region(C);
+ if (region != tgpa->region) {
+ return;
+ }
+ gpencil_2d_cage_draw(tgpa);
+}
+
+/* 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];
+
+ /* Do an initial load of the strokes in the target data block. */
+ if (!gpencil_asset_append_strokes(tgpa)) {
+ if (op->customdata) {
+ MEM_freeN(op->customdata);
+ }
+ return OPERATOR_CANCELLED;
+ }
+
+ tgpa->draw_handle_3d = ED_region_draw_cb_activate(
+ tgpa->region->type, gpencil_asset_draw, tgpa, REGION_DRAW_POST_PIXEL);
+
+ /* Update screen. */
+ gpencil_asset_import_status_indicators(C, tgpa);
+
+ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
+
+ /* Add a modal handler for this operator. */
+ WM_event_add_modal_handler(C, op);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static void gpencil_confirm_asset_import(bContext *C,
+ wmOperator *op,
+ wmWindow *win,
+ tGPDasset *tgpa)
+{
+ /* Return to normal cursor and header status. */
+ ED_area_status_text(tgpa->area, NULL);
+ ED_workspace_status_text(C, NULL);
+ WM_cursor_modal_restore(win);
+ /* Select imported strokes. */
+ gpencil_asset_set_selection(tgpa, true);
+}
+
+/* Modal handler: Events handling during interactive part. */
+static int gpencil_asset_import_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ tGPDasset *tgpa = op->customdata;
+ wmWindow *win = CTX_wm_window(C);
+ bool shift = (event->modifier & KM_SHIFT) != 0;
+
+ switch (event->type) {
+ case LEFTMOUSE: {
+ /* If click ouside cage, confirm. */
+ if (event->val == KM_PRESS) {
+ rctf rect_big;
+ rect_big.xmin = tgpa->rect_cage.xmin - (ROTATION_CONTROL_GAP * 2.0f);
+ rect_big.ymin = tgpa->rect_cage.ymin - (ROTATION_CONTROL_GAP * 2.0f);
+ rect_big.xmax = tgpa->rect_cage.xmax + (ROTATION_CONTROL_GAP * 2.0f);
+ rect_big.ymax = tgpa->rect_cage.ymax + (ROTATION_CONTROL_GAP * 2.0f);
+
+ if (tgpa->flag & GP_ASSET_FLAG_IDLE) {
+
+ /* If click outside cage, confirm the current asset import. */
+ if (!BLI_rctf_isect_pt(&rect_big, (float)event->mval[0], (float)event->mval[1])) {
+ gpencil_confirm_asset_import(C, op, win, tgpa);
+ /* Clean up temp data. */
+ gpencil_asset_import_exit(C, op);
+ /* Done! */
+ return OPERATOR_FINISHED;
+ }
+
+ copy_v2_v2_int(tgpa->mouse, event->mval);
+
+ /* Distance to asset center. */
+ copy_v2_v2(tgpa->transform_center, tgpa->cage_center);
+ float mousef[2];
+ copy_v2fl_v2i(mousef, tgpa->mouse);
+ sub_v2_v2v2(mousef, mousef, tgpa->transform_center);
+ tgpa->initial_dist = len_v2(mousef);
+
+ /* Initial orientation for rotation. */
+ copy_v2fl_v2i(tgpa->vinit_rotation, tgpa->mouse);
+ sub_v2_v2v2(tgpa->vinit_rotation, tgpa->vinit_rotation, tgpa->transform_center);
+ normalize_v2(tgpa->vinit_rotation);
+
+ tgpa->flag &= ~GP_ASSET_FLAG_IDLE;
+ tgpa->flag |= GP_ASSET_FLAG_TRANSFORMING;
+ }
+ break;
+ }
+
+ if (event->val == KM_RELEASE) {
+ tgpa->flag |= GP_ASSET_FLAG_IDLE;
+ tgpa->flag &= ~GP_ASSET_FLAG_TRANSFORMING;
+ tgpa->mode = GP_ASSET_TRANSFORM_NONE;
+ WM_cursor_modal_set(tgpa->win, WM_CURSOR_DEFAULT);
+ break;
+ }
+ }
+ /* Confirm. */
+ case EVT_PADENTER:
+ case EVT_RETKEY: {
+ gpencil_confirm_asset_import(C, op, win, tgpa);
+ /* Clean up temp data. */
+ gpencil_asset_import_exit(C, op);
+ /* Done! */
+ return OPERATOR_FINISHED;
+ }
+
+ case EVT_ESCKEY: /* Cancel */
+ case RIGHTMOUSE: {
+ /* Delete temp strokes. */
+ gpencil_asset_clean_temp_data(tgpa);
+ /* Return to normal cursor and header status. */
+ ED_area_status_text(tgpa->area, NULL);
+ ED_workspace_status_text(C, NULL);
+ WM_cursor_modal_restore(win);
+
+ /* Clean up temp data. */
+ gpencil_asset_import_exit(C, op);
+
+ /* Canceled! */
+ return OPERATOR_CANCELLED;
+ }
+ case MOUSEMOVE: {
+ /* Apply transform. */
+ if (tgpa->flag & GP_ASSET_FLAG_TRANSFORMING) {
+ gpencil_asset_transform_strokes(tgpa, event->mval, shift);
+ gpencil_2d_cage_calc(tgpa);
+ ED_area_tag_redraw(tgpa->area);
+ }
+ else {
+ /* Check cage manipulators. */
+ gpencil_2d_cage_area_detect(tgpa, event->mval);
+ }
+ /* Update screen. */
+ gpencil_asset_import_update(C, op, tgpa);
+ break;
+ }
+ case EVT_FKEY: {
+ if (event->val == KM_PRESS) {
+ /* Flip. */
+ tgpa->flag &= ~GP_ASSET_FLAG_IDLE;
+ tgpa->flag |= GP_ASSET_FLAG_TRANSFORMING;
+ tgpa->manipulator_index = (shift) ? CAGE_FLIP_VERT : CAGE_FLIP_HORZ;
+ tgpa->mode = GP_ASSET_TRANSFORM_SCALE;
+
+ gpencil_asset_transform_strokes(tgpa, event->mval, shift);
+ gpencil_2d_cage_calc(tgpa);
+ ED_area_tag_redraw(tgpa->area);
+
+ tgpa->flag |= GP_ASSET_FLAG_IDLE;
+ tgpa->flag &= ~GP_ASSET_FLAG_TRANSFORMING;
+ tgpa->mode = GP_ASSET_TRANSFORM_NONE;
+
+ /* Update screen. */
+ gpencil_asset_import_update(C, op, tgpa);
+ }
+ break;
+ }
+ default: {
+ /* Unhandled event - allow to pass through. */
+ return OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH;
+ }
+ }
+ /* Still running... */
+ return OPERATOR_RUNNING_MODAL;
+}
+
+/* Cancel handler. */
+static void gpencil_asset_import_cancel(bContext *C, wmOperator *op)
+{
+ /* this is just a wrapper around exit() */
+ gpencil_asset_import_exit(C, op);
+}
+
+static bool gpencil_asset_import_poll(bContext *C)
+{
+ return gpencil_asset_generic_poll(C);
+}
+
+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->modal = gpencil_asset_import_modal;
+ ot->cancel = gpencil_asset_import_cancel;
+ ot->poll = gpencil_asset_import_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;