GPencil: Dot dash modifier.

Create dot-dash effect for grease pencil strokes. User can manually edit the length, gap and styles for each segment of dashed lines.

The values in each segment can all be key-framed to make animations.

Reviewed By: Hans Goudey (HooglyBoogly), Antonio Vazquez (antoniov)

Differential Revision: http://developer.blender.org/D11876
This commit is contained in:
YimingWu 2021-09-15 14:24:28 +08:00
parent c1cf66bff3
commit a2c5c2b406
19 changed files with 909 additions and 8 deletions

@ -1 +1 @@
Subproject commit 8a05b618f031582c006c6f62b9e60619ab3eef8b
Subproject commit 62e82958a760dad775d9b3387d7fb535fd6de4c6

@ -1 +1 @@
Subproject commit 67f1fbca1482d9d9362a4001332e785c3fd5d230
Subproject commit 4475cbd11a636382d57571e0f5dfeff1f90bd6b7

@ -1 +1 @@
Subproject commit ef6ef414d22c2578fad99327743b925ab640a99c
Subproject commit 788441f2930465bbfba8f0797b12dcef1d46694d

View File

@ -938,6 +938,11 @@ void BKE_gpencil_modifier_blend_write(BlendWriter *writer, ListBase *modbase)
BKE_curvemapping_blend_write(writer, gpmd->curve_intensity);
}
}
else if (md->type == eGpencilModifierType_Dash) {
DashGpencilModifierData *gpmd = (DashGpencilModifierData *)md;
BLO_write_struct_array(
writer, DashGpencilModifierSegment, gpmd->segments_len, gpmd->segments);
}
}
}
@ -1017,6 +1022,10 @@ void BKE_gpencil_modifier_blend_read_data(BlendDataReader *reader, ListBase *lb)
BKE_curvemapping_init(gpmd->curve_intensity);
}
}
else if (md->type == eGpencilModifierType_Dash) {
DashGpencilModifierData *gpmd = (DashGpencilModifierData *)md;
BLO_read_data_address(reader, &gpmd->segments);
}
}
}

View File

