GPencil MultiStroke modifier

This patch includes a modifiers that developed for NPR rendering.

- MultiStroke modifier that generates multiple strokes around the original ones.

Differential Revision: https://developer.blender.org/D5795
This commit is contained in:
YimingWu 2019-11-14 19:18:23 +01:00 committed by Antonio Vazquez
parent 8ff9eb97fb
commit 91248876e5
10 changed files with 830 additions and 0 deletions

View File

@ -2351,7 +2351,52 @@ class DATA_PT_gpencil_modifiers(ModifierButtonsPanel, Panel):
sub.active = bool(md.vertex_group)
sub.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')
def GP_MULTIPLY(self, layout, ob, md):
gpd = ob.data
col = layout.column()
col.prop(md, "duplications")
subcol = col.column()
subcol.enabled = md.duplications > 0
subcol.prop(md, "distance")
subcol.prop(md, "offset", slider=True)
subcol.separator()
subcol.prop(md, "enable_fading")
if md.enable_fading:
subcol.prop(md, "fading_center")
subcol.prop(md, "fading_thickness", slider=True)
subcol.prop(md, "fading_opacity", slider=True)
subcol.separator()
col.prop(md, "enable_angle_splitting")
if md.enable_angle_splitting:
col.prop(md, "split_angle")
col = layout.column()
col.separator()
col.label(text="Material:")
row = col.row(align=True)
row.prop_search(md, "material", gpd, "materials", text="", icon='SHADING_TEXTURE')
row.prop(md, "invert_materials", text="", icon='ARROW_LEFTRIGHT')
row = layout.row(align=True)
row.prop(md, "pass_index", text="Pass")
row.prop(md, "invert_material_pass", text="", icon='ARROW_LEFTRIGHT')
col = layout.column()
col.separator()
col.label(text="Layer:")
row = col.row(align=True)
row.prop_search(md, "layer", gpd, "layers", text="", icon='GREASEPENCIL')
row.prop(md, "invert_layers", text="", icon='ARROW_LEFTRIGHT')
row = layout.row(align=True)
row.prop(md, "layer_pass", text="Pass")
row.prop(md, "invert_layer_pass", text="", icon='ARROW_LEFTRIGHT')
classes = (
DATA_PT_modifiers,
DATA_PT_gpencil_modifiers,

View File

@ -125,6 +125,12 @@ struct bGPDstroke *BKE_gpencil_add_stroke(struct bGPDframe *gpf,
int totpoints,
short thickness);
struct bGPDstroke *BKE_gpencil_add_stroke_existing_style(struct bGPDframe *gpf,
struct bGPDstroke *existing,
int mat_idx,
int totpoints,
short thickness);
/* Stroke and Fill - Alpha Visibility Threshold */
#define GPENCIL_ALPHA_OPACITY_THRESH 0.001f
#define GPENCIL_STRENGTH_MIN 0.003f
@ -238,6 +244,18 @@ bool BKE_gpencil_smooth_stroke_uv(struct bGPDstroke *gps, int point_index, float
bool BKE_gpencil_close_stroke(struct bGPDstroke *gps);
void BKE_gpencil_dissolve_points(struct bGPDframe *gpf, struct bGPDstroke *gps, const short tag);
bool BKE_gpencil_stretch_stroke(struct bGPDstroke *gps, const float dist, const float tip_length);
bool BKE_gpencil_trim_stroke_points(struct bGPDstroke *gps,
const int index_from,
const int index_to);
bool BKE_gpencil_split_stroke(struct bGPDframe *gpf,
struct bGPDstroke *gps,
const int before_index,
struct bGPDstroke **remaining_gps);
bool BKE_gpencil_shrink_stroke(struct bGPDstroke *gps, const float dist);
float BKE_gpencil_stroke_length(const struct bGPDstroke *gps, bool use_3d);
void BKE_gpencil_get_range_selected(struct bGPDlayer *gpl, int *r_initframe, int *r_endframe);
float BKE_gpencil_multiframe_falloff_calc(
struct bGPDframe *gpf, int actnum, int f_init, int f_end, struct CurveMapping *cur_falloff);

View File

@ -505,6 +505,18 @@ bGPDstroke *BKE_gpencil_add_stroke(bGPDframe *gpf, int mat_idx, int totpoints, s
return gps;
}
/* Add a stroke and copy the temporary drawing color value from one of the existing stroke */
bGPDstroke *BKE_gpencil_add_stroke_existing_style(
bGPDframe *gpf, bGPDstroke *existing, int mat_idx, int totpoints, short thickness)
{
bGPDstroke *gps = BKE_gpencil_add_stroke(gpf, mat_idx, totpoints, thickness);
/* Copy runtime color data so that strokes added in the modifier has the style.
* There are depsgrapgh reference pointers inside,
* change the copy function if interfere with future drawing implementation. */
memcpy(&gps->runtime, &existing->runtime, sizeof(bGPDstroke_Runtime));
return gps;
}
/* ************************************************** */
/* Data Duplication */
@ -1752,6 +1764,240 @@ bool BKE_gpencil_sample_stroke(bGPDstroke *gps, const float dist, const bool sel
return true;
}
/**
* Backbone stretch similar to Freestyle.
* \param gps: Stroke to sample
* \param dist: Distance of one segment
* \param tip_length: Ignore tip jittering, set zero to use default value.
*/
bool BKE_gpencil_stretch_stroke(bGPDstroke *gps, const float dist, const float tip_length)
{
bGPDspoint *pt = gps->points, *last_pt, *second_last, *next_pt;
int i;
float threshold = (tip_length == 0 ? 0.001f : tip_length);
if (gps->totpoints < 2 || dist < FLT_EPSILON) {
return false;
}
last_pt = &pt[gps->totpoints - 1];
second_last = &pt[gps->totpoints - 2];
next_pt = &pt[1];
float len1 = 0.0f;
float len2 = 0.0f;
i = 1;
while (len1 < threshold && gps->totpoints > i) {
next_pt = &pt[i];
len1 = len_v3v3(&next_pt->x, &pt->x);
i++;
}
i = 2;
while (len2 < threshold && gps->totpoints >= i) {
second_last = &pt[gps->totpoints - i];
len2 = len_v3v3(&last_pt->x, &second_last->x);
i++;
}
float extend1 = (len1 + dist) / len1;
float extend2 = (len2 + dist) / len2;
float result1[3], result2[3];
interp_v3_v3v3(result1, &next_pt->x, &pt->x, extend1);
interp_v3_v3v3(result2, &second_last->x, &last_pt->x, extend2);
copy_v3_v3(&pt->x, result1);
copy_v3_v3(&last_pt->x, result2);
return true;
}
/**
* Trim stroke to needed segments
* \param gps: Target stroke
* \param index_from: the index of the first point to be used in the trimmed result
* \param index_to: the index of the last point to be used in the trimmed result
*/
bool BKE_gpencil_trim_stroke_points(bGPDstroke *gps, const int index_from, const int index_to)
{
bGPDspoint *pt = gps->points, *new_pt;
MDeformVert *dv, *new_dv;
const int new_count = index_to - index_from + 1;
if (new_count >= gps->totpoints) {
return false;
}
if (new_count == 1) {
BKE_gpencil_free_stroke_weights(gps);
MEM_freeN(gps->points);
gps->points = NULL;
gps->dvert = NULL;
gps->totpoints = 0;
return false;
}
new_pt = MEM_callocN(sizeof(bGPDspoint) * new_count, "gp_stroke_points_trimmed");
for (int i = 0; i < new_count; i++) {
memcpy(&new_pt[i], &pt[i + index_from], sizeof(bGPDspoint));
}
if (gps->dvert) {
new_dv = MEM_callocN(sizeof(MDeformVert) * new_count, "gp_stroke_dverts_trimmed");
for (int i = 0; i < new_count; i++) {
dv = &gps->dvert[i + index_from];
new_dv[i].flag = dv->flag;
new_dv[i].totweight = dv->totweight;
new_dv[i].dw = MEM_callocN(sizeof(MDeformWeight) * dv->totweight,
"gp_stroke_dverts_dw_trimmed");
for (int j = 0; j < dv->totweight; j++) {
new_dv[i].dw[j].weight = dv->dw[j].weight;
new_dv[i].dw[j].def_nr = dv->dw[j].def_nr;
}
}
MEM_freeN(gps->dvert);
gps->dvert = new_dv;
}
MEM_freeN(gps->points);
gps->points = new_pt;
gps->totpoints = new_count;
return true;
}
bool BKE_gpencil_split_stroke(bGPDframe *gpf,
bGPDstroke *gps,
const int before_index,
bGPDstroke **remaining_gps)
{
bGPDstroke *new_gps;
bGPDspoint *pt = gps->points, *new_pt;
MDeformVert *dv, *new_dv;
if (before_index >= gps->totpoints || before_index == 0) {
return false;
}
const int new_count = gps->totpoints - before_index;
const int old_count = before_index;
/* Handle remaining segments first. */
new_gps = BKE_gpencil_add_stroke_existing_style(
gpf, gps, gps->mat_nr, new_count, gps->thickness);
new_pt = new_gps->points; /* Allocated from above. */
for (int i = 0; i < new_count; i++) {
memcpy(&new_pt[i], &pt[i + before_index], sizeof(bGPDspoint));
}
if (gps->dvert) {
new_dv = MEM_callocN(sizeof(MDeformVert) * new_count, "gp_stroke_dverts_remaining");
for (int i = 0; i < new_count; i++) {
dv = &gps->dvert[i + before_index];
new_dv[i].flag = dv->flag;
new_dv[i].totweight = dv->totweight;
new_dv[i].dw = MEM_callocN(sizeof(MDeformWeight) * dv->totweight,
"gp_stroke_dverts_dw_remaining");
for (int j = 0; j < dv->totweight; j++) {
new_dv[i].dw[j].weight = dv->dw[j].weight;
new_dv[i].dw[j].def_nr = dv->dw[j].def_nr;
}
}
new_gps->dvert = new_dv;
}
(*remaining_gps) = new_gps;
/* Trim the original stroke into a shorter one.
* Keep the end point. */
BKE_gpencil_trim_stroke_points(gps, 0, old_count);
return true;
}
/**
* Shrink the stroke by length.
* \param gps: Stroke to shrink
* \param dist: delta length
*/
bool BKE_gpencil_shrink_stroke(bGPDstroke *gps, const float dist)
{
bGPDspoint *pt = gps->points, *last_pt, *second_last, *next_pt;
int i;
if (gps->totpoints < 2 || dist < FLT_EPSILON) {
return false;
}
last_pt = &pt[gps->totpoints - 1];
second_last = &pt[gps->totpoints - 2];
next_pt = &pt[1];
float len1, this_len1, cut_len1;
float len2, this_len2, cut_len2;
int index_start, index_end;
len1 = len2 = this_len1 = this_len2 = cut_len1 = cut_len2 = 0.0f;
i = 1;
while (len1 < dist && gps->totpoints > i - 1) {
next_pt = &pt[i];
this_len1 = len_v3v3(&pt[i].x, &pt[i + 1].x);
len1 += this_len1;
cut_len1 = len1 - dist;
i++;
}
index_start = i - 2;
i = 2;
while (len2 < dist && gps->totpoints >= i) {
second_last = &pt[gps->totpoints - i];
this_len2 = len_v3v3(&second_last[1].x, &second_last->x);
len2 += this_len2;
cut_len2 = len2 - dist;
i++;
}
index_end = gps->totpoints - i + 2;
if (len1 < dist || len2 < dist || index_end <= index_start) {
index_start = index_end = 0; /* empty stroke */
}
if ((index_end == index_start + 1) && (cut_len1 + cut_len2 > 1.0f)) {
index_start = index_end = 0; /* no length left to cut */
}
BKE_gpencil_trim_stroke_points(gps, index_start, index_end);
if (gps->totpoints == 0) {
return false;
}
pt = gps->points;
float cut1 = cut_len1 / this_len1;
float cut2 = cut_len2 / this_len2;
float result1[3], result2[3];
interp_v3_v3v3(result1, &pt[1].x, &pt[0].x, cut1);
interp_v3_v3v3(result2, &pt[gps->totpoints - 2].x, &pt[gps->totpoints - 1].x, cut2);
copy_v3_v3(&pt[0].x, result1);
copy_v3_v3(&pt[gps->totpoints - 1].x, result2);
return true;
}
/**
* Apply smooth to stroke point
* \param gps: Stroke to smooth
@ -2273,6 +2519,28 @@ void BKE_gpencil_stroke_2d_flat_ref(const bGPDspoint *ref_points,
*r_direction = (int)locy[2];
}
float BKE_gpencil_stroke_length(const bGPDstroke *gps, bool use_3d)
{
if (!gps->points || gps->totpoints < 2) {
return 0.0f;
}
float *last_pt = &gps->points[0].x;
int i;
bGPDspoint *pt;
float total_length = 0.0f;
for (i = 1; i < gps->totpoints; i++) {
pt = &gps->points[i];
if (use_3d) {
total_length += len_v3v3(&pt->x, last_pt);
}
else {
total_length += len_v2v2(&pt->x, last_pt);
}
last_pt = &pt->x;
}
return total_length;
}
/**
* Trim stroke to the first intersection or loop
* \param gps: Stroke data

View File

@ -50,6 +50,7 @@ set(SRC
intern/MOD_gpencilhook.c
intern/MOD_gpencillattice.c
intern/MOD_gpencilmirror.c
intern/MOD_gpencilmultiply.c
intern/MOD_gpencilnoise.c
intern/MOD_gpenciloffset.c
intern/MOD_gpencilopacity.c

View File

@ -42,6 +42,7 @@ extern GpencilModifierTypeInfo modifierType_Gpencil_Hook;
extern GpencilModifierTypeInfo modifierType_Gpencil_Offset;
extern GpencilModifierTypeInfo modifierType_Gpencil_Armature;
extern GpencilModifierTypeInfo modifierType_Gpencil_Time;
extern GpencilModifierTypeInfo modifierType_Gpencil_Multiply;
/* MOD_gpencil_util.c */
void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[]);

View File

@ -71,6 +71,7 @@ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[])
INIT_GP_TYPE(Offset);
INIT_GP_TYPE(Armature);
INIT_GP_TYPE(Time);
INIT_GP_TYPE(Multiply);
#undef INIT_GP_TYPE
}

