GPv3: Dash modifier #117758

Merged
Lukas Tönne merged 29 commits from LukasTonne/blender:gp3-dash-modifier into main 2024-02-08 15:35:30 +01:00
12 changed files with 1040 additions and 0 deletions

View File

@ -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)

View File

@ -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 */

View File

@ -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
LukasTonne marked this conversation as resolved
Review

Maybe we should put the static grease pencil functions in a greasepencil namespace and get rid of the grease_pencil prefix.

Maybe we should put the `static` grease pencil functions in a `greasepencil` namespace and get rid of the `grease_pencil` prefix.
Review

I think blender::ed::grease_pencil would be better than a naked root grease_pencil namespace, but yes.

I think `blender::ed::grease_pencil` would be better than a naked root `grease_pencil` namespace, but yes.
Review

Even better, yes. So far, we've been using greasepencil (with no underscore) for the namespaces. (Maybe grease_pencil would be better, but then we should change all of them in one go)

Even better, yes. So far, we've been using `greasepencil` (with no underscore) for the namespaces. (Maybe `grease_pencil` would be better, but then we should change all of them in one go)
* \{ */
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;
LukasTonne marked this conversation as resolved
Review

Maybe use enum class here. Something like this:

enum class DashSegmentMoveDirection : int8_t {
   Up = -1,
   Down = 1,
};
Maybe use `enum class` here. Something like this: ``` enum class DashSegmentMoveDirection : int8_t { Up = -1, Down = 1, }; ```
/* 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,
LukasTonne marked this conversation as resolved Outdated

-> int(DashSegmentMoveDirection::Up)

-> `int(DashSegmentMoveDirection::Up)`
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", "");
}
/** \} */

View File

@ -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);

View File

@ -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 \
{ \
LukasTonne marked this conversation as resolved
Review

This currently makes all dashes "invisible" by default by setting an invalid material index. Have to check how GPv2 handles these settings.

This currently makes all dashes "invisible" by default by setting an invalid material index. Have to check how GPv2 handles these settings.
.name = "", \
.dash = 2, \
.gap = 1, \
.radius = 1.0f, \
.opacity = 1.0f, \
.mat_nr = -1, \
}
/* clang-format off */

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 */

View File

@ -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,
LukasTonne marked this conversation as resolved Outdated

Could this lambda be declared above the loop? Maybe as create_dash/write_dash ?

Could this lambda be declared above the loop? Maybe as `create_dash`/`write_dash` ?
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};
}

View File

@ -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
}