@ -336,6 +336,7 @@ set(ICON_NAMES
lightprobe_cubemap
lightprobe_planar
lightprobe_grid
mod_dash
color_red
color_green
color_blue

View File

@ -37,6 +37,9 @@ set(INC
../../../../intern/clog
../../../../intern/glew-mx
../../../../intern/guardedalloc
# dna_type_offsets.h in BLO_read_write.h
${CMAKE_BINARY_DIR}/source/blender/makesdna/intern
)
set(SRC
@ -93,3 +96,5 @@ if(WITH_EXPERIMENTAL_FEATURES)
endif()
blender_add_lib(bf_editor_object "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
add_dependencies(bf_editor_object bf_dna)

View File

@ -28,6 +28,7 @@
#include "MEM_guardedalloc.h"
#include "DNA_defaults.h"
#include "DNA_gpencil_modifier_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_object_types.h"
@ -35,6 +36,7 @@
#include "BLI_listbase.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.h"
#include "BLI_utildefines.h"
#include "BKE_context.h"
@ -55,6 +57,8 @@
#include "ED_object.h"
#include "ED_screen.h"
#include "BLT_translation.h"
#include "UI_interface.h"
#include "WM_api.h"
@ -939,3 +943,237 @@ void OBJECT_OT_gpencil_modifier_copy_to_selected(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
gpencil_edit_modifier_properties(ot);
}
/************************* Dash Modifier *******************************/
static bool dash_segment_poll(bContext *C)
{
return gpencil_edit_modifier_poll_generic(C, &RNA_DashGpencilModifierData, 0, false);
}
static bool dash_segment_name_exists_fn(void *arg, const char *name)
{
const DashGpencilModifierData *dmd = (const DashGpencilModifierData *)arg;
for (int i = 0; i < dmd->segments_len; i++) {
if (STREQ(dmd->segments[i].name, name)) {
return true;
}
}
return false;
}
static int dash_segment_add_exec(bContext *C, wmOperator *op)
{
Object *ob = ED_object_active_context(C);
DashGpencilModifierData *dmd = (DashGpencilModifierData *)gpencil_edit_modifier_property_get(
op, ob, eGpencilModifierType_Dash);
const int new_active_index = dmd->segment_active_index + 1;
DashGpencilModifierSegment *new_segments = MEM_malloc_arrayN(
dmd->segments_len + 1, sizeof(DashGpencilModifierSegment), __func__);
if (dmd->segments_len != 0) {
/* Copy the segments before the new segment. */
memcpy(new_segments, dmd->segments, sizeof(DashGpencilModifierSegment) * new_active_index);
/* Copy the segments after the new segment. */
memcpy(new_segments + new_active_index + 1,
dmd->segments + new_active_index,
sizeof(DashGpencilModifierSegment) * (dmd->segments_len - new_active_index));
}
/* Create the new segment. */
DashGpencilModifierSegment *ds = &new_segments[new_active_index];
memcpy(
ds, DNA_struct_default_get(DashGpencilModifierSegment), sizeof(DashGpencilModifierSegment));
BLI_uniquename_cb(
dash_segment_name_exists_fn, dmd, DATA_("Segment"), '.', ds->name, sizeof(ds->name));
ds->dmd = dmd;
MEM_SAFE_FREE(dmd->segments);
dmd->segments = new_segments;
dmd->segments_len++;
dmd->segment_active_index++;
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
return OPERATOR_FINISHED;
}
static int dash_segment_add_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
if (gpencil_edit_modifier_invoke_properties(C, op, NULL, NULL)) {
return dash_segment_add_exec(C, op);
}
return OPERATOR_CANCELLED;
}
void GPENCIL_OT_segment_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Segment";
ot->description = "Add a segment to the dash modifier";
ot->idname = "GPENCIL_OT_segment_add";
/* api callbacks */
ot->poll = dash_segment_poll;
ot->invoke = dash_segment_add_invoke;
ot->exec = dash_segment_add_exec;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
edit_modifier_properties(ot);
}
static int dash_segment_remove_exec(bContext *C, wmOperator *op)
{
Object *ob = ED_object_active_context(C);
DashGpencilModifierData *dmd = (DashGpencilModifierData *)gpencil_edit_modifier_property_get(
op, ob, eGpencilModifierType_Dash);
if (dmd->segment_active_index < 0 || dmd->segment_active_index >= dmd->segments_len) {
return OPERATOR_CANCELLED;
}
if (dmd->segments_len == 1) {
MEM_SAFE_FREE(dmd->segments);
dmd->segment_active_index = -1;
}
else {
DashGpencilModifierSegment *new_segments = MEM_malloc_arrayN(
dmd->segments_len, sizeof(DashGpencilModifierSegment), __func__);
/* Copy the segments before the deleted segment. */
memcpy(new_segments,
dmd->segments,
sizeof(DashGpencilModifierSegment) * dmd->segment_active_index);
/* Copy the segments after the deleted segment. */
memcpy(new_segments + dmd->segment_active_index,
dmd->segments + dmd->segment_active_index + 1,
sizeof(DashGpencilModifierSegment) *
(dmd->segments_len - dmd->segment_active_index - 1));
MEM_freeN(dmd->segments);
dmd->segments = new_segments;
dmd->segment_active_index = MAX2(dmd->segment_active_index - 1, 0);
}
dmd->segments_len--;
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
return OPERATOR_FINISHED;
}
static int dash_segment_remove_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
if (gpencil_edit_modifier_invoke_properties(C, op, NULL, NULL)) {
return dash_segment_remove_exec(C, op);
}
return OPERATOR_CANCELLED;
}
void GPENCIL_OT_segment_remove(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Remove Dash Segment";
ot->description = "Remove the active segment from the dash modifier";
ot->idname = "GPENCIL_OT_segment_remove";
/* api callbacks */
ot->poll = dash_segment_poll;
ot->invoke = dash_segment_remove_invoke;
ot->exec = dash_segment_remove_exec;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
edit_modifier_properties(ot);
RNA_def_int(
ot->srna, "index", 0, 0, INT_MAX, "Index", "Index of the segment to remove", 0, INT_MAX);
}
enum {
GP_SEGEMENT_MOVE_UP = -1,
GP_SEGEMENT_MOVE_DOWN = 1,
};
static int dash_segment_move_exec(bContext *C, wmOperator *op)
{
Object *ob = ED_object_active_context(C);
DashGpencilModifierData *dmd = (DashGpencilModifierData *)gpencil_edit_modifier_property_get(
op, ob, eGpencilModifierType_Dash);
if (dmd->segments_len < 2) {
return OPERATOR_CANCELLED;
}
const int direction = RNA_enum_get(op->ptr, "type");
if (direction == GP_SEGEMENT_MOVE_UP) {
if (dmd->segment_active_index == 0) {
return OPERATOR_CANCELLED;
}
SWAP(DashGpencilModifierSegment,
dmd->segments[dmd->segment_active_index],
dmd->segments[dmd->segment_active_index - 1]);
dmd->segment_active_index--;
}
else if (direction == GP_SEGEMENT_MOVE_DOWN) {
if (dmd->segment_active_index == dmd->segments_len - 1) {
return OPERATOR_CANCELLED;
}
SWAP(DashGpencilModifierSegment,
dmd->segments[dmd->segment_active_index],
dmd->segments[dmd->segment_active_index + 1]);
dmd->segment_active_index++;
}
else {
return OPERATOR_CANCELLED;
}
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
return OPERATOR_FINISHED;
}
static int dash_segment_move_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
if (gpencil_edit_modifier_invoke_properties(C, op, NULL, NULL)) {
return dash_segment_move_exec(C, op);
}
return OPERATOR_CANCELLED;
}
void GPENCIL_OT_segment_move(wmOperatorType *ot)
{
static const EnumPropertyItem segment_move[] = {
{GP_SEGEMENT_MOVE_UP, "UP", 0, "Up", ""},
{GP_SEGEMENT_MOVE_DOWN, "DOWN", 0, "Down", ""},
{0, NULL, 0, NULL, NULL},
};
/* identifiers */
ot->name = "Move Dash Segment";
ot->description = "Move the active dash segment up or down";
ot->idname = "GPENCIL_OT_segment_move";
/* api callbacks */
ot->poll = dash_segment_poll;
ot->invoke = dash_segment_move_invoke;
ot->exec = dash_segment_move_exec;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
edit_modifier_properties(ot);
ot->prop = RNA_def_enum(ot->srna, "type", segment_move, 0, "Type", "");
}

