GPv3: Dash modifier

Reimplements the "Dash" (aka Dash-Dot) modifier of GPv2.

Pull Request: https://projects.blender.org/blender/blender/pulls/117758
This commit is contained in:
Lukas Tönne 2024-02-08 15:35:20 +01:00
parent 506556fb3f
commit 2c8de207b7
12 changed files with 1040 additions and 0 deletions

@ -150,6 +150,7 @@ class OBJECT_MT_modifier_add_generate(ModifierAddMenu, Menu):
if ob_type == 'MESH':
self.operator_modifier_add(layout, 'WIREFRAME')
if ob_type == 'GREASEPENCIL':
self.operator_modifier_add(layout, 'GREASE_PENCIL_DASH')
self.operator_modifier_add(layout, 'GREASE_PENCIL_MIRROR')
self.operator_modifier_add(layout, 'GREASE_PENCIL_SUBDIV')
layout.template_modifier_asset_menu_items(catalog_path=self.bl_label)

@ -225,6 +225,9 @@ void OBJECT_OT_laplaciandeform_bind(struct wmOperatorType *ot);
void OBJECT_OT_surfacedeform_bind(struct wmOperatorType *ot);
void OBJECT_OT_geometry_nodes_input_attribute_toggle(struct wmOperatorType *ot);
void OBJECT_OT_geometry_node_tree_copy_assign(struct wmOperatorType *ot);
void OBJECT_OT_grease_pencil_dash_modifier_segment_add(struct wmOperatorType *ot);
void OBJECT_OT_grease_pencil_dash_modifier_segment_remove(struct wmOperatorType *ot);
void OBJECT_OT_grease_pencil_dash_modifier_segment_move(struct wmOperatorType *ot);
/* object_gpencil_modifiers.c */

