diff --git a/scripts/startup/bl_ui/properties_data_modifier.py b/scripts/startup/bl_ui/properties_data_modifier.py index a33e924cf1e..bee3751b661 100644 --- a/scripts/startup/bl_ui/properties_data_modifier.py +++ b/scripts/startup/bl_ui/properties_data_modifier.py @@ -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) diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index 1d0c62c5300..8fdf98b3d4f 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -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 */ diff --git a/source/blender/editors/object/object_modifier.cc b/source/blender/editors/object/object_modifier.cc index a68325aeb2f..3fbee8340ad 100644 --- a/source/blender/editors/object/object_modifier.cc +++ b/source/blender/editors/object/object_modifier.cc @@ -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( + edit_modifier_property_get(op, ob, eModifierType_GreasePencilDash)); + + if (dmd == nullptr) { + return OPERATOR_CANCELLED; + } + + GreasePencilDashModifierSegment *new_segments = static_cast( + 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( + 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( + 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", ""); +} + +/** \} */ diff --git a/source/blender/editors/object/object_ops.cc b/source/blender/editors/object/object_ops.cc index df916ed2f34..7956c1957c9 100644 --- a/source/blender/editors/object/object_ops.cc +++ b/source/blender/editors/object/object_ops.cc @@ -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); diff --git a/source/blender/makesdna/DNA_modifier_defaults.h b/source/blender/makesdna/DNA_modifier_defaults.h index b72e9343d70..33ad7b51f88 100644 --- a/source/blender/makesdna/DNA_modifier_defaults.h +++ b/source/blender/makesdna/DNA_modifier_defaults.h @@ -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 */ diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index 6fdbda01937..acc6c5a1fa3 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -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 segments() const; + blender::MutableSpan segments(); +#endif +} GreasePencilDashModifierData; + +typedef enum GreasePencilDashModifierFlag { + MOD_GREASE_PENCIL_DASH_USE_CYCLIC = (1 << 0), +} GreasePencilDashModifierFlag; diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c index 74971194937..fddeb82d671 100644 --- a/source/blender/makesdna/intern/dna_defaults.c +++ b/source/blender/makesdna/intern/dna_defaults.c @@ -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 diff --git a/source/blender/makesrna/intern/rna_modifier.cc b/source/blender/makesrna/intern/rna_modifier.cc index d46172af58a..690c28d3f42 100644 --- a/source/blender/makesrna/intern/rna_modifier.cc +++ b/source/blender/makesrna/intern/rna_modifier.cc @@ -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(md); + if (dmd->segments().contains_ptr(&dash_segment)) { + return dmd; + } + } + } + return nullptr; +} + +static std::optional rna_GreasePencilDashModifierSegment_path(const PointerRNA *ptr) + +{ + const Object *ob = reinterpret_cast(ptr->owner_id); + const auto *dash_segment = static_cast(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(ptr->owner_id); + auto *dash_segment = static_cast(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(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 diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index b96d0a8194f..b737ab618ed 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -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 diff --git a/source/blender/modifiers/MOD_modifiertypes.hh b/source/blender/modifiers/MOD_modifiertypes.hh index 6d31d31df49..5b4ca036fb9 100644 --- a/source/blender/modifiers/MOD_modifiertypes.hh +++ b/source/blender/modifiers/MOD_modifiertypes.hh @@ -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 */ diff --git a/source/blender/modifiers/intern/MOD_grease_pencil_dash.cc b/source/blender/modifiers/intern/MOD_grease_pencil_dash.cc new file mode 100644 index 00000000000..448d8cd11a7 --- /dev/null +++ b/source/blender/modifiers/intern/MOD_grease_pencil_dash.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(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(md); + auto *tmmd = reinterpret_cast(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( + MEM_dupallocN(dmd->segments_array)); +} + +static void free_data(ModifierData *md) +{ + auto *dmd = reinterpret_cast(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(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(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 segments; + Array cyclic; + Array material; + Array radius; + Array 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 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 src_cyclic = *src_attributes.lookup_or_default( + "cyclic", bke::AttrDomain::Curve, false); + const VArray src_material = *src_attributes.lookup_or_default( + "material_index", bke::AttrDomain::Curve, 0); + const VArray src_radius = *src_attributes.lookup("radius", bke::AttrDomain::Point); + const VArray src_opacity = *src_attributes.lookup("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 dst_cyclic = dst_attributes.lookup_or_add_for_write_span( + "cyclic", bke::AttrDomain::Curve); + bke::SpanAttributeWriter dst_material = dst_attributes.lookup_or_add_for_write_span( + "material_index", bke::AttrDomain::Curve); + bke::SpanAttributeWriter dst_radius = dst_attributes.lookup_or_add_for_write_span( + "radius", bke::AttrDomain::Point); + bke::SpanAttributeWriter dst_opacity = dst_attributes.lookup_or_add_for_write_span( + "opacity", bke::AttrDomain::Point); + /* Map each destination point and curve to its source. */ + Array src_point_indices(dst_point_num); + Array 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(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 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(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( + 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(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(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 GreasePencilDashModifierData::segments() const +{ + return {this->segments_array, this->segments_num}; +} + +blender::MutableSpan GreasePencilDashModifierData::segments() +{ + return {this->segments_array, this->segments_num}; +} diff --git a/source/blender/modifiers/intern/MOD_util.cc b/source/blender/modifiers/intern/MOD_util.cc index 35444d19f4e..613c14054bc 100644 --- a/source/blender/modifiers/intern/MOD_util.cc +++ b/source/blender/modifiers/intern/MOD_util.cc @@ -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 }