View File

@ -202,6 +202,10 @@ void OBJECT_OT_gpencil_modifier_apply(struct wmOperatorType *ot);
void OBJECT_OT_gpencil_modifier_copy(struct wmOperatorType *ot);
void OBJECT_OT_gpencil_modifier_copy_to_selected(struct wmOperatorType *ot);
void GPENCIL_OT_segment_add(struct wmOperatorType *ot);
void GPENCIL_OT_segment_remove(struct wmOperatorType *ot);
void GPENCIL_OT_segment_move(struct wmOperatorType *ot);
/* object_shader_fx.c */
void OBJECT_OT_shaderfx_add(struct wmOperatorType *ot);
void OBJECT_OT_shaderfx_copy(struct wmOperatorType *ot);

View File

@ -156,6 +156,10 @@ void ED_operatortypes_object(void)
WM_operatortype_append(OBJECT_OT_gpencil_modifier_copy);
WM_operatortype_append(OBJECT_OT_gpencil_modifier_copy_to_selected);
WM_operatortype_append(GPENCIL_OT_segment_add);
WM_operatortype_append(GPENCIL_OT_segment_remove);
WM_operatortype_append(GPENCIL_OT_segment_move);
/* grease pencil line art */
WM_operatortypes_lineart();

View File

@ -52,6 +52,7 @@ set(SRC
intern/MOD_gpencilarray.c
intern/MOD_gpencilbuild.c
intern/MOD_gpencilcolor.c
intern/MOD_gpencildash.c
intern/MOD_gpencilhook.c
intern/MOD_gpencillattice.c
intern/MOD_gpencillength.c

View File

@ -46,6 +46,7 @@ extern GpencilModifierTypeInfo modifierType_Gpencil_Multiply;
extern GpencilModifierTypeInfo modifierType_Gpencil_Texture;
extern GpencilModifierTypeInfo modifierType_Gpencil_Weight;
extern GpencilModifierTypeInfo modifierType_Gpencil_Lineart;
extern GpencilModifierTypeInfo modifierType_Gpencil_Dash;
/* MOD_gpencil_util.c */
void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[]);

View File

@ -65,6 +65,7 @@ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[])
INIT_GP_TYPE(Texture);
INIT_GP_TYPE(Weight);
INIT_GP_TYPE(Lineart);
INIT_GP_TYPE(Dash);
#undef INIT_GP_TYPE
}

View File

