These attributes are not guaranteed to exist. So they should either be used conditionally, or with a fallback value. The fallback value for the opacity is obvious, but for the radius it's more tricky. There is not a consistent fallback value across Blender unfortunately. The one I used here is used in a couple of places. Pull Request: https://projects.blender.org/blender/blender/pulls/128847
534 lines
20 KiB
C++
534 lines
20 KiB
C++
/* 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_modifier.hh"
|
|
#include "BKE_screen.hh"
|
|
|
|
#include "BLO_read_write.hh"
|
|
|
|
#include "UI_interface.hh"
|
|
#include "UI_resources.hh"
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
#include "WM_api.hh"
|
|
#include "WM_types.hh"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_prototypes.hh"
|
|
|
|
#include "MOD_grease_pencil_util.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_curves.cyclic();
|
|
const VArray<int> src_material = *src_attributes.lookup_or_default(
|
|
"material_index", bke::AttrDomain::Curve, 0);
|
|
const VArray<float> src_radius = *src_attributes.lookup_or_default<float>(
|
|
"radius", bke::AttrDomain::Point, 0.01f);
|
|
const VArray<float> src_opacity = *src_attributes.lookup_or_default<float>(
|
|
"opacity", bke::AttrDomain::Point, 1.0f);
|
|
|
|
/* 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,
|
|
bke::AttrDomain::Point,
|
|
bke::attribute_filter_from_skip_ref({"radius", "opacity"}),
|
|
src_point_indices,
|
|
dst_attributes);
|
|
bke::gather_attributes(src_attributes,
|
|
bke::AttrDomain::Curve,
|
|
bke::AttrDomain::Curve,
|
|
bke::attribute_filter_from_skip_ref({"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_struct_array(
|
|
reader, GreasePencilDashModifierSegment, dmd->segments_num, &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};
|
|
}
|