View File

@ -0,0 +1,350 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* 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) 2017, Blender Foundation
* This is a new part of Blender
*
* ***** END GPL LICENSE BLOCK *****
*
*/
/** \file blender/gpencil_modifiers/intern/MOD_gpencilstrokes.c
* \ingroup modifiers
*/
#include <stdio.h>
#include "MEM_guardedalloc.h"
#include "DNA_scene_types.h"
#include "DNA_object_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_gpencil_modifier_types.h"
#include "BLI_blenlib.h"
#include "BLI_rand.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "BLI_linklist.h"
#include "BLI_alloca.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_modifier.h"
#include "BKE_modifier.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_object.h"
#include "BKE_main.h"
#include "BKE_scene.h"
#include "BKE_layer.h"
#include "BKE_library_query.h"
#include "BKE_collection.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
#include "bmesh.h"
#include "bmesh_tools.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
#include "DEG_depsgraph_query.h"
#include "MOD_gpencil_util.h"
#include "MOD_gpencil_modifiertypes.h"
static void initData(GpencilModifierData *md)
{
MultiplyGpencilModifierData *mmd = (MultiplyGpencilModifierData *)md;
mmd->duplications = 3;
mmd->distance = 0.1f;
mmd->split_angle = 1.0f;
}
static void copyData(const GpencilModifierData *md, GpencilModifierData *target)
{
BKE_gpencil_modifier_copyData_generic(md, target);
}
static void splitStroke(bGPDframe *gpf, bGPDstroke *gps, float split_angle)
{
bGPDspoint *pt = gps->points;
bGPDstroke *new_gps = gps;
int i;
volatile float angle;
if (split_angle <= FLT_EPSILON) {
return;
}
for (i = 1; i < new_gps->totpoints - 1; i++) {
angle = angle_v3v3v3(&pt[i - 1].x, &pt[i].x, &pt[i + 1].x);
if (angle < split_angle) {
if (BKE_gpencil_split_stroke(gpf, new_gps, i, &new_gps)) {
pt = new_gps->points;
i = 0;
continue; /* then i == 1 again */
}
}
}
}
static void minter_v3_v3v3v3_ref(
float *result, float *left, float *middle, float *right, float *stroke_normal)
{
float left_arm[3], right_arm[3], inter1[3], inter2[3];
float minter[3];
if (left) {
sub_v3_v3v3(left_arm, middle, left);
cross_v3_v3v3(inter1, stroke_normal, left_arm);
}
if (right) {
sub_v3_v3v3(right_arm, right, middle);
cross_v3_v3v3(inter2, stroke_normal, right_arm);
}
if (!left) {
normalize_v3(inter2);
copy_v3_v3(result, inter2);
return;
}
if (!right) {
normalize_v3(inter1);
copy_v3_v3(result, inter1);
return;
}
interp_v3_v3v3(minter, inter1, inter2, 0.5);
normalize_v3(minter);
copy_v3_v3(result, minter);
}
static void duplicateStroke(bGPDframe *gpf,
bGPDstroke *gps,
int count,
float dist,
float offset,
ListBase *results,
int fading,
float fading_center,
float fading_thickness,
float fading_opacity)
{
int i;
bGPDstroke *new_gps;
float stroke_normal[3];
float minter[3];
bGPDspoint *pt;
float offset_factor;
float thickness_factor;
float opacity_factor;
BKE_gpencil_stroke_normal(gps, stroke_normal);
if (len_v3(stroke_normal) < FLT_EPSILON) {
add_v3_fl(stroke_normal, 1);
normalize_v3(stroke_normal);
}
float *t1_array = MEM_callocN(sizeof(float) * 3 * gps->totpoints,
"duplicate_temp_result_array_1");
float *t2_array = MEM_callocN(sizeof(float) * 3 * gps->totpoints,
"duplicate_temp_result_array_2");
pt = gps->points;
for (int j = 0; j < gps->totpoints; j++) {
if (j == 0) {
minter_v3_v3v3v3_ref(minter, NULL, &pt[j].x, &pt[j + 1].x, stroke_normal);
}
else if (j == gps->totpoints - 1) {
minter_v3_v3v3v3_ref(minter, &pt[j - 1].x, &pt[j].x, NULL, stroke_normal);
}
else {
minter_v3_v3v3v3_ref(minter, &pt[j - 1].x, &pt[j].x, &pt[j + 1].x, stroke_normal);
}
mul_v3_fl(minter, dist);
add_v3_v3v3(&t1_array[j * 3], &pt[j].x, minter);
sub_v3_v3v3(&t2_array[j * 3], &pt[j].x, minter);
}
/* This ensures the original stroke is the last one to be processed. */
for (i = count - 1; i >= 0; i--) {
if (i != 0) {
new_gps = BKE_gpencil_stroke_duplicate(gps);
new_gps->flag |= GP_STROKE_RECALC_GEOMETRY;
BLI_addtail(results, new_gps);
}
else {
new_gps = gps;
}
pt = new_gps->points;
if (count == 1) {
offset_factor = 0;
}
else {
offset_factor = (float)i / (float)(count - 1);
}
if (fading) {
thickness_factor = (offset_factor > fading_center) ?
(interpf(1 - fading_thickness, 1.0f, offset_factor - fading_center)) :
(interpf(
1.0f, 1 - fading_thickness, offset_factor - fading_center + 1));
opacity_factor = (offset_factor > fading_center) ?
(interpf(1 - fading_opacity, 1.0f, offset_factor - fading_center)) :
(interpf(1.0f, 1 - fading_opacity, offset_factor - fading_center + 1));
}
for (int j = 0; j < new_gps->totpoints; j++) {
interp_v3_v3v3(&pt[j].x,
&t1_array[j * 3],
&t2_array[j * 3],
interpf(1 + offset, offset, offset_factor));
if (fading) {
pt[j].pressure = gps->points[j].pressure * thickness_factor;
pt[j].strength = gps->points[j].strength * opacity_factor;
}
}
}
MEM_freeN(t1_array);
MEM_freeN(t2_array);
}
static void bakeModifier(Main *UNUSED(bmain),
Depsgraph *depsgraph,
GpencilModifierData *md,
Object *ob)
{
bGPdata *gpd = ob->data;
for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) {
ListBase duplicates = {0};
MultiplyGpencilModifierData *mmd = (MultiplyGpencilModifierData *)md;
bGPDstroke *gps;
for (gps = gpf->strokes.first; gps; gps = gps->next) {
if (!is_stroke_affected_by_modifier(ob,
mmd->layername,
mmd->materialname,
mmd->pass_index,
mmd->layer_pass,
1,
gpl,
gps,
mmd->flag & GP_MIRROR_INVERT_LAYER,
mmd->flag & GP_MIRROR_INVERT_PASS,
mmd->flag & GP_MIRROR_INVERT_LAYERPASS,
mmd->flag & GP_MIRROR_INVERT_MATERIAL)) {
continue;
}
if (mmd->flags & GP_MULTIPLY_ENABLE_ANGLE_SPLITTING) {
splitStroke(gpf, gps, mmd->split_angle);
}
if (mmd->duplications > 0) {
duplicateStroke(gpf,
gps,
mmd->duplications,
mmd->distance,
mmd->offset,
&duplicates,
mmd->flags & GP_MULTIPLY_ENABLE_FADING,
mmd->fading_center,
mmd->fading_thickness,
mmd->fading_opacity);
}
}
if (duplicates.first) {
((bGPDstroke *)gpf->strokes.last)->next = duplicates.first;
((bGPDstroke *)duplicates.first)->prev = gpf->strokes.last;
gpf->strokes.last = duplicates.first;
}
}
}
}
/* -------------------------------- */
/* Generic "generateStrokes" callback */
static void generateStrokes(
GpencilModifierData *md, Depsgraph *depsgraph, Object *ob, bGPDlayer *gpl, bGPDframe *gpf)
{
MultiplyGpencilModifierData *mmd = (MultiplyGpencilModifierData *)md;
bGPDstroke *gps;
ListBase duplicates = {0};
for (gps = gpf->strokes.first; gps; gps = gps->next) {
if (!is_stroke_affected_by_modifier(ob,
mmd->layername,
mmd->materialname,
mmd->pass_index,
mmd->layer_pass,
1,
gpl,
gps,
mmd->flag & GP_MIRROR_INVERT_LAYER,
mmd->flag & GP_MIRROR_INVERT_PASS,
mmd->flag & GP_MIRROR_INVERT_LAYERPASS,
mmd->flag & GP_MIRROR_INVERT_MATERIAL)) {
continue;
}
if (mmd->flags & GP_MULTIPLY_ENABLE_ANGLE_SPLITTING) {
splitStroke(gpf, gps, mmd->split_angle);
}
if (mmd->duplications > 0) {
duplicateStroke(gpf,
gps,
mmd->duplications,
mmd->distance,
mmd->offset,
&duplicates,
mmd->flags & GP_MULTIPLY_ENABLE_FADING,
mmd->fading_center,
mmd->fading_thickness,
mmd->fading_opacity);
}
}
if (duplicates.first) {
((bGPDstroke *)gpf->strokes.last)->next = duplicates.first;
((bGPDstroke *)duplicates.first)->prev = gpf->strokes.last;
gpf->strokes.last = duplicates.first;
}
}
GpencilModifierTypeInfo modifierType_Gpencil_Multiply = {
/* name */ "Multiple Strokes",
/* structName */ "MultiplyGpencilModifierData",
/* structSize */ sizeof(MultiplyGpencilModifierData),
/* type */ eGpencilModifierTypeType_Gpencil,
/* flags */ 0,
/* copyData */ copyData,
/* deformStroke */ NULL,
/* generateStrokes */ generateStrokes,
/* bakeModifier */ bakeModifier,
/* remapTime */ NULL,
/* initData */ initData,
/* freeData */ NULL,
/* isDisabled */ NULL,
/* updateDepsgraph */ NULL,
/* dependsOnTime */ NULL,
/* foreachObjectLink */ NULL,
/* foreachIDLink */ NULL,
/* foreachTexLink */ NULL,
};