@ -18,6 +18,7 @@
#include "DNA_armature_types.h"
#include "DNA_array_utils.hh"
#include "DNA_curve_types.h"
#include "DNA_defaults.h"
#include "DNA_dynamicpaint_types.h"
#include "DNA_fluid_types.h"
#include "DNA_key_types.h"
@ -34,6 +35,7 @@
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.hh"
#include "BLI_utildefines.h"
#include "BKE_DerivedMesh.hh"
@ -3728,3 +3730,251 @@ void OBJECT_OT_geometry_node_tree_copy_assign(wmOperatorType *ot)
}
/** \} */
/* ------------------------------------------------------------------- */
/** \name Dash Modifier
* \{ */
namespace blender::ed::greasepencil {
static bool dash_modifier_segment_poll(bContext *C)
{
return edit_modifier_poll_generic(C, &RNA_GreasePencilDashModifierData, 0, false, false);
}
static int dash_modifier_segment_add_exec(bContext *C, wmOperator *op)
{
Object *ob = ED_object_active_context(C);
auto *dmd = reinterpret_cast<GreasePencilDashModifierData *>(
edit_modifier_property_get(op, ob, eModifierType_GreasePencilDash));
if (dmd == nullptr) {
return OPERATOR_CANCELLED;
}
GreasePencilDashModifierSegment *new_segments = static_cast<GreasePencilDashModifierSegment *>(
MEM_malloc_arrayN(dmd->segments_num + 1, sizeof(GreasePencilDashModifierSegment), __func__));
const int new_active_index = dmd->segment_active_index + 1;
if (dmd->segments_num != 0) {
/* Copy the segments before the new segment. */
memcpy(new_segments,
dmd->segments_array,
sizeof(GreasePencilDashModifierSegment) * new_active_index);
/* Copy the segments after the new segment. */
memcpy(new_segments + new_active_index + 1,
dmd->segments_array + new_active_index,
sizeof(GreasePencilDashModifierSegment) * (dmd->segments_num - new_active_index));
}
/* Create the new segment. */
GreasePencilDashModifierSegment *ds = &new_segments[new_active_index];
memcpy(ds,
DNA_struct_default_get(GreasePencilDashModifierSegment),
sizeof(GreasePencilDashModifierSegment));
BLI_uniquename_cb(
[&](const blender::StringRef name) {
for (const GreasePencilDashModifierSegment &ds : dmd->segments()) {
if (STREQ(ds.name, name.data())) {
return true;
}
}
return false;
},
'.',
ds->name);
MEM_SAFE_FREE(dmd->segments_array);
dmd->segments_array = new_segments;
dmd->segments_num++;
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_modifier_segment_add_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
if (edit_modifier_invoke_properties(C, op)) {
return dash_modifier_segment_add_exec(C, op);
}
return OPERATOR_CANCELLED;
}
} // namespace blender::ed::greasepencil
void OBJECT_OT_grease_pencil_dash_modifier_segment_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Segment";
ot->description = "Add a segment to the dash modifier";
ot->idname = "OBJECT_OT_grease_pencil_dash_modifier_segment_add";
/* api callbacks */
ot->poll = blender::ed::greasepencil::dash_modifier_segment_poll;
ot->invoke = blender::ed::greasepencil::dash_modifier_segment_add_invoke;
ot->exec = blender::ed::greasepencil::dash_modifier_segment_add_exec;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
edit_modifier_properties(ot);
}
namespace blender::ed::greasepencil {
static void dash_modifier_segment_free(GreasePencilDashModifierSegment * /*ds*/) {}
static int dash_modifier_segment_remove_exec(bContext *C, wmOperator *op)
{
Object *ob = ED_object_active_context(C);
auto *dmd = reinterpret_cast<GreasePencilDashModifierData *>(
edit_modifier_property_get(op, ob, eModifierType_GreasePencilDash));
if (dmd == nullptr) {
return OPERATOR_CANCELLED;
}
if (!dmd->segments().index_range().contains(dmd->segment_active_index)) {
return OPERATOR_CANCELLED;
}
blender::dna::array::remove_index(&dmd->segments_array,
&dmd->segments_num,
&dmd->segment_active_index,
dmd->segment_active_index,
dash_modifier_segment_free);
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_modifier_segment_remove_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
if (edit_modifier_invoke_properties(C, op)) {
return dash_modifier_segment_remove_exec(C, op);
}
return OPERATOR_CANCELLED;
}
} // namespace blender::ed::greasepencil
void OBJECT_OT_grease_pencil_dash_modifier_segment_remove(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Remove Dash Segment";
ot->description = "Remove the active segment from the dash modifier";
ot->idname = "OBJECT_OT_grease_pencil_dash_modifier_segment_remove";
/* api callbacks */
ot->poll = blender::ed::greasepencil::dash_modifier_segment_poll;
ot->invoke = blender::ed::greasepencil::dash_modifier_segment_remove_invoke;
ot->exec = blender::ed::greasepencil::dash_modifier_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);
}
namespace blender::ed::greasepencil {
enum class DashSegmentMoveDirection {
Up = -1,
Down = 1,
};
static int dash_modifier_segment_move_exec(bContext *C, wmOperator *op)
{
Object *ob = ED_object_active_context(C);
auto *dmd = reinterpret_cast<GreasePencilDashModifierData *>(
edit_modifier_property_get(op, ob, eModifierType_GreasePencilDash));
if (dmd == nullptr) {
return OPERATOR_CANCELLED;
}
if (dmd->segments_num < 2) {
return OPERATOR_CANCELLED;
}
const DashSegmentMoveDirection direction = DashSegmentMoveDirection(
RNA_enum_get(op->ptr, "type"));
switch (direction) {
case DashSegmentMoveDirection::Up:
if (dmd->segment_active_index == 0) {
return OPERATOR_CANCELLED;
}
std::swap(dmd->segments_array[dmd->segment_active_index],
dmd->segments_array[dmd->segment_active_index - 1]);
dmd->segment_active_index--;
break;
case DashSegmentMoveDirection::Down:
if (dmd->segment_active_index == dmd->segments_num - 1) {
return OPERATOR_CANCELLED;
}
std::swap(dmd->segments_array[dmd->segment_active_index],
dmd->segments_array[dmd->segment_active_index + 1]);
dmd->segment_active_index++;
break;
default:
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_modifier_segment_move_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
if (edit_modifier_invoke_properties(C, op)) {
return dash_modifier_segment_move_exec(C, op);
}
return OPERATOR_CANCELLED;
}
} // namespace blender::ed::greasepencil
void OBJECT_OT_grease_pencil_dash_modifier_segment_move(wmOperatorType *ot)
{
using blender::ed::greasepencil::DashSegmentMoveDirection;
static const EnumPropertyItem segment_move[] = {
{int(DashSegmentMoveDirection::Up), "UP", 0, "Up", ""},
{int(DashSegmentMoveDirection::Down), "DOWN", 0, "Down", ""},
{0, nullptr, 0, nullptr, nullptr},
};
/* identifiers */
ot->name = "Move Dash Segment";
ot->description = "Move the active dash segment up or down";
ot->idname = "OBJECT_OT_grease_pencil_dash_modifier_segment_move";
/* api callbacks */
ot->poll = blender::ed::greasepencil::dash_modifier_segment_poll;
ot->invoke = blender::ed::greasepencil::dash_modifier_segment_move_invoke;
ot->exec = blender::ed::greasepencil::dash_modifier_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", "");
}
/** \} */

@ -141,6 +141,9 @@ void ED_operatortypes_object()
WM_operatortype_append(OBJECT_OT_skin_armature_create);
WM_operatortype_append(OBJECT_OT_geometry_nodes_input_attribute_toggle);
WM_operatortype_append(OBJECT_OT_geometry_node_tree_copy_assign);
WM_operatortype_append(OBJECT_OT_grease_pencil_dash_modifier_segment_add);
WM_operatortype_append(OBJECT_OT_grease_pencil_dash_modifier_segment_remove);
WM_operatortype_append(OBJECT_OT_grease_pencil_dash_modifier_segment_move);
/* grease pencil modifiers */
WM_operatortype_append(OBJECT_OT_gpencil_modifier_add);

@ -878,4 +878,22 @@
.strength = 1.0f, \
}
#define _DNA_DEFAULT_GreasePencilDashModifierData \
{ \
.dash_offset = 0, \
.segments_array = NULL, \
.segments_num = 0, \
.segment_active_index = 0, \
}
#define _DNA_DEFAULT_GreasePencilDashModifierSegment \
{ \
.name = "", \
.dash = 2, \
.gap = 1, \
.radius = 1.0f, \
.opacity = 1.0f, \
.mat_nr = -1, \
}
/* clang-format off */