@ -0,0 +1,387 @@
/*
* 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,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2021, Blender Foundation
* This is a new part of Blender
*/
/** \file
* \ingroup modifiers
*/
#include <stdio.h>
#include <string.h>
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_string.h"
#include "BLT_translation.h"
#include "DNA_defaults.h"
#include "DNA_gpencil_modifier_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "DNA_screen_types.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_geom.h"
#include "BKE_gpencil_modifier.h"
#include "BKE_lib_query.h"
#include "BKE_main.h"
#include "BKE_modifier.h"
#include "BKE_screen.h"
#include "MEM_guardedalloc.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "RNA_access.h"
#include "BLT_translation.h"
#include "MOD_gpencil_modifiertypes.h"
#include "MOD_gpencil_ui_common.h"
#include "MOD_gpencil_util.h"
#include "DEG_depsgraph.h"
#include "WM_api.h"
static void initData(GpencilModifierData *md)
{
DashGpencilModifierData *dmd = (DashGpencilModifierData *)md;
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(dmd, modifier));
MEMCPY_STRUCT_AFTER(dmd, DNA_struct_default_get(DashGpencilModifierData), modifier);
DashGpencilModifierSegment *ds = DNA_struct_default_alloc(DashGpencilModifierSegment);
ds->dmd = dmd;
BLI_strncpy(ds->name, DATA_("Segment"), sizeof(ds->name));
dmd->segments = ds;
}
static void copyData(const GpencilModifierData *md, GpencilModifierData *target)
{
DashGpencilModifierData *dmd = (DashGpencilModifierData *)target;
const DashGpencilModifierData *dmd_src = (const DashGpencilModifierData *)md;
BKE_gpencil_modifier_copydata_generic(md, target);
dmd->segments = MEM_dupallocN(dmd_src->segments);
}
static void freeData(GpencilModifierData *md)
{
DashGpencilModifierData *dmd = (DashGpencilModifierData *)md;
MEM_SAFE_FREE(dmd->segments);
}
/**
* Gap==0 means to start the next segment at the immediate next point, which will leave a visual
* gap of "1 point". This makes the algorithm give the same visual appearance as displayed on the
* UI and also simplifies the check for "no-length" situation where SEG==0 (which will not produce
* any effective dash).
*/
static int real_gap(const DashGpencilModifierSegment *ds)
{
return ds->gap - 1;
}
static bool stroke_dash(const bGPDstroke *gps,
const DashGpencilModifierData *dmd,
ListBase *r_strokes)
{
int new_stroke_offset = 0;
int trim_start = 0;
for (int i = 0; i < dmd->segments_len; i++) {
if (dmd->segments[i].dash + real_gap(&dmd->segments[i]) < 1) {
BLI_assert_unreachable();
/* This means there's a part that doesn't have any length, can't do dot-dash. */
return false;
}
}
const DashGpencilModifierSegment *const first_segment = &dmd->segments[0];
const DashGpencilModifierSegment *const last_segment = &dmd->segments[dmd->segments_len - 1];
const DashGpencilModifierSegment *ds = first_segment;
/* Determine starting configuration using offset. */
int offset_trim = dmd->dash_offset;
while (offset_trim < 0) {
ds = (ds == first_segment) ? last_segment : ds - 1;
offset_trim += ds->dash + real_gap(ds);
}
/* This segment is completely removed from view by the index offset, ignore it. */
while (ds->dash + real_gap(ds) < offset_trim) {
offset_trim -= ds->dash + real_gap(ds);
ds = (ds == last_segment) ? first_segment : ds + 1;
}
/* This segment is partially visible at the beginning of the stroke. */
if (ds->dash > offset_trim) {
trim_start = offset_trim;
}
else {
/* This segment is not visible but the gap immediately after this segment is partially visible,
* use next segment's dash. */
new_stroke_offset += ds->dash + real_gap(ds) - offset_trim;
ds = (ds == last_segment) ? first_segment : ds + 1;
}
while (new_stroke_offset < gps->totpoints - 1) {
const int seg = ds->dash - trim_start;
if (!(seg || real_gap(ds))) {
ds = (ds == last_segment) ? first_segment : ds + 1;
continue;
}
const int size = MIN2(gps->totpoints - new_stroke_offset, seg);
if (size == 0) {
continue;
}
bGPDstroke *stroke = BKE_gpencil_stroke_new(
ds->mat_nr < 0 ? gps->mat_nr : ds->mat_nr, size, gps->thickness);
for (int is = 0; is < size; is++) {
bGPDspoint *p = &gps->points[new_stroke_offset + is];
stroke->points[is].x = p->x;
stroke->points[is].y = p->y;
stroke->points[is].z = p->z;
stroke->points[is].pressure = p->pressure * ds->radius;
stroke->points[is].strength = p->strength * ds->opacity;
}
BLI_addtail(r_strokes, stroke);
if (gps->dvert) {
BKE_gpencil_dvert_ensure(stroke);
for (int di = 0; di < stroke->totpoints; di++) {
MDeformVert *dv = &gps->dvert[new_stroke_offset + di];
if (dv && dv->totweight && dv->dw) {
MDeformWeight *dw = (MDeformWeight *)MEM_callocN(sizeof(MDeformWeight) * dv->totweight,
__func__);
memcpy(dw, dv->dw, sizeof(MDeformWeight) * dv->totweight);
stroke->dvert[di].dw = dw;
stroke->dvert[di].totweight = dv->totweight;
stroke->dvert[di].flag = dv->flag;
}
}
}
new_stroke_offset += seg + real_gap(ds);
ds = (ds == last_segment) ? first_segment : ds + 1;
trim_start = 0;
}
return true;
}
static void apply_dash_for_frame(
Object *ob, bGPDlayer *gpl, bGPdata *gpd, bGPDframe *gpf, DashGpencilModifierData *dmd)
{
if (dmd->segments_len == 0) {
return;
}
ListBase result = {NULL, NULL};
LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
if (is_stroke_affected_by_modifier(ob,
dmd->layername,
dmd->material,
dmd->pass_index,
dmd->layer_pass,
1,
gpl,
gps,
dmd->flag & GP_LENGTH_INVERT_LAYER,
dmd->flag & GP_LENGTH_INVERT_PASS,
dmd->flag & GP_LENGTH_INVERT_LAYERPASS,
dmd->flag & GP_LENGTH_INVERT_MATERIAL)) {
stroke_dash(gps, dmd, &result);
BLI_remlink(&gpf->strokes, gps);
BKE_gpencil_free_stroke(gps);
}
}
bGPDstroke *gps_dash;
while ((gps_dash = BLI_pophead(&result))) {
BLI_addtail(&gpf->strokes, gps_dash);
BKE_gpencil_stroke_geometry_update(gpd, gps_dash);
}
}
static void bakeModifier(Main *UNUSED(bmain),
Depsgraph *UNUSED(depsgraph),
GpencilModifierData *md,
Object *ob)
{
bGPdata *gpd = ob->data;
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
apply_dash_for_frame(ob, gpl, gpd, gpf, (DashGpencilModifierData *)md);
}
}
}
/* -------------------------------- */
/* Generic "generateStrokes" callback */
static void generateStrokes(GpencilModifierData *md, Depsgraph *depsgraph, Object *ob)
{
bGPdata *gpd = ob->data;
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
BKE_gpencil_frame_active_set(depsgraph, gpd);
bGPDframe *gpf = gpl->actframe;
if (gpf == NULL) {
return;
}
apply_dash_for_frame(ob, gpl, gpd, gpf, (DashGpencilModifierData *)md);
}
}
static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData)
{
DashGpencilModifierData *mmd = (DashGpencilModifierData *)md;
walk(userData, ob, (ID **)&mmd->material, IDWALK_CB_USER);
}
static void segment_list_item(struct uiList *UNUSED(ui_list),
struct bContext *UNUSED(C),
struct uiLayout *layout,
struct PointerRNA *UNUSED(idataptr),
struct PointerRNA *itemptr,
int UNUSED(icon),
struct PointerRNA *UNUSED(active_dataptr),
const char *UNUSED(active_propname),
int UNUSED(index),
int UNUSED(flt_flag))
{
uiLayout *row = uiLayoutRow(layout, true);
uiItemR(row, itemptr, "name", UI_ITEM_R_NO_BG, "", ICON_NONE);
}
static void panel_draw(const bContext *C, Panel *panel)
{
uiLayout *layout = panel->layout;
PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL);
uiLayoutSetPropSep(layout, true);
uiItemR(layout, ptr, "dash_offset", 0, NULL, ICON_NONE);
uiLayout *row = uiLayoutRow(layout, false);
uiLayoutSetPropSep(row, false);
uiTemplateList(row,
(bContext *)C,
"MOD_UL_dash_segment",
"",
ptr,
"segments",
ptr,
"segment_active_index",
NULL,
3,
10,
0,
1,
UI_TEMPLATE_LIST_FLAG_NONE);
uiLayout *col = uiLayoutColumn(row, false);
uiLayoutSetContextPointer(col, "modifier", ptr);
uiLayout *sub = uiLayoutColumn(col, true);
uiItemO(sub, "", ICON_ADD, "GPENCIL_OT_segment_add");
uiItemO(sub, "", ICON_REMOVE, "GPENCIL_OT_segment_remove");
uiItemS(col);
sub = uiLayoutColumn(col, true);
uiItemEnumO_string(sub, "", ICON_TRIA_UP, "GPENCIL_OT_segment_move", "type", "UP");
uiItemEnumO_string(sub, "", ICON_TRIA_DOWN, "GPENCIL_OT_segment_move", "type", "DOWN");
DashGpencilModifierData *dmd = ptr->data;
if (dmd->segment_active_index >= 0 && dmd->segment_active_index < dmd->segments_len) {
PointerRNA ds_ptr;
RNA_pointer_create(ptr->owner_id,
&RNA_DashGpencilModifierSegment,
&dmd->segments[dmd->segment_active_index],
&ds_ptr);
sub = uiLayoutColumn(layout, true);
uiItemR(sub, &ds_ptr, "dash", 0, NULL, ICON_NONE);
uiItemR(sub, &ds_ptr, "gap", 0, NULL, ICON_NONE);
sub = uiLayoutColumn(layout, false);
uiItemR(sub, &ds_ptr, "radius", 0, NULL, ICON_NONE);
uiItemR(sub, &ds_ptr, "opacity", 0, NULL, ICON_NONE);
uiItemR(sub, &ds_ptr, "material_index", 0, NULL, ICON_NONE);
}
gpencil_modifier_panel_end(layout, ptr);
}
static void mask_panel_draw(const bContext *UNUSED(C), Panel *panel)
{
gpencil_modifier_masking_panel_draw(panel, true, false);
}
static void panelRegister(ARegionType *region_type)
{
PanelType *panel_type = gpencil_modifier_panel_register(
region_type, eGpencilModifierType_Dash, panel_draw);
gpencil_modifier_subpanel_register(
region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type);
uiListType *list_type = MEM_callocN(sizeof(uiListType), "dash modifier segment uilist");
strcpy(list_type->idname, "MOD_UL_dash_segment");
list_type->draw_item = segment_list_item;
WM_uilisttype_add(list_type);
}
GpencilModifierTypeInfo modifierType_Gpencil_Dash = {
/* name */ "Dot Dash",
/* structName */ "DashGpencilModifierData",
/* structSize */ sizeof(DashGpencilModifierData),
/* type */ eGpencilModifierTypeType_Gpencil,
/* flags */ eGpencilModifierTypeFlag_SupportsEditmode,
/* copyData */ copyData,
/* deformStroke */ NULL,
/* generateStrokes */ generateStrokes,
/* bakeModifier */ bakeModifier,
/* remapTime */ NULL,
/* initData */ initData,
/* freeData */ freeData,
/* isDisabled */ NULL,
/* updateDepsgraph */ NULL,
/* dependsOnTime */ NULL,
/* foreachIDLink */ foreachIDLink,
/* foreachTexLink */ NULL,
/* panelRegister */ panelRegister,
};