View File

@ -46,6 +46,7 @@ typedef enum GpencilModifierType {
eGpencilModifierType_Mirror = 14,
eGpencilModifierType_Armature = 15,
eGpencilModifierType_Time = 16,
eGpencilModifierType_Multiply = 17,
NUM_GREASEPENCIL_MODIFIER_TYPES,
} GpencilModifierType;
@ -640,4 +641,40 @@ typedef struct ArmatureGpencilModifierData {
} ArmatureGpencilModifierData;
typedef struct MultiplyGpencilModifierData {
GpencilModifierData modifier;
/** Layer name. */
char layername[64];
/** Material name. */
char materialname[64];
/** Custom index for passes. */
int pass_index;
/** Flags. */
int flag;
/** Custom index for passes. */
int layer_pass;
char _pad[4];
int flags;
int duplications;
float distance;
/* -1:inner 0:middle 1:outer */
float offset;
float fading_center;
float fading_thickness;
float fading_opacity;
/* in rad not deg */
float split_angle;
/* char _pad[4]; */
} MultiplyGpencilModifierData;
typedef enum eMultiplyGpencil_Flag {
GP_MULTIPLY_ENABLE_ANGLE_SPLITTING = (1 << 1),
GP_MULTIPLY_ENABLE_FADING = (1 << 2),
} eMultiplyGpencil_Flag;
#endif /* __DNA_GPENCIL_MODIFIER_TYPES_H__ */