@ -15,6 +15,8 @@
#include "DNA_session_uid_types.h"
#ifdef __cplusplus
# include "BLI_span.hh"
namespace blender {
struct NodesModifierRuntime;
}
@ -103,6 +105,7 @@ typedef enum ModifierType {
eModifierType_GreasePencilMirror = 68,
eModifierType_GreasePencilThickness = 69,
eModifierType_GreasePencilLattice = 70,
eModifierType_GreasePencilDash = 71,
NUM_MODIFIER_TYPES,
} ModifierType;
@ -2781,3 +2784,35 @@ typedef struct GreasePencilLatticeModifierData {
float strength;
char _pad[4];
} GreasePencilLatticeModifierData;
typedef struct GreasePencilDashModifierSegment {
char name[64];
int dash;
int gap;
float radius;
float opacity;
int mat_nr;
/** #GreasePencilDashModifierFlag */
int flag;
} GreasePencilDashModifierSegment;
typedef struct GreasePencilDashModifierData {
ModifierData modifier;
GreasePencilModifierInfluenceData influence;
GreasePencilDashModifierSegment *segments_array;
int segments_num;
int segment_active_index;
int dash_offset;
char _pad[4];
#ifdef __cplusplus
blender::Span<GreasePencilDashModifierSegment> segments() const;
blender::MutableSpan<GreasePencilDashModifierSegment> segments();
#endif
} GreasePencilDashModifierData;
typedef enum GreasePencilDashModifierFlag {
MOD_GREASE_PENCIL_DASH_USE_CYCLIC = (1 << 0),
} GreasePencilDashModifierFlag;

@ -334,6 +334,8 @@ SDNA_DEFAULT_DECL_STRUCT(GreasePencilOffsetModifierData);
SDNA_DEFAULT_DECL_STRUCT(GreasePencilMirrorModifierData);
SDNA_DEFAULT_DECL_STRUCT(GreasePencilThickModifierData);
SDNA_DEFAULT_DECL_STRUCT(GreasePencilLatticeModifierData);
SDNA_DEFAULT_DECL_STRUCT(GreasePencilDashModifierSegment);
SDNA_DEFAULT_DECL_STRUCT(GreasePencilDashModifierData);
#undef SDNA_DEFAULT_DECL_STRUCT
@ -590,6 +592,8 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
SDNA_DEFAULT_DECL(GreasePencilMirrorModifierData),
SDNA_DEFAULT_DECL(GreasePencilThickModifierData),
SDNA_DEFAULT_DECL(GreasePencilLatticeModifierData),
SDNA_DEFAULT_DECL(GreasePencilDashModifierSegment),
SDNA_DEFAULT_DECL(GreasePencilDashModifierData),
};
#undef SDNA_DEFAULT_DECL
#undef SDNA_DEFAULT_DECL_EX

