2011-02-23 10:52:22 +00:00
|
|
|
/*
|
2009-04-20 10:20:18 +00:00
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program; if not, write to the Free Software Foundation,
|
2010-02-12 13:34:04 +00:00
|
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
2009-04-20 10:20:18 +00:00
|
|
|
*
|
|
|
|
|
* The Original Code is Copyright (C) 2008, Blender Foundation
|
|
|
|
|
* This is a new part of Blender
|
|
|
|
|
*/
|
2011-02-27 20:40:57 +00:00
|
|
|
|
2019-02-18 08:08:12 +11:00
|
|
|
/** \file
|
|
|
|
|
* \ingroup bke
|
2011-02-27 20:40:57 +00:00
|
|
|
*/
|
|
|
|
|
|
2020-03-19 09:33:03 +01:00
|
|
|
#include <math.h>
|
|
|
|
|
#include <stddef.h>
|
2009-04-20 10:20:18 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
2020-03-19 09:33:03 +01:00
|
|
|
#include <string.h>
|
2009-04-20 10:20:18 +00:00
|
|
|
|
2019-02-01 12:44:19 +11:00
|
|
|
#include "CLG_log.h"
|
|
|
|
|
|
2009-04-20 10:20:18 +00:00
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
|
|
|
|
|
|
#include "BLI_blenlib.h"
|
2013-11-06 10:59:05 +00:00
|
|
|
#include "BLI_math_vector.h"
|
2017-01-16 17:33:34 +01:00
|
|
|
#include "BLI_string_utils.h"
|
2009-04-20 10:20:18 +00:00
|
|
|
|
2015-08-16 17:32:01 +10:00
|
|
|
#include "BLT_translation.h"
|
2013-03-25 08:29:06 +00:00
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
#include "IMB_imbuf.h"
|
2020-03-19 09:33:03 +01:00
|
|
|
#include "IMB_imbuf_types.h"
|
2020-03-09 16:27:24 +01:00
|
|
|
|
2009-04-20 10:20:18 +00:00
|
|
|
#include "DNA_gpencil_types.h"
|
2020-03-19 09:33:03 +01:00
|
|
|
#include "DNA_material_types.h"
|
|
|
|
|
#include "DNA_meshdata_types.h"
|
2020-03-09 16:27:24 +01:00
|
|
|
#include "DNA_space_types.h"
|
2009-04-20 10:20:18 +00:00
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
#include "BKE_action.h"
|
2019-08-31 17:26:48 +02:00
|
|
|
#include "BKE_collection.h"
|
|
|
|
|
#include "BKE_colortools.h"
|
2018-08-27 14:20:40 +10:00
|
|
|
#include "BKE_deform.h"
|
2009-04-20 10:20:18 +00:00
|
|
|
#include "BKE_gpencil.h"
|
2020-03-19 11:35:17 +01:00
|
|
|
#include "BKE_gpencil_geom.h"
|
2018-07-31 10:22:19 +02:00
|
|
|
#include "BKE_icons.h"
|
2020-03-09 17:51:49 +01:00
|
|
|
#include "BKE_idtype.h"
|
2020-03-09 16:27:24 +01:00
|
|
|
#include "BKE_image.h"
|
2020-02-10 12:58:59 +01:00
|
|
|
#include "BKE_lib_id.h"
|
2020-05-20 16:31:47 +02:00
|
|
|
#include "BKE_lib_query.h"
|
2016-07-10 14:52:00 +02:00
|
|
|
#include "BKE_main.h"
|
2018-07-31 10:22:19 +02:00
|
|
|
#include "BKE_material.h"
|
2020-03-09 16:27:24 +01:00
|
|
|
#include "BKE_paint.h"
|
2019-08-31 17:26:48 +02:00
|
|
|
|
|
|
|
|
#include "BLI_math_color.h"
|
2009-04-20 10:20:18 +00:00
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
#include "DEG_depsgraph_query.h"
|
2009-04-20 10:20:18 +00:00
|
|
|
|
2019-02-01 12:44:19 +11:00
|
|
|
static CLG_LogRef LOG = {"bke.gpencil"};
|
|
|
|
|
|
2020-03-09 17:51:49 +01:00
|
|
|
static void greasepencil_copy_data(Main *UNUSED(bmain),
|
|
|
|
|
ID *id_dst,
|
|
|
|
|
const ID *id_src,
|
|
|
|
|
const int UNUSED(flag))
|
|
|
|
|
{
|
|
|
|
|
bGPdata *gpd_dst = (bGPdata *)id_dst;
|
|
|
|
|
const bGPdata *gpd_src = (const bGPdata *)id_src;
|
|
|
|
|
|
|
|
|
|
/* duplicate material array */
|
|
|
|
|
if (gpd_src->mat) {
|
|
|
|
|
gpd_dst->mat = MEM_dupallocN(gpd_src->mat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* copy layers */
|
|
|
|
|
BLI_listbase_clear(&gpd_dst->layers);
|
|
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl_src, &gpd_src->layers) {
|
|
|
|
|
/* make a copy of source layer and its data */
|
|
|
|
|
|
|
|
|
|
/* TODO here too could add unused flags... */
|
|
|
|
|
bGPDlayer *gpl_dst = BKE_gpencil_layer_duplicate(gpl_src);
|
|
|
|
|
|
|
|
|
|
BLI_addtail(&gpd_dst->layers, gpl_dst);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void greasepencil_free_data(ID *id)
|
|
|
|
|
{
|
|
|
|
|
/* Really not ideal, but for now will do... In theory custom behaviors like not freeing cache
|
|
|
|
|
* should be handled through specific API, and not be part of the generic one. */
|
|
|
|
|
BKE_gpencil_free((bGPdata *)id, true);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-20 16:31:47 +02:00
|
|
|
static void greasepencil_foreach_id(ID *id, LibraryForeachIDData *data)
|
|
|
|
|
{
|
|
|
|
|
bGPdata *gpencil = (bGPdata *)id;
|
|
|
|
|
/* materials */
|
|
|
|
|
for (int i = 0; i < gpencil->totcol; i++) {
|
|
|
|
|
BKE_LIB_FOREACHID_PROCESS(data, gpencil->mat[i], IDWALK_CB_USER);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gplayer, &gpencil->layers) {
|
|
|
|
|
BKE_LIB_FOREACHID_PROCESS(data, gplayer->parent, IDWALK_CB_NOP);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-09 17:51:49 +01:00
|
|
|
IDTypeInfo IDType_ID_GD = {
|
|
|
|
|
.id_code = ID_GD,
|
|
|
|
|
.id_filter = FILTER_ID_GD,
|
|
|
|
|
.main_listbase_index = INDEX_ID_GD,
|
|
|
|
|
.struct_size = sizeof(bGPdata),
|
|
|
|
|
.name = "GPencil",
|
|
|
|
|
.name_plural = "grease_pencils",
|
|
|
|
|
.translation_context = BLT_I18NCONTEXT_ID_GPENCIL,
|
|
|
|
|
.flags = 0,
|
|
|
|
|
|
|
|
|
|
.init_data = NULL,
|
|
|
|
|
.copy_data = greasepencil_copy_data,
|
|
|
|
|
.free_data = greasepencil_free_data,
|
|
|
|
|
.make_local = NULL,
|
2020-05-20 16:31:47 +02:00
|
|
|
.foreach_id = greasepencil_foreach_id,
|
2020-03-09 17:51:49 +01:00
|
|
|
};
|
|
|
|
|
|
2009-04-20 10:20:18 +00:00
|
|
|
/* ************************************************** */
|
2018-07-31 10:22:19 +02:00
|
|
|
/* Draw Engine */
|
2009-04-20 10:20:18 +00:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
void (*BKE_gpencil_batch_cache_dirty_tag_cb)(bGPdata *gpd) = NULL;
|
|
|
|
|
void (*BKE_gpencil_batch_cache_free_cb)(bGPdata *gpd) = NULL;
|
2018-07-31 10:22:19 +02:00
|
|
|
|
2018-08-23 10:14:29 -03:00
|
|
|
void BKE_gpencil_batch_cache_dirty_tag(bGPdata *gpd)
|
2018-07-31 10:22:19 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
if (gpd) {
|
|
|
|
|
DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
|
|
|
|
|
BKE_gpencil_batch_cache_dirty_tag_cb(gpd);
|
|
|
|
|
}
|
2018-07-31 10:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BKE_gpencil_batch_cache_free(bGPdata *gpd)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
if (gpd) {
|
|
|
|
|
BKE_gpencil_batch_cache_free_cb(gpd);
|
|
|
|
|
}
|
2018-07-31 10:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ************************************************** */
|
|
|
|
|
/* Memory Management */
|
|
|
|
|
|
|
|
|
|
/* clean vertex groups weights */
|
|
|
|
|
void BKE_gpencil_free_point_weights(MDeformVert *dvert)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
if (dvert == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
MEM_SAFE_FREE(dvert->dw);
|
2018-07-31 10:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BKE_gpencil_free_stroke_weights(bGPDstroke *gps)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
if (gps == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-07-31 10:22:19 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
if (gps->dvert == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-07-31 10:22:19 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
for (int i = 0; i < gps->totpoints; i++) {
|
|
|
|
|
MDeformVert *dvert = &gps->dvert[i];
|
|
|
|
|
BKE_gpencil_free_point_weights(dvert);
|
|
|
|
|
}
|
2018-07-31 10:22:19 +02:00
|
|
|
}
|
2009-04-20 10:20:18 +00:00
|
|
|
|
2016-08-03 23:31:48 +02:00
|
|
|
/* free stroke, doesn't unlink from any listbase */
|
2016-08-04 15:03:18 +02:00
|
|
|
void BKE_gpencil_free_stroke(bGPDstroke *gps)
|
2016-08-03 23:31:48 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
if (gps == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
/* free stroke memory arrays, then stroke itself */
|
|
|
|
|
if (gps->points) {
|
|
|
|
|
MEM_freeN(gps->points);
|
|
|
|
|
}
|
|
|
|
|
if (gps->dvert) {
|
|
|
|
|
BKE_gpencil_free_stroke_weights(gps);
|
|
|
|
|
MEM_freeN(gps->dvert);
|
|
|
|
|
}
|
2019-04-22 09:39:35 +10:00
|
|
|
if (gps->triangles) {
|
2019-04-17 06:17:24 +02:00
|
|
|
MEM_freeN(gps->triangles);
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
MEM_freeN(gps);
|
2016-08-03 23:31:48 +02:00
|
|
|
}
|
|
|
|
|
|
2009-04-20 10:20:18 +00:00
|
|
|
/* Free strokes belonging to a gp-frame */
|
2016-08-04 15:03:18 +02:00
|
|
|
bool BKE_gpencil_free_strokes(bGPDframe *gpf)
|
2009-04-20 10:20:18 +00:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bool changed = (BLI_listbase_is_empty(&gpf->strokes) == false);
|
2013-12-03 09:22:29 +11:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* free strokes */
|
2020-03-09 16:27:24 +01:00
|
|
|
LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
|
2019-04-17 06:17:24 +02:00
|
|
|
BKE_gpencil_free_stroke(gps);
|
|
|
|
|
}
|
|
|
|
|
BLI_listbase_clear(&gpf->strokes);
|
2013-11-25 04:55:26 +01:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
return changed;
|
2009-04-20 10:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Free all of a gp-layer's frames */
|
2016-08-04 15:03:18 +02:00
|
|
|
void BKE_gpencil_free_frames(bGPDlayer *gpl)
|
2009-04-20 10:20:18 +00:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPDframe *gpf_next;
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* error checking */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (gpl == NULL) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* free frames */
|
|
|
|
|
for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf_next) {
|
|
|
|
|
gpf_next = gpf->next;
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* free strokes and their associated memory */
|
|
|
|
|
BKE_gpencil_free_strokes(gpf);
|
|
|
|
|
BLI_freelinkN(&gpl->frames, gpf);
|
|
|
|
|
}
|
|
|
|
|
gpl->actframe = NULL;
|
2009-04-20 10:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
void BKE_gpencil_free_layer_masks(bGPDlayer *gpl)
|
|
|
|
|
{
|
|
|
|
|
/* Free masks.*/
|
|
|
|
|
bGPDlayer_Mask *mask_next = NULL;
|
|
|
|
|
for (bGPDlayer_Mask *mask = gpl->mask_layers.first; mask; mask = mask_next) {
|
|
|
|
|
mask_next = mask->next;
|
|
|
|
|
BLI_freelinkN(&gpl->mask_layers, mask);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-31 10:22:19 +02:00
|
|
|
/* Free all of the gp-layers for a viewport (list should be &gpd->layers or so) */
|
|
|
|
|
void BKE_gpencil_free_layers(ListBase *list)
|
2016-08-03 23:31:48 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPDlayer *gpl_next;
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* error checking */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (list == NULL) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* delete layers */
|
|
|
|
|
for (bGPDlayer *gpl = list->first; gpl; gpl = gpl_next) {
|
|
|
|
|
gpl_next = gpl->next;
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* free layers and their data */
|
|
|
|
|
BKE_gpencil_free_frames(gpl);
|
2020-03-09 16:27:24 +01:00
|
|
|
|
|
|
|
|
/* Free masks.*/
|
|
|
|
|
BKE_gpencil_free_layer_masks(gpl);
|
|
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
BLI_freelinkN(list, gpl);
|
|
|
|
|
}
|
2016-08-03 23:31:48 +02:00
|
|
|
}
|
|
|
|
|
|
ID-Remap - Step one: core work (cleanup and rework of generic ID datablock handling).
This commit changes a lot of how IDs are handled internally, especially the unlinking/freeing
processes. So far, this was very fuzy, to summarize cleanly deleting or replacing a datablock
was pretty much impossible, except for a few special cases.
Also, unlinking was handled by each datatype, in a rather messy and prone-to-errors way (quite
a few ID usages were missed or wrongly handled that way).
One of the main goal of id-remap branch was to cleanup this, and fatorize ID links handling
by using library_query utils to allow generic handling of those, which is now the case
(now, generic ID links handling is only "knwon" from readfile.c and library_query.c).
This commit also adds backends to allow live replacement and deletion of datablocks in Blender
(so-called 'remapping' process, where we replace all usages of a given ID pointer by a new one,
or NULL one in case of unlinking).
This will allow nice new features, like ability to easily reload or relocate libraries, real immediate
deletion of datablocks in blender, replacement of one datablock by another, etc.
Some of those are for next commits.
A word of warning: this commit is highly risky, because it affects potentially a lot in Blender core.
Though it was tested rather deeply, being totally impossible to check all possible ID usage cases,
it's likely there are some remaining issues and bugs in new code... Please report them! ;)
Review task: D2027 (https://developer.blender.org/D2027).
Reviewed by campbellbarton, thanks a bunch.
2016-06-22 17:29:38 +02:00
|
|
|
/** Free (or release) any data used by this grease pencil (does not free the gpencil itself). */
|
2018-07-31 10:22:19 +02:00
|
|
|
void BKE_gpencil_free(bGPdata *gpd, bool free_all)
|
2009-04-20 10:20:18 +00:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* free layers */
|
|
|
|
|
BKE_gpencil_free_layers(&gpd->layers);
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* materials */
|
|
|
|
|
MEM_SAFE_FREE(gpd->mat);
|
2018-07-31 10:22:19 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* free all data */
|
|
|
|
|
if (free_all) {
|
|
|
|
|
/* clear cache */
|
|
|
|
|
BKE_gpencil_batch_cache_free(gpd);
|
|
|
|
|
}
|
2009-04-20 10:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
void BKE_gpencil_eval_delete(bGPdata *gpd_eval)
|
|
|
|
|
{
|
|
|
|
|
BKE_gpencil_free(gpd_eval, true);
|
|
|
|
|
BKE_libblock_free_data(&gpd_eval->id, false);
|
|
|
|
|
MEM_freeN(gpd_eval);
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* ************************************************** */
|
|
|
|
|
/* Container Creation */
|
2009-04-20 10:20:18 +00:00
|
|
|
|
|
|
|
|
/* add a new gp-frame to the given layer */
|
2016-08-04 15:03:18 +02:00
|
|
|
bGPDframe *BKE_gpencil_frame_addnew(bGPDlayer *gpl, int cframe)
|
2009-04-20 10:20:18 +00:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPDframe *gpf = NULL, *gf = NULL;
|
|
|
|
|
short state = 0;
|
|
|
|
|
|
|
|
|
|
/* error checking */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (gpl == NULL) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return NULL;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
/* allocate memory for this frame */
|
|
|
|
|
gpf = MEM_callocN(sizeof(bGPDframe), "bGPDframe");
|
|
|
|
|
gpf->framenum = cframe;
|
|
|
|
|
|
|
|
|
|
/* find appropriate place to add frame */
|
|
|
|
|
if (gpl->frames.first) {
|
|
|
|
|
for (gf = gpl->frames.first; gf; gf = gf->next) {
|
|
|
|
|
/* check if frame matches one that is supposed to be added */
|
|
|
|
|
if (gf->framenum == cframe) {
|
|
|
|
|
state = -1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* if current frame has already exceeded the frame to add, add before */
|
|
|
|
|
if (gf->framenum > cframe) {
|
|
|
|
|
BLI_insertlinkbefore(&gpl->frames, gf, gpf);
|
|
|
|
|
state = 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* check whether frame was added successfully */
|
|
|
|
|
if (state == -1) {
|
2019-09-03 18:58:57 +02:00
|
|
|
CLOG_ERROR(
|
|
|
|
|
&LOG, "Frame (%d) existed already for this layer_active. Using existing frame", cframe);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
/* free the newly created one, and use the old one instead */
|
|
|
|
|
MEM_freeN(gpf);
|
|
|
|
|
|
|
|
|
|
/* return existing frame instead... */
|
|
|
|
|
BLI_assert(gf != NULL);
|
|
|
|
|
gpf = gf;
|
|
|
|
|
}
|
|
|
|
|
else if (state == 0) {
|
|
|
|
|
/* add to end then! */
|
|
|
|
|
BLI_addtail(&gpl->frames, gpf);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* return frame */
|
|
|
|
|
return gpf;
|
2009-04-20 10:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
2015-12-13 21:03:13 +13:00
|
|
|
/* add a copy of the active gp-frame to the given layer */
|
2016-08-04 15:03:18 +02:00
|
|
|
bGPDframe *BKE_gpencil_frame_addcopy(bGPDlayer *gpl, int cframe)
|
2015-12-13 21:03:13 +13:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPDframe *new_frame;
|
|
|
|
|
bool found = false;
|
|
|
|
|
|
|
|
|
|
/* Error checking/handling */
|
|
|
|
|
if (gpl == NULL) {
|
|
|
|
|
/* no layer */
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
else if (gpl->actframe == NULL) {
|
|
|
|
|
/* no active frame, so just create a new one from scratch */
|
|
|
|
|
return BKE_gpencil_frame_addnew(gpl, cframe);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Create a copy of the frame */
|
|
|
|
|
new_frame = BKE_gpencil_frame_duplicate(gpl->actframe);
|
|
|
|
|
|
|
|
|
|
/* Find frame to insert it before */
|
2020-03-09 16:27:24 +01:00
|
|
|
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
|
2019-04-17 06:17:24 +02:00
|
|
|
if (gpf->framenum > cframe) {
|
|
|
|
|
/* Add it here */
|
|
|
|
|
BLI_insertlinkbefore(&gpl->frames, gpf, new_frame);
|
|
|
|
|
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else if (gpf->framenum == cframe) {
|
|
|
|
|
/* This only happens when we're editing with framelock on...
|
|
|
|
|
* - Delete the new frame and don't do anything else here...
|
|
|
|
|
*/
|
|
|
|
|
BKE_gpencil_free_strokes(new_frame);
|
|
|
|
|
MEM_freeN(new_frame);
|
|
|
|
|
new_frame = NULL;
|
|
|
|
|
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (found == false) {
|
|
|
|
|
/* Add new frame to the end */
|
|
|
|
|
BLI_addtail(&gpl->frames, new_frame);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Ensure that frame is set up correctly, and return it */
|
|
|
|
|
if (new_frame) {
|
|
|
|
|
new_frame->framenum = cframe;
|
|
|
|
|
gpl->actframe = new_frame;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new_frame;
|
2015-12-13 21:03:13 +13:00
|
|
|
}
|
|
|
|
|
|
2009-04-20 10:20:18 +00:00
|
|
|
/* add a new gp-layer and make it the active layer */
|
2016-08-04 15:03:18 +02:00
|
|
|
bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd, const char *name, bool setactive)
|
2009-04-20 10:20:18 +00:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPDlayer *gpl = NULL;
|
|
|
|
|
bGPDlayer *gpl_active = NULL;
|
|
|
|
|
|
|
|
|
|
/* check that list is ok */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (gpd == NULL) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return NULL;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
/* allocate memory for frame and add to end of list */
|
|
|
|
|
gpl = MEM_callocN(sizeof(bGPDlayer), "bGPDlayer");
|
|
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
gpl_active = BKE_gpencil_layer_active_get(gpd);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
/* add to datablock */
|
|
|
|
|
if (gpl_active == NULL) {
|
|
|
|
|
BLI_addtail(&gpd->layers, gpl);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* if active layer, add after that layer */
|
|
|
|
|
BLI_insertlinkafter(&gpd->layers, gpl_active, gpl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* annotation vs GP Object behavior is slightly different */
|
|
|
|
|
if (gpd->flag & GP_DATA_ANNOTATIONS) {
|
|
|
|
|
/* set default color of new strokes for this layer */
|
|
|
|
|
copy_v4_v4(gpl->color, U.gpencil_new_layer_col);
|
|
|
|
|
gpl->opacity = 1.0f;
|
|
|
|
|
|
|
|
|
|
/* set default thickness of new strokes for this layer */
|
|
|
|
|
gpl->thickness = 3;
|
|
|
|
|
|
|
|
|
|
/* Onion colors */
|
|
|
|
|
ARRAY_SET_ITEMS(gpl->gcolor_prev, 0.302f, 0.851f, 0.302f);
|
|
|
|
|
ARRAY_SET_ITEMS(gpl->gcolor_next, 0.250f, 0.1f, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* thickness parameter represents "thickness change", not absolute thickness */
|
|
|
|
|
gpl->thickness = 0;
|
|
|
|
|
gpl->opacity = 1.0f;
|
|
|
|
|
/* default channel color */
|
|
|
|
|
ARRAY_SET_ITEMS(gpl->color, 0.2f, 0.2f, 0.2f);
|
2020-03-09 16:27:24 +01:00
|
|
|
/* Default vertex mix. */
|
|
|
|
|
gpl->vertex_paint_opacity = 1.0f;
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* auto-name */
|
|
|
|
|
BLI_strncpy(gpl->info, name, sizeof(gpl->info));
|
|
|
|
|
BLI_uniquename(&gpd->layers,
|
|
|
|
|
gpl,
|
|
|
|
|
(gpd->flag & GP_DATA_ANNOTATIONS) ? DATA_("Note") : DATA_("GP_Layer"),
|
|
|
|
|
'.',
|
|
|
|
|
offsetof(bGPDlayer, info),
|
|
|
|
|
sizeof(gpl->info));
|
|
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
/* Enable always affected by scene lights. */
|
|
|
|
|
gpl->flag |= GP_LAYER_USE_LIGHTS;
|
2019-04-17 06:17:24 +02:00
|
|
|
/* make this one the active one */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (setactive) {
|
2020-03-09 16:27:24 +01:00
|
|
|
BKE_gpencil_layer_active_set(gpd, gpl);
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
/* return layer */
|
|
|
|
|
return gpl;
|
2009-04-20 10:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* add a new gp-datablock */
|
|
|
|
|
bGPdata *BKE_gpencil_data_addnew(Main *bmain, const char name[])
|
2016-08-03 23:31:48 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPdata *gpd;
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* allocate memory for a new block */
|
|
|
|
|
gpd = BKE_libblock_alloc(bmain, ID_GD, name, 0);
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* initial settings */
|
|
|
|
|
gpd->flag = (GP_DATA_DISPINFO | GP_DATA_EXPAND);
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* general flags */
|
|
|
|
|
gpd->flag |= GP_DATA_VIEWALIGN;
|
2019-08-01 13:53:25 +10:00
|
|
|
/* always enable object onion skin switch */
|
2019-04-17 06:17:24 +02:00
|
|
|
gpd->flag |= GP_DATA_SHOW_ONIONSKINS;
|
|
|
|
|
/* GP object specific settings */
|
|
|
|
|
ARRAY_SET_ITEMS(gpd->line_color, 0.6f, 0.6f, 0.6f, 0.5f);
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
gpd->pixfactor = GP_DEFAULT_PIX_FACTOR;
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
gpd->zdepth_offset = 0.150f;
|
|
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* grid settings */
|
2019-09-27 23:15:09 +02:00
|
|
|
ARRAY_SET_ITEMS(gpd->grid.color, 0.5f, 0.5f, 0.5f); /* Color */
|
|
|
|
|
ARRAY_SET_ITEMS(gpd->grid.scale, 1.0f, 1.0f); /* Scale */
|
|
|
|
|
gpd->grid.lines = GP_DEFAULT_GRID_LINES; /* Number of lines */
|
2018-10-04 23:27:34 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* onion-skinning settings (datablock level) */
|
|
|
|
|
gpd->onion_flag |= (GP_ONION_GHOST_PREVCOL | GP_ONION_GHOST_NEXTCOL);
|
|
|
|
|
gpd->onion_flag |= GP_ONION_FADE;
|
|
|
|
|
gpd->onion_mode = GP_ONION_MODE_RELATIVE;
|
|
|
|
|
gpd->onion_factor = 0.5f;
|
|
|
|
|
ARRAY_SET_ITEMS(gpd->gcolor_prev, 0.145098f, 0.419608f, 0.137255f); /* green */
|
|
|
|
|
ARRAY_SET_ITEMS(gpd->gcolor_next, 0.125490f, 0.082353f, 0.529412f); /* blue */
|
|
|
|
|
gpd->gstep = 1;
|
|
|
|
|
gpd->gstep_next = 1;
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
return gpd;
|
2018-07-31 10:22:19 +02:00
|
|
|
}
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* ************************************************** */
|
|
|
|
|
/* Primitive Creation */
|
|
|
|
|
/* Utilities for easier bulk-creation of geometry */
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/**
|
|
|
|
|
* Populate stroke with point data from data buffers
|
|
|
|
|
*
|
2018-12-12 12:55:20 +11:00
|
|
|
* \param array: Flat array of point data values. Each entry has GP_PRIM_DATABUF_SIZE values
|
|
|
|
|
* \param mat: 4x4 transform matrix to transform points into the right coordinate space
|
2018-07-31 10:22:19 +02:00
|
|
|
*/
|
2019-04-17 06:17:24 +02:00
|
|
|
void BKE_gpencil_stroke_add_points(bGPDstroke *gps,
|
|
|
|
|
const float *array,
|
|
|
|
|
const int totpoints,
|
|
|
|
|
const float mat[4][4])
|
2018-07-31 10:22:19 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
for (int i = 0; i < totpoints; i++) {
|
|
|
|
|
bGPDspoint *pt = &gps->points[i];
|
|
|
|
|
const int x = GP_PRIM_DATABUF_SIZE * i;
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
pt->x = array[x];
|
|
|
|
|
pt->y = array[x + 1];
|
|
|
|
|
pt->z = array[x + 2];
|
|
|
|
|
mul_m4_v3(mat, &pt->x);
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
pt->pressure = array[x + 3];
|
|
|
|
|
pt->strength = array[x + 4];
|
|
|
|
|
}
|
2018-07-31 10:22:19 +02:00
|
|
|
}
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
/* Create a new stroke, with pre-allocated data buffers. */
|
|
|
|
|
bGPDstroke *BKE_gpencil_stroke_new(int mat_idx, int totpoints, short thickness)
|
2018-07-31 10:22:19 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* allocate memory for a new stroke */
|
|
|
|
|
bGPDstroke *gps = MEM_callocN(sizeof(bGPDstroke), "gp_stroke");
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
gps->thickness = thickness;
|
2020-03-09 16:27:24 +01:00
|
|
|
gps->fill_opacity_fac = 1.0f;
|
|
|
|
|
gps->hardeness = 1.0f;
|
|
|
|
|
copy_v2_fl(gps->aspect_ratio, 1.0f);
|
|
|
|
|
|
|
|
|
|
gps->uv_scale = 1.0f;
|
2019-04-15 10:32:06 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
gps->inittime = 0;
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
gps->flag = GP_STROKE_3DSPACE;
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
gps->totpoints = totpoints;
|
2020-04-02 16:47:52 +02:00
|
|
|
if (gps->totpoints > 0) {
|
|
|
|
|
gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
gps->points = NULL;
|
|
|
|
|
}
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* initialize triangle memory to dummy data */
|
2020-01-07 12:14:36 +01:00
|
|
|
gps->triangles = NULL;
|
2019-04-17 06:17:24 +02:00
|
|
|
gps->tot_triangles = 0;
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
gps->mat_nr = mat_idx;
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
return gps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Create a new stroke and add to frame. */
|
|
|
|
|
bGPDstroke *BKE_gpencil_stroke_add(
|
|
|
|
|
bGPDframe *gpf, int mat_idx, int totpoints, short thickness, const bool insert_at_head)
|
|
|
|
|
{
|
|
|
|
|
bGPDstroke *gps = BKE_gpencil_stroke_new(mat_idx, totpoints, thickness);
|
|
|
|
|
|
|
|
|
|
/* Add to frame. */
|
|
|
|
|
if ((gps != NULL) && (gpf != NULL)) {
|
|
|
|
|
if (!insert_at_head) {
|
|
|
|
|
BLI_addtail(&gpf->strokes, gps);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
BLI_addhead(&gpf->strokes, gps);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
return gps;
|
2016-08-03 23:31:48 +02:00
|
|
|
}
|
|
|
|
|
|
2019-11-14 19:18:23 +01:00
|
|
|
/* Add a stroke and copy the temporary drawing color value from one of the existing stroke */
|
2020-03-09 16:27:24 +01:00
|
|
|
bGPDstroke *BKE_gpencil_stroke_add_existing_style(
|
2019-11-14 19:18:23 +01:00
|
|
|
bGPDframe *gpf, bGPDstroke *existing, int mat_idx, int totpoints, short thickness)
|
|
|
|
|
{
|
2020-03-09 16:27:24 +01:00
|
|
|
bGPDstroke *gps = BKE_gpencil_stroke_add(gpf, mat_idx, totpoints, thickness, false);
|
2019-11-25 00:55:11 +11:00
|
|
|
/* Copy run-time color data so that strokes added in the modifier has the style.
|
|
|
|
|
* There are depsgraph reference pointers inside,
|
2019-11-14 19:18:23 +01:00
|
|
|
* change the copy function if interfere with future drawing implementation. */
|
|
|
|
|
memcpy(&gps->runtime, &existing->runtime, sizeof(bGPDstroke_Runtime));
|
|
|
|
|
return gps;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* ************************************************** */
|
|
|
|
|
/* Data Duplication */
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* make a copy of a given gpencil weights */
|
|
|
|
|
void BKE_gpencil_stroke_weights_duplicate(bGPDstroke *gps_src, bGPDstroke *gps_dst)
|
2016-08-03 23:31:48 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
if (gps_src == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
BLI_assert(gps_src->totpoints == gps_dst->totpoints);
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
BKE_defvert_array_copy(gps_dst->dvert, gps_src->dvert, gps_src->totpoints);
|
2016-08-03 23:31:48 +02:00
|
|
|
}
|
|
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* make a copy of a given gpencil stroke */
|
2020-03-09 16:27:24 +01:00
|
|
|
bGPDstroke *BKE_gpencil_stroke_duplicate(bGPDstroke *gps_src, const bool dup_points)
|
2009-04-20 10:20:18 +00:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPDstroke *gps_dst = NULL;
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
gps_dst = MEM_dupallocN(gps_src);
|
|
|
|
|
gps_dst->prev = gps_dst->next = NULL;
|
2020-03-09 16:27:24 +01:00
|
|
|
gps_dst->triangles = MEM_dupallocN(gps_src->triangles);
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
if (dup_points) {
|
|
|
|
|
gps_dst->points = MEM_dupallocN(gps_src->points);
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
if (gps_src->dvert != NULL) {
|
|
|
|
|
gps_dst->dvert = MEM_dupallocN(gps_src->dvert);
|
|
|
|
|
BKE_gpencil_stroke_weights_duplicate(gps_src, gps_dst);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
gps_dst->dvert = NULL;
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2018-07-31 10:22:19 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* return new stroke */
|
|
|
|
|
return gps_dst;
|
2009-04-20 10:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* make a copy of a given gpencil frame */
|
2016-08-04 15:03:18 +02:00
|
|
|
bGPDframe *BKE_gpencil_frame_duplicate(const bGPDframe *gpf_src)
|
2009-04-20 10:20:18 +00:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPDstroke *gps_dst = NULL;
|
|
|
|
|
bGPDframe *gpf_dst;
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* error checking */
|
|
|
|
|
if (gpf_src == NULL) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* make a copy of the source frame */
|
|
|
|
|
gpf_dst = MEM_dupallocN(gpf_src);
|
|
|
|
|
gpf_dst->prev = gpf_dst->next = NULL;
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* copy strokes */
|
|
|
|
|
BLI_listbase_clear(&gpf_dst->strokes);
|
2020-04-03 19:15:01 +02:00
|
|
|
LISTBASE_FOREACH (bGPDstroke *, gps_src, &gpf_src->strokes) {
|
2019-04-17 06:17:24 +02:00
|
|
|
/* make copy of source stroke */
|
2020-03-09 16:27:24 +01:00
|
|
|
gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true);
|
2019-04-17 06:17:24 +02:00
|
|
|
BLI_addtail(&gpf_dst->strokes, gps_dst);
|
|
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* return new frame */
|
|
|
|
|
return gpf_dst;
|
2016-08-03 23:31:48 +02:00
|
|
|
}
|
|
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* make a copy of strokes between gpencil frames */
|
|
|
|
|
void BKE_gpencil_frame_copy_strokes(bGPDframe *gpf_src, struct bGPDframe *gpf_dst)
|
2016-08-03 23:31:48 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPDstroke *gps_dst = NULL;
|
|
|
|
|
/* error checking */
|
|
|
|
|
if ((gpf_src == NULL) || (gpf_dst == NULL)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* copy strokes */
|
|
|
|
|
BLI_listbase_clear(&gpf_dst->strokes);
|
2020-04-03 19:15:01 +02:00
|
|
|
LISTBASE_FOREACH (bGPDstroke *, gps_src, &gpf_src->strokes) {
|
2019-04-17 06:17:24 +02:00
|
|
|
/* make copy of source stroke */
|
2020-03-09 16:27:24 +01:00
|
|
|
gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true);
|
2019-04-17 06:17:24 +02:00
|
|
|
BLI_addtail(&gpf_dst->strokes, gps_dst);
|
|
|
|
|
}
|
2016-08-03 23:31:48 +02:00
|
|
|
}
|
2018-07-31 10:22:19 +02:00
|
|
|
|
2009-04-20 10:20:18 +00:00
|
|
|
/* make a copy of a given gpencil layer */
|
2016-08-04 15:03:18 +02:00
|
|
|
bGPDlayer *BKE_gpencil_layer_duplicate(const bGPDlayer *gpl_src)
|
2009-04-20 10:20:18 +00:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
const bGPDframe *gpf_src;
|
|
|
|
|
bGPDframe *gpf_dst;
|
|
|
|
|
bGPDlayer *gpl_dst;
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* error checking */
|
|
|
|
|
if (gpl_src == NULL) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* make a copy of source layer */
|
|
|
|
|
gpl_dst = MEM_dupallocN(gpl_src);
|
|
|
|
|
gpl_dst->prev = gpl_dst->next = NULL;
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
/* Copy masks. */
|
|
|
|
|
BLI_listbase_clear(&gpl_dst->mask_layers);
|
|
|
|
|
LISTBASE_FOREACH (bGPDlayer_Mask *, mask_src, &gpl_src->mask_layers) {
|
|
|
|
|
bGPDlayer_Mask *mask_dst = MEM_dupallocN(mask_src);
|
|
|
|
|
mask_dst->prev = mask_dst->next = NULL;
|
|
|
|
|
BLI_addtail(&gpl_dst->mask_layers, mask_dst);
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* copy frames */
|
|
|
|
|
BLI_listbase_clear(&gpl_dst->frames);
|
|
|
|
|
for (gpf_src = gpl_src->frames.first; gpf_src; gpf_src = gpf_src->next) {
|
|
|
|
|
/* make a copy of source frame */
|
|
|
|
|
gpf_dst = BKE_gpencil_frame_duplicate(gpf_src);
|
|
|
|
|
BLI_addtail(&gpl_dst->frames, gpf_dst);
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* if source frame was the current layer's 'active' frame, reassign that too */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (gpf_src == gpl_dst->actframe) {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl_dst->actframe = gpf_dst;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* return new layer */
|
|
|
|
|
return gpl_dst;
|
2009-04-20 10:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* Standard API to make a copy of GP datablock, separate from copying its data */
|
|
|
|
|
bGPdata *BKE_gpencil_copy(Main *bmain, const bGPdata *gpd)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPdata *gpd_copy;
|
|
|
|
|
BKE_id_copy(bmain, &gpd->id, (ID **)&gpd_copy);
|
|
|
|
|
return gpd_copy;
|
Refactor ID copying (and to some extent, ID freeing).
This will allow much finer controll over how we copy data-blocks, from
full copy in Main database, to "lighter" ones (out of Main, inside an
already allocated datablock, etc.).
This commit also transfers a llot of what was previously handled by
per-ID-type custom code to generic ID handling code in BKE_library.
Hopefully will avoid in future inconsistencies and missing bits we had
all over the codebase in the past.
It also adds missing copying handling for a few types, most notably
Scene (which where using a fully customized handling previously).
Note that the type of allocation used during copying (regular in Main,
allocated but outside of Main, or not allocated by ID handling code at
all) is stored in ID's, which allows to handle them correctly when
freeing. This needs to be taken care of with caution when doing 'weird'
unusual things with ID copying and/or allocation!
As a final note, while rather noisy, this commit will hopefully not
break too much existing branches, old 'API' has been kept for the main
part, as a wrapper around new code. Cleaning it up will happen later.
Design task : T51804
Phab Diff: D2714
2017-08-07 16:39:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* make a copy of a given gpencil datablock */
|
2019-09-27 23:15:09 +02:00
|
|
|
/* XXX: Should this be deprecated? */
|
Refactor ID copying (and to some extent, ID freeing).
This will allow much finer controll over how we copy data-blocks, from
full copy in Main database, to "lighter" ones (out of Main, inside an
already allocated datablock, etc.).
This commit also transfers a llot of what was previously handled by
per-ID-type custom code to generic ID handling code in BKE_library.
Hopefully will avoid in future inconsistencies and missing bits we had
all over the codebase in the past.
It also adds missing copying handling for a few types, most notably
Scene (which where using a fully customized handling previously).
Note that the type of allocation used during copying (regular in Main,
allocated but outside of Main, or not allocated by ID handling code at
all) is stored in ID's, which allows to handle them correctly when
freeing. This needs to be taken care of with caution when doing 'weird'
unusual things with ID copying and/or allocation!
As a final note, while rather noisy, this commit will hopefully not
break too much existing branches, old 'API' has been kept for the main
part, as a wrapper around new code. Cleaning it up will happen later.
Design task : T51804
Phab Diff: D2714
2017-08-07 16:39:55 +02:00
|
|
|
bGPdata *BKE_gpencil_data_duplicate(Main *bmain, const bGPdata *gpd_src, bool internal_copy)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPdata *gpd_dst;
|
2018-07-31 10:22:19 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* Yuck and super-uber-hyper yuck!!!
|
|
|
|
|
* Should be replaceable with a no-main copy (LIB_ID_COPY_NO_MAIN etc.), but not sure about it,
|
|
|
|
|
* so for now keep old code for that one. */
|
Refactor ID copying (and to some extent, ID freeing).
This will allow much finer controll over how we copy data-blocks, from
full copy in Main database, to "lighter" ones (out of Main, inside an
already allocated datablock, etc.).
This commit also transfers a llot of what was previously handled by
per-ID-type custom code to generic ID handling code in BKE_library.
Hopefully will avoid in future inconsistencies and missing bits we had
all over the codebase in the past.
It also adds missing copying handling for a few types, most notably
Scene (which where using a fully customized handling previously).
Note that the type of allocation used during copying (regular in Main,
allocated but outside of Main, or not allocated by ID handling code at
all) is stored in ID's, which allows to handle them correctly when
freeing. This needs to be taken care of with caution when doing 'weird'
unusual things with ID copying and/or allocation!
As a final note, while rather noisy, this commit will hopefully not
break too much existing branches, old 'API' has been kept for the main
part, as a wrapper around new code. Cleaning it up will happen later.
Design task : T51804
Phab Diff: D2714
2017-08-07 16:39:55 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* error checking */
|
|
|
|
|
if (gpd_src == NULL) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2018-07-31 10:22:19 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
if (internal_copy) {
|
|
|
|
|
/* make a straight copy for undo buffers used during stroke drawing */
|
|
|
|
|
gpd_dst = MEM_dupallocN(gpd_src);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
BLI_assert(bmain != NULL);
|
|
|
|
|
BKE_id_copy(bmain, &gpd_src->id, (ID **)&gpd_dst);
|
|
|
|
|
}
|
2018-07-31 10:22:19 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* Copy internal data (layers, etc.) */
|
2020-03-09 17:51:49 +01:00
|
|
|
greasepencil_copy_data(bmain, &gpd_dst->id, &gpd_src->id, 0);
|
2018-07-31 10:22:19 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* return new */
|
|
|
|
|
return gpd_dst;
|
2009-04-20 10:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* ************************************************** */
|
|
|
|
|
/* GP Stroke API */
|
Grease Pencil - Storyboarding Features (merge from GPencil_EditStrokes branch)
This merge-commit brings in a number of new features and workflow/UI improvements for
working with Grease Pencil. While these were originally targetted at improving
the workflow for creating 3D storyboards in Blender using the Grease Pencil,
many of these changes should also prove useful in other workflows too.
The main highlights here are:
1) It is now possible to edit Grease Pencil strokes
- Use D Tab, or toggle the "Enable Editing" toggles in the Toolbar/Properties regions
to enter "Stroke Edit Mode". In this mode, many common editing tools will
operate on Grease Pencil stroke points instead.
- Tools implemented include Select, Select All/Border/Circle/Linked/More/Less,
Grab, Rotate, Scale, Bend, Shear, To Sphere, Mirror, Duplicate, Delete.
- Proportional Editing works when using the transform tools
2) Grease Pencil stroke settings can now be animated
NOTE: Currently drivers don't work, but if time allows, this may still be
added before the release.
3) Strokes can be drawn with "filled" interiors, using a separate set of
colour/opacity settings to the ones used for the lines themselves.
This makes use of OpenGL filled polys, which has the limitation of only
being able to fill convex shapes. Some artifacts may be visible on concave
shapes (e.g. pacman's mouth will be overdrawn)
4) "Volumetric Strokes" - An alternative drawing technique for stroke drawing
has been added which draws strokes as a series of screen-aligned discs.
While this was originally a partial experimental technique at getting better
quality 3D lines, the effects possible using this technique were interesting
enough to warrant making this a dedicated feature. Best results when partial
opacity and large stroke widths are used.
5) Improved Onion Skinning Support
- Different colours can be selected for the before/after ghosts. To do so,
enable the "colour wheel" toggle beside the Onion Skinning toggle, and set
the colours accordingly.
- Different numbers of ghosts can be shown before/after the current frame
6) Grease Pencil datablocks are now attached to the scene by default instead of
the active object.
- For a long time, the object-attachment has proved to be quite problematic
for users to keep track of. Now that this is done at scene level, it is
easier for most users to use.
- An exception for old files (and for any addons which may benefit from object
attachment instead), is that if the active object has a Grease Pencil datablock,
that will be used instead.
- It is not currently possible to choose object-attachment from the UI, but
it is simple to do this from the console instead, by doing:
context.active_object.grease_pencil = bpy.data.grease_pencil["blah"]
7) Various UI Cleanups
- The layers UI has been cleaned up to use a list instead of the nested-panels
design. Apart from saving space, this is also much nicer to look at now.
- The UI code is now all defined in Python. To support this, it has been necessary
to add some new context properties to make it easier to access these settings.
e.g. "gpencil_data" for the datablock
"active_gpencil_layer" and "active_gpencil_frame" for active data,
"editable_gpencil_strokes" for the strokes that can be edited
- The "stroke placement/alignment" settings (previously "Drawing Settings" at the
bottom of the Grease Pencil panel in the Properties Region) is now located in
the toolbar. These were more toolsettings than properties for how GPencil got drawn.
- "Use Sketching Sessions" has been renamed "Continuous Drawing", as per a
suggestion for an earlier discussion on developer.blender.org
- By default, the painting operator will wait for a mouse button to be pressed
before it starts creating the stroke. This is to make it easier to include
this operator in various toolbars/menus/etc. To get it immediately starting
(as when you hold down DKEy to draw), set "wait_for_input" to False.
- GPencil Layers can be rearranged in the "Grease Pencil" mode of the Action Editor
- Toolbar panels have been added to all the other editors which support these.
8) Pie menus for quick-access to tools
A set of experimental pie menus has been included for quick access to many
tools and settings. It is not necessary to use these to get things done,
but they have been designed to help make certain common tasks easier.
- Ctrl-D = The main pie menu. Reveals tools in a context sensitive and
spatially stable manner.
- D Q = "Quick Settings" pie. This allows quick access to the active
layer's settings. Notably, colours, thickness, and turning
onion skinning on/off.
2014-12-01 01:52:06 +13:00
|
|
|
|
|
|
|
|
/* ensure selection status of stroke is in sync with its points */
|
2016-08-04 15:03:18 +02:00
|
|
|
void BKE_gpencil_stroke_sync_selection(bGPDstroke *gps)
|
Grease Pencil - Storyboarding Features (merge from GPencil_EditStrokes branch)
This merge-commit brings in a number of new features and workflow/UI improvements for
working with Grease Pencil. While these were originally targetted at improving
the workflow for creating 3D storyboards in Blender using the Grease Pencil,
many of these changes should also prove useful in other workflows too.
The main highlights here are:
1) It is now possible to edit Grease Pencil strokes
- Use D Tab, or toggle the "Enable Editing" toggles in the Toolbar/Properties regions
to enter "Stroke Edit Mode". In this mode, many common editing tools will
operate on Grease Pencil stroke points instead.
- Tools implemented include Select, Select All/Border/Circle/Linked/More/Less,
Grab, Rotate, Scale, Bend, Shear, To Sphere, Mirror, Duplicate, Delete.
- Proportional Editing works when using the transform tools
2) Grease Pencil stroke settings can now be animated
NOTE: Currently drivers don't work, but if time allows, this may still be
added before the release.
3) Strokes can be drawn with "filled" interiors, using a separate set of
colour/opacity settings to the ones used for the lines themselves.
This makes use of OpenGL filled polys, which has the limitation of only
being able to fill convex shapes. Some artifacts may be visible on concave
shapes (e.g. pacman's mouth will be overdrawn)
4) "Volumetric Strokes" - An alternative drawing technique for stroke drawing
has been added which draws strokes as a series of screen-aligned discs.
While this was originally a partial experimental technique at getting better
quality 3D lines, the effects possible using this technique were interesting
enough to warrant making this a dedicated feature. Best results when partial
opacity and large stroke widths are used.
5) Improved Onion Skinning Support
- Different colours can be selected for the before/after ghosts. To do so,
enable the "colour wheel" toggle beside the Onion Skinning toggle, and set
the colours accordingly.
- Different numbers of ghosts can be shown before/after the current frame
6) Grease Pencil datablocks are now attached to the scene by default instead of
the active object.
- For a long time, the object-attachment has proved to be quite problematic
for users to keep track of. Now that this is done at scene level, it is
easier for most users to use.
- An exception for old files (and for any addons which may benefit from object
attachment instead), is that if the active object has a Grease Pencil datablock,
that will be used instead.
- It is not currently possible to choose object-attachment from the UI, but
it is simple to do this from the console instead, by doing:
context.active_object.grease_pencil = bpy.data.grease_pencil["blah"]
7) Various UI Cleanups
- The layers UI has been cleaned up to use a list instead of the nested-panels
design. Apart from saving space, this is also much nicer to look at now.
- The UI code is now all defined in Python. To support this, it has been necessary
to add some new context properties to make it easier to access these settings.
e.g. "gpencil_data" for the datablock
"active_gpencil_layer" and "active_gpencil_frame" for active data,
"editable_gpencil_strokes" for the strokes that can be edited
- The "stroke placement/alignment" settings (previously "Drawing Settings" at the
bottom of the Grease Pencil panel in the Properties Region) is now located in
the toolbar. These were more toolsettings than properties for how GPencil got drawn.
- "Use Sketching Sessions" has been renamed "Continuous Drawing", as per a
suggestion for an earlier discussion on developer.blender.org
- By default, the painting operator will wait for a mouse button to be pressed
before it starts creating the stroke. This is to make it easier to include
this operator in various toolbars/menus/etc. To get it immediately starting
(as when you hold down DKEy to draw), set "wait_for_input" to False.
- GPencil Layers can be rearranged in the "Grease Pencil" mode of the Action Editor
- Toolbar panels have been added to all the other editors which support these.
8) Pie menus for quick-access to tools
A set of experimental pie menus has been included for quick access to many
tools and settings. It is not necessary to use these to get things done,
but they have been designed to help make certain common tasks easier.
- Ctrl-D = The main pie menu. Reveals tools in a context sensitive and
spatially stable manner.
- D Q = "Quick Settings" pie. This allows quick access to the active
layer's settings. Notably, colours, thickness, and turning
onion skinning on/off.
2014-12-01 01:52:06 +13:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPDspoint *pt;
|
|
|
|
|
int i;
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* error checking */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (gps == NULL) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* we'll stop when we find the first selected point,
|
|
|
|
|
* so initially, we must deselect
|
|
|
|
|
*/
|
|
|
|
|
gps->flag &= ~GP_STROKE_SELECT;
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
|
|
|
|
|
if (pt->flag & GP_SPOINT_SELECT) {
|
|
|
|
|
gps->flag |= GP_STROKE_SELECT;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
Grease Pencil - Storyboarding Features (merge from GPencil_EditStrokes branch)
This merge-commit brings in a number of new features and workflow/UI improvements for
working with Grease Pencil. While these were originally targetted at improving
the workflow for creating 3D storyboards in Blender using the Grease Pencil,
many of these changes should also prove useful in other workflows too.
The main highlights here are:
1) It is now possible to edit Grease Pencil strokes
- Use D Tab, or toggle the "Enable Editing" toggles in the Toolbar/Properties regions
to enter "Stroke Edit Mode". In this mode, many common editing tools will
operate on Grease Pencil stroke points instead.
- Tools implemented include Select, Select All/Border/Circle/Linked/More/Less,
Grab, Rotate, Scale, Bend, Shear, To Sphere, Mirror, Duplicate, Delete.
- Proportional Editing works when using the transform tools
2) Grease Pencil stroke settings can now be animated
NOTE: Currently drivers don't work, but if time allows, this may still be
added before the release.
3) Strokes can be drawn with "filled" interiors, using a separate set of
colour/opacity settings to the ones used for the lines themselves.
This makes use of OpenGL filled polys, which has the limitation of only
being able to fill convex shapes. Some artifacts may be visible on concave
shapes (e.g. pacman's mouth will be overdrawn)
4) "Volumetric Strokes" - An alternative drawing technique for stroke drawing
has been added which draws strokes as a series of screen-aligned discs.
While this was originally a partial experimental technique at getting better
quality 3D lines, the effects possible using this technique were interesting
enough to warrant making this a dedicated feature. Best results when partial
opacity and large stroke widths are used.
5) Improved Onion Skinning Support
- Different colours can be selected for the before/after ghosts. To do so,
enable the "colour wheel" toggle beside the Onion Skinning toggle, and set
the colours accordingly.
- Different numbers of ghosts can be shown before/after the current frame
6) Grease Pencil datablocks are now attached to the scene by default instead of
the active object.
- For a long time, the object-attachment has proved to be quite problematic
for users to keep track of. Now that this is done at scene level, it is
easier for most users to use.
- An exception for old files (and for any addons which may benefit from object
attachment instead), is that if the active object has a Grease Pencil datablock,
that will be used instead.
- It is not currently possible to choose object-attachment from the UI, but
it is simple to do this from the console instead, by doing:
context.active_object.grease_pencil = bpy.data.grease_pencil["blah"]
7) Various UI Cleanups
- The layers UI has been cleaned up to use a list instead of the nested-panels
design. Apart from saving space, this is also much nicer to look at now.
- The UI code is now all defined in Python. To support this, it has been necessary
to add some new context properties to make it easier to access these settings.
e.g. "gpencil_data" for the datablock
"active_gpencil_layer" and "active_gpencil_frame" for active data,
"editable_gpencil_strokes" for the strokes that can be edited
- The "stroke placement/alignment" settings (previously "Drawing Settings" at the
bottom of the Grease Pencil panel in the Properties Region) is now located in
the toolbar. These were more toolsettings than properties for how GPencil got drawn.
- "Use Sketching Sessions" has been renamed "Continuous Drawing", as per a
suggestion for an earlier discussion on developer.blender.org
- By default, the painting operator will wait for a mouse button to be pressed
before it starts creating the stroke. This is to make it easier to include
this operator in various toolbars/menus/etc. To get it immediately starting
(as when you hold down DKEy to draw), set "wait_for_input" to False.
- GPencil Layers can be rearranged in the "Grease Pencil" mode of the Action Editor
- Toolbar panels have been added to all the other editors which support these.
8) Pie menus for quick-access to tools
A set of experimental pie menus has been included for quick access to many
tools and settings. It is not necessary to use these to get things done,
but they have been designed to help make certain common tasks easier.
- Ctrl-D = The main pie menu. Reveals tools in a context sensitive and
spatially stable manner.
- D Q = "Quick Settings" pie. This allows quick access to the active
layer's settings. Notably, colours, thickness, and turning
onion skinning on/off.
2014-12-01 01:52:06 +13:00
|
|
|
}
|
|
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* ************************************************** */
|
|
|
|
|
/* GP Frame API */
|
2009-04-20 10:20:18 +00:00
|
|
|
|
|
|
|
|
/* delete the last stroke of the given frame */
|
2016-08-04 15:03:18 +02:00
|
|
|
void BKE_gpencil_frame_delete_laststroke(bGPDlayer *gpl, bGPDframe *gpf)
|
2009-04-20 10:20:18 +00:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPDstroke *gps = (gpf) ? gpf->strokes.last : NULL;
|
|
|
|
|
int cfra = (gpf) ? gpf->framenum : 0; /* assume that the current frame was not locked */
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* error checking */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (ELEM(NULL, gpf, gps)) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* free the stroke and its data */
|
|
|
|
|
if (gps->points) {
|
|
|
|
|
MEM_freeN(gps->points);
|
|
|
|
|
}
|
|
|
|
|
if (gps->dvert) {
|
|
|
|
|
BKE_gpencil_free_stroke_weights(gps);
|
|
|
|
|
MEM_freeN(gps->dvert);
|
|
|
|
|
}
|
|
|
|
|
MEM_freeN(gps->triangles);
|
|
|
|
|
BLI_freelinkN(&gpf->strokes, gps);
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* if frame has no strokes after this, delete it */
|
|
|
|
|
if (BLI_listbase_is_empty(&gpf->strokes)) {
|
2020-03-09 16:27:24 +01:00
|
|
|
BKE_gpencil_layer_frame_delete(gpl, gpf);
|
|
|
|
|
BKE_gpencil_layer_frame_get(gpl, cfra, GP_GETFRAME_USE_PREV);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2009-04-20 10:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* ************************************************** */
|
|
|
|
|
/* GP Layer API */
|
2009-04-20 10:20:18 +00:00
|
|
|
|
2016-02-09 02:44:02 +13:00
|
|
|
/* Check if the given layer is able to be edited or not */
|
2020-03-09 16:27:24 +01:00
|
|
|
bool BKE_gpencil_layer_is_editable(const bGPDlayer *gpl)
|
2016-02-09 02:44:02 +13:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* Sanity check */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (gpl == NULL) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return false;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* Layer must be: Visible + Editable */
|
|
|
|
|
if ((gpl->flag & (GP_LAYER_HIDE | GP_LAYER_LOCKED)) == 0) {
|
|
|
|
|
/* Opacity must be sufficiently high that it is still "visible"
|
|
|
|
|
* Otherwise, it's not really "visible" to the user, so no point editing...
|
|
|
|
|
*/
|
|
|
|
|
if (gpl->opacity > GPENCIL_ALPHA_OPACITY_THRESH) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* Something failed */
|
|
|
|
|
return false;
|
2016-02-09 02:44:02 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Look up the gp-frame on the requested frame number, but don't add a new one */
|
2020-03-09 16:27:24 +01:00
|
|
|
bGPDframe *BKE_gpencil_layer_frame_find(bGPDlayer *gpl, int cframe)
|
2012-06-08 22:07:57 +00:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPDframe *gpf;
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* Search in reverse order, since this is often used for playback/adding,
|
|
|
|
|
* where it's less likely that we're interested in the earlier frames
|
|
|
|
|
*/
|
|
|
|
|
for (gpf = gpl->frames.last; gpf; gpf = gpf->prev) {
|
|
|
|
|
if (gpf->framenum == cframe) {
|
|
|
|
|
return gpf;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
return NULL;
|
2012-06-08 22:07:57 +00:00
|
|
|
}
|
|
|
|
|
|
2009-04-20 10:20:18 +00:00
|
|
|
/* get the appropriate gp-frame from a given layer
|
2018-11-14 12:53:15 +11:00
|
|
|
* - this sets the layer's actframe var (if allowed to)
|
|
|
|
|
* - extension beyond range (if first gp-frame is after all frame in interest and cannot add)
|
2009-04-20 10:20:18 +00:00
|
|
|
*/
|
2020-03-09 16:27:24 +01:00
|
|
|
bGPDframe *BKE_gpencil_layer_frame_get(bGPDlayer *gpl, int cframe, eGP_GetFrame_Mode addnew)
|
2009-04-20 10:20:18 +00:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPDframe *gpf = NULL;
|
2019-09-13 13:17:50 +02:00
|
|
|
bool found = false;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
/* error checking */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (gpl == NULL) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return NULL;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
/* check if there is already an active frame */
|
|
|
|
|
if (gpl->actframe) {
|
|
|
|
|
gpf = gpl->actframe;
|
|
|
|
|
|
|
|
|
|
/* do not allow any changes to layer's active frame if layer is locked from changes
|
|
|
|
|
* or if the layer has been set to stay on the current frame
|
|
|
|
|
*/
|
2019-04-22 09:39:35 +10:00
|
|
|
if (gpl->flag & GP_LAYER_FRAMELOCK) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return gpf;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
/* do not allow any changes to actframe if frame has painting tag attached to it */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (gpf->flag & GP_FRAME_PAINT) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return gpf;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
/* try to find matching frame */
|
|
|
|
|
if (gpf->framenum < cframe) {
|
|
|
|
|
for (; gpf; gpf = gpf->next) {
|
|
|
|
|
if (gpf->framenum == cframe) {
|
2019-09-13 13:17:50 +02:00
|
|
|
found = true;
|
2019-04-17 06:17:24 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else if ((gpf->next) && (gpf->next->framenum > cframe)) {
|
2019-09-13 13:17:50 +02:00
|
|
|
found = true;
|
2019-04-17 06:17:24 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* set the appropriate frame */
|
|
|
|
|
if (addnew) {
|
2019-04-22 09:39:35 +10:00
|
|
|
if ((found) && (gpf->framenum == cframe)) {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = gpf;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
|
|
|
|
else if (addnew == GP_GETFRAME_ADD_COPY) {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = BKE_gpencil_frame_addcopy(gpl, cframe);
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
|
|
|
|
else {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = BKE_gpencil_frame_addnew(gpl, cframe);
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2019-04-22 09:39:35 +10:00
|
|
|
else if (found) {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = gpf;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
|
|
|
|
else {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = gpl->frames.last;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
for (; gpf; gpf = gpf->prev) {
|
|
|
|
|
if (gpf->framenum <= cframe) {
|
2019-09-13 13:17:50 +02:00
|
|
|
found = true;
|
2019-04-17 06:17:24 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* set the appropriate frame */
|
|
|
|
|
if (addnew) {
|
2019-04-22 09:39:35 +10:00
|
|
|
if ((found) && (gpf->framenum == cframe)) {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = gpf;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
|
|
|
|
else if (addnew == GP_GETFRAME_ADD_COPY) {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = BKE_gpencil_frame_addcopy(gpl, cframe);
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
|
|
|
|
else {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = BKE_gpencil_frame_addnew(gpl, cframe);
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2019-04-22 09:39:35 +10:00
|
|
|
else if (found) {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = gpf;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
|
|
|
|
else {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = gpl->frames.first;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (gpl->frames.first) {
|
|
|
|
|
/* check which of the ends to start checking from */
|
|
|
|
|
const int first = ((bGPDframe *)(gpl->frames.first))->framenum;
|
|
|
|
|
const int last = ((bGPDframe *)(gpl->frames.last))->framenum;
|
|
|
|
|
|
|
|
|
|
if (abs(cframe - first) > abs(cframe - last)) {
|
|
|
|
|
/* find gp-frame which is less than or equal to cframe */
|
|
|
|
|
for (gpf = gpl->frames.last; gpf; gpf = gpf->prev) {
|
|
|
|
|
if (gpf->framenum <= cframe) {
|
2019-09-13 13:17:50 +02:00
|
|
|
found = true;
|
2019-04-17 06:17:24 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* find gp-frame which is less than or equal to cframe */
|
|
|
|
|
for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
|
|
|
|
|
if (gpf->framenum <= cframe) {
|
2019-09-13 13:17:50 +02:00
|
|
|
found = true;
|
2019-04-17 06:17:24 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* set the appropriate frame */
|
|
|
|
|
if (addnew) {
|
2019-04-22 09:39:35 +10:00
|
|
|
if ((found) && (gpf->framenum == cframe)) {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = gpf;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
|
|
|
|
else {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = BKE_gpencil_frame_addnew(gpl, cframe);
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2019-04-22 09:39:35 +10:00
|
|
|
else if (found) {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = gpf;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
else {
|
2020-01-20 16:18:04 +01:00
|
|
|
/* If delete first frame, need to find one. */
|
|
|
|
|
if (gpl->frames.first != NULL) {
|
|
|
|
|
gpl->actframe = gpl->frames.first;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* unresolved errogenous situation! */
|
|
|
|
|
CLOG_STR_ERROR(&LOG, "cannot find appropriate gp-frame");
|
|
|
|
|
/* gpl->actframe should still be NULL */
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* currently no frames (add if allowed to) */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (addnew) {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = BKE_gpencil_frame_addnew(gpl, cframe);
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
else {
|
|
|
|
|
/* don't do anything... this may be when no frames yet! */
|
|
|
|
|
/* gpl->actframe should still be NULL */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* return */
|
|
|
|
|
return gpl->actframe;
|
2009-04-20 10:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* delete the given frame from a layer */
|
2020-03-09 16:27:24 +01:00
|
|
|
bool BKE_gpencil_layer_frame_delete(bGPDlayer *gpl, bGPDframe *gpf)
|
2009-04-20 10:20:18 +00:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bool changed = false;
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* error checking */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (ELEM(NULL, gpl, gpf)) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return false;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* if this frame was active, make the previous frame active instead
|
|
|
|
|
* since it's tricky to set active frame otherwise
|
|
|
|
|
*/
|
2019-04-22 09:39:35 +10:00
|
|
|
if (gpl->actframe == gpf) {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->actframe = gpf->prev;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* free the frame and its data */
|
|
|
|
|
changed = BKE_gpencil_free_strokes(gpf);
|
|
|
|
|
BLI_freelinkN(&gpl->frames, gpf);
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
return changed;
|
2009-04-20 10:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
bGPDlayer *BKE_gpencil_layer_named_get(bGPdata *gpd, const char *name)
|
|
|
|
|
{
|
|
|
|
|
if (name[0] == '\0') {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
return BLI_findstring(&gpd->layers, name, offsetof(bGPDlayer, info));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bGPDlayer_Mask *BKE_gpencil_layer_mask_named_get(bGPDlayer *gpl, const char *name)
|
|
|
|
|
{
|
|
|
|
|
if (name[0] == '\0') {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
return BLI_findstring(&gpl->mask_layers, name, offsetof(bGPDlayer_Mask, name));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bGPDlayer_Mask *BKE_gpencil_layer_mask_add(bGPDlayer *gpl, const char *name)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
bGPDlayer_Mask *mask = MEM_callocN(sizeof(bGPDlayer_Mask), "bGPDlayer_Mask");
|
|
|
|
|
BLI_addtail(&gpl->mask_layers, mask);
|
|
|
|
|
BLI_strncpy(mask->name, name, sizeof(mask->name));
|
|
|
|
|
gpl->act_mask++;
|
|
|
|
|
|
|
|
|
|
return mask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BKE_gpencil_layer_mask_remove(bGPDlayer *gpl, bGPDlayer_Mask *mask)
|
|
|
|
|
{
|
|
|
|
|
BLI_freelinkN(&gpl->mask_layers, mask);
|
|
|
|
|
gpl->act_mask--;
|
|
|
|
|
CLAMP_MIN(gpl->act_mask, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BKE_gpencil_layer_mask_remove_ref(bGPdata *gpd, const char *name)
|
|
|
|
|
{
|
|
|
|
|
bGPDlayer_Mask *mask_next;
|
|
|
|
|
|
|
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
|
|
|
|
|
for (bGPDlayer_Mask *mask = gpl->mask_layers.first; mask; mask = mask_next) {
|
|
|
|
|
mask_next = mask->next;
|
|
|
|
|
if (STREQ(mask->name, name)) {
|
|
|
|
|
BKE_gpencil_layer_mask_remove(gpl, mask);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int gpencil_cb_sort_masks(const void *arg1, const void *arg2)
|
|
|
|
|
{
|
|
|
|
|
/* sort is inverted as layer list. */
|
|
|
|
|
const struct bGPDlayer_Mask *mask1 = arg1;
|
|
|
|
|
const struct bGPDlayer_Mask *mask2 = arg2;
|
|
|
|
|
int val = 0;
|
|
|
|
|
|
|
|
|
|
if (mask1->sort_index < mask2->sort_index) {
|
|
|
|
|
val = 1;
|
|
|
|
|
}
|
|
|
|
|
else if (mask1->sort_index > mask2->sort_index) {
|
|
|
|
|
val = -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return val;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BKE_gpencil_layer_mask_sort(bGPdata *gpd, bGPDlayer *gpl)
|
|
|
|
|
{
|
|
|
|
|
/* Update sort index. */
|
|
|
|
|
LISTBASE_FOREACH (bGPDlayer_Mask *, mask, &gpl->mask_layers) {
|
|
|
|
|
bGPDlayer *gpl_mask = BKE_gpencil_layer_named_get(gpd, mask->name);
|
|
|
|
|
if (gpl_mask != NULL) {
|
|
|
|
|
mask->sort_index = BLI_findindex(&gpd->layers, gpl_mask);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
mask->sort_index = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
BLI_listbase_sort(&gpl->mask_layers, gpencil_cb_sort_masks);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BKE_gpencil_layer_mask_sort_all(bGPdata *gpd)
|
2009-04-20 10:20:18 +00:00
|
|
|
{
|
2020-03-09 16:27:24 +01:00
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
|
|
|
|
|
BKE_gpencil_layer_mask_sort(gpd, gpl);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2020-03-16 16:16:35 +01:00
|
|
|
static int gpencil_cb_cmp_frame(void *thunk, const void *a, const void *b)
|
|
|
|
|
{
|
|
|
|
|
const bGPDframe *frame_a = a;
|
|
|
|
|
const bGPDframe *frame_b = b;
|
|
|
|
|
|
|
|
|
|
if (frame_a->framenum < frame_b->framenum) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if (frame_a->framenum > frame_b->framenum) {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (thunk != NULL) {
|
|
|
|
|
*((bool *)thunk) = true;
|
|
|
|
|
}
|
|
|
|
|
/* Sort selected last. */
|
|
|
|
|
if ((frame_a->flag & GP_FRAME_SELECT) && ((frame_b->flag & GP_FRAME_SELECT) == 0)) {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BKE_gpencil_layer_frames_sort(struct bGPDlayer *gpl, bool *r_has_duplicate_frames)
|
|
|
|
|
{
|
|
|
|
|
BLI_listbase_sort_r(&gpl->frames, gpencil_cb_cmp_frame, r_has_duplicate_frames);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
/* get the active gp-layer for editing */
|
|
|
|
|
bGPDlayer *BKE_gpencil_layer_active_get(bGPdata *gpd)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* error checking */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (ELEM(NULL, gpd, gpd->layers.first)) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return NULL;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* loop over layers until found (assume only one active) */
|
2020-03-09 16:27:24 +01:00
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
|
2019-04-22 09:39:35 +10:00
|
|
|
if (gpl->flag & GP_LAYER_ACTIVE) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return gpl;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* no active layer found */
|
|
|
|
|
return NULL;
|
2009-04-20 10:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* set the active gp-layer */
|
2020-03-09 16:27:24 +01:00
|
|
|
void BKE_gpencil_layer_active_set(bGPdata *gpd, bGPDlayer *active)
|
2009-04-20 10:20:18 +00:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* error checking */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (ELEM(NULL, gpd, gpd->layers.first, active)) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* loop over layers deactivating all */
|
2020-03-09 16:27:24 +01:00
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
|
2019-04-17 06:17:24 +02:00
|
|
|
gpl->flag &= ~GP_LAYER_ACTIVE;
|
|
|
|
|
if (gpd->flag & GP_DATA_AUTOLOCK_LAYERS) {
|
|
|
|
|
gpl->flag |= GP_LAYER_LOCKED;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* set as active one */
|
|
|
|
|
active->flag |= GP_LAYER_ACTIVE;
|
|
|
|
|
if (gpd->flag & GP_DATA_AUTOLOCK_LAYERS) {
|
|
|
|
|
active->flag &= ~GP_LAYER_LOCKED;
|
|
|
|
|
}
|
2009-04-20 10:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
2019-09-03 18:58:57 +02:00
|
|
|
/* Set locked layers for autolock mode. */
|
2019-09-06 23:33:11 +02:00
|
|
|
void BKE_gpencil_layer_autolock_set(bGPdata *gpd, const bool unlock)
|
2019-09-03 18:58:57 +02:00
|
|
|
{
|
|
|
|
|
BLI_assert(gpd != NULL);
|
|
|
|
|
|
|
|
|
|
if (gpd->flag & GP_DATA_AUTOLOCK_LAYERS) {
|
2020-03-09 16:27:24 +01:00
|
|
|
bGPDlayer *layer_active = BKE_gpencil_layer_active_get(gpd);
|
2019-09-03 18:58:57 +02:00
|
|
|
|
|
|
|
|
/* Lock all other layers */
|
2020-03-09 16:27:24 +01:00
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
|
2019-09-03 18:58:57 +02:00
|
|
|
/* unlock active layer */
|
|
|
|
|
if (gpl == layer_active) {
|
|
|
|
|
gpl->flag &= ~GP_LAYER_LOCKED;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
gpl->flag |= GP_LAYER_LOCKED;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* If disable is better unlock all layers by default or it looks there is
|
|
|
|
|
* a problem in the UI because the user expects all layers will be unlocked
|
|
|
|
|
*/
|
2019-09-06 23:33:11 +02:00
|
|
|
if (unlock) {
|
2020-03-09 16:27:24 +01:00
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
|
2019-09-06 23:33:11 +02:00
|
|
|
gpl->flag &= ~GP_LAYER_LOCKED;
|
|
|
|
|
}
|
2019-09-03 18:58:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-04-20 10:20:18 +00:00
|
|
|
/* delete the active gp-layer */
|
2016-08-04 15:03:18 +02:00
|
|
|
void BKE_gpencil_layer_delete(bGPdata *gpd, bGPDlayer *gpl)
|
2009-04-20 10:20:18 +00:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
/* error checking */
|
2019-04-22 09:39:35 +10:00
|
|
|
if (ELEM(NULL, gpd, gpl)) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2018-06-17 17:05:51 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* free layer */
|
|
|
|
|
BKE_gpencil_free_frames(gpl);
|
2018-07-31 10:22:19 +02:00
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
/* Free Masks. */
|
|
|
|
|
BKE_gpencil_free_layer_masks(gpl);
|
|
|
|
|
|
|
|
|
|
/* Remove any reference to that layer in masking lists. */
|
|
|
|
|
BKE_gpencil_layer_mask_remove_ref(gpd, gpl->info);
|
|
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* free icon providing preview of icon color */
|
|
|
|
|
BKE_icon_delete(gpl->runtime.icon_id);
|
2018-07-31 10:22:19 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
BLI_freelinkN(&gpd->layers, gpl);
|
2009-04-20 10:20:18 +00:00
|
|
|
}
|
|
|
|
|
|
2019-04-09 09:22:46 +02:00
|
|
|
Material *BKE_gpencil_brush_material_get(Brush *brush)
|
2016-08-03 23:31:48 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
Material *ma = NULL;
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
if ((brush != NULL) && (brush->gpencil_settings != NULL) &&
|
|
|
|
|
(brush->gpencil_settings->material != NULL)) {
|
|
|
|
|
ma = brush->gpencil_settings->material;
|
|
|
|
|
}
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
return ma;
|
2018-07-31 10:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
2019-04-09 09:22:46 +02:00
|
|
|
void BKE_gpencil_brush_material_set(Brush *brush, Material *ma)
|
2019-03-25 17:02:42 +01:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
BLI_assert(brush);
|
|
|
|
|
BLI_assert(brush->gpencil_settings);
|
|
|
|
|
if (brush->gpencil_settings->material != ma) {
|
|
|
|
|
if (brush->gpencil_settings->material) {
|
|
|
|
|
id_us_min(&brush->gpencil_settings->material->id);
|
|
|
|
|
}
|
|
|
|
|
if (ma) {
|
|
|
|
|
id_us_plus(&ma->id);
|
|
|
|
|
}
|
|
|
|
|
brush->gpencil_settings->material = ma;
|
|
|
|
|
}
|
2019-03-25 17:02:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Adds the pinned material to the object if necessary. */
|
2019-04-09 09:22:46 +02:00
|
|
|
Material *BKE_gpencil_object_material_ensure_from_brush(Main *bmain, Object *ob, Brush *brush)
|
2019-03-25 17:02:42 +01:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) {
|
|
|
|
|
Material *ma = BKE_gpencil_brush_material_get(brush);
|
2019-03-25 17:02:42 +01:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* check if the material is already on object material slots and add it if missing */
|
2020-03-09 16:27:24 +01:00
|
|
|
if (ma && BKE_gpencil_object_material_index_get(ob, ma) < 0) {
|
2019-04-17 06:17:24 +02:00
|
|
|
BKE_object_material_slot_add(bmain, ob);
|
2020-02-05 11:23:58 +01:00
|
|
|
BKE_object_material_assign(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_USERPREF);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2019-03-25 17:02:42 +01:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
return ma;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* using active material instead */
|
2020-02-05 11:23:58 +01:00
|
|
|
return BKE_object_material_get(ob, ob->actcol);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2019-03-25 17:02:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Assigns the material to object (if not already present) and returns its index (mat_nr). */
|
2019-04-09 09:22:46 +02:00
|
|
|
int BKE_gpencil_object_material_ensure(Main *bmain, Object *ob, Material *material)
|
2019-03-25 17:02:42 +01:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
if (!material) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
2020-03-09 16:27:24 +01:00
|
|
|
int index = BKE_gpencil_object_material_index_get(ob, material);
|
2019-04-17 06:17:24 +02:00
|
|
|
if (index < 0) {
|
|
|
|
|
BKE_object_material_slot_add(bmain, ob);
|
2020-02-05 11:23:58 +01:00
|
|
|
BKE_object_material_assign(bmain, ob, material, ob->totcol, BKE_MAT_ASSIGN_USERPREF);
|
2019-04-17 06:17:24 +02:00
|
|
|
return ob->totcol - 1;
|
|
|
|
|
}
|
|
|
|
|
return index;
|
2019-03-25 17:02:42 +01:00
|
|
|
}
|
|
|
|
|
|
2019-11-25 01:14:39 +11:00
|
|
|
/**
|
|
|
|
|
* Creates a new gpencil material and assigns it to object.
|
2019-03-25 17:02:42 +01:00
|
|
|
*
|
|
|
|
|
* \param *r_index: value is set to zero based index of the new material if r_index is not NULL
|
|
|
|
|
*/
|
2019-04-09 09:22:46 +02:00
|
|
|
Material *BKE_gpencil_object_material_new(Main *bmain, Object *ob, const char *name, int *r_index)
|
2019-03-25 17:02:42 +01:00
|
|
|
{
|
2020-02-05 11:23:58 +01:00
|
|
|
Material *ma = BKE_gpencil_material_add(bmain, name);
|
2019-04-17 06:17:24 +02:00
|
|
|
id_us_min(&ma->id); /* no users yet */
|
2019-03-25 17:02:42 +01:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
BKE_object_material_slot_add(bmain, ob);
|
2020-02-05 11:23:58 +01:00
|
|
|
BKE_object_material_assign(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_USERPREF);
|
2019-03-25 17:02:42 +01:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
if (r_index) {
|
|
|
|
|
*r_index = ob->actcol - 1;
|
|
|
|
|
}
|
|
|
|
|
return ma;
|
2019-03-25 17:02:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Returns the material for a brush with respect to its pinned state. */
|
2020-03-09 16:27:24 +01:00
|
|
|
Material *BKE_gpencil_object_material_from_brush_get(Object *ob, Brush *brush)
|
2019-03-25 17:02:42 +01:00
|
|
|
{
|
2019-05-05 09:10:44 +02:00
|
|
|
if ((brush) && (brush->gpencil_settings) &&
|
|
|
|
|
(brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED)) {
|
2019-04-17 06:17:24 +02:00
|
|
|
Material *ma = BKE_gpencil_brush_material_get(brush);
|
|
|
|
|
return ma;
|
|
|
|
|
}
|
|
|
|
|
else {
|
2020-02-05 11:23:58 +01:00
|
|
|
return BKE_object_material_get(ob, ob->actcol);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2019-03-25 17:02:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Returns the material index for a brush with respect to its pinned state. */
|
2019-04-09 09:22:46 +02:00
|
|
|
int BKE_gpencil_object_material_get_index_from_brush(Object *ob, Brush *brush)
|
2019-03-25 17:02:42 +01:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
if ((brush) && (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED)) {
|
2020-03-09 16:27:24 +01:00
|
|
|
return BKE_gpencil_object_material_index_get(ob, brush->gpencil_settings->material);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return ob->actcol - 1;
|
|
|
|
|
}
|
2019-03-25 17:02:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Guaranteed to return a material assigned to object. Returns never NULL. */
|
2019-04-17 06:17:24 +02:00
|
|
|
Material *BKE_gpencil_object_material_ensure_from_active_input_toolsettings(Main *bmain,
|
|
|
|
|
Object *ob,
|
|
|
|
|
ToolSettings *ts)
|
2019-03-25 17:02:42 +01:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
if (ts && ts->gp_paint && ts->gp_paint->paint.brush) {
|
|
|
|
|
return BKE_gpencil_object_material_ensure_from_active_input_brush(
|
|
|
|
|
bmain, ob, ts->gp_paint->paint.brush);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return BKE_gpencil_object_material_ensure_from_active_input_brush(bmain, ob, NULL);
|
|
|
|
|
}
|
2019-03-25 17:02:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Guaranteed to return a material assigned to object. Returns never NULL. */
|
2019-04-17 06:17:24 +02:00
|
|
|
Material *BKE_gpencil_object_material_ensure_from_active_input_brush(Main *bmain,
|
|
|
|
|
Object *ob,
|
|
|
|
|
Brush *brush)
|
|
|
|
|
{
|
|
|
|
|
if (brush) {
|
|
|
|
|
Material *ma = BKE_gpencil_object_material_ensure_from_brush(bmain, ob, brush);
|
|
|
|
|
if (ma) {
|
|
|
|
|
return ma;
|
|
|
|
|
}
|
|
|
|
|
else if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) {
|
|
|
|
|
/* it is easier to just unpin a NULL material, instead of setting a new one */
|
|
|
|
|
brush->gpencil_settings->flag &= ~GP_BRUSH_MATERIAL_PINNED;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-30 14:19:07 +02:00
|
|
|
return BKE_gpencil_object_material_ensure_from_active_input_material(ob);
|
2019-03-25 17:02:42 +01:00
|
|
|
}
|
|
|
|
|
|
2019-04-27 12:07:07 +10:00
|
|
|
/**
|
|
|
|
|
* Guaranteed to return a material assigned to object. Returns never NULL.
|
|
|
|
|
* Only use this for materials unrelated to user input.
|
|
|
|
|
*/
|
2019-08-30 14:19:07 +02:00
|
|
|
Material *BKE_gpencil_object_material_ensure_from_active_input_material(Object *ob)
|
2019-03-25 17:02:42 +01:00
|
|
|
{
|
2020-02-05 11:23:58 +01:00
|
|
|
Material *ma = BKE_object_material_get(ob, ob->actcol);
|
2019-04-17 06:17:24 +02:00
|
|
|
if (ma) {
|
|
|
|
|
return ma;
|
|
|
|
|
}
|
2019-08-30 14:19:07 +02:00
|
|
|
|
2020-02-06 13:05:45 +01:00
|
|
|
return BKE_material_default_gpencil();
|
2019-03-25 17:02:42 +01:00
|
|
|
}
|
|
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* Get active color, and add all default settings if we don't find anything */
|
2019-08-30 14:19:07 +02:00
|
|
|
Material *BKE_gpencil_object_material_ensure_active(Object *ob)
|
2018-07-31 10:22:19 +02:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
Material *ma = NULL;
|
2018-07-31 10:22:19 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
/* sanity checks */
|
2019-08-30 14:19:07 +02:00
|
|
|
if (ob == NULL) {
|
2019-04-17 06:17:24 +02:00
|
|
|
return NULL;
|
2019-04-22 09:39:35 +10:00
|
|
|
}
|
2018-07-31 10:22:19 +02:00
|
|
|
|
2019-08-30 14:19:07 +02:00
|
|
|
ma = BKE_gpencil_object_material_ensure_from_active_input_material(ob);
|
2019-04-17 06:17:24 +02:00
|
|
|
if (ma->gp_style == NULL) {
|
2020-02-05 11:23:58 +01:00
|
|
|
BKE_gpencil_material_attr_init(ma);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2019-04-17 06:17:24 +02:00
|
|
|
return ma;
|
2016-08-03 23:31:48 +02:00
|
|
|
}
|
|
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* ************************************************** */
|
2019-04-17 06:17:24 +02:00
|
|
|
bool BKE_gpencil_stroke_select_check(const bGPDstroke *gps)
|
2018-11-13 14:04:00 +11:00
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
const bGPDspoint *pt;
|
|
|
|
|
int i;
|
|
|
|
|
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
|
|
|
|
|
if (pt->flag & GP_SPOINT_SELECT) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2018-11-13 14:04:00 +11:00
|
|
|
}
|
|
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* ************************************************** */
|
|
|
|
|
/* GP Object - Vertex Groups */
|
|
|
|
|
|
|
|
|
|
/* remove a vertex group */
|
|
|
|
|
void BKE_gpencil_vgroup_remove(Object *ob, bDeformGroup *defgroup)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
bGPdata *gpd = ob->data;
|
|
|
|
|
MDeformVert *dvert = NULL;
|
|
|
|
|
const int def_nr = BLI_findindex(&ob->defbase, defgroup);
|
2019-10-07 20:39:51 +02:00
|
|
|
const int totgrp = BLI_listbase_count(&ob->defbase);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
|
|
|
|
/* Remove points data */
|
|
|
|
|
if (gpd) {
|
2020-03-09 16:27:24 +01:00
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
|
|
|
|
|
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
|
|
|
|
|
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
|
2019-04-17 06:17:24 +02:00
|
|
|
if (gps->dvert != NULL) {
|
|
|
|
|
for (int i = 0; i < gps->totpoints; i++) {
|
|
|
|
|
dvert = &gps->dvert[i];
|
2020-03-06 12:50:56 +11:00
|
|
|
MDeformWeight *dw = BKE_defvert_find_index(dvert, def_nr);
|
2019-04-17 06:17:24 +02:00
|
|
|
if (dw != NULL) {
|
2020-03-06 12:50:56 +11:00
|
|
|
BKE_defvert_remove_group(dvert, dw);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2019-10-07 20:44:55 +02:00
|
|
|
/* Reorganize weights for other groups after deleted one. */
|
2019-10-07 20:39:51 +02:00
|
|
|
for (int g = 0; g < totgrp; g++) {
|
2020-03-06 12:50:56 +11:00
|
|
|
dw = BKE_defvert_find_index(dvert, g);
|
2019-04-17 06:17:24 +02:00
|
|
|
if ((dw != NULL) && (dw->def_nr > def_nr)) {
|
|
|
|
|
dw->def_nr--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Remove the group */
|
|
|
|
|
BLI_freelinkN(&ob->defbase, defgroup);
|
|
|
|
|
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
|
2016-08-03 23:31:48 +02:00
|
|
|
}
|
|
|
|
|
|
2018-08-26 16:39:01 +02:00
|
|
|
void BKE_gpencil_dvert_ensure(bGPDstroke *gps)
|
|
|
|
|
{
|
2019-04-17 06:17:24 +02:00
|
|
|
if (gps->dvert == NULL) {
|
|
|
|
|
gps->dvert = MEM_callocN(sizeof(MDeformVert) * gps->totpoints, "gp_stroke_weights");
|
|
|
|
|
}
|
2018-08-26 16:39:01 +02:00
|
|
|
}
|
2016-08-03 23:31:48 +02:00
|
|
|
|
2018-07-31 10:22:19 +02:00
|
|
|
/* ************************************************** */
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
/**
|
|
|
|
|
* Get range of selected frames in layer.
|
|
|
|
|
* Always the active frame is considered as selected, so if no more selected the range
|
|
|
|
|
* will be equal to the current active frame.
|
|
|
|
|
* \param gpl: Layer
|
|
|
|
|
* \param r_initframe: Number of first selected frame
|
|
|
|
|
* \param r_endframe: Number of last selected frame
|
|
|
|
|
*/
|
|
|
|
|
void BKE_gpencil_frame_range_selected(bGPDlayer *gpl, int *r_initframe, int *r_endframe)
|
2019-08-08 16:12:13 +02:00
|
|
|
{
|
2020-03-19 11:35:17 +01:00
|
|
|
*r_initframe = gpl->actframe->framenum;
|
|
|
|
|
*r_endframe = gpl->actframe->framenum;
|
2019-08-08 16:12:13 +02:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
|
|
|
|
|
if (gpf->flag & GP_FRAME_SELECT) {
|
|
|
|
|
if (gpf->framenum < *r_initframe) {
|
|
|
|
|
*r_initframe = gpf->framenum;
|
|
|
|
|
}
|
|
|
|
|
if (gpf->framenum > *r_endframe) {
|
|
|
|
|
*r_endframe = gpf->framenum;
|
|
|
|
|
}
|
2019-08-08 16:12:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
/**
|
|
|
|
|
* Get Falloff factor base on frame range
|
|
|
|
|
* \param gpf: Frame
|
|
|
|
|
* \param actnum: Number of active frame in layer
|
|
|
|
|
* \param f_init: Number of first selected frame
|
|
|
|
|
* \param f_end: Number of last selected frame
|
|
|
|
|
* \param cur_falloff: Curve with falloff factors
|
|
|
|
|
*/
|
|
|
|
|
float BKE_gpencil_multiframe_falloff_calc(
|
|
|
|
|
bGPDframe *gpf, int actnum, int f_init, int f_end, CurveMapping *cur_falloff)
|
2019-08-08 16:12:13 +02:00
|
|
|
{
|
2020-03-19 11:35:17 +01:00
|
|
|
float fnum = 0.5f; /* default mid curve */
|
|
|
|
|
float value;
|
2019-08-08 16:12:13 +02:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
/* check curve is available */
|
|
|
|
|
if (cur_falloff == NULL) {
|
|
|
|
|
return 1.0f;
|
2019-08-08 16:12:13 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
/* frames to the right of the active frame */
|
|
|
|
|
if (gpf->framenum < actnum) {
|
|
|
|
|
fnum = (float)(gpf->framenum - f_init) / (actnum - f_init);
|
|
|
|
|
fnum *= 0.5f;
|
|
|
|
|
value = BKE_curvemapping_evaluateF(cur_falloff, 0, fnum);
|
2019-08-08 16:12:13 +02:00
|
|
|
}
|
2020-03-19 11:35:17 +01:00
|
|
|
/* frames to the left of the active frame */
|
|
|
|
|
else if (gpf->framenum > actnum) {
|
|
|
|
|
fnum = (float)(gpf->framenum - actnum) / (f_end - actnum);
|
|
|
|
|
fnum *= 0.5f;
|
|
|
|
|
value = BKE_curvemapping_evaluateF(cur_falloff, 0, fnum + 0.5f);
|
2019-08-08 16:12:13 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2020-03-19 11:35:17 +01:00
|
|
|
value = 1.0f;
|
2019-08-08 16:12:13 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
return value;
|
2019-08-08 16:12:13 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
/* reassign strokes using a material */
|
|
|
|
|
void BKE_gpencil_material_index_reassign(bGPdata *gpd, int totcol, int index)
|
2019-08-08 16:12:13 +02:00
|
|
|
{
|
2020-03-19 11:35:17 +01:00
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
|
|
|
|
|
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
|
|
|
|
|
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
|
|
|
|
|
/* reassign strokes */
|
|
|
|
|
if ((gps->mat_nr > index) || (gps->mat_nr > totcol - 1)) {
|
|
|
|
|
gps->mat_nr--;
|
|
|
|
|
CLAMP_MIN(gps->mat_nr, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-08 16:12:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
/* remove strokes using a material */
|
|
|
|
|
bool BKE_gpencil_material_index_used(bGPdata *gpd, int index)
|
2019-08-08 16:12:13 +02:00
|
|
|
{
|
2020-03-19 11:35:17 +01:00
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
|
|
|
|
|
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
|
|
|
|
|
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
|
|
|
|
|
if (gps->mat_nr == index) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-08 16:12:13 +02:00
|
|
|
}
|
2020-03-19 11:35:17 +01:00
|
|
|
}
|
2019-08-08 16:12:13 +02:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
2019-08-08 16:12:13 +02:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
void BKE_gpencil_material_remap(struct bGPdata *gpd,
|
|
|
|
|
const unsigned int *remap,
|
|
|
|
|
unsigned int remap_len)
|
|
|
|
|
{
|
|
|
|
|
const short remap_len_short = (short)remap_len;
|
2019-08-08 16:12:13 +02:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
#define MAT_NR_REMAP(n) \
|
|
|
|
|
if (n < remap_len_short) { \
|
|
|
|
|
BLI_assert(n >= 0 && remap[n] < remap_len_short); \
|
|
|
|
|
n = remap[n]; \
|
|
|
|
|
} \
|
|
|
|
|
((void)0)
|
2019-08-08 16:12:13 +02:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
|
|
|
|
|
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
|
|
|
|
|
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
|
|
|
|
|
/* reassign strokes */
|
|
|
|
|
MAT_NR_REMAP(gps->mat_nr);
|
|
|
|
|
}
|
2019-08-08 16:12:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
#undef MAT_NR_REMAP
|
2019-08-08 16:12:13 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
/* Load a table with material conversion index for merged materials. */
|
|
|
|
|
bool BKE_gpencil_merge_materials_table_get(Object *ob,
|
|
|
|
|
const float hue_threshold,
|
|
|
|
|
const float sat_threshold,
|
|
|
|
|
const float val_threshold,
|
|
|
|
|
GHash *r_mat_table)
|
2019-11-14 19:18:23 +01:00
|
|
|
{
|
2020-03-19 11:35:17 +01:00
|
|
|
bool changed = false;
|
2019-11-14 19:18:23 +01:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
Material *ma_primary = NULL;
|
|
|
|
|
Material *ma_secondary = NULL;
|
|
|
|
|
MaterialGPencilStyle *gp_style_primary = NULL;
|
|
|
|
|
MaterialGPencilStyle *gp_style_secondary = NULL;
|
2019-11-14 19:18:23 +01:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
short *totcol = BKE_object_material_len_p(ob);
|
|
|
|
|
if (totcol == 0) {
|
|
|
|
|
return changed;
|
2019-11-14 19:18:23 +01:00
|
|
|
}
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
for (int idx_primary = 0; idx_primary < *totcol; idx_primary++) {
|
|
|
|
|
/* Read primary material to compare. */
|
|
|
|
|
ma_primary = BKE_gpencil_material(ob, idx_primary + 1);
|
|
|
|
|
if (ma_primary == NULL) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-11-14 19:18:23 +01:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
for (int idx_secondary = idx_primary + 1; idx_secondary < *totcol; idx_secondary++) {
|
|
|
|
|
/* Read secondary material to compare with primary material. */
|
|
|
|
|
ma_secondary = BKE_gpencil_material(ob, idx_secondary + 1);
|
|
|
|
|
if ((ma_secondary == NULL) ||
|
|
|
|
|
(BLI_ghash_haskey(r_mat_table, POINTER_FROM_INT(idx_secondary)))) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
gp_style_primary = ma_primary->gp_style;
|
|
|
|
|
gp_style_secondary = ma_secondary->gp_style;
|
2019-11-14 19:18:23 +01:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
if ((gp_style_primary == NULL) || (gp_style_secondary == NULL) ||
|
|
|
|
|
(gp_style_secondary->flag & GP_MATERIAL_LOCKED)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-11-14 19:18:23 +01:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
/* Check materials have the same mode. */
|
|
|
|
|
if (gp_style_primary->mode != gp_style_secondary->mode) {
|
|
|
|
|
continue;
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
/* Check materials have same stroke and fill attributes. */
|
|
|
|
|
if ((gp_style_primary->flag & GP_MATERIAL_STROKE_SHOW) !=
|
|
|
|
|
(gp_style_secondary->flag & GP_MATERIAL_STROKE_SHOW)) {
|
|
|
|
|
continue;
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2020-03-09 16:27:24 +01:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
if ((gp_style_primary->flag & GP_MATERIAL_FILL_SHOW) !=
|
|
|
|
|
(gp_style_secondary->flag & GP_MATERIAL_FILL_SHOW)) {
|
|
|
|
|
continue;
|
2019-06-30 21:03:24 +02:00
|
|
|
}
|
2019-08-02 11:01:00 +02:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
/* Check materials have the same type. */
|
|
|
|
|
if ((gp_style_primary->stroke_style != gp_style_secondary->stroke_style) ||
|
|
|
|
|
(gp_style_primary->fill_style != gp_style_secondary->fill_style)) {
|
|
|
|
|
continue;
|
2019-08-02 11:01:00 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
float s_hsv_a[3], s_hsv_b[3], f_hsv_a[3], f_hsv_b[3], col[3];
|
|
|
|
|
copy_v3_v3(col, gp_style_primary->stroke_rgba);
|
|
|
|
|
rgb_to_hsv_compat_v(col, s_hsv_a);
|
|
|
|
|
copy_v3_v3(col, gp_style_secondary->stroke_rgba);
|
|
|
|
|
rgb_to_hsv_compat_v(col, s_hsv_b);
|
2019-08-02 11:01:00 +02:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
copy_v3_v3(col, gp_style_primary->fill_rgba);
|
|
|
|
|
rgb_to_hsv_compat_v(col, f_hsv_a);
|
|
|
|
|
copy_v3_v3(col, gp_style_secondary->fill_rgba);
|
|
|
|
|
rgb_to_hsv_compat_v(col, f_hsv_b);
|
2019-08-08 10:23:05 +02:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
/* Check stroke and fill color (only Hue and Saturation). */
|
|
|
|
|
if ((!compare_ff(s_hsv_a[0], s_hsv_b[0], hue_threshold)) ||
|
|
|
|
|
(!compare_ff(s_hsv_a[1], s_hsv_b[1], sat_threshold)) ||
|
|
|
|
|
(!compare_ff(f_hsv_a[0], f_hsv_b[0], hue_threshold)) ||
|
|
|
|
|
(!compare_ff(f_hsv_a[1], f_hsv_b[1], sat_threshold)) ||
|
|
|
|
|
(!compare_ff(s_hsv_a[2], s_hsv_b[2], val_threshold)) ||
|
|
|
|
|
(!compare_ff(s_hsv_a[2], s_hsv_b[2], val_threshold)) ||
|
|
|
|
|
(!compare_ff(s_hsv_a[2], s_hsv_b[2], val_threshold)) ||
|
|
|
|
|
(!compare_ff(s_hsv_a[2], s_hsv_b[2], val_threshold))) {
|
2019-08-08 10:23:05 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2019-08-31 17:26:48 +02:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
/* Save conversion indexes. */
|
|
|
|
|
BLI_ghash_insert(
|
|
|
|
|
r_mat_table, POINTER_FROM_INT(idx_secondary), POINTER_FROM_INT(idx_primary));
|
|
|
|
|
changed = true;
|
2019-08-31 17:26:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
return changed;
|
2019-08-31 17:26:48 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
/* statistics functions */
|
|
|
|
|
void BKE_gpencil_stats_update(bGPdata *gpd)
|
2019-08-31 17:26:48 +02:00
|
|
|
{
|
2020-03-19 11:35:17 +01:00
|
|
|
gpd->totlayer = 0;
|
|
|
|
|
gpd->totframe = 0;
|
|
|
|
|
gpd->totstroke = 0;
|
|
|
|
|
gpd->totpoint = 0;
|
2019-08-31 17:26:48 +02:00
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
|
|
|
|
|
gpd->totlayer++;
|
|
|
|
|
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
|
|
|
|
|
gpd->totframe++;
|
|
|
|
|
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
|
|
|
|
|
gpd->totstroke++;
|
|
|
|
|
gpd->totpoint += gps->totpoints;
|
2019-08-31 17:26:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
/* get material index (0-based like mat_nr not actcol) */
|
|
|
|
|
int BKE_gpencil_object_material_index_get(Object *ob, Material *ma)
|
2019-08-31 17:26:48 +02:00
|
|
|
{
|
2020-03-19 11:35:17 +01:00
|
|
|
short *totcol = BKE_object_material_len_p(ob);
|
|
|
|
|
Material *read_ma = NULL;
|
|
|
|
|
for (short i = 0; i < *totcol; i++) {
|
|
|
|
|
read_ma = BKE_object_material_get(ob, i + 1);
|
|
|
|
|
if (ma == read_ma) {
|
|
|
|
|
return i;
|
2019-08-31 17:26:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-19 11:35:17 +01:00
|
|
|
return -1;
|
2019-08-31 17:26:48 +02:00
|
|
|
}
|
2020-03-09 16:27:24 +01:00
|
|
|
|
|
|
|
|
/* Create a default palette */
|
|
|
|
|
void BKE_gpencil_palette_ensure(Main *bmain, Scene *scene)
|
|
|
|
|
{
|
|
|
|
|
const int totcol = 120;
|
|
|
|
|
const char *hexcol[] = {
|
|
|
|
|
"FFFFFF", "F2F2F2", "E6E6E6", "D9D9D9", "CCCCCC", "BFBFBF", "B2B2B2", "A6A6A6", "999999",
|
|
|
|
|
"8C8C8C", "808080", "737373", "666666", "595959", "4C4C4C", "404040", "333333", "262626",
|
|
|
|
|
"1A1A1A", "000000", "F2FC24", "FFEA00", "FEA711", "FE8B68", "FB3B02", "FE3521", "D00000",
|
|
|
|
|
"A81F3D", "780422", "2B0000", "F1E2C5", "FEE4B3", "FEDABB", "FEC28E", "D88F57", "BD6340",
|
|
|
|
|
"A2402B", "63352D", "6B2833", "34120C", "E7CB8F", "D1B38B", "C1B17F", "D7980B", "FFB100",
|
|
|
|
|
"FE8B00", "FF6A00", "B74100", "5F3E1D", "3B2300", "FECADA", "FE65CB", "FE1392", "DD3062",
|
|
|
|
|
"C04A6D", "891688", "4D2689", "441521", "2C1139", "241422", "FFFF7D", "FFFF00", "FF7F00",
|
|
|
|
|
"FF7D7D", "FF7DFF", "FF00FE", "FF007F", "FF0000", "7F0000", "0A0A00", "F6FDFF", "E9F7FF",
|
|
|
|
|
"CFE6FE", "AAC7FE", "77B3FE", "1E74FD", "0046AA", "2F4476", "003052", "0E0E25", "EEF5F0",
|
|
|
|
|
"D6E5DE", "ACD8B9", "6CADC6", "42A9AF", "007F7F", "49675C", "2E4E4E", "1D3239", "0F1C21",
|
|
|
|
|
"D8FFF4", "B8F4F5", "AECCB5", "76C578", "358757", "409B68", "468768", "1F512B", "2A3C37",
|
|
|
|
|
"122E1D", "EFFFC9", "E6F385", "BCF51C", "D4DC18", "82D322", "5C7F00", "59932B", "297F00",
|
|
|
|
|
"004320", "1C3322", "00FF7F", "00FF00", "7DFF7D", "7DFFFF", "00FFFF", "7D7DFF", "7F00FF",
|
|
|
|
|
"0000FF", "3F007F", "00007F"};
|
|
|
|
|
|
|
|
|
|
ToolSettings *ts = scene->toolsettings;
|
|
|
|
|
GpPaint *gp_paint = ts->gp_paint;
|
|
|
|
|
Paint *paint = &gp_paint->paint;
|
|
|
|
|
|
2020-05-15 16:43:50 +02:00
|
|
|
if (paint->palette != NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
paint->palette = BLI_findstring(&bmain->palettes, "Palette", offsetof(ID, name) + 2);
|
2020-05-15 16:43:50 +02:00
|
|
|
/* Try with first palette. */
|
|
|
|
|
if (bmain->palettes.first != NULL) {
|
|
|
|
|
paint->palette = bmain->palettes.first;
|
|
|
|
|
ts->gp_vertexpaint->paint.palette = paint->palette;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
if (paint->palette == NULL) {
|
|
|
|
|
paint->palette = BKE_palette_add(bmain, "Palette");
|
|
|
|
|
ts->gp_vertexpaint->paint.palette = paint->palette;
|
|
|
|
|
|
|
|
|
|
/* Create Colors. */
|
|
|
|
|
for (int i = 0; i < totcol; i++) {
|
|
|
|
|
PaletteColor *palcol = BKE_palette_color_add(paint->palette);
|
|
|
|
|
if (palcol) {
|
|
|
|
|
hex_to_rgb((char *)hexcol[i], palcol->rgb, palcol->rgb + 1, palcol->rgb + 2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BKE_gpencil_from_image(SpaceImage *sima, bGPDframe *gpf, const float size, const bool mask)
|
|
|
|
|
{
|
|
|
|
|
Image *image = sima->image;
|
|
|
|
|
bool done = false;
|
|
|
|
|
|
|
|
|
|
if (image == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImageUser iuser = sima->iuser;
|
|
|
|
|
void *lock;
|
|
|
|
|
ImBuf *ibuf;
|
|
|
|
|
|
|
|
|
|
ibuf = BKE_image_acquire_ibuf(image, &iuser, &lock);
|
|
|
|
|
|
|
|
|
|
if (ibuf->rect) {
|
|
|
|
|
int img_x = ibuf->x;
|
|
|
|
|
int img_y = ibuf->y;
|
|
|
|
|
|
|
|
|
|
float color[4];
|
|
|
|
|
bGPDspoint *pt;
|
|
|
|
|
for (int row = 0; row < img_y; row++) {
|
|
|
|
|
/* Create new stroke */
|
|
|
|
|
bGPDstroke *gps = BKE_gpencil_stroke_add(gpf, 0, img_x, size * 1000, false);
|
|
|
|
|
done = true;
|
|
|
|
|
for (int col = 0; col < img_x; col++) {
|
|
|
|
|
IMB_sampleImageAtLocation(ibuf, col, row, true, color);
|
|
|
|
|
pt = &gps->points[col];
|
|
|
|
|
pt->pressure = 1.0f;
|
|
|
|
|
pt->x = col * size;
|
|
|
|
|
pt->z = row * size;
|
|
|
|
|
if (!mask) {
|
|
|
|
|
copy_v3_v3(pt->vert_color, color);
|
|
|
|
|
pt->vert_color[3] = 1.0f;
|
|
|
|
|
pt->strength = color[3];
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
zero_v3(pt->vert_color);
|
|
|
|
|
pt->vert_color[3] = 1.0f;
|
|
|
|
|
pt->strength = 1.0f - color[3];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Selet Alpha points. */
|
|
|
|
|
if (pt->strength < 0.03f) {
|
|
|
|
|
gps->flag |= GP_STROKE_SELECT;
|
|
|
|
|
pt->flag |= GP_SPOINT_SELECT;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
BKE_gpencil_stroke_geometry_update(gps);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Free memory. */
|
|
|
|
|
BKE_image_release_ibuf(image, ibuf, lock);
|
|
|
|
|
|
|
|
|
|
return done;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Iterators
|
|
|
|
|
*
|
|
|
|
|
* Iterate over all visible stroke of all visible layers inside a gpObject.
|
|
|
|
|
* Also take into account onion skining.
|
|
|
|
|
*
|
|
|
|
|
* \{ */
|
|
|
|
|
|
2020-06-03 15:57:09 +02:00
|
|
|
void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer,
|
|
|
|
|
Object *ob,
|
|
|
|
|
gpIterCb layer_cb,
|
|
|
|
|
gpIterCb stroke_cb,
|
|
|
|
|
void *thunk,
|
|
|
|
|
bool do_onion,
|
|
|
|
|
int cfra)
|
2020-03-09 16:27:24 +01:00
|
|
|
{
|
|
|
|
|
bGPdata *gpd = (bGPdata *)ob->data;
|
|
|
|
|
const bool is_multiedit = GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
|
|
|
|
|
const bool is_onion = do_onion && ((gpd->flag & GP_DATA_STROKE_WEIGHTMODE) == 0);
|
|
|
|
|
|
|
|
|
|
/* Onion skinning. */
|
|
|
|
|
const bool onion_mode_abs = (gpd->onion_mode == GP_ONION_MODE_ABSOLUTE);
|
|
|
|
|
const bool onion_mode_sel = (gpd->onion_mode == GP_ONION_MODE_SELECTED);
|
|
|
|
|
const bool onion_loop = (gpd->onion_flag & GP_ONION_LOOP) != 0;
|
|
|
|
|
const short onion_keytype = gpd->onion_keytype;
|
|
|
|
|
|
|
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
|
|
|
|
|
|
|
|
|
|
bGPDframe *act_gpf = gpl->actframe;
|
|
|
|
|
bGPDframe *sta_gpf = act_gpf;
|
|
|
|
|
bGPDframe *end_gpf = act_gpf ? act_gpf->next : NULL;
|
|
|
|
|
|
|
|
|
|
if (gpl->flag & GP_LAYER_HIDE) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-03 15:57:09 +02:00
|
|
|
/* Hide the layer if it's defined a view layer filter. This is used to
|
|
|
|
|
* generate renders, putting only selected GP layers for each View Layer.
|
|
|
|
|
* This is used only in final render and never in Viewport. */
|
|
|
|
|
if ((view_layer != NULL) && (gpl->viewlayername[0] != '\0') &&
|
|
|
|
|
(!STREQ(view_layer->name, gpl->viewlayername))) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-09 16:27:24 +01:00
|
|
|
if (is_multiedit) {
|
|
|
|
|
sta_gpf = end_gpf = NULL;
|
|
|
|
|
/* Check the whole range and tag the editable frames. */
|
|
|
|
|
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
|
|
|
|
|
if (gpf == act_gpf || (gpf->flag & GP_FRAME_SELECT)) {
|
|
|
|
|
gpf->runtime.onion_id = 0;
|
|
|
|
|
if (sta_gpf == NULL) {
|
|
|
|
|
sta_gpf = gpf;
|
|
|
|
|
}
|
|
|
|
|
end_gpf = gpf->next;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
gpf->runtime.onion_id = INT_MAX;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (is_onion && (gpl->onion_flag & GP_LAYER_ONIONSKIN)) {
|
|
|
|
|
if (act_gpf) {
|
|
|
|
|
bGPDframe *last_gpf = gpl->frames.last;
|
|
|
|
|
|
|
|
|
|
int frame_len = 0;
|
|
|
|
|
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
|
|
|
|
|
gpf->runtime.frameid = frame_len++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
|
|
|
|
|
bool is_wrong_keytype = (onion_keytype > -1) && (gpf->key_type != onion_keytype);
|
|
|
|
|
bool is_in_range;
|
|
|
|
|
int delta = (onion_mode_abs) ? (gpf->framenum - cfra) :
|
|
|
|
|
(gpf->runtime.frameid - act_gpf->runtime.frameid);
|
|
|
|
|
|
|
|
|
|
if (onion_mode_sel) {
|
|
|
|
|
is_in_range = (gpf->flag & GP_FRAME_SELECT) != 0;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
is_in_range = (-delta <= gpd->gstep) && (delta <= gpd->gstep_next);
|
|
|
|
|
|
|
|
|
|
if (onion_loop && !is_in_range) {
|
|
|
|
|
/* We wrap the value using the last frame and 0 as reference. */
|
|
|
|
|
/* FIXME: This might not be good for animations not starting at 0. */
|
|
|
|
|
int shift = (onion_mode_abs) ? last_gpf->framenum : last_gpf->runtime.frameid;
|
|
|
|
|
delta += (delta < 0) ? (shift + 1) : -(shift + 1);
|
|
|
|
|
/* Test again with wrapped value. */
|
|
|
|
|
is_in_range = (-delta <= gpd->gstep) && (delta <= gpd->gstep_next);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* Mask frames that have wrong keytype of are not in range. */
|
|
|
|
|
gpf->runtime.onion_id = (is_wrong_keytype || !is_in_range) ? INT_MAX : delta;
|
|
|
|
|
}
|
|
|
|
|
/* Active frame is always shown. */
|
|
|
|
|
act_gpf->runtime.onion_id = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sta_gpf = gpl->frames.first;
|
|
|
|
|
end_gpf = NULL;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Bypass multiedit/onion skinning. */
|
|
|
|
|
end_gpf = sta_gpf = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sta_gpf == NULL && act_gpf == NULL) {
|
|
|
|
|
if (layer_cb) {
|
|
|
|
|
layer_cb(gpl, act_gpf, NULL, thunk);
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Draw multiedit/onion skinning first */
|
|
|
|
|
for (bGPDframe *gpf = sta_gpf; gpf && gpf != end_gpf; gpf = gpf->next) {
|
|
|
|
|
if (gpf->runtime.onion_id == INT_MAX || gpf == act_gpf) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (layer_cb) {
|
|
|
|
|
layer_cb(gpl, gpf, NULL, thunk);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
|
2020-03-28 12:33:32 +01:00
|
|
|
if (gps->totpoints == 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-03-09 16:27:24 +01:00
|
|
|
stroke_cb(gpl, gpf, gps, thunk);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* Draw Active frame on top. */
|
|
|
|
|
/* Use evaluated frame (with modifiers for active stroke)/ */
|
|
|
|
|
act_gpf = gpl->actframe;
|
|
|
|
|
act_gpf->runtime.onion_id = 0;
|
|
|
|
|
if (act_gpf) {
|
|
|
|
|
if (layer_cb) {
|
|
|
|
|
layer_cb(gpl, act_gpf, NULL, thunk);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LISTBASE_FOREACH (bGPDstroke *, gps, &act_gpf->strokes) {
|
2020-03-28 12:33:32 +01:00
|
|
|
if (gps->totpoints == 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-03-09 16:27:24 +01:00
|
|
|
stroke_cb(gpl, act_gpf, gps, thunk);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BKE_gpencil_frame_original_pointers_update(const struct bGPDframe *gpf_orig,
|
|
|
|
|
const struct bGPDframe *gpf_eval)
|
|
|
|
|
{
|
|
|
|
|
bGPDstroke *gps_eval = gpf_eval->strokes.first;
|
|
|
|
|
LISTBASE_FOREACH (bGPDstroke *, gps_orig, &gpf_orig->strokes) {
|
|
|
|
|
|
|
|
|
|
/* Assign original stroke pointer. */
|
|
|
|
|
if (gps_eval != NULL) {
|
|
|
|
|
gps_eval->runtime.gps_orig = gps_orig;
|
|
|
|
|
|
|
|
|
|
/* Assign original point pointer. */
|
|
|
|
|
for (int i = 0; i < gps_orig->totpoints; i++) {
|
2020-03-13 12:57:08 +01:00
|
|
|
if (i > gps_eval->totpoints - 1) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-04-17 19:35:24 +02:00
|
|
|
bGPDspoint *pt_orig = &gps_orig->points[i];
|
2020-03-09 16:27:24 +01:00
|
|
|
bGPDspoint *pt_eval = &gps_eval->points[i];
|
2020-04-17 19:35:24 +02:00
|
|
|
pt_orig->runtime.pt_orig = NULL;
|
|
|
|
|
pt_orig->runtime.idx_orig = i;
|
|
|
|
|
pt_eval->runtime.pt_orig = pt_orig;
|
2020-03-09 16:27:24 +01:00
|
|
|
pt_eval->runtime.idx_orig = i;
|
|
|
|
|
}
|
|
|
|
|
/* Increase pointer. */
|
|
|
|
|
gps_eval = gps_eval->next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BKE_gpencil_update_orig_pointers(const Object *ob_orig, const Object *ob_eval)
|
|
|
|
|
{
|
|
|
|
|
bGPdata *gpd_eval = (bGPdata *)ob_eval->data;
|
|
|
|
|
bGPdata *gpd_orig = (bGPdata *)ob_orig->data;
|
|
|
|
|
|
|
|
|
|
/* Assign pointers to the original stroke and points to the evaluated data. This must
|
|
|
|
|
* be done before applying any modifier because at this moment the structure is equals,
|
|
|
|
|
* so we can assume the layer index is the same in both datablocks.
|
|
|
|
|
* This data will be used by operators. */
|
|
|
|
|
|
|
|
|
|
bGPDlayer *gpl_eval = gpd_eval->layers.first;
|
|
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_orig->layers) {
|
|
|
|
|
if (gpl_eval != NULL) {
|
|
|
|
|
/* Update layer reference pointers. */
|
|
|
|
|
gpl_eval->runtime.gpl_orig = (bGPDlayer *)gpl;
|
|
|
|
|
|
|
|
|
|
bGPDframe *gpf_eval = gpl_eval->frames.first;
|
|
|
|
|
LISTBASE_FOREACH (bGPDframe *, gpf_orig, &gpl->frames) {
|
|
|
|
|
if (gpf_eval != NULL) {
|
|
|
|
|
/* Update frame reference pointers. */
|
|
|
|
|
gpf_eval->runtime.gpf_orig = (bGPDframe *)gpf_orig;
|
|
|
|
|
BKE_gpencil_frame_original_pointers_update(gpf_orig, gpf_eval);
|
|
|
|
|
gpf_eval = gpf_eval->next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
gpl_eval = gpl_eval->next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BKE_gpencil_parent_matrix_get(const Depsgraph *depsgraph,
|
|
|
|
|
Object *obact,
|
|
|
|
|
bGPDlayer *gpl,
|
|
|
|
|
float diff_mat[4][4])
|
|
|
|
|
{
|
|
|
|
|
Object *ob_eval = depsgraph != NULL ? DEG_get_evaluated_object(depsgraph, obact) : obact;
|
|
|
|
|
Object *obparent = gpl->parent;
|
|
|
|
|
Object *obparent_eval = depsgraph != NULL ? DEG_get_evaluated_object(depsgraph, obparent) :
|
|
|
|
|
obparent;
|
|
|
|
|
|
|
|
|
|
/* if not layer parented, try with object parented */
|
|
|
|
|
if (obparent_eval == NULL) {
|
|
|
|
|
if (ob_eval != NULL) {
|
|
|
|
|
if (ob_eval->type == OB_GPENCIL) {
|
|
|
|
|
copy_m4_m4(diff_mat, ob_eval->obmat);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* not gpencil object */
|
|
|
|
|
unit_m4(diff_mat);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if ((gpl->partype == PAROBJECT) || (gpl->partype == PARSKEL)) {
|
|
|
|
|
mul_m4_m4m4(diff_mat, obparent_eval->obmat, gpl->inverse);
|
|
|
|
|
add_v3_v3(diff_mat[3], ob_eval->obmat[3]);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else if (gpl->partype == PARBONE) {
|
|
|
|
|
bPoseChannel *pchan = BKE_pose_channel_find_name(obparent_eval->pose, gpl->parsubstr);
|
|
|
|
|
if (pchan) {
|
|
|
|
|
float tmp_mat[4][4];
|
|
|
|
|
mul_m4_m4m4(tmp_mat, obparent_eval->obmat, pchan->pose_mat);
|
|
|
|
|
mul_m4_m4m4(diff_mat, tmp_mat, gpl->inverse);
|
|
|
|
|
add_v3_v3(diff_mat[3], ob_eval->obmat[3]);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* if bone not found use object (armature) */
|
|
|
|
|
mul_m4_m4m4(diff_mat, obparent_eval->obmat, gpl->inverse);
|
|
|
|
|
add_v3_v3(diff_mat[3], ob_eval->obmat[3]);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
unit_m4(diff_mat); /* not defined type */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BKE_gpencil_update_layer_parent(const Depsgraph *depsgraph, Object *ob)
|
|
|
|
|
{
|
|
|
|
|
if (ob->type != OB_GPENCIL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bGPdata *gpd = (bGPdata *)ob->data;
|
|
|
|
|
float cur_mat[4][4];
|
|
|
|
|
|
|
|
|
|
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
|
|
|
|
|
if ((gpl->parent != NULL) && (gpl->actframe != NULL)) {
|
2020-03-17 17:09:20 +01:00
|
|
|
Object *ob_parent = DEG_get_evaluated_object(depsgraph, gpl->parent);
|
2020-03-09 16:27:24 +01:00
|
|
|
/* calculate new matrix */
|
|
|
|
|
if ((gpl->partype == PAROBJECT) || (gpl->partype == PARSKEL)) {
|
2020-03-17 17:09:20 +01:00
|
|
|
copy_m4_m4(cur_mat, ob_parent->obmat);
|
2020-03-09 16:27:24 +01:00
|
|
|
}
|
|
|
|
|
else if (gpl->partype == PARBONE) {
|
2020-03-17 17:09:20 +01:00
|
|
|
bPoseChannel *pchan = BKE_pose_channel_find_name(ob_parent->pose, gpl->parsubstr);
|
|
|
|
|
if (pchan != NULL) {
|
|
|
|
|
copy_m4_m4(cur_mat, ob->imat);
|
|
|
|
|
mul_m4_m4m4(cur_mat, ob_parent->obmat, pchan->pose_mat);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
unit_m4(cur_mat);
|
2020-03-09 16:27:24 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* only redo if any change */
|
|
|
|
|
if (!equals_m4m4(gpl->inverse, cur_mat)) {
|
|
|
|
|
LISTBASE_FOREACH (bGPDstroke *, gps, &gpl->actframe->strokes) {
|
2020-03-17 17:09:20 +01:00
|
|
|
bGPDspoint *pt;
|
|
|
|
|
int i;
|
2020-03-09 16:27:24 +01:00
|
|
|
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
|
2020-03-17 17:09:20 +01:00
|
|
|
mul_m4_v3(gpl->inverse, &pt->x);
|
|
|
|
|
mul_m4_v3(cur_mat, &pt->x);
|
2020-03-09 16:27:24 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/** \} */
|