Curves: Add edit mode duplicate operator
Reuse the grease pencil implementation added in:
- fb275bc040
- 5799a26568819ce27e8c12df96b7ffba84cc00f9
This commit is contained in:
@@ -6077,6 +6077,7 @@ def km_edit_curves(params):
|
||||
("curves.set_selection_domain", {"type": 'TWO', "value": 'PRESS'}, {"properties": [("domain", 'CURVE')]}),
|
||||
("curves.disable_selection", {"type": 'ONE', "value": 'PRESS', "alt": True}, None),
|
||||
("curves.disable_selection", {"type": 'TWO', "value": 'PRESS', "alt": True}, None),
|
||||
("curves.duplicate_move", {"type": 'D', "value": 'PRESS', "shift": True}, None),
|
||||
*_template_items_select_actions(params, "curves.select_all"),
|
||||
("curves.select_linked", {"type": 'L', "value": 'PRESS', "ctrl": True}, None),
|
||||
("curves.delete", {"type": 'X', "value": 'PRESS'}, None),
|
||||
|
||||
@@ -4119,6 +4119,7 @@ def km_curves(params):
|
||||
("curves.set_selection_domain", {"type": 'TWO', "value": 'PRESS'}, {"properties": [("domain", 'CURVE')]}),
|
||||
("curves.disable_selection", {"type": 'ONE', "value": 'PRESS', "alt": True}, None),
|
||||
("curves.disable_selection", {"type": 'TWO', "value": 'PRESS', "alt": True}, None),
|
||||
("curves.duplicate_move", {"type": 'D', "value": 'PRESS', "ctrl": True}, None),
|
||||
# Selection Operators
|
||||
("curves.select_all", {"type": 'A', "value": 'PRESS', "ctrl": True}, {"properties": [("action", 'SELECT')]}),
|
||||
("curves.select_all", {"type": 'A', "value": 'PRESS', "shift": True,
|
||||
|
||||
@@ -5887,6 +5887,8 @@ class VIEW3D_MT_edit_curves(Menu):
|
||||
|
||||
layout.menu("VIEW3D_MT_transform")
|
||||
layout.separator()
|
||||
layout.operator("curves.duplicate_move")
|
||||
layout.separator()
|
||||
layout.operator("curves.attribute_set")
|
||||
layout.operator("curves.delete")
|
||||
layout.template_node_operator_asset_menu_items(catalog_path=self.bl_label)
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* \ingroup edcurves
|
||||
*/
|
||||
|
||||
#include "BLI_array_utils.hh"
|
||||
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
@@ -34,4 +36,187 @@ bool remove_selection(bke::CurvesGeometry &curves, const eAttrDomain selection_d
|
||||
return attributes.domain_size(selection_domain) != domain_size_orig;
|
||||
}
|
||||
|
||||
void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask)
|
||||
{
|
||||
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||
const VArray<bool> src_cyclic = curves.cyclic();
|
||||
|
||||
Array<bool> points_to_duplicate(curves.points_num());
|
||||
mask.to_bools(points_to_duplicate.as_mutable_span());
|
||||
const int num_points_to_add = mask.size();
|
||||
|
||||
int curr_dst_point_start = 0;
|
||||
Array<int> dst_to_src_point(num_points_to_add);
|
||||
Vector<int> dst_curve_counts;
|
||||
Vector<int> dst_to_src_curve;
|
||||
Vector<bool> dst_cyclic;
|
||||
|
||||
/* Add the duplicated curves and points. */
|
||||
for (const int curve_i : curves.curves_range()) {
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
const Span<bool> curve_points_to_duplicate = points_to_duplicate.as_span().slice(points);
|
||||
const bool curve_cyclic = src_cyclic[curve_i];
|
||||
|
||||
/* Note, these ranges start at zero and needed to be shifted by `points.first()` */
|
||||
const Vector<IndexRange> ranges_to_duplicate = array_utils::find_all_ranges(
|
||||
curve_points_to_duplicate, true);
|
||||
|
||||
if (ranges_to_duplicate.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool is_last_segment_selected = curve_cyclic &&
|
||||
ranges_to_duplicate.first().first() == 0 &&
|
||||
ranges_to_duplicate.last().last() == points.size() - 1;
|
||||
const bool is_curve_self_joined = is_last_segment_selected && ranges_to_duplicate.size() != 1;
|
||||
const bool is_cyclic = ranges_to_duplicate.size() == 1 && is_last_segment_selected;
|
||||
|
||||
const IndexRange range_ids = ranges_to_duplicate.index_range();
|
||||
/* Skip the first range because it is joined to the end of the last range. */
|
||||
for (const int range_i : ranges_to_duplicate.index_range().drop_front(is_curve_self_joined)) {
|
||||
const IndexRange range = ranges_to_duplicate[range_i];
|
||||
|
||||
array_utils::fill_index_range<int>(
|
||||
dst_to_src_point.as_mutable_span().slice(curr_dst_point_start, range.size()),
|
||||
range.start() + points.first());
|
||||
curr_dst_point_start += range.size();
|
||||
|
||||
dst_curve_counts.append(range.size());
|
||||
dst_to_src_curve.append(curve_i);
|
||||
dst_cyclic.append(is_cyclic);
|
||||
}
|
||||
|
||||
/* Join the first range to the end of the last range. */
|
||||
if (is_curve_self_joined) {
|
||||
const IndexRange first_range = ranges_to_duplicate[range_ids.first()];
|
||||
array_utils::fill_index_range<int>(
|
||||
dst_to_src_point.as_mutable_span().slice(curr_dst_point_start, first_range.size()),
|
||||
first_range.start() + points.first());
|
||||
curr_dst_point_start += first_range.size();
|
||||
dst_curve_counts[dst_curve_counts.size() - 1] += first_range.size();
|
||||
}
|
||||
}
|
||||
|
||||
const int old_curves_num = curves.curves_num();
|
||||
const int old_points_num = curves.points_num();
|
||||
const int num_curves_to_add = dst_to_src_curve.size();
|
||||
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
|
||||
/* Delete selection attribute so that it will not have to be resized. */
|
||||
attributes.remove(".selection");
|
||||
|
||||
curves.resize(old_points_num + num_points_to_add, old_curves_num + num_curves_to_add);
|
||||
|
||||
MutableSpan<int> new_curve_offsets = curves.offsets_for_write();
|
||||
array_utils::copy(dst_curve_counts.as_span(),
|
||||
new_curve_offsets.drop_front(old_curves_num).drop_back(1));
|
||||
offset_indices::accumulate_counts_to_offsets(new_curve_offsets.drop_front(old_curves_num),
|
||||
old_points_num);
|
||||
|
||||
/* Transfer curve and point attributes. */
|
||||
attributes.for_all([&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) {
|
||||
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
if (!attribute) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (meta_data.domain) {
|
||||
case ATTR_DOMAIN_CURVE: {
|
||||
if (id.name() == "cyclic") {
|
||||
return true;
|
||||
}
|
||||
bke::attribute_math::gather(
|
||||
attribute.span,
|
||||
dst_to_src_curve,
|
||||
attribute.span.slice(IndexRange(old_curves_num, num_curves_to_add)));
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_POINT: {
|
||||
bke::attribute_math::gather(
|
||||
attribute.span,
|
||||
dst_to_src_point,
|
||||
attribute.span.slice(IndexRange(old_points_num, num_points_to_add)));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
attribute.finish();
|
||||
BLI_assert_unreachable();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
attribute.finish();
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!(src_cyclic.is_single() && !src_cyclic.get_internal_single())) {
|
||||
array_utils::copy(dst_cyclic.as_span(), curves.cyclic_for_write().drop_front(old_curves_num));
|
||||
}
|
||||
|
||||
curves.update_curve_types();
|
||||
curves.tag_topology_changed();
|
||||
|
||||
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
|
||||
".selection", ATTR_DOMAIN_POINT);
|
||||
selection.span.take_back(num_points_to_add).fill(true);
|
||||
selection.finish();
|
||||
}
|
||||
|
||||
void duplicate_curves(bke::CurvesGeometry &curves, const IndexMask &mask)
|
||||
{
|
||||
const int orig_points_num = curves.points_num();
|
||||
const int orig_curves_num = curves.curves_num();
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
|
||||
/* Delete selection attribute so that it will not have to be resized. */
|
||||
attributes.remove(".selection");
|
||||
|
||||
/* Resize the curves and copy the offsets of duplicated curves into the new offsets. */
|
||||
curves.resize(curves.points_num(), orig_curves_num + mask.size());
|
||||
const IndexRange orig_curves_range = curves.curves_range().take_front(orig_curves_num);
|
||||
const IndexRange new_curves_range = curves.curves_range().drop_front(orig_curves_num);
|
||||
|
||||
MutableSpan<int> offset_data = curves.offsets_for_write();
|
||||
offset_indices::gather_selected_offsets(
|
||||
OffsetIndices<int>(offset_data.take_front(orig_curves_num + 1)),
|
||||
mask,
|
||||
orig_points_num,
|
||||
offset_data.drop_front(orig_curves_num));
|
||||
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||
|
||||
/* Resize the points array to match the new total point count. */
|
||||
curves.resize(points_by_curve.total_size(), curves.curves_num());
|
||||
|
||||
attributes.for_all([&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) {
|
||||
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
switch (meta_data.domain) {
|
||||
case ATTR_DOMAIN_POINT:
|
||||
bke::attribute_math::gather_group_to_group(points_by_curve.slice(orig_curves_range),
|
||||
points_by_curve.slice(new_curves_range),
|
||||
mask,
|
||||
attribute.span,
|
||||
attribute.span);
|
||||
break;
|
||||
case ATTR_DOMAIN_CURVE:
|
||||
array_utils::gather(attribute.span, mask, attribute.span.take_back(mask.size()));
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return true;
|
||||
}
|
||||
attribute.finish();
|
||||
return true;
|
||||
});
|
||||
|
||||
curves.update_curve_types();
|
||||
curves.tag_topology_changed();
|
||||
|
||||
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
|
||||
".selection", ATTR_DOMAIN_CURVE);
|
||||
selection.span.take_back(mask.size()).fill(true);
|
||||
selection.finish();
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves
|
||||
|
||||
@@ -1244,6 +1244,44 @@ static void CURVES_OT_delete(wmOperatorType *ot)
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
namespace curves_duplicate {
|
||||
|
||||
static int delete_exec(bContext *C, wmOperator * /*op*/)
|
||||
{
|
||||
for (Curves *curves_id : get_unique_editable_curves(*C)) {
|
||||
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
|
||||
IndexMaskMemory memory;
|
||||
switch (eAttrDomain(curves_id->selection_domain)) {
|
||||
case ATTR_DOMAIN_POINT:
|
||||
duplicate_points(curves, retrieve_selected_points(*curves_id, memory));
|
||||
break;
|
||||
case ATTR_DOMAIN_CURVE:
|
||||
duplicate_curves(curves, retrieve_selected_curves(*curves_id, memory));
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
|
||||
}
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
} // namespace curves_duplicate
|
||||
|
||||
static void CURVES_OT_duplicate(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Duplicate";
|
||||
ot->idname = __func__;
|
||||
ot->description = "Copy selected points or curves";
|
||||
|
||||
ot->exec = curves_duplicate::delete_exec;
|
||||
ot->poll = editable_curves_in_edit_mode_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves
|
||||
|
||||
void ED_operatortypes_curves()
|
||||
@@ -1263,6 +1301,23 @@ void ED_operatortypes_curves()
|
||||
WM_operatortype_append(CURVES_OT_select_less);
|
||||
WM_operatortype_append(CURVES_OT_surface_set);
|
||||
WM_operatortype_append(CURVES_OT_delete);
|
||||
WM_operatortype_append(CURVES_OT_duplicate);
|
||||
}
|
||||
|
||||
void ED_operatormacros_curves()
|
||||
{
|
||||
wmOperatorType *ot;
|
||||
wmOperatorTypeMacro *otmacro;
|
||||
|
||||
/* Duplicate + Move = Interactively place newly duplicated strokes */
|
||||
ot = WM_operatortype_append_macro("CURVES_OT_duplicate_move",
|
||||
"Duplicate",
|
||||
"Make copies of selected elements and move them",
|
||||
OPTYPE_UNDO | OPTYPE_REGISTER);
|
||||
WM_operatortype_macro_define(ot, "CURVES_OT_duplicate");
|
||||
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
|
||||
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
|
||||
RNA_boolean_set(otmacro->ptr, "mirror", false);
|
||||
}
|
||||
|
||||
void ED_keymap_curves(wmKeyConfig *keyconf)
|
||||
|
||||
@@ -1530,189 +1530,6 @@ static void GREASE_PENCIL_OT_set_material(wmOperatorType *ot)
|
||||
/** \name Duplicate Operator
|
||||
* \{ */
|
||||
|
||||
static void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask)
|
||||
{
|
||||
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||
const VArray<bool> src_cyclic = curves.cyclic();
|
||||
|
||||
Array<bool> points_to_duplicate(curves.points_num());
|
||||
mask.to_bools(points_to_duplicate.as_mutable_span());
|
||||
const int num_points_to_add = mask.size();
|
||||
|
||||
int curr_dst_point_start = 0;
|
||||
Array<int> dst_to_src_point(num_points_to_add);
|
||||
Vector<int> dst_curve_counts;
|
||||
Vector<int> dst_to_src_curve;
|
||||
Vector<bool> dst_cyclic;
|
||||
|
||||
/* Add the duplicated curves and points. */
|
||||
for (const int curve_i : curves.curves_range()) {
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
const Span<bool> curve_points_to_duplicate = points_to_duplicate.as_span().slice(points);
|
||||
const bool curve_cyclic = src_cyclic[curve_i];
|
||||
|
||||
/* Note, these ranges start at zero and needed to be shifted by `points.first()` */
|
||||
const Vector<IndexRange> ranges_to_duplicate = array_utils::find_all_ranges(
|
||||
curve_points_to_duplicate, true);
|
||||
|
||||
if (ranges_to_duplicate.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool is_last_segment_selected = curve_cyclic &&
|
||||
ranges_to_duplicate.first().first() == 0 &&
|
||||
ranges_to_duplicate.last().last() == points.size() - 1;
|
||||
const bool is_curve_self_joined = is_last_segment_selected && ranges_to_duplicate.size() != 1;
|
||||
const bool is_cyclic = ranges_to_duplicate.size() == 1 && is_last_segment_selected;
|
||||
|
||||
const IndexRange range_ids = ranges_to_duplicate.index_range();
|
||||
/* Skip the first range because it is joined to the end of the last range. */
|
||||
for (const int range_i : ranges_to_duplicate.index_range().drop_front(is_curve_self_joined)) {
|
||||
const IndexRange range = ranges_to_duplicate[range_i];
|
||||
|
||||
array_utils::fill_index_range<int>(
|
||||
dst_to_src_point.as_mutable_span().slice(curr_dst_point_start, range.size()),
|
||||
range.start() + points.first());
|
||||
curr_dst_point_start += range.size();
|
||||
|
||||
dst_curve_counts.append(range.size());
|
||||
dst_to_src_curve.append(curve_i);
|
||||
dst_cyclic.append(is_cyclic);
|
||||
}
|
||||
|
||||
/* Join the first range to the end of the last range. */
|
||||
if (is_curve_self_joined) {
|
||||
const IndexRange first_range = ranges_to_duplicate[range_ids.first()];
|
||||
array_utils::fill_index_range<int>(
|
||||
dst_to_src_point.as_mutable_span().slice(curr_dst_point_start, first_range.size()),
|
||||
first_range.start() + points.first());
|
||||
curr_dst_point_start += first_range.size();
|
||||
dst_curve_counts[dst_curve_counts.size() - 1] += first_range.size();
|
||||
}
|
||||
}
|
||||
|
||||
const int old_curves_num = curves.curves_num();
|
||||
const int old_points_num = curves.points_num();
|
||||
const int num_curves_to_add = dst_to_src_curve.size();
|
||||
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
|
||||
/* Delete selection attribute so that it will not have to be resized. */
|
||||
attributes.remove(".selection");
|
||||
|
||||
curves.resize(old_points_num + num_points_to_add, old_curves_num + num_curves_to_add);
|
||||
|
||||
MutableSpan<int> new_curve_offsets = curves.offsets_for_write();
|
||||
array_utils::copy(dst_curve_counts.as_span(),
|
||||
new_curve_offsets.drop_front(old_curves_num).drop_back(1));
|
||||
offset_indices::accumulate_counts_to_offsets(new_curve_offsets.drop_front(old_curves_num),
|
||||
old_points_num);
|
||||
|
||||
/* Transfer curve and point attributes. */
|
||||
attributes.for_all([&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) {
|
||||
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
if (!attribute) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (meta_data.domain) {
|
||||
case ATTR_DOMAIN_CURVE: {
|
||||
if (id.name() == "cyclic") {
|
||||
return true;
|
||||
}
|
||||
bke::attribute_math::gather(
|
||||
attribute.span,
|
||||
dst_to_src_curve,
|
||||
attribute.span.slice(IndexRange(old_curves_num, num_curves_to_add)));
|
||||
break;
|
||||
}
|
||||
case ATTR_DOMAIN_POINT: {
|
||||
bke::attribute_math::gather(
|
||||
attribute.span,
|
||||
dst_to_src_point,
|
||||
attribute.span.slice(IndexRange(old_points_num, num_points_to_add)));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
attribute.finish();
|
||||
BLI_assert_unreachable();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
attribute.finish();
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!(src_cyclic.is_single() && !src_cyclic.get_internal_single())) {
|
||||
array_utils::copy(dst_cyclic.as_span(), curves.cyclic_for_write().drop_front(old_curves_num));
|
||||
}
|
||||
|
||||
curves.update_curve_types();
|
||||
curves.tag_topology_changed();
|
||||
|
||||
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
|
||||
".selection", ATTR_DOMAIN_POINT);
|
||||
selection.span.take_back(num_points_to_add).fill(true);
|
||||
selection.finish();
|
||||
}
|
||||
|
||||
static void duplicate_curves(bke::CurvesGeometry &curves, const IndexMask &mask)
|
||||
{
|
||||
const int orig_points_num = curves.points_num();
|
||||
const int orig_curves_num = curves.curves_num();
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
|
||||
/* Delete selection attribute so that it will not have to be resized. */
|
||||
attributes.remove(".selection");
|
||||
|
||||
/* Resize the curves and copy the offsets of duplicated curves into the new offsets. */
|
||||
curves.resize(curves.points_num(), orig_curves_num + mask.size());
|
||||
const IndexRange orig_curves_range = curves.curves_range().take_front(orig_curves_num);
|
||||
const IndexRange new_curves_range = curves.curves_range().drop_front(orig_curves_num);
|
||||
|
||||
MutableSpan<int> offset_data = curves.offsets_for_write();
|
||||
offset_indices::gather_selected_offsets(
|
||||
OffsetIndices<int>(offset_data.take_front(orig_curves_num + 1)),
|
||||
mask,
|
||||
orig_points_num,
|
||||
offset_data.drop_front(orig_curves_num));
|
||||
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||
|
||||
/* Resize the points array to match the new total point count. */
|
||||
curves.resize(points_by_curve.total_size(), curves.curves_num());
|
||||
|
||||
attributes.for_all([&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) {
|
||||
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
|
||||
switch (meta_data.domain) {
|
||||
case ATTR_DOMAIN_POINT:
|
||||
bke::attribute_math::gather_group_to_group(points_by_curve.slice(orig_curves_range),
|
||||
points_by_curve.slice(new_curves_range),
|
||||
mask,
|
||||
attribute.span,
|
||||
attribute.span);
|
||||
break;
|
||||
case ATTR_DOMAIN_CURVE:
|
||||
array_utils::gather(attribute.span, mask, attribute.span.take_back(mask.size()));
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return true;
|
||||
}
|
||||
attribute.finish();
|
||||
return true;
|
||||
});
|
||||
|
||||
curves.update_curve_types();
|
||||
curves.tag_topology_changed();
|
||||
|
||||
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
|
||||
".selection", ATTR_DOMAIN_CURVE);
|
||||
selection.span.take_back(mask.size()).fill(true);
|
||||
selection.finish();
|
||||
}
|
||||
|
||||
static int grease_pencil_duplicate_exec(bContext *C, wmOperator * /*op*/)
|
||||
{
|
||||
const Scene *scene = CTX_data_scene(C);
|
||||
@@ -1733,10 +1550,10 @@ static int grease_pencil_duplicate_exec(bContext *C, wmOperator * /*op*/)
|
||||
|
||||
bke::CurvesGeometry &curves = info.drawing.strokes_for_write();
|
||||
if (selection_domain == ATTR_DOMAIN_CURVE) {
|
||||
duplicate_curves(curves, elements);
|
||||
curves::duplicate_curves(curves, elements);
|
||||
}
|
||||
else if (selection_domain == ATTR_DOMAIN_POINT) {
|
||||
duplicate_points(curves, elements);
|
||||
curves::duplicate_points(curves, elements);
|
||||
}
|
||||
info.drawing.tag_topology_changed();
|
||||
changed.store(true, std::memory_order_relaxed);
|
||||
|
||||
@@ -32,6 +32,7 @@ struct wmKeyConfig;
|
||||
* \{ */
|
||||
|
||||
void ED_operatortypes_curves();
|
||||
void ED_operatormacros_curves();
|
||||
void ED_curves_undosys_type(UndoType *ut);
|
||||
void ED_keymap_curves(wmKeyConfig *keyconf);
|
||||
|
||||
@@ -286,6 +287,9 @@ bool select_circle(const ViewContext &vc,
|
||||
*/
|
||||
bool remove_selection(bke::CurvesGeometry &curves, eAttrDomain selection_domain);
|
||||
|
||||
void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask);
|
||||
void duplicate_curves(bke::CurvesGeometry &curves, const IndexMask &mask);
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::ed::curves
|
||||
|
||||
@@ -162,6 +162,7 @@ void ED_spacemacros_init()
|
||||
ED_operatormacros_action();
|
||||
ED_operatormacros_clip();
|
||||
ED_operatormacros_curve();
|
||||
ED_operatormacros_curves();
|
||||
ED_operatormacros_mask();
|
||||
ED_operatormacros_sequencer();
|
||||
ED_operatormacros_paint();
|
||||
|
||||
Reference in New Issue
Block a user