diff options
-rw-r--r-- | release/scripts/startup/bl_ui/space_view3d.py | 21 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_gpencil.h | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/gpencil.c | 35 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/icons.cc | 3 | ||||
-rw-r--r-- | source/blender/editors/gpencil/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_asset.c | 1542 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_intern.h | 4 | ||||
-rw-r--r-- | source/blender/editors/gpencil/gpencil_ops.c | 4 | ||||
-rw-r--r-- | source/blender/editors/render/render_preview.cc | 123 | ||||
-rw-r--r-- | source/blender/editors/space_view3d/space_view3d.c | 23 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_gpencil_types.h | 3 |
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; |