View File

@ -319,5 +319,23 @@
.material = NULL,\
}
#define _DNA_DEFAULT_DashGpencilModifierData \
{ \
.dash_offset = 0, \
.segments = NULL, \
.segments_len = 1, \
.segment_active_index = 0, \
}
#define _DNA_DEFAULT_DashGpencilModifierSegment \
{ \
.name = "", \
.dash = 2, \
.gap = 1, \
.radius = 1.0f, \
.opacity = 1.0f, \
.mat_nr = -1, \
}
/* clang-format off */

View File

@ -56,6 +56,7 @@ typedef enum GpencilModifierType {
eGpencilModifierType_Lineart = 19,
eGpencilModifierType_Length = 20,
eGpencilModifierType_Weight = 21,
eGpencilModifierType_Dash = 22,
/* Keep last. */
NUM_GREASEPENCIL_MODIFIER_TYPES,
} GpencilModifierType;
@ -507,6 +508,39 @@ typedef enum eLengthGpencil_Type {
GP_LENGTH_ABSOLUTE = 1,
} eLengthGpencil_Type;
typedef struct DashGpencilModifierSegment {
char name[64];
/* For path reference. */
struct DashGpencilModifierData *dmd;
int dash;
int gap;
float radius;
float opacity;
int mat_nr;
int _pad;
} DashGpencilModifierSegment;
typedef struct DashGpencilModifierData {
GpencilModifierData modifier;
/** Material for filtering. */
struct Material *material;
/** Layer name. */
char layername[64];
/** Custom index for passes. */
int pass_index;
/** Flags. */
int flag;
/** Custom index for passes. */
int layer_pass;
int dash_offset;
DashGpencilModifierSegment *segments;
int segments_len;
int segment_active_index;
} DashGpencilModifierData;
typedef struct MirrorGpencilModifierData {
GpencilModifierData modifier;
struct Object *object;

View File

@ -321,6 +321,8 @@ SDNA_DEFAULT_DECL_STRUCT(TintGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(WeightGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(LineartGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(LengthGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(DashGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(DashGpencilModifierSegment);
#undef SDNA_DEFAULT_DECL_STRUCT
@ -549,6 +551,8 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
SDNA_DEFAULT_DECL(WeightGpencilModifierData),
SDNA_DEFAULT_DECL(LineartGpencilModifierData),
SDNA_DEFAULT_DECL(LengthGpencilModifierData),
SDNA_DEFAULT_DECL(DashGpencilModifierData),
SDNA_DEFAULT_DECL(DashGpencilModifierSegment),
};
#undef SDNA_DEFAULT_DECL
#undef SDNA_DEFAULT_DECL_EX

View File

@ -222,6 +222,8 @@ extern StructRNA RNA_CurvePoint;
extern StructRNA RNA_CurveProfile;
extern StructRNA RNA_CurveProfilePoint;
extern StructRNA RNA_DampedTrackConstraint;
extern StructRNA RNA_DashGpencilModifierData;
extern StructRNA RNA_DashGpencilModifierSegment;
extern StructRNA RNA_DataTransferModifier;
extern StructRNA RNA_DecimateModifier;
extern StructRNA RNA_Depsgraph;

View File

@ -35,6 +35,7 @@
#include "BLI_math.h"
#include "BLI_rand.h"
#include "BLI_string_utils.h"
#include "BLT_translation.h"
@ -68,6 +69,11 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = {
ICON_MOD_BUILD,
"Build",
"Create duplication of strokes"},
{eGpencilModifierType_Dash,
"GP_DASH",
ICON_MOD_DASH,
"Dot Dash",
"Generate dot-dash styled strokes"},
{eGpencilModifierType_Lineart,
"GP_LINEART",
ICON_MOD_LINEART,
@ -268,6 +274,8 @@ static StructRNA *rna_GpencilModifier_refine(struct PointerRNA *ptr)
return &RNA_TextureGpencilModifier;
case eGpencilModifierType_Lineart:
return &RNA_LineartGpencilModifier;
case eGpencilModifierType_Dash:
return &RNA_DashGpencilModifierData;
/* Default */
case eGpencilModifierType_None:
case NUM_GREASEPENCIL_MODIFIER_TYPES:
@ -282,19 +290,19 @@ static void rna_GpencilModifier_name_set(PointerRNA *ptr, const char *value)
GpencilModifierData *gmd = ptr->data;
char oldname[sizeof(gmd->name)];
/* make a copy of the old name first */
/* Make a copy of the old name first. */
BLI_strncpy(oldname, gmd->name, sizeof(gmd->name));
/* copy the new name into the name slot */
/* Copy the new name into the name slot. */
BLI_strncpy_utf8(gmd->name, value, sizeof(gmd->name));
/* make sure the name is truly unique */
/* Make sure the name is truly unique. */
if (ptr->owner_id) {
Object *ob = (Object *)ptr->owner_id;
BKE_gpencil_modifier_unique_name(&ob->greasepencil_modifiers, gmd);
}
/* fix all the animation data which may link to this */
/* Fix all the animation data which may link to this. */
BKE_animdata_fix_paths_rename_all(NULL, "grease_pencil_modifiers", oldname, gmd->name);
}
@ -674,6 +682,59 @@ static void rna_Lineart_end_level_set(PointerRNA *ptr, int value)
lmd->level_start = MIN2(value, lmd->level_start);
}
static void rna_GpencilDash_segments_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
{
DashGpencilModifierData *dmd = (DashGpencilModifierData *)ptr->data;
rna_iterator_array_begin(
iter, dmd->segments, sizeof(DashGpencilModifierSegment), dmd->segments_len, false, NULL);
}
static char *rna_DashGpencilModifierSegment_path(PointerRNA *ptr)
{
DashGpencilModifierSegment *ds = (DashGpencilModifierSegment *)ptr->data;
DashGpencilModifierData *dmd = (DashGpencilModifierData *)ds->dmd;
BLI_assert(dmd != NULL);
char name_esc[sizeof(dmd->modifier.name) * 2 + 1];
BLI_str_escape(name_esc, dmd->modifier.name, sizeof(name_esc));
return BLI_sprintfN("grease_pencil_modifiers[\"%s\"].segments[\"%s\"]", name_esc, ds->name);
}
static bool dash_segment_name_exists_fn(void *arg, const char *name)
{
const DashGpencilModifierData *dmd = (const DashGpencilModifierData *)arg;
for (int i = 0; i < dmd->segments_len; i++) {
if (STREQ(dmd->segments[i].name, name)) {
return true;
}
}
return false;
}
static void rna_DashGpencilModifierSegment_name_set(PointerRNA *ptr, const char *value)
{
DashGpencilModifierSegment *ds = ptr->data;
char oldname[sizeof(ds->name)];
BLI_strncpy(oldname, ds->name, sizeof(ds->name));
BLI_strncpy_utf8(ds->name, value, sizeof(ds->name));
BLI_assert(ds->dmd != NULL);
BLI_uniquename_cb(
dash_segment_name_exists_fn, ds->dmd, "Segment", '.', ds->name, sizeof(ds->name));
char prefix[256];
sprintf(prefix, "grease_pencil_modifiers[\"%s\"].segments", ds->dmd->modifier.name);
/* Fix all the animation data which may link to this. */
BKE_animdata_fix_paths_rename_all(NULL, prefix, oldname, ds->name);
}
#else
static void rna_def_modifier_gpencilnoise(BlenderRNA *brna)
@ -3287,6 +3348,136 @@ static void rna_def_modifier_gpencillength(BlenderRNA *brna)
RNA_define_lib_overridable(false);
}
static void rna_def_modifier_gpencildash(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "DashGpencilModifierSegment", NULL);
RNA_def_struct_ui_text(srna, "Dash Modifier Segment", "Configuration for a single dash segment");
RNA_def_struct_sdna(srna, "DashGpencilModifierSegment");
RNA_def_struct_path_func(srna, "rna_DashGpencilModifierSegment_path");
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
RNA_def_property_ui_text(prop, "Name", "Name of the dash segment");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
RNA_def_property_string_funcs(prop, NULL, NULL, "rna_DashGpencilModifierSegment_name_set");
RNA_def_property_update(prop, NC_OBJECT | ND_MODIFIER | NA_RENAME, NULL);
RNA_def_struct_name_property(srna, prop);
prop = RNA_def_property(srna, "dash", PROP_INT, PROP_NONE);
RNA_def_property_range(prop, 1, INT16_MAX);
RNA_def_property_ui_text(
prop,
"Dash",
"The number of consecutive points from the original stroke to include in this segment");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "gap", PROP_INT, PROP_NONE);
RNA_def_property_range(prop, 1, INT16_MAX);
RNA_def_property_ui_text(prop, "Gap", "The number of points skipped after this segment");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "radius", PROP_FLOAT, PROP_FACTOR | PROP_UNSIGNED);
RNA_def_property_ui_range(prop, 0, 1, 0.1, 2);
RNA_def_property_ui_text(
prop, "Radius", "The factor to apply to the original point's radius for the new points");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "opacity", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_ui_range(prop, 0, 1, 0.1, 2);
RNA_def_property_ui_text(
prop, "Opacity", "The factor to apply to the original point's opacity for the new points");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "material_index", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "mat_nr");
RNA_def_property_range(prop, -1, INT16_MAX);
RNA_def_property_ui_text(
prop,
"Material Index",
"Use this index on generated segment. -1 means using the existing material");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
srna = RNA_def_struct(brna, "DashGpencilModifierData", "GpencilModifier");
RNA_def_struct_ui_text(srna, "Dash Modifier", "Create dot-dash effect for strokes");
RNA_def_struct_sdna(srna, "DashGpencilModifierData");
RNA_def_struct_ui_icon(srna, ICON_MOD_DASH);
RNA_define_lib_overridable(true);
prop = RNA_def_property(srna, "segments", PROP_COLLECTION, PROP_NONE);
RNA_def_property_struct_type(prop, "DashGpencilModifierSegment");
RNA_def_property_collection_sdna(prop, NULL, "segments", NULL);
RNA_def_property_collection_funcs(prop,
"rna_GpencilDash_segments_begin",
"rna_iterator_array_next",
"rna_iterator_array_end",
"rna_iterator_array_get",
NULL,
NULL,
NULL,
NULL);
RNA_def_property_ui_text(prop, "Segments", "");
prop = RNA_def_property(srna, "segment_active_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Active Dash Segement Index", "Active index in the segment list");
prop = RNA_def_property(srna, "dash_offset", PROP_INT, PROP_NONE);
RNA_def_property_ui_text(
prop,
"Offset",
"Offset into each stroke before the beginning of the dashed segment generation");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
/* Common properties. */
prop = RNA_def_property(srna, "layer", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "layername");
RNA_def_property_ui_text(prop, "Layer", "Layer name");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "material", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK);
RNA_def_property_ui_text(prop, "Material", "Material used for filtering effect");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "pass_index");
RNA_def_property_range(prop, 0, 100);
RNA_def_property_ui_text(prop, "Pass", "Pass index");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "invert_layers", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_LAYER);
RNA_def_property_ui_text(prop, "Inverse Layers", "Inverse filter");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "invert_materials", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_MATERIAL);
RNA_def_property_ui_text(prop, "Inverse Materials", "Inverse filter");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "invert_material_pass", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_PASS);
RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "layer_pass", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "layer_pass");
RNA_def_property_range(prop, 0, 100);
RNA_def_property_ui_text(prop, "Pass", "Layer pass index");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "invert_layer_pass", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_LAYERPASS);
RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
RNA_define_lib_overridable(false);
}
void RNA_def_greasepencil_modifier(BlenderRNA *brna)
{
StructRNA *srna;
@ -3364,6 +3555,7 @@ void RNA_def_greasepencil_modifier(BlenderRNA *brna)
rna_def_modifier_gpencilweight(brna);
rna_def_modifier_gpencillineart(brna);
rna_def_modifier_gpencillength(brna);
rna_def_modifier_gpencildash(brna);
}
#endif

@ -1 +1 @@
Subproject commit 5cf2fc3e5dc28025394b57d8743401295528f310
Subproject commit c8579c5cf43229843df505da9644b5b0b7201974