View File

@ -432,6 +432,7 @@ extern StructRNA RNA_MovieTrackingObject;
extern StructRNA RNA_MovieTrackingStabilization;
extern StructRNA RNA_MovieTrackingTrack;
extern StructRNA RNA_MulticamSequence;
extern StructRNA RNA_MultiplyGpencilModifier;
extern StructRNA RNA_MultiresModifier;
extern StructRNA RNA_MusgraveTexture;
extern StructRNA RNA_NandController;

View File

@ -81,6 +81,11 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = {
ICON_MOD_SUBSURF,
"Subdivide",
"Subdivide stroke adding more control points"},
{eGpencilModifierType_Multiply,
"GP_MULTIPLY",
ICON_GP_MULTIFRAME_EDITING,
"Multiple Strokes",
"Produce multiple strokes along one stroke"},
{0, "", 0, N_("Deform"), ""},
{eGpencilModifierType_Armature,
"GP_ARMATURE",
@ -217,6 +222,8 @@ static StructRNA *rna_GpencilModifier_refine(struct PointerRNA *ptr)
return &RNA_OffsetGpencilModifier;
case eGpencilModifierType_Armature:
return &RNA_ArmatureGpencilModifier;
case eGpencilModifierType_Multiply:
return &RNA_MultiplyGpencilModifier;
/* Default */
case eGpencilModifierType_None:
case NUM_GREASEPENCIL_MODIFIER_TYPES:
@ -1866,6 +1873,106 @@ static void rna_def_modifier_gpencilarmature(BlenderRNA *brna)
RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update");
}
static void rna_def_modifier_gpencilmultiply(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "MultiplyGpencilModifier", "GpencilModifier");
RNA_def_struct_ui_text(srna, "Multiply Modifier", "Generate multiple strokes from one stroke");
RNA_def_struct_sdna(srna, "MultiplyGpencilModifierData");
RNA_def_struct_ui_icon(srna, ICON_GP_MULTIFRAME_EDITING);
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_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "materialname");
RNA_def_property_ui_text(prop, "Material", "Material name");
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_MIRROR_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_MIRROR_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_MIRROR_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_MIRROR_INVERT_LAYERPASS);
RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "enable_angle_splitting", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flags", GP_MULTIPLY_ENABLE_ANGLE_SPLITTING);
RNA_def_property_ui_text(prop, "Angle Splitting", "Enable angle splitting");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "enable_fading", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flags", GP_MULTIPLY_ENABLE_FADING);
RNA_def_property_ui_text(prop, "Enable Fading", "Enable fading");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "split_angle", PROP_FLOAT, PROP_NONE);
RNA_def_property_range(prop, 0, M_PI);
RNA_def_property_ui_text(prop, "Angle", "Split angle for segments");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "duplications", PROP_INT, PROP_NONE);
RNA_def_property_range(prop, 0, 10);
RNA_def_property_ui_text(prop, "Duplications", "How many copies of strokes be displayed");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "distance", PROP_FLOAT, PROP_NONE);
RNA_def_property_range(prop, 0, M_PI);
RNA_def_property_ui_text(prop, "Distance", "Distance of duplications.");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "offset", PROP_FLOAT, PROP_NONE);
RNA_def_property_ui_range(prop, -1, 1, 0.1, 3);
RNA_def_property_ui_text(prop, "Offset", "Offset of duplications. -1 to 1: inner to outer");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "fading_thickness", PROP_FLOAT, PROP_NONE);
RNA_def_property_range(prop, 0, 1);
RNA_def_property_float_default(prop, 0.5);
RNA_def_property_ui_text(prop, "Thickness", "Fade influence of stroke's thickness");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "fading_opacity", PROP_FLOAT, PROP_NONE);
RNA_def_property_range(prop, 0, 1);
RNA_def_property_float_default(prop, 0.5);
RNA_def_property_ui_text(prop, "Opacity", "Fade influence of stroke's opacity");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "fading_center", PROP_FLOAT, PROP_NONE);
RNA_def_property_range(prop, 0, 1);
RNA_def_property_ui_text(prop, "Center", "Fade center");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
}
void RNA_def_greasepencil_modifier(BlenderRNA *brna)
{
StructRNA *srna;
@ -1938,6 +2045,7 @@ void RNA_def_greasepencil_modifier(BlenderRNA *brna)
rna_def_modifier_gpencilmirror(brna);
rna_def_modifier_gpencilhook(brna);
rna_def_modifier_gpencilarmature(brna);
rna_def_modifier_gpencilmultiply(brna);
}
#endif