Curves: Add extrude operator #116354
|
@ -6077,6 +6077,7 @@ def km_edit_curves(params):
|
|||
("curves.set_selection_domain", {"type": 'TWO', "value": 'PRESS'}, {"properties": [("domain", 'CURVE')]}),
|
||||
("curves.duplicate_move", {"type": 'D', "value": 'PRESS', "shift": True}, None),
|
||||
*_template_items_select_actions(params, "curves.select_all"),
|
||||
("curves.extrude_move", {"type": 'E', "value": 'PRESS'}, None),
|
||||
("curves.select_linked", {"type": 'L', "value": 'PRESS', "ctrl": True}, None),
|
||||
("curves.delete", {"type": 'X', "value": 'PRESS'}, None),
|
||||
("curves.delete", {"type": 'DEL', "value": 'PRESS'}, None),
|
||||
|
|
|
@ -26,6 +26,7 @@ set(SRC
|
|||
intern/curves_data.cc
|
||||
intern/curves_draw.cc
|
||||
intern/curves_edit.cc
|
||||
intern/curves_extrude.cc
|
||||
intern/curves_masks.cc
|
||||
intern/curves_ops.cc
|
||||
intern/curves_selection.cc
|
||||
|
|
|
@ -0,0 +1,357 @@
|
|||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_curves_utils.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
|
||||
laurynas marked this conversation as resolved
Outdated
|
||||
namespace blender::ed::curves {
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
This is getting more subjective now, and I think the PR is getting close. But my one larger remaining comment is that bundling all this data in a single struct ( Sometimes that will make it easier to separate different parts of the algorithm into separate loops too. For example, maybe some data can be calculated in parallel while other data can't. Not sure if that applies here. This is getting more subjective now, and I think the PR is getting close. But my one larger remaining comment is that bundling all this data in a single struct (`CurvesCopy`) doesn't really help the situation. It may be clearer to declare each array as its needed in the caller. That gives a nice order of execution to the reader, and clarifies which parts of the algorithm need which data.
Sometimes that will make it easier to separate different parts of the algorithm into separate loops too. For example, maybe some data can be calculated in parallel while other data can't. Not sure if that applies here.
|
||||
|
||||
/**
|
||||
* Merges copy intervals at curve endings to minimize number of copy operations.
|
||||
* For example above intervals [0, 3, 4, 4, 4] became [0, 4, 4].
|
||||
* Leading to only two copy operations.
|
||||
*/
|
||||
static Span<int> compress_intervals(const Span<IndexRange> curve_interval_ranges,
|
||||
MutableSpan<int> intervals)
|
||||
{
|
||||
const int *src = intervals.data();
|
||||
/* Skip the first curve, as all the data stays in the same place. */
|
||||
int *dst = intervals.data() + curve_interval_ranges[0].size();
|
||||
|
||||
for (const int curve : IndexRange(1, curve_interval_ranges.size() - 1)) {
|
||||
const IndexRange range = curve_interval_ranges[curve];
|
||||
const int width = range.size() - 1;
|
||||
std::copy_n(src + range.first() + 1, width, dst);
|
||||
dst += width;
|
||||
}
|
||||
(*dst) = src[curve_interval_ranges[curve_interval_ranges.size() - 1].last() + 1];
|
||||
return {intervals.data(), dst - intervals.data() + 1};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates copy intervals for selection #range in the context of #curve_index.
|
||||
* If part of the #range is outside given curve, slices it and returns false indicating remaining
|
||||
* still needs to be handled. If whole #range was handled returns true.
|
||||
*/
|
||||
static bool handle_range(const int curve_index,
|
||||
const int interval_offset,
|
||||
const Span<int> offsets,
|
||||
int ¤t_interval,
|
||||
IndexRange &range,
|
||||
MutableSpan<int> curve_intervals,
|
||||
MutableSpan<bool> is_first_selected)
|
||||
{
|
||||
const int first_elem = offsets[curve_index];
|
||||
const int last_elem = offsets[curve_index + 1] - 1;
|
||||
|
||||
if (current_interval == 0) {
|
||||
is_first_selected[curve_index] = range.first() == first_elem && range.size() == 1;
|
||||
if (!is_first_selected[curve_index]) {
|
||||
current_interval++;
|
||||
}
|
||||
}
|
||||
curve_intervals[interval_offset + current_interval] = range.first();
|
||||
current_interval++;
|
||||
|
||||
bool inside_curve = last_elem >= range.last();
|
||||
if (inside_curve) {
|
||||
curve_intervals[interval_offset + current_interval] = range.last();
|
||||
}
|
||||
else {
|
||||
curve_intervals[interval_offset + current_interval] = last_elem;
|
||||
range = IndexRange(last_elem + 1, range.last() - last_elem);
|
||||
}
|
||||
current_interval++;
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
Pass Pass `Span` by value, otherwise it's like a pointer to a pointer. And `IndexMask` is passed by const reference :P
Laurynas Duburas
commented
I don't have a good explanation for how it happened :) I don't have a good explanation for how it happened :)
|
||||
return inside_curve;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates number of points in resulting curve denoted by #curve_index and sets it's
|
||||
* #curve_offsets value.
|
||||
*/
|
||||
static void calc_curve_offset(const int curve_index,
|
||||
int &interval_offset,
|
||||
const Span<int> offsets,
|
||||
MutableSpan<int> new_offsets,
|
||||
MutableSpan<IndexRange> curve_interval_ranges)
|
||||
{
|
||||
const int points_in_curve = (offsets[curve_index + 1] - offsets[curve_index] +
|
||||
curve_interval_ranges[curve_index].size() - 1);
|
||||
new_offsets[curve_index + 1] = new_offsets[curve_index] + points_in_curve;
|
||||
interval_offset += curve_interval_ranges[curve_index].size() + 1;
|
||||
}
|
||||
|
||||
static void finish_curve(int &curve_index,
|
||||
int &interval_offset,
|
||||
int last_interval,
|
||||
int last_elem,
|
||||
const Span<int> offsets,
|
||||
MutableSpan<int> new_offsets,
|
||||
MutableSpan<int> curve_intervals,
|
||||
MutableSpan<IndexRange> curve_interval_ranges,
|
||||
MutableSpan<bool> is_first_selected)
|
||||
{
|
||||
if (curve_intervals[interval_offset + last_interval] != last_elem ||
|
||||
curve_intervals[interval_offset + last_interval - 1] !=
|
||||
curve_intervals[interval_offset + last_interval])
|
||||
{
|
||||
/* Append last element of the current curve if it is not extruded or extruded together with
|
||||
* preceding points. */
|
||||
last_interval++;
|
||||
curve_intervals[interval_offset + last_interval] = last_elem;
|
||||
}
|
||||
else if (is_first_selected[curve_index] && last_interval == 1) {
|
||||
/* Extrusion from one point. */
|
||||
curve_intervals[interval_offset + last_interval + 1] =
|
||||
curve_intervals[interval_offset + last_interval];
|
||||
is_first_selected[curve_index] = false;
|
||||
last_interval++;
|
||||
}
|
||||
curve_interval_ranges[curve_index] = IndexRange(interval_offset, last_interval);
|
||||
calc_curve_offset(curve_index, interval_offset, offsets, new_offsets, curve_interval_ranges);
|
||||
curve_index++;
|
||||
}
|
||||
|
||||
static void finish_curve_or_full_copy(int &curve_index,
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
Could you expand Could you expand `ins` to a name that helps explain the code to the reader? Generally this sort of contraction makes this sort of thing more complex
|
||||
int &interval_offset,
|
||||
int current_interval,
|
||||
const std::optional<IndexRange> prev_range,
|
||||
const Span<int> offsets,
|
||||
MutableSpan<int> new_offsets,
|
||||
MutableSpan<int> curve_intervals,
|
||||
MutableSpan<IndexRange> curve_interval_ranges,
|
||||
MutableSpan<bool> is_first_selected)
|
||||
{
|
||||
const int last = offsets[curve_index + 1] - 1;
|
||||
|
||||
if (prev_range.has_value() && prev_range.value().last() >= offsets[curve_index]) {
|
||||
finish_curve(curve_index,
|
||||
interval_offset,
|
||||
current_interval - 1,
|
||||
last,
|
||||
offsets,
|
||||
new_offsets,
|
||||
curve_intervals,
|
||||
curve_interval_ranges,
|
||||
is_first_selected);
|
||||
}
|
||||
else {
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
Flip the condition and un-indent the rest of the function, same below with the empty selection check Flip the condition and un-indent the rest of the function, same below with the empty selection check
Hans Goudey
commented
Add Add `static` to functions that aren't exposed to the header
|
||||
/* Copy full curve if previous selected point vas not on this curve. */
|
||||
const int first = offsets[curve_index];
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
It's not totally clear what "shallow copy" means here. Usually I'd imagine that copying some data structure but not any data it owns itself. It's not totally clear what "shallow copy" means here. Usually I'd imagine that copying some data structure but not any data it owns itself.
Laurynas Duburas
commented
I meant here: copy curve as is or copy curve without modifications. Maybe finish_curve_or_full_copy ? I meant here: copy curve as is or copy curve without modifications. Maybe finish_curve_or_full_copy ?
Hans Goudey
commented
Full copy sounds good! Full copy sounds good!
|
||||
curve_interval_ranges[curve_index] = IndexRange(interval_offset, 1);
|
||||
is_first_selected[curve_index] = false;
|
||||
curve_intervals[interval_offset] = first;
|
||||
curve_intervals[interval_offset + 1] = last;
|
||||
calc_curve_offset(curve_index, interval_offset, offsets, new_offsets, curve_interval_ranges);
|
||||
curve_index++;
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
Use Use `Array` rather than manual allocations
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_curves_extrusion(const IndexMask &selection,
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
Comment style Comment style
|
||||
const Span<int> offsets,
|
||||
MutableSpan<int> new_offsets,
|
||||
MutableSpan<int> curve_intervals,
|
||||
MutableSpan<IndexRange> curve_interval_ranges,
|
||||
MutableSpan<bool> is_first_selected)
|
||||
{
|
||||
std::optional<IndexRange> prev_range;
|
||||
int current_interval = 0;
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
Unused types variables here Unused types variables here
|
||||
|
||||
int curve_index = 0;
|
||||
int interval_offset = 0;
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
Let's use the attribute API fully, without Let's use the attribute API fully, without `CustomData`. That might require a bit more boilerplate, but that's easy to clean up in the future, and we have a high level goal of replacing `CustomData` storage internally
|
||||
curve_intervals[interval_offset] = offsets[0];
|
||||
new_offsets[0] = offsets[0];
|
||||
|
||||
selection.foreach_range([&](const IndexRange range) {
|
||||
/* Beginning of the range outside current curve. */
|
||||
if (range.first() > offsets[curve_index + 1] - 1) {
|
||||
do {
|
||||
finish_curve_or_full_copy(curve_index,
|
||||
interval_offset,
|
||||
current_interval,
|
||||
prev_range,
|
||||
offsets,
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
I guess I guess `old_attributes` could be a `bke::AttributeAccessor` since those values don't have to be changed?
Laurynas Duburas
commented
Couldn't find how to do attribute value copying from Couldn't find how to do attribute value copying from `bke::AttributeAccessor` to `MutableAttributeAccessor` in chunks.
Like with `type.copy_assign_n(src.data(), dst.data(), dst.size());`
Hans Goudey
commented
You can use You can use `const GVArraySpan src_data = *src.lookup(name);` to make sure you have a span of source data
|
||||
new_offsets,
|
||||
curve_intervals,
|
||||
curve_interval_ranges,
|
||||
is_first_selected);
|
||||
} while (range.first() > offsets[curve_index + 1] - 1);
|
||||
current_interval = 0;
|
||||
curve_intervals[interval_offset] = offsets[curve_index];
|
||||
}
|
||||
|
||||
IndexRange range_to_handle = range;
|
||||
while (!handle_range(curve_index,
|
||||
interval_offset,
|
||||
offsets,
|
||||
current_interval,
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
Usually these algorithms are structured to only iterate over all the attributes once. So the first part will build mappings from input data to result data. In this case, that might be a combination of instructions for copying contiguous sections of data, and some data for which indices the copied points are from. That structure can significantly improve performance, since it works much better with the cache of CPUs, since we access less data at once. It might also help separate concerns too. Usually these algorithms are structured to only iterate over all the attributes once. So the first part will build mappings from input data to result data. In this case, that might be a combination of instructions for copying contiguous sections of data, and some data for which indices the copied points are from.
That structure can significantly improve performance, since it works much better with the cache of CPUs, since we access less data at once. It might also help separate concerns too.
|
||||
range_to_handle,
|
||||
curve_intervals,
|
||||
is_first_selected))
|
||||
{
|
||||
finish_curve(curve_index,
|
||||
interval_offset,
|
||||
current_interval - 1,
|
||||
offsets[curve_index + 1] - 1,
|
||||
offsets,
|
||||
new_offsets,
|
||||
curve_intervals,
|
||||
curve_interval_ranges,
|
||||
is_first_selected);
|
||||
current_interval = 0;
|
||||
curve_intervals[interval_offset] = offsets[curve_index];
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
The The `bke::curves::copy_only_curve_domain` utility in `BKE_curves_utils.hh` takes care of creating new curves and copying over the curve domain attributes for you.
|
||||
}
|
||||
prev_range = range;
|
||||
});
|
||||
|
||||
do {
|
||||
finish_curve_or_full_copy(curve_index,
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
Using a Span still makes sense here probably, rather than a raw pointer Using a Span still makes sense here probably, rather than a raw pointer
|
||||
interval_offset,
|
||||
current_interval,
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
Just realized this should handle multi-object edit mode too. There's an example of that in Just realized this should handle multi-object edit mode too. There's an example of that in `CURVES_OT_delete`, or other operators around there.
|
||||
prev_range,
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
I think leaving I think leaving `ot->description` blank will give some warning somewhere in the future
|
||||
offsets,
|
||||
new_offsets,
|
||||
curve_intervals,
|
||||
curve_interval_ranges,
|
||||
is_first_selected);
|
||||
prev_range.reset();
|
||||
} while (curve_index < offsets.size() - 1);
|
||||
}
|
||||
|
||||
static void extrude_curves(Curves &curves_id)
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
Looks like this can be a const pointer, since the new curves are created separately Looks like this can be a const pointer, since the new curves are created separately
|
||||
{
|
||||
const bke::AttrDomain selection_domain = bke::AttrDomain(curves_id.selection_domain);
|
||||
if (selection_domain != bke::AttrDomain::Point) {
|
||||
return;
|
||||
}
|
||||
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask extruded_points = retrieve_selected_points(curves_id, memory);
|
||||
if (extruded_points.is_empty()) {
|
||||
return;
|
||||
}
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
If you create the new curves before If you create the new curves before `calc_curves_extrusion`, you can copy the new offsets directly into the new curves' offsets array. If you don't know the number of points at that point, you can resize just the point domain after.
|
||||
|
||||
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
|
||||
const Span<int> old_offsets = curves.offsets();
|
||||
|
||||
bke::CurvesGeometry new_curves = bke::curves::copy_only_curve_domain(curves);
|
||||
|
||||
const int curves_num = curves.curves_num();
|
||||
const int curve_intervals_size = extruded_points.size() * 2 + curves_num * 2;
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
We know the domain is I noticed that this won't copy the float vs. bool status of the input selection though. Since these curves are completely new, it will always create a boolean selection. It would be pretty fancy to be able to copy the float values from the input curves as well. But if you don't feel inspired to do that, it's not a big deal either :) We know the domain is `bke::AttrDomain::Point` at this point.
I noticed that this won't copy the float vs. bool status of the input selection though. Since these curves are completely new, it will always create a boolean selection.
It would be pretty fancy to be able to copy the float values from the input curves as well. But if you don't feel inspired to do that, it's not a big deal either :)
Laurynas Duburas
commented
Don't know why selection is made with two possible types, but extrude shouldn't change the type. Don't know why selection is made with two possible types, but extrude shouldn't change the type.
Hans Goudey
commented
In sculpt mode there's "soft" selection which uses floats. Any value greater than zero is considered selected in edit mode. In sculpt mode there's "soft" selection which uses floats. Any value greater than zero is considered selected in edit mode.
Laurynas Duburas
commented
I was going to ask if other values besides 1.0f are possible, but decided that this can not be :) I was going to ask if other values besides 1.0f are possible, but decided that this can not be :)
Good that you told this.
|
||||
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
This variable could be declared here, and could have a better name besides This variable could be declared here, and could have a better name besides `d` that reflected what it was doing
|
||||
new_curves.resize(0, curves_num);
|
||||
MutableSpan<int> new_offsets = new_curves.offsets_for_write();
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
`c` -> `curve`
|
||||
|
||||
/* Buffer for intervals of all curves. Beginning and end of a curve can be determined only by
|
||||
* #curve_interval_ranges. For ex. [0, 3, 4, 4, 4] indicates one copy interval for first curve
|
||||
* [0, 3] and two for second [4, 4][4, 4]. The first curve will be copied as is without changes,
|
||||
* in the second one (consisting only one point - 4) first point will be duplicated (extruded).
|
||||
*/
|
||||
Array<int> curve_intervals(curve_intervals_size);
|
||||
|
||||
/* Points to intervals for each curve in the curve_intervals array.
|
||||
* For example above value would be [{0, 1}, {2, 2}] */
|
||||
Array<IndexRange> curve_interval_ranges(curves_num);
|
||||
|
||||
/* Per curve boolean indicating if first interval in a curve is selected.
|
||||
* Other can be calculated as in a curve two adjacent intervals can have same selection state. */
|
||||
Array<bool> is_first_selected(curves_num);
|
||||
|
||||
calc_curves_extrusion(extruded_points,
|
||||
old_offsets,
|
||||
new_offsets,
|
||||
curve_intervals,
|
||||
curve_interval_ranges,
|
||||
is_first_selected);
|
||||
|
||||
new_curves.resize(new_offsets.last(), new_curves.curves_num());
|
||||
|
||||
const bke::AttributeAccessor src_attributes = curves.attributes();
|
||||
const GVArraySpan src_selection = *src_attributes.lookup(".selection", bke::AttrDomain::Point);
|
||||
const CPPType &src_selection_type = src_selection.type();
|
||||
bke::GSpanAttributeWriter dst_selection = ensure_selection_attribute(
|
||||
new_curves,
|
||||
bke::AttrDomain::Point,
|
||||
src_selection_type.is<bool>() ? CD_PROP_BOOL : CD_PROP_FLOAT);
|
||||
|
||||
threading::parallel_for(curves.curves_range(), 256, [&](IndexRange curves_range) {
|
||||
for (const int curve : curves_range) {
|
||||
const int first_index = curve_interval_ranges[curve].start();
|
||||
const int first_value = curve_intervals[first_index];
|
||||
bool is_selected = is_first_selected[curve];
|
||||
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
This should be unnecessary, since This should be unnecessary, since `copy_only_curve_domain` copies this info
|
||||
for (const int i : curve_interval_ranges[curve]) {
|
||||
const int dest_index = new_offsets[curve] + curve_intervals[i] - first_value + i -
|
||||
laurynas marked this conversation as resolved
Outdated
Hans Goudey
commented
`std::move(new_curves)` to avoid a copy
|
||||
first_index;
|
||||
const int size = curve_intervals[i + 1] - curve_intervals[i] + 1;
|
||||
GMutableSpan dst_span = dst_selection.span.slice(IndexRange(dest_index, size));
|
||||
if (is_selected) {
|
||||
src_selection_type.copy_assign_n(
|
||||
src_selection.slice(IndexRange(curve_intervals[i], size)).data(),
|
||||
dst_span.data(),
|
||||
size);
|
||||
}
|
||||
else {
|
||||
fill_selection(dst_span, false);
|
||||
}
|
||||
|
||||
is_selected = !is_selected;
|
||||
}
|
||||
}
|
||||
});
|
||||
dst_selection.finish();
|
||||
|
||||
const Span<int> intervals = compress_intervals(curve_interval_ranges, curve_intervals);
|
||||
|
||||
bke::MutableAttributeAccessor dst_attributes = new_curves.attributes_for_write();
|
||||
|
||||
for (auto &attribute : bke::retrieve_attributes_for_transfer(
|
||||
src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, {}, {".selection"}))
|
||||
{
|
||||
const CPPType &type = attribute.src.type();
|
||||
threading::parallel_for(IndexRange(intervals.size() - 1), 512, [&](IndexRange range) {
|
||||
for (const int i : range) {
|
||||
const int first = intervals[i];
|
||||
const int size = intervals[i + 1] - first + 1;
|
||||
const int dest_index = intervals[i] + i;
|
||||
type.copy_assign_n(attribute.src.slice(IndexRange(first, size)).data(),
|
||||
attribute.dst.span.slice(IndexRange(dest_index, size)).data(),
|
||||
size);
|
||||
}
|
||||
});
|
||||
attribute.dst.finish();
|
||||
}
|
||||
curves_id.geometry.wrap() = std::move(new_curves);
|
||||
DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
|
||||
}
|
||||
|
||||
static int curves_extrude_exec(bContext *C, wmOperator * /*op*/)
|
||||
{
|
||||
for (Curves *curves_id : get_unique_editable_curves(*C)) {
|
||||
extrude_curves(*curves_id);
|
||||
}
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void CURVES_OT_extrude(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Extrude";
|
||||
ot->description = "Extrude selected control point(s)";
|
||||
ot->idname = "CURVES_OT_extrude";
|
||||
|
||||
ot->exec = curves_extrude_exec;
|
||||
ot->poll = editable_curves_in_edit_mode_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves
|
|
@ -1332,6 +1332,7 @@ void ED_operatortypes_curves()
|
|||
WM_operatortype_append(CURVES_OT_convert_to_particle_system);
|
||||
WM_operatortype_append(CURVES_OT_convert_from_particle_system);
|
||||
WM_operatortype_append(CURVES_OT_draw);
|
||||
WM_operatortype_append(CURVES_OT_extrude);
|
||||
WM_operatortype_append(CURVES_OT_snap_curves_to_surface);
|
||||
WM_operatortype_append(CURVES_OT_set_selection_domain);
|
||||
WM_operatortype_append(CURVES_OT_select_all);
|
||||
|
@ -1360,6 +1361,16 @@ void ED_operatormacros_curves()
|
|||
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);
|
||||
|
||||
/* Extrude + Move */
|
||||
ot = WM_operatortype_append_macro("CURVES_OT_extrude_move",
|
||||
"Extrude Curve and Move",
|
||||
"Extrude curve and move result",
|
||||
OPTYPE_UNDO | OPTYPE_REGISTER);
|
||||
WM_operatortype_macro_define(ot, "CURVES_OT_extrude");
|
||||
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)
|
||||
|
|
|
@ -128,6 +128,16 @@ void fill_selection_true(GMutableSpan selection)
|
|||
}
|
||||
}
|
||||
|
||||
void fill_selection(GMutableSpan selection, bool value)
|
||||
{
|
||||
if (selection.type().is<bool>()) {
|
||||
selection.typed<bool>().fill(value);
|
||||
}
|
||||
else if (selection.type().is<float>()) {
|
||||
selection.typed<float>().fill(value ? 1.0f : 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void fill_selection_false(GMutableSpan selection, const IndexMask &mask)
|
||||
{
|
||||
if (selection.type().is<bool>()) {
|
||||
|
|
|
@ -80,6 +80,7 @@ bool curves_poll(bContext *C);
|
|||
|
||||
void CURVES_OT_attribute_set(wmOperatorType *ot);
|
||||
void CURVES_OT_draw(wmOperatorType *ot);
|
||||
void CURVES_OT_extrude(wmOperatorType *ot);
|
||||
|
||||
/** \} */
|
||||
|
||||
|
@ -143,6 +144,7 @@ IndexMask random_mask(const bke::CurvesGeometry &curves,
|
|||
|
||||
void fill_selection_false(GMutableSpan span);
|
||||
void fill_selection_true(GMutableSpan span);
|
||||
void fill_selection(GMutableSpan selection, bool value);
|
||||
void fill_selection_false(GMutableSpan selection, const IndexMask &mask);
|
||||
void fill_selection_true(GMutableSpan selection, const IndexMask &mask);
|
||||
|
||||
|
|
I'd suggest doing a "array of structs" to "struct of arrays" transformation and split this struct into separate arrays for each member. That would have a few benefits here:
selection.foreach_range
,OffsetIndices
,array_utils::gather
, etc.Well
CurveCopy
is refactored, but I'll have to think deeper regarding abstractions. Somehow I didn't noticeselection.foreach_range
exists, only it might change a lot.By the way I was thinking about using
OffsetIndices
, but comment says "References an array of ascending indices."Here I have "non decreasing indices". Will it work?
Not totally sure what the requirements are here, didn't look that in depth yet. But an array like
5, 5, 6, 8, 21, 21
will work; repeating offsets are okay, it just means that group is empty.I need to express ranges like
[0, 2][2, 2][2, 4]
. Ranges always share end points with a neighbor.Copied to destination indexes would result in
[0, 1, 2, 2, 2, 3, 4]
.This would be extrusion of point 2 in 5 point curve
[0, .., 4]
.Because of sharing neighbor I currently express this as
[0, 2, 2, 4]
and iterate with(i, i + 1)
.So for destination I see use of
OffsetIndices
, but for source indexes not sure.Ideally if to handle multiple curves in one data structure. Extrusion of points (2) and (7, 8) in curves
[0, .., 4][5, .., 9]
could look like:a.
[0, 2][2, 2][2, 4][5, 7][7, 8][8, 9]
. Here sharing endpoint rule is broken.b.
[0, 2][2, 2][2, 7][7, 8][8, 9]
. This would reduce copying count, but because of special cases is very hard to implement. Before I had a working version, I gave up on this after few days of trying.Any Thoughts?
Just checked code of
OffsetIndices
. It uses second index as exclusive, in this situation both inclusive needed.OffsetIndices
calculatesIndexRange
size bysize = end - begin;
, I needsize = last - first + 1;
.Can't think of any smart way to restructure data to make use of
OffsetIndices
...That's fine, no need to force it! Thanks for taking a look though.