@ -326,6 +326,11 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = {
ICON_MOD_LATTICE,
"Lattice",
"Deform strokes using a lattice object"},
{eModifierType_GreasePencilDash,
"GREASE_PENCIL_DASH",
ICON_MOD_DASH,
"Dot Dash",
"Generate dot-dash styled strokes"},
RNA_ENUM_ITEM_HEADING(N_("Physics"), nullptr),
{eModifierType_Cloth, "CLOTH", ICON_MOD_CLOTH, "Cloth", ""},
@ -736,6 +741,7 @@ const EnumPropertyItem rna_enum_subdivision_boundary_smooth_items[] = {
# include "BKE_particle.h"
# include "BLI_sort_utils.h"
# include "BLI_string_utils.hh"
# include "DEG_depsgraph.hh"
# include "DEG_depsgraph_build.hh"
@ -1863,6 +1869,7 @@ RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilSmooth);
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilNoise);
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilThick);
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilLattice);
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilDash);
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilOffset);
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilOpacity);
@ -1893,6 +1900,80 @@ static void rna_GreasePencilOpacityModifier_opacity_factor_max_set(PointerRNA *p
value;
}
static const GreasePencilDashModifierData *find_grease_pencil_dash_modifier_of_segment(
const Object &ob, const GreasePencilDashModifierSegment &dash_segment)
{
LISTBASE_FOREACH (const ModifierData *, md, &ob.modifiers) {
if (md->type == eModifierType_GreasePencilDash) {
const auto *dmd = reinterpret_cast<const GreasePencilDashModifierData *>(md);
if (dmd->segments().contains_ptr(&dash_segment)) {
return dmd;
}
}
}
return nullptr;
}
static std::optional<std::string> rna_GreasePencilDashModifierSegment_path(const PointerRNA *ptr)
{
const Object *ob = reinterpret_cast<Object *>(ptr->owner_id);
const auto *dash_segment = static_cast<GreasePencilDashModifierSegment *>(ptr->data);
const GreasePencilDashModifierData *dmd = find_grease_pencil_dash_modifier_of_segment(
*ob, *dash_segment);
BLI_assert(dmd != nullptr);
char name_esc[sizeof(dmd->modifier.name) * 2];
BLI_str_escape(name_esc, dmd->modifier.name, sizeof(name_esc));
char ds_name_esc[sizeof(dash_segment->name) * 2];
BLI_str_escape(ds_name_esc, dash_segment->name, sizeof(ds_name_esc));
return fmt::format("modifiers[\"{}\"].segments[\"{}\"]", name_esc, ds_name_esc);
}
static void rna_GreasePencilDashModifierSegment_name_set(PointerRNA *ptr, const char *value)
{
const Object *ob = reinterpret_cast<Object *>(ptr->owner_id);
auto *dash_segment = static_cast<GreasePencilDashModifierSegment *>(ptr->data);
const GreasePencilDashModifierData *dmd = find_grease_pencil_dash_modifier_of_segment(
*ob, *dash_segment);
BLI_assert(dmd != nullptr);
const std::string oldname = dash_segment->name;
STRNCPY_UTF8(dash_segment->name, value);
BLI_uniquename_cb(
[dmd, dash_segment](const blender::StringRef name) {
for (const GreasePencilDashModifierSegment &ds : dmd->segments()) {
if (&ds != dash_segment && ds.name == name) {
return true;
}
}
return false;
},
'.',
dash_segment->name);
/* Fix all the animation data which may link to this. */
char name_esc[sizeof(dmd->modifier.name) * 2];
BLI_str_escape(name_esc, dmd->modifier.name, sizeof(name_esc));
char rna_path_prefix[36 + sizeof(name_esc) + 1];
SNPRINTF(rna_path_prefix, "modifiers[\"%s\"].segments", name_esc);
BKE_animdata_fix_paths_rename_all(nullptr, rna_path_prefix, oldname.c_str(), dash_segment->name);
}
static void rna_GreasePencilDashModifier_segments_begin(CollectionPropertyIterator *iter,
PointerRNA *ptr)
{
auto *dmd = static_cast<GreasePencilDashModifierData *>(ptr->data);
rna_iterator_array_begin(iter,
dmd->segments_array,
sizeof(GreasePencilDashModifierSegment),
dmd->segments_num,
false,
nullptr);
}
#else
static void rna_def_modifier_panel_open_prop(StructRNA *srna, const char *identifier, const int id)
@ -8449,6 +8530,111 @@ static void rna_def_modifier_grease_pencil_lattice(BlenderRNA *brna)
RNA_define_lib_overridable(false);
}
static void rna_def_modifier_grease_pencil_dash_segment(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "GreasePencilDashModifierSegment", nullptr);
RNA_def_struct_ui_text(srna, "Dash Modifier Segment", "Configuration for a single dash segment");
RNA_def_struct_sdna(srna, "GreasePencilDashModifierSegment");
RNA_def_struct_path_func(srna, "rna_GreasePencilDashModifierSegment_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_string_funcs(
prop, nullptr, nullptr, "rna_GreasePencilDashModifierSegment_name_set");
RNA_def_property_update(prop, NC_OBJECT | ND_MODIFIER | NA_RENAME, nullptr);
RNA_def_struct_name_property(srna, prop);
RNA_def_property_update(prop, 0, "rna_Modifier_update");
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_Modifier_update");
prop = RNA_def_property(srna, "gap", PROP_INT, PROP_NONE);
RNA_def_property_range(prop, 0, INT16_MAX);
RNA_def_property_ui_text(prop, "Gap", "The number of points skipped after this segment");
RNA_def_property_update(prop, 0, "rna_Modifier_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_Modifier_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_Modifier_update");
prop = RNA_def_property(srna, "material_index", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "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_Modifier_update");
prop = RNA_def_property(srna, "use_cyclic", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", MOD_GREASE_PENCIL_DASH_USE_CYCLIC);
RNA_def_property_ui_text(prop, "Cyclic", "Enable cyclic on individual stroke dashes");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
}
static void rna_def_modifier_grease_pencil_dash(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "GreasePencilDashModifierData", "Modifier");
RNA_def_struct_ui_text(
srna, "Grease Pencil Dash Modifier", "Create dot-dash effect for strokes");
RNA_def_struct_sdna(srna, "GreasePencilDashModifierData");
RNA_def_struct_ui_icon(srna, ICON_MOD_DASH);
rna_def_modifier_grease_pencil_layer_filter(srna);
rna_def_modifier_grease_pencil_material_filter(
srna, "rna_GreasePencilDashModifier_material_filter_set");
rna_def_modifier_panel_open_prop(srna, "open_influence_panel", 0);
RNA_define_lib_overridable(true);
prop = RNA_def_property(srna, "segments", PROP_COLLECTION, PROP_NONE);
RNA_def_property_struct_type(prop, "GreasePencilDashModifierSegment");
RNA_def_property_collection_sdna(prop, nullptr, "segments_array", nullptr);
RNA_def_property_collection_funcs(prop,
"rna_GreasePencilDashModifier_segments_begin",
"rna_iterator_array_next",
"rna_iterator_array_end",
"rna_iterator_array_get",
nullptr,
nullptr,
nullptr,
nullptr);
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 Segment 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_Modifier_update");
RNA_define_lib_overridable(false);
}
void RNA_def_modifier(BlenderRNA *brna)
{
StructRNA *srna;
@ -8626,6 +8812,8 @@ void RNA_def_modifier(BlenderRNA *brna)
rna_def_modifier_grease_pencil_mirror(brna);
rna_def_modifier_grease_pencil_thickness(brna);
rna_def_modifier_grease_pencil_lattice(brna);
rna_def_modifier_grease_pencil_dash_segment(brna);
rna_def_modifier_grease_pencil_dash(brna);
}
#endif

@ -45,6 +45,7 @@ set(SRC
intern/MOD_explode.cc
intern/MOD_fluid.cc
intern/MOD_grease_pencil_color.cc
intern/MOD_grease_pencil_dash.cc
intern/MOD_grease_pencil_lattice.cc
intern/MOD_grease_pencil_mirror.cc
intern/MOD_grease_pencil_noise.cc

@ -83,6 +83,7 @@ extern ModifierTypeInfo modifierType_GreasePencilNoise;
extern ModifierTypeInfo modifierType_GreasePencilMirror;
extern ModifierTypeInfo modifierType_GreasePencilThickness;
extern ModifierTypeInfo modifierType_GreasePencilLattice;
extern ModifierTypeInfo modifierType_GreasePencilDash;
/* MOD_util.cc */

@ -0,0 +1,535 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup modifiers
*/
#include "BLI_index_range.hh"
#include "BLI_span.hh"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "DNA_defaults.h"
#include "DNA_modifier_types.h"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "BKE_lib_query.hh"
#include "BKE_material.h"
#include "BKE_modifier.hh"
#include "BKE_screen.hh"
#include "BLO_read_write.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "BLT_translation.h"
#include "WM_api.hh"
#include "WM_types.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.h"
#include "MOD_grease_pencil_util.hh"
#include "MOD_modifiertypes.hh"
#include "MOD_ui_common.hh"
namespace blender {
static void init_data(ModifierData *md)
{
auto *dmd = reinterpret_cast<GreasePencilDashModifierData *>(md);
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(dmd, modifier));
MEMCPY_STRUCT_AFTER(dmd, DNA_struct_default_get(GreasePencilDashModifierData), modifier);
modifier::greasepencil::init_influence_data(&dmd->influence, false);
GreasePencilDashModifierSegment *ds = DNA_struct_default_alloc(GreasePencilDashModifierSegment);
STRNCPY_UTF8(ds->name, DATA_("Segment"));
dmd->segments_array = ds;
dmd->segments_num = 1;
}
static void copy_data(const ModifierData *md, ModifierData *target, const int flag)
{
const auto *dmd = reinterpret_cast<const GreasePencilDashModifierData *>(md);
auto *tmmd = reinterpret_cast<GreasePencilDashModifierData *>(target);
modifier::greasepencil::free_influence_data(&tmmd->influence);
BKE_modifier_copydata_generic(md, target, flag);
modifier::greasepencil::copy_influence_data(&dmd->influence, &tmmd->influence, flag);
tmmd->segments_array = static_cast<GreasePencilDashModifierSegment *>(
MEM_dupallocN(dmd->segments_array));
}
static void free_data(ModifierData *md)
{
auto *dmd = reinterpret_cast<GreasePencilDashModifierData *>(md);
modifier::greasepencil::free_influence_data(&dmd->influence);
MEM_SAFE_FREE(dmd->segments_array);
}
static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
{
auto *dmd = reinterpret_cast<GreasePencilDashModifierData *>(md);
modifier::greasepencil::foreach_influence_ID_link(&dmd->influence, ob, walk, user_data);
}
static bool is_disabled(const Scene * /*scene*/, ModifierData *md, bool /*use_render_params*/)
{
const auto *dmd = reinterpret_cast<GreasePencilDashModifierData *>(md);
/* Enable if at least one segment has non-zero length. */
for (const GreasePencilDashModifierSegment &dash_segment : dmd->segments()) {
if (dash_segment.dash + dash_segment.gap - 1 > 0) {
return false;
}
}
return true;
}
static int floored_modulo(const int a, const int b)
{
return a - math::floor(float(a) / float(b)) * b;
}
/* Combined segment info used by all strokes. */
struct PatternInfo {
int offset = 0;
int length = 0;
Array<IndexRange> segments;
Array<bool> cyclic;
Array<int> material;
Array<float> radius;
Array<float> opacity;
};
static PatternInfo get_pattern_info(const GreasePencilDashModifierData &dmd)
{
PatternInfo info;
for (const GreasePencilDashModifierSegment &dash_segment : dmd.segments()) {
info.length += dash_segment.dash + dash_segment.gap;
}
info.segments.reinitialize(dmd.segments().size());
info.cyclic.reinitialize(dmd.segments().size());
info.material.reinitialize(dmd.segments().size());
info.radius.reinitialize(dmd.segments().size());
info.opacity.reinitialize(dmd.segments().size());
info.offset = floored_modulo(dmd.dash_offset, info.length);
/* Store segments as ranges. */
IndexRange dash_range(0);
IndexRange gap_range(0);
for (const int i : dmd.segments().index_range()) {
const GreasePencilDashModifierSegment &dash_segment = dmd.segments()[i];
dash_range = gap_range.after(dash_segment.dash);
gap_range = dash_range.after(dash_segment.gap);
info.segments[i] = dash_range;
info.cyclic[i] = dash_segment.flag & MOD_GREASE_PENCIL_DASH_USE_CYCLIC;
info.material[i] = dash_segment.mat_nr;
info.radius[i] = dash_segment.radius;
info.opacity[i] = dash_segment.opacity;
}
return info;
}
/* Returns the segment covering the given index, including repetitions.*/
static int find_dash_segment(const PatternInfo &pattern_info, const int index)
{
const int repeat = index / pattern_info.length;
const int segments_num = pattern_info.segments.size();
const int local_index = index - repeat * pattern_info.length;
for (const int i : pattern_info.segments.index_range().drop_back(1)) {
const IndexRange segment = pattern_info.segments[i];
const IndexRange next_segment = pattern_info.segments[i + 1];
if (local_index >= segment.start() && local_index < next_segment.start()) {
return i + repeat * segments_num;
}
}
return segments_num - 1 + repeat * segments_num;
}
/**
* Iterate over all dash curves.
* \param fn: Function taking an index range of source points describing new curves.
* \note Point range can be larger than the source point range in case of cyclic curves.
*/
static void foreach_dash(const PatternInfo &pattern_info,
const IndexRange src_points,
const bool cyclic,
FunctionRef<void(IndexRange, bool, int, float, float)> fn)
{
const int points_num = src_points.size();
const int segments_num = pattern_info.segments.size();
const int first_segment = find_dash_segment(pattern_info, pattern_info.offset);
const int last_segment = find_dash_segment(pattern_info, pattern_info.offset + points_num - 1);
BLI_assert(first_segment < segments_num);
BLI_assert(last_segment >= first_segment);
const IndexRange all_segments = IndexRange(first_segment, last_segment - first_segment + 1);
for (const int i : all_segments) {
const int repeat = i / segments_num;
const int segment_index = i - repeat * segments_num;
const IndexRange range = pattern_info.segments[segment_index].shift(repeat *
pattern_info.length);
const int64_t point_shift = src_points.start() - pattern_info.offset;
const int64_t min_point = src_points.start();
const int64_t max_point = cyclic ? src_points.one_after_last() : src_points.last();
const int64_t start = std::clamp(range.start() + point_shift, min_point, max_point);
const int64_t end = std::clamp(range.one_after_last() + point_shift, min_point, max_point + 1);
IndexRange points(start, end - start);
if (!points.is_empty()) {
fn(points,
pattern_info.cyclic[segment_index],
pattern_info.material[segment_index],
pattern_info.radius[segment_index],
pattern_info.opacity[segment_index]);
}
}
}
static bke::CurvesGeometry create_dashes(const PatternInfo &pattern_info,
const bke::CurvesGeometry &src_curves,
const IndexMask &curves_mask)
{
const bke::AttributeAccessor src_attributes = src_curves.attributes();
const VArray<bool> src_cyclic = *src_attributes.lookup_or_default(
"cyclic", bke::AttrDomain::Curve, false);
const VArray<int> src_material = *src_attributes.lookup_or_default(
"material_index", bke::AttrDomain::Curve, 0);
const VArray<float> src_radius = *src_attributes.lookup<float>("radius", bke::AttrDomain::Point);
const VArray<float> src_opacity = *src_attributes.lookup<float>("opacity",
bke::AttrDomain::Point);
/* Count new curves and points. */
int dst_point_num = 0;
int dst_curve_num = 0;
curves_mask.foreach_index([&](const int64_t src_curve_i) {
const IndexRange src_points = src_curves.points_by_curve()[src_curve_i];
foreach_dash(pattern_info,
src_points,
src_cyclic[src_curve_i],
[&](const IndexRange copy_points,
bool /*cyclic*/,
int /*material*/,
float /*radius*/,
float /*opacity*/) {
dst_point_num += copy_points.size();
dst_curve_num += 1;
});
});
bke::CurvesGeometry dst_curves(dst_point_num, dst_curve_num);
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
bke::SpanAttributeWriter<bool> dst_cyclic = dst_attributes.lookup_or_add_for_write_span<bool>(
"cyclic", bke::AttrDomain::Curve);
bke::SpanAttributeWriter<int> dst_material = dst_attributes.lookup_or_add_for_write_span<int>(
"material_index", bke::AttrDomain::Curve);
bke::SpanAttributeWriter<float> dst_radius = dst_attributes.lookup_or_add_for_write_span<float>(
"radius", bke::AttrDomain::Point);
bke::SpanAttributeWriter<float> dst_opacity = dst_attributes.lookup_or_add_for_write_span<float>(
"opacity", bke::AttrDomain::Point);
/* Map each destination point and curve to its source. */
Array<int> src_point_indices(dst_point_num);
Array<int> src_curve_indices(dst_curve_num);
{
/* Start at curve offset and add points for each dash. */
IndexRange dst_point_range(0);
int dst_curve_i = 0;
auto add_dash_curve = [&](const int src_curve,
const IndexRange src_points,
const IndexRange copy_points,
bool cyclic,
int material,
float radius,
float opacity) {
dst_point_range = dst_point_range.after(copy_points.size());
dst_curves.offsets_for_write()[dst_curve_i] = dst_point_range.start();
if (src_points.contains(copy_points.last())) {
array_utils::fill_index_range(src_point_indices.as_mutable_span().slice(dst_point_range),
int(copy_points.start()));
}
else {
/* Cyclic curve. */
array_utils::fill_index_range(
src_point_indices.as_mutable_span().slice(dst_point_range.drop_back(1)),
int(copy_points.start()));
src_point_indices[dst_point_range.last()] = src_points.first();
}
src_curve_indices[dst_curve_i] = src_curve;
dst_cyclic.span[dst_curve_i] = cyclic;
dst_material.span[dst_curve_i] = material >= 0 ? material : src_material[src_curve];
for (const int i : dst_point_range) {
dst_radius.span[i] = src_radius[src_point_indices[i]] * radius;
dst_opacity.span[i] = src_opacity[src_point_indices[i]] * opacity;
}
++dst_curve_i;
};
curves_mask.foreach_index([&](const int64_t src_curve_i) {
const IndexRange src_points = src_curves.points_by_curve()[src_curve_i];
foreach_dash(pattern_info,
src_points,
src_cyclic[src_curve_i],
[&](const IndexRange copy_points,
bool cyclic,
int material,
float radius,
float opacity) {
add_dash_curve(
src_curve_i, src_points, copy_points, cyclic, material, radius, opacity);
});
});
if (dst_curve_i > 0) {
/* Last offset entry is total point count. */
dst_curves.offsets_for_write()[dst_curve_i] = dst_point_range.one_after_last();
}
}
bke::gather_attributes(src_attributes,
bke::AttrDomain::Point,
{},
{"radius", "opacity"},
src_point_indices,
dst_attributes);
bke::gather_attributes(src_attributes,
bke::AttrDomain::Curve,
{},
{"cyclic", "material_index"},
src_curve_indices,
dst_attributes);
dst_cyclic.finish();
dst_material.finish();
dst_radius.finish();
dst_opacity.finish();
dst_curves.update_curve_types();
return dst_curves;
}
static void modify_drawing(const GreasePencilDashModifierData &dmd,
const ModifierEvalContext &ctx,
const PatternInfo &pattern_info,
bke::greasepencil::Drawing &drawing)
{
const bke::CurvesGeometry &src_curves = drawing.strokes();
if (src_curves.curve_num == 0) {
return;
}
/* Selected source curves. */
IndexMaskMemory curve_mask_memory;
const IndexMask curves_mask = modifier::greasepencil::get_filtered_stroke_mask(
ctx.object, src_curves, dmd.influence, curve_mask_memory);
drawing.strokes_for_write() = create_dashes(pattern_info, src_curves, curves_mask);
drawing.tag_topology_changed();
}
static void modify_geometry_set(ModifierData *md,
const ModifierEvalContext *ctx,
bke::GeometrySet *geometry_set)
{
using bke::greasepencil::Drawing;
auto *dmd = reinterpret_cast<GreasePencilDashModifierData *>(md);
if (!geometry_set->has_grease_pencil()) {
return;
}
GreasePencil &grease_pencil = *geometry_set->get_grease_pencil_for_write();
const int frame = grease_pencil.runtime->eval_frame;
const PatternInfo pattern_info = get_pattern_info(*dmd);
IndexMaskMemory mask_memory;
const IndexMask layer_mask = modifier::greasepencil::get_filtered_layer_mask(
grease_pencil, dmd->influence, mask_memory);
const Vector<Drawing *> drawings = modifier::greasepencil::get_drawings_for_write(
grease_pencil, layer_mask, frame);
threading::parallel_for_each(
drawings, [&](Drawing *drawing) { modify_drawing(*dmd, *ctx, pattern_info, *drawing); });
}
static void panel_draw(const bContext *C, Panel *panel)
{
uiLayout *layout = panel->layout;
PointerRNA ob_ptr;
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
auto *dmd = static_cast<GreasePencilDashModifierData *>(ptr->data);
uiLayoutSetPropSep(layout, true);
uiItemR(layout, ptr, "dash_offset", UI_ITEM_NONE, nullptr, ICON_NONE);
uiLayout *row = uiLayoutRow(layout, false);
uiLayoutSetPropSep(row, false);
uiTemplateList(row,
(bContext *)C,
"MOD_UL_grease_pencil_dash_modifier_segments",
"",
ptr,
"segments",
ptr,
"segment_active_index",
nullptr,
3,
10,
0,
1,
UI_TEMPLATE_LIST_FLAG_NONE);
uiLayout *col = uiLayoutColumn(row, false);
uiLayout *sub = uiLayoutColumn(col, true);
uiItemO(sub, "", ICON_ADD, "OBJECT_OT_grease_pencil_dash_modifier_segment_add");
uiItemO(sub, "", ICON_REMOVE, "OBJECT_OT_grease_pencil_dash_modifier_segment_remove");
uiItemS(col);
sub = uiLayoutColumn(col, true);
uiItemEnumO_string(
sub, "", ICON_TRIA_UP, "OBJECT_OT_grease_pencil_dash_modifier_segment_move", "type", "UP");
uiItemEnumO_string(sub,
"",
ICON_TRIA_DOWN,
"OBJECT_OT_grease_pencil_dash_modifier_segment_move",
"type",
"DOWN");
if (dmd->segment_active_index >= 0 && dmd->segment_active_index < dmd->segments_num) {
PointerRNA ds_ptr = RNA_pointer_create(ptr->owner_id,
&RNA_GreasePencilDashModifierSegment,
&dmd->segments()[dmd->segment_active_index]);
sub = uiLayoutColumn(layout, true);
uiItemR(sub, &ds_ptr, "dash", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(sub, &ds_ptr, "gap", UI_ITEM_NONE, nullptr, ICON_NONE);
sub = uiLayoutColumn(layout, false);
uiItemR(sub, &ds_ptr, "radius", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(sub, &ds_ptr, "opacity", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(sub, &ds_ptr, "material_index", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(sub, &ds_ptr, "use_cyclic", UI_ITEM_NONE, nullptr, ICON_NONE);
}
if (uiLayout *influence_panel = uiLayoutPanelProp(
C, layout, ptr, "open_influence_panel", "Influence"))
{
modifier::greasepencil::draw_layer_filter_settings(C, influence_panel, ptr);
modifier::greasepencil::draw_material_filter_settings(C, influence_panel, ptr);
}
modifier_panel_end(layout, ptr);
}
static void segment_list_item_draw(uiList * /*ui_list*/,
const bContext * /*C*/,
uiLayout *layout,
PointerRNA * /*idataptr*/,
PointerRNA *itemptr,
int /*icon*/,
PointerRNA * /*active_dataptr*/,
const char * /*active_propname*/,
int /*index*/,
int /*flt_flag*/)
{
uiLayout *row = uiLayoutRow(layout, true);
uiItemR(row, itemptr, "name", UI_ITEM_R_NO_BG, "", ICON_NONE);
}
static void panel_register(ARegionType *region_type)
{
modifier_panel_register(region_type, eModifierType_GreasePencilDash, panel_draw);
uiListType *list_type = static_cast<uiListType *>(
MEM_callocN(sizeof(uiListType), "Grease Pencil Dash modifier segments"));
STRNCPY(list_type->idname, "MOD_UL_grease_pencil_dash_modifier_segments");
list_type->draw_item = segment_list_item_draw;
WM_uilisttype_add(list_type);
}
static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md)
{
const auto *dmd = reinterpret_cast<const GreasePencilDashModifierData *>(md);
BLO_write_struct(writer, GreasePencilDashModifierData, dmd);
modifier::greasepencil::write_influence_data(writer, &dmd->influence);
BLO_write_struct_array(
writer, GreasePencilDashModifierSegment, dmd->segments_num, dmd->segments_array);
}
static void blend_read(BlendDataReader *reader, ModifierData *md)
{
auto *dmd = reinterpret_cast<GreasePencilDashModifierData *>(md);
modifier::greasepencil::read_influence_data(reader, &dmd->influence);
BLO_read_data_address(reader, &dmd->segments_array);
}
} // namespace blender
ModifierTypeInfo modifierType_GreasePencilDash = {
/*idname*/ "GreasePencilDash",
/*name*/ N_("Dot Dash"),
/*struct_name*/ "GreasePencilDashModifierData",
/*struct_size*/ sizeof(GreasePencilDashModifierData),
/*srna*/ &RNA_GreasePencilDashModifierData,
/*type*/ ModifierTypeType::Nonconstructive,
/*flags*/ eModifierTypeFlag_AcceptsGreasePencil | eModifierTypeFlag_SupportsEditmode |
eModifierTypeFlag_EnableInEditmode | eModifierTypeFlag_SupportsMapping,
/*icon*/ ICON_MOD_DASH,
/*copy_data*/ blender::copy_data,
/*deform_verts*/ nullptr,
/*deform_matrices*/ nullptr,
/*deform_verts_EM*/ nullptr,
/*deform_matrices_EM*/ nullptr,
/*modify_mesh*/ nullptr,
/*modify_geometry_set*/ blender::modify_geometry_set,
/*init_data*/ blender::init_data,
/*required_data_mask*/ nullptr,
/*free_data*/ blender::free_data,
/*is_disabled*/ blender::is_disabled,
/*update_depsgraph*/ nullptr,
/*depends_on_time*/ nullptr,
/*depends_on_normals*/ nullptr,
/*foreach_ID_link*/ blender::foreach_ID_link,
/*foreach_tex_link*/ nullptr,
/*free_runtime_data*/ nullptr,
/*panel_register*/ blender::panel_register,
/*blend_write*/ blender::blend_write,
/*blend_read*/ blender::blend_read,
};
blender::Span<GreasePencilDashModifierSegment> GreasePencilDashModifierData::segments() const
{
return {this->segments_array, this->segments_num};
}
blender::MutableSpan<GreasePencilDashModifierSegment> GreasePencilDashModifierData::segments()
{
return {this->segments_array, this->segments_num};
}

@ -280,5 +280,6 @@ void modifier_type_init(ModifierTypeInfo *types[])
INIT_TYPE(GreasePencilMirror);
INIT_TYPE(GreasePencilThickness);
INIT_TYPE(GreasePencilLattice);
INIT_TYPE(GreasePencilDash);
#undef INIT_TYPE
}