Add Chromium-only Blender WebEngine parity work
This commit is contained in:
62
blender-5.2.0/source/blender/editors/curves/CMakeLists.txt
Normal file
62
blender-5.2.0/source/blender/editors/curves/CMakeLists.txt
Normal file
@@ -0,0 +1,62 @@
|
||||
# SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(INC
|
||||
../include
|
||||
../../makesrna
|
||||
|
||||
# RNA_prototypes.hh
|
||||
${CMAKE_BINARY_DIR}/source/blender/makesrna
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
)
|
||||
|
||||
set(SRC
|
||||
intern/curves_add.cc
|
||||
intern/curves_attribute_set.cc
|
||||
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_pen.cc
|
||||
intern/curves_selection.cc
|
||||
intern/curves_undo.cc
|
||||
intern/join.cc
|
||||
intern/select_linked_pick.cc
|
||||
intern/separate.cc
|
||||
)
|
||||
|
||||
set(LIB
|
||||
PRIVATE bf::blenkernel
|
||||
PRIVATE bf::blenlib
|
||||
PRIVATE bf::blentranslation
|
||||
PRIVATE bf::bmesh
|
||||
PRIVATE bf::depsgraph
|
||||
PRIVATE bf::dna
|
||||
PRIVATE bf::extern::curve_fit_nd
|
||||
PRIVATE bf::functions
|
||||
PRIVATE bf::geometry
|
||||
PRIVATE bf::gpu
|
||||
PRIVATE bf::intern::clog
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
PRIVATE bf::nodes
|
||||
PRIVATE bf::windowmanager
|
||||
)
|
||||
|
||||
blender_add_lib(bf_editor_curves "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
add_dependencies(bf_editor_curves bf_rna)
|
||||
|
||||
if(WITH_GTESTS)
|
||||
set(TEST_SRC
|
||||
tests/curves_edit_test.cc
|
||||
)
|
||||
set(TEST_INC
|
||||
)
|
||||
set(TEST_LIB
|
||||
)
|
||||
blender_add_test_suite_lib(editor_curves "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
|
||||
endif()
|
||||
157
blender-5.2.0/source/blender/editors/curves/intern/curves_add.cc
Normal file
157
blender-5.2.0/source/blender/editors/curves/intern/curves_add.cc
Normal file
@@ -0,0 +1,157 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup edcurves
|
||||
*/
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_base_safe.h"
|
||||
#include "BLI_rand.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_main_invariants.hh"
|
||||
#include "BKE_node_legacy_types.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
#include "ED_node.hh"
|
||||
#include "ED_object.hh"
|
||||
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
namespace blender::ed::curves {
|
||||
|
||||
static bool has_surface_deformation_node(const bNodeTree &ntree)
|
||||
{
|
||||
if (!ntree.nodes_by_type("GeometryNodeDeformCurvesOnSurface"_ustr).is_empty()) {
|
||||
return true;
|
||||
}
|
||||
for (const bNode *node : ntree.group_nodes()) {
|
||||
if (const bNodeTree *sub_tree = reinterpret_cast<const bNodeTree *>(node->id)) {
|
||||
if (has_surface_deformation_node(*sub_tree)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool has_surface_deformation_node(const Object &curves_ob)
|
||||
{
|
||||
for (const ModifierData &md : curves_ob.modifiers) {
|
||||
if (md.type != eModifierType_Nodes) {
|
||||
continue;
|
||||
}
|
||||
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(&md);
|
||||
if (nmd->node_group == nullptr || ID_MISSING(nmd->node_group)) {
|
||||
continue;
|
||||
}
|
||||
if (has_surface_deformation_node(*nmd->node_group)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ensure_surface_deformation_node_exists(bContext &C, Object &curves_ob)
|
||||
{
|
||||
if (has_surface_deformation_node(curves_ob)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Main *bmain = CTX_data_main(&C);
|
||||
Scene *scene = CTX_data_scene(&C);
|
||||
|
||||
ModifierData *md = object::modifier_add(
|
||||
nullptr, bmain, scene, &curves_ob, DATA_("Surface Deform"), eModifierType_Nodes);
|
||||
NodesModifierData &nmd = *reinterpret_cast<NodesModifierData *>(md);
|
||||
nmd.node_group = bke::node_tree_add_tree(bmain, DATA_("Surface Deform"), "GeometryNodeTree");
|
||||
|
||||
if (!nmd.node_group->geometry_node_asset_traits) {
|
||||
nmd.node_group->geometry_node_asset_traits = MEM_new<GeometryNodeAssetTraits>(__func__);
|
||||
}
|
||||
|
||||
nmd.node_group->geometry_node_asset_traits->flag |= GEO_NODE_ASSET_MODIFIER;
|
||||
|
||||
bNodeTree *ntree = nmd.node_group;
|
||||
ntree->tree_interface.add_socket(
|
||||
"Geometry", "", "NodeSocketGeometry", NODE_INTERFACE_SOCKET_OUTPUT, nullptr);
|
||||
ntree->tree_interface.add_socket(
|
||||
"Geometry", "", "NodeSocketGeometry", NODE_INTERFACE_SOCKET_INPUT, nullptr);
|
||||
bNode *group_input = bke::node_add_static_node(&C, *ntree, NODE_GROUP_INPUT);
|
||||
bNode *group_output = bke::node_add_static_node(&C, *ntree, NODE_GROUP_OUTPUT);
|
||||
bNode *deform_node = bke::node_add_static_node(&C, *ntree, GEO_NODE_DEFORM_CURVES_ON_SURFACE);
|
||||
|
||||
BKE_main_ensure_invariants(*bmain, nmd.node_group->id);
|
||||
|
||||
bke::node_add_link(*ntree,
|
||||
*group_input,
|
||||
*static_cast<bNodeSocket *>(group_input->outputs.first),
|
||||
*deform_node,
|
||||
*bke::node_find_socket(*deform_node, SOCK_IN, "Curves"_ustr));
|
||||
bke::node_add_link(*ntree,
|
||||
*deform_node,
|
||||
*bke::node_find_socket(*deform_node, SOCK_OUT, "Curves"_ustr),
|
||||
*group_output,
|
||||
*static_cast<bNodeSocket *>(group_output->inputs.first));
|
||||
|
||||
group_input->location[0] = -200;
|
||||
group_output->location[0] = 200;
|
||||
deform_node->location[0] = 0;
|
||||
|
||||
BKE_main_ensure_invariants(*bmain, nmd.node_group->id);
|
||||
}
|
||||
|
||||
bke::CurvesGeometry primitive_random_sphere(const int curves_size, const int points_per_curve)
|
||||
{
|
||||
bke::CurvesGeometry curves(points_per_curve * curves_size, curves_size);
|
||||
|
||||
MutableSpan<int> offsets = curves.offsets_for_write();
|
||||
MutableSpan<float3> positions = curves.positions_for_write();
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
bke::SpanAttributeWriter<float> radius = attributes.lookup_or_add_for_write_only_span<float>(
|
||||
"radius", bke::AttrDomain::Point);
|
||||
|
||||
for (const int i : offsets.index_range()) {
|
||||
offsets[i] = points_per_curve * i;
|
||||
}
|
||||
|
||||
RandomNumberGenerator rng;
|
||||
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
for (const int i : curves.curves_range()) {
|
||||
const IndexRange points = points_by_curve[i];
|
||||
MutableSpan<float3> curve_positions = positions.slice(points);
|
||||
MutableSpan<float> curve_radii = radius.span.slice(points);
|
||||
|
||||
const float theta = 2.0f * M_PI * rng.get_float();
|
||||
const float phi = safe_acosf(2.0f * rng.get_float() - 1.0f);
|
||||
|
||||
float3 no = {std::sin(theta) * std::sin(phi), std::cos(theta) * std::sin(phi), std::cos(phi)};
|
||||
no = math::normalize(no);
|
||||
|
||||
float3 co = no;
|
||||
for (int key = 0; key < points_per_curve; key++) {
|
||||
float t = key / float(points_per_curve - 1);
|
||||
curve_positions[key] = co;
|
||||
curve_radii[key] = 0.02f * (1.0f - t);
|
||||
|
||||
float3 offset = float3(rng.get_float(), rng.get_float(), rng.get_float()) * 2.0f - 1.0f;
|
||||
co += (offset + no) / points_per_curve;
|
||||
}
|
||||
}
|
||||
|
||||
radius.finish();
|
||||
|
||||
return curves;
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves
|
||||
@@ -0,0 +1,233 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup edmesh
|
||||
*/
|
||||
|
||||
#include "BLI_generic_pointer.hh"
|
||||
|
||||
#include "BKE_attribute.h"
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_attribute_math.hh"
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_type_conversions.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
#include "ED_geometry.hh"
|
||||
#include "ED_object.hh"
|
||||
#include "ED_screen.hh"
|
||||
#include "ED_transform.hh"
|
||||
#include "ED_view3d.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "UI_interface_layout.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Delete Operator
|
||||
* \{ */
|
||||
|
||||
namespace blender::ed::curves {
|
||||
|
||||
static bool active_attribute_poll(bContext *C)
|
||||
{
|
||||
if (!editable_curves_in_edit_mode_poll(C)) {
|
||||
return false;
|
||||
}
|
||||
const Object *object = CTX_data_active_object(C);
|
||||
const ID &object_data = *static_cast<const ID *>(object->data);
|
||||
if (!geometry::attribute_set_poll(*C, object_data)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static IndexMask retrieve_selected_elements(const Curves &curves_id,
|
||||
const bke::AttrDomain domain,
|
||||
IndexMaskMemory &memory)
|
||||
{
|
||||
switch (domain) {
|
||||
case bke::AttrDomain::Point:
|
||||
return retrieve_selected_points(curves_id, memory);
|
||||
case bke::AttrDomain::Curve:
|
||||
return retrieve_selected_curves(curves_id, memory);
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
static void validate_value(const bke::AttributeAccessor attributes,
|
||||
const StringRef name,
|
||||
const CPPType &type,
|
||||
void *buffer)
|
||||
{
|
||||
const bke::AttributeValidator validator = attributes.lookup_validator(name);
|
||||
if (!validator) {
|
||||
return;
|
||||
}
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(type, validated_buffer);
|
||||
BLI_SCOPED_DEFER([&]() { type.destruct(validated_buffer); });
|
||||
|
||||
const IndexMask single_mask(1);
|
||||
mf::ParamsBuilder params(*validator.function, &single_mask);
|
||||
params.add_readonly_single_input(GPointer(type, buffer));
|
||||
params.add_uninitialized_single_output({type, validated_buffer, 1});
|
||||
mf::ContextBuilder context;
|
||||
validator.function->call(single_mask, params, context);
|
||||
|
||||
type.copy_assign(validated_buffer, buffer);
|
||||
}
|
||||
|
||||
static wmOperatorStatus set_attribute_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Object *active_object = CTX_data_active_object(C);
|
||||
Curves &active_curves_id = *id_cast<Curves *>(active_object->data);
|
||||
|
||||
AttributeOwner active_owner = AttributeOwner::from_id(&active_curves_id.id);
|
||||
const StringRef name = *BKE_attributes_active_name_get(active_owner);
|
||||
const bke::AttributeMetaData active_meta_data =
|
||||
*active_curves_id.geometry.wrap().attributes().lookup_meta_data(name);
|
||||
const bke::AttrType active_type = active_meta_data.data_type;
|
||||
const CPPType &type = bke::attribute_type_to_cpp_type(active_type);
|
||||
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
|
||||
BLI_SCOPED_DEFER([&]() { type.destruct(buffer); });
|
||||
const GPointer value = geometry::rna_property_for_attribute_type_retrieve_value(
|
||||
*op->ptr, active_type, buffer);
|
||||
|
||||
const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions();
|
||||
|
||||
for (Curves *curves_id : get_unique_editable_curves(*C)) {
|
||||
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
const std::optional<bke::AttributeMetaData> meta_data = attributes.lookup_meta_data(name);
|
||||
if (!meta_data) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask selection = retrieve_selected_elements(*curves_id, meta_data->domain, memory);
|
||||
if (selection.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Use implicit conversions to try to handle the case where the active attribute has a
|
||||
* different type on multiple objects. */
|
||||
const CPPType &dst_type = bke::attribute_type_to_cpp_type(meta_data->data_type);
|
||||
if (&type != &dst_type && !conversions.is_convertible(type, dst_type)) {
|
||||
continue;
|
||||
}
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(dst_type, dst_buffer);
|
||||
BLI_SCOPED_DEFER([&]() { dst_type.destruct(dst_buffer); });
|
||||
conversions.convert_to_uninitialized(type, dst_type, value.get(), dst_buffer);
|
||||
|
||||
validate_value(attributes, name, dst_type, dst_buffer);
|
||||
const GPointer dst_value(dst_type, dst_buffer);
|
||||
if (selection.size() == attributes.domain_size(meta_data->domain)) {
|
||||
if (attributes.assign_data(name, bke::AttributeInitValue(dst_value))) {
|
||||
DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(name);
|
||||
dst_type.fill_assign_indices(dst_value.get(), attribute.span.data(), selection);
|
||||
attribute.finish();
|
||||
|
||||
DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
|
||||
}
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static wmOperatorStatus set_attribute_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
Object *active_object = CTX_data_active_object(C);
|
||||
Curves &active_curves_id = *id_cast<Curves *>(active_object->data);
|
||||
|
||||
AttributeOwner owner = AttributeOwner::from_id(&active_curves_id.id);
|
||||
const StringRef name = *BKE_attributes_active_name_get(owner);
|
||||
const bke::CurvesGeometry &curves = active_curves_id.geometry.wrap();
|
||||
const bke::AttributeAccessor attributes = curves.attributes();
|
||||
const bke::GAttributeReader attribute = attributes.lookup(name);
|
||||
const bke::AttrDomain domain = attribute.domain;
|
||||
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask selection = retrieve_selected_elements(active_curves_id, domain, memory);
|
||||
|
||||
const CPPType &type = attribute.varray.type();
|
||||
|
||||
PropertyRNA *prop = geometry::rna_property_for_type(*op->ptr,
|
||||
bke::cpp_type_to_attribute_type(type));
|
||||
if (RNA_property_is_set(op->ptr, prop)) {
|
||||
return WM_operator_props_popup(C, op, event);
|
||||
}
|
||||
|
||||
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
|
||||
BLI_SCOPED_DEFER([&]() { type.destruct(buffer); });
|
||||
|
||||
bke::attribute_math::to_static_type(type, [&]<typename T>() {
|
||||
if constexpr (!std::is_void_v<bke::attribute_math::DefaultMixer<T>>) {
|
||||
const VArray<T> values_typed = attribute.varray.typed<T>();
|
||||
bke::attribute_math::DefaultMixer<T> mixer{MutableSpan(static_cast<T *>(buffer), 1)};
|
||||
selection.foreach_index([&](const int i) { mixer.mix_in(0, values_typed[i]); });
|
||||
mixer.finalize();
|
||||
}
|
||||
});
|
||||
|
||||
geometry::rna_property_for_attribute_type_set_value(*op->ptr, *prop, GPointer(type, buffer));
|
||||
|
||||
return WM_operator_props_popup(C, op, event);
|
||||
}
|
||||
|
||||
static void set_attribute_ui(bContext *C, wmOperator *op)
|
||||
{
|
||||
ui::Layout &layout = op->layout->column(true);
|
||||
layout.use_property_split_set(true);
|
||||
layout.use_property_decorate_set(false);
|
||||
|
||||
Object *object = CTX_data_active_object(C);
|
||||
Curves &curves_id = *id_cast<Curves *>(object->data);
|
||||
|
||||
AttributeOwner owner = AttributeOwner::from_id(&curves_id.id);
|
||||
const StringRef name = *BKE_attributes_active_name_get(owner);
|
||||
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
|
||||
const bke::AttributeMetaData meta_data = *curves.attributes().lookup_meta_data(name);
|
||||
const StringRefNull prop_name = geometry::rna_property_name_for_type(meta_data.data_type);
|
||||
layout.prop(op->ptr, prop_name, UI_ITEM_NONE, name, ICON_NONE);
|
||||
}
|
||||
|
||||
void CURVES_OT_attribute_set(wmOperatorType *ot)
|
||||
{
|
||||
using namespace blender::ed;
|
||||
using namespace blender::ed::curves;
|
||||
ot->name = "Set Attribute";
|
||||
ot->description = "Set values of the active attribute for selected elements";
|
||||
ot->idname = "CURVES_OT_attribute_set";
|
||||
|
||||
ot->exec = set_attribute_exec;
|
||||
ot->invoke = set_attribute_invoke;
|
||||
ot->poll = active_attribute_poll;
|
||||
ot->ui = set_attribute_ui;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
geometry::register_rna_properties_for_attribute_types(*ot->srna);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves
|
||||
|
||||
/** \} */
|
||||
@@ -0,0 +1,136 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_curves_utils.hh"
|
||||
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
#include "ED_transverts.hh"
|
||||
|
||||
namespace blender::ed::curves {
|
||||
|
||||
Vector<MutableSpan<float3>> get_curves_positions_for_write(bke::CurvesGeometry &curves)
|
||||
{
|
||||
Vector<MutableSpan<float3>> positions_per_attribute;
|
||||
positions_per_attribute.append(curves.positions_for_write());
|
||||
if (curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
|
||||
positions_per_attribute.append(curves.handle_positions_left_for_write());
|
||||
positions_per_attribute.append(curves.handle_positions_right_for_write());
|
||||
}
|
||||
return positions_per_attribute;
|
||||
}
|
||||
|
||||
Vector<Span<float3>> get_curves_positions(const bke::CurvesGeometry &curves)
|
||||
{
|
||||
Vector<Span<float3>> positions_per_attribute;
|
||||
positions_per_attribute.append(curves.positions());
|
||||
const std::optional<Span<float3>> handles_left = curves.handle_positions_left();
|
||||
const std::optional<Span<float3>> handles_right = curves.handle_positions_right();
|
||||
if (handles_left && handles_right) {
|
||||
positions_per_attribute.append(*handles_left);
|
||||
positions_per_attribute.append(*handles_right);
|
||||
}
|
||||
return positions_per_attribute;
|
||||
}
|
||||
|
||||
static std::array<IndexMask, 3> transverts_curves_selection(const bke::CurvesGeometry &curves,
|
||||
const bool skip_handles,
|
||||
IndexMaskMemory &memory)
|
||||
{
|
||||
std::array<IndexMask, 3> selection;
|
||||
const Span<StringRef> selection_names = ed::curves::get_curves_selection_attribute_names(curves);
|
||||
if (selection_names.size() == 1) {
|
||||
selection[0] = ed::curves::retrieve_selected_points(curves, memory);
|
||||
}
|
||||
else {
|
||||
const IndexMask bezier_points = bke::curves::curve_type_point_selection(
|
||||
curves, CURVE_TYPE_BEZIER, memory);
|
||||
|
||||
for (const int i : selection_names.index_range()) {
|
||||
selection[i] = ed::curves::retrieve_selected_points(
|
||||
curves, selection_names[i], bezier_points, memory);
|
||||
}
|
||||
}
|
||||
|
||||
if (skip_handles) {
|
||||
/* When the control point is selected, both handles are ignored. */
|
||||
selection[1] = IndexMask::from_difference(selection[1], selection[0], memory);
|
||||
selection[2] = IndexMask::from_difference(selection[2], selection[0], memory);
|
||||
}
|
||||
|
||||
return selection;
|
||||
}
|
||||
|
||||
void transverts_from_curves_positions_create(bke::CurvesGeometry &curves,
|
||||
TransVertStore *tvs,
|
||||
const bool skip_handles)
|
||||
{
|
||||
IndexMaskMemory memory;
|
||||
std::array<IndexMask, 3> selection = transverts_curves_selection(curves, skip_handles, memory);
|
||||
const int size = selection[0].size() + selection[1].size() + selection[2].size();
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
tvs->transverts = MEM_new_array_zeroed<TransVert>(size, __func__);
|
||||
tvs->transverts_tot = size;
|
||||
|
||||
int offset = 0;
|
||||
const Vector<MutableSpan<float3>> positions = ed::curves::get_curves_positions_for_write(curves);
|
||||
for (const int attribute_i : positions.index_range()) {
|
||||
selection[attribute_i].foreach_index(
|
||||
[&](const int64_t i, const int64_t pos) {
|
||||
TransVert &tv = tvs->transverts[pos + offset];
|
||||
tv.loc = positions[attribute_i][i];
|
||||
tv.flag = SELECT;
|
||||
copy_v3_v3(tv.oldloc, tv.loc);
|
||||
},
|
||||
exec_mode::grain_size(1024));
|
||||
|
||||
offset += selection[attribute_i].size();
|
||||
}
|
||||
}
|
||||
|
||||
void transverts_update_curves(bke::CurvesGeometry &curves,
|
||||
const TransVertStore *tvs,
|
||||
const bool skip_handles)
|
||||
{
|
||||
IndexMaskMemory memory;
|
||||
std::array<IndexMask, 3> selection = transverts_curves_selection(curves, skip_handles, memory);
|
||||
const int size = selection[0].size() + selection[1].size() + selection[2].size();
|
||||
if (size == 0 || size != tvs->transverts_tot) {
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
const Vector<MutableSpan<float3>> positions = ed::curves::get_curves_positions_for_write(curves);
|
||||
const Span<TransVert> all_transverts = {tvs->transverts, tvs->transverts_tot};
|
||||
for (const int attribute_i : positions.index_range()) {
|
||||
const Span<TransVert> transverts = all_transverts.slice_safe(offset,
|
||||
selection[attribute_i].size());
|
||||
selection[attribute_i].foreach_index(
|
||||
[&](const int64_t i, const int64_t pos) {
|
||||
const TransVert &tv = transverts[pos];
|
||||
positions[attribute_i][i] += float3(tv.loc) - float3(tv.oldloc);
|
||||
},
|
||||
exec_mode::grain_size(1024));
|
||||
|
||||
offset += selection[attribute_i].size();
|
||||
}
|
||||
|
||||
curves.tag_positions_changed();
|
||||
curves.calculate_bezier_auto_handles();
|
||||
}
|
||||
|
||||
float (*point_normals_array_create(const Curves *curves_id))[3]
|
||||
{
|
||||
const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
|
||||
const int size = curves.points_num();
|
||||
float3 *data = MEM_new_array_uninitialized<float3>(size, __func__);
|
||||
bke::curves_normals_point_domain_calc(curves, {data, size});
|
||||
return reinterpret_cast<float (*)[3]>(data);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves
|
||||
1372
blender-5.2.0/source/blender/editors/curves/intern/curves_draw.cc
Normal file
1372
blender-5.2.0/source/blender/editors/curves/intern/curves_draw.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,733 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup edcurves
|
||||
*/
|
||||
|
||||
#include "BLI_array_utils.hh"
|
||||
|
||||
#include "BKE_anonymous_attribute_id.hh"
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_curves_utils.hh"
|
||||
#include "BKE_deform.hh"
|
||||
|
||||
#include "GEO_reorder.hh"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
|
||||
namespace blender::ed::curves {
|
||||
|
||||
bool remove_selection(bke::CurvesGeometry &curves, const bke::AttrDomain selection_domain)
|
||||
{
|
||||
const bke::AttributeAccessor attributes = curves.attributes();
|
||||
const VArray<bool> selection = *attributes.lookup_or_default<bool>(
|
||||
".selection", selection_domain, true);
|
||||
const int domain_size_orig = attributes.domain_size(selection_domain);
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_bools(selection, memory);
|
||||
switch (selection_domain) {
|
||||
case bke::AttrDomain::Point:
|
||||
curves.remove_points(mask, {});
|
||||
break;
|
||||
case bke::AttrDomain::Curve:
|
||||
curves.remove_curves(mask, {});
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
return attributes.domain_size(selection_domain) != domain_size_orig;
|
||||
}
|
||||
|
||||
static void curve_offsets_from_selection(const Span<IndexRange> selected_points,
|
||||
const IndexRange points,
|
||||
const int curve,
|
||||
const bool cyclic,
|
||||
Vector<int> &r_new_curve_offsets,
|
||||
Vector<bool> &r_new_cyclic,
|
||||
Vector<IndexRange> &r_src_ranges,
|
||||
Vector<int> &r_dst_offsets,
|
||||
Vector<int> &r_dst_to_src_curve)
|
||||
{
|
||||
if (selected_points.is_empty()) {
|
||||
return;
|
||||
}
|
||||
const bool merge_loop = cyclic && selected_points.first().size() < points.size() &&
|
||||
selected_points.first().first() == points.first() &&
|
||||
selected_points.last().last() == points.last();
|
||||
|
||||
int last_dst_offset = r_dst_offsets.last();
|
||||
int last_curve_offset = r_new_curve_offsets.last();
|
||||
for (const IndexRange range : selected_points.drop_front(merge_loop)) {
|
||||
r_src_ranges.append(range);
|
||||
last_dst_offset += range.size();
|
||||
r_dst_offsets.append(last_dst_offset);
|
||||
last_curve_offset += range.size();
|
||||
r_new_curve_offsets.append(last_curve_offset);
|
||||
};
|
||||
if (merge_loop) {
|
||||
const IndexRange merge_to_end = selected_points.first();
|
||||
r_src_ranges.append(merge_to_end);
|
||||
r_dst_offsets.append(last_dst_offset + merge_to_end.size());
|
||||
r_new_curve_offsets.last() += merge_to_end.size();
|
||||
}
|
||||
const int curves_added = selected_points.size() - merge_loop;
|
||||
r_dst_to_src_curve.append_n_times(curve, curves_added);
|
||||
r_new_cyclic.append_n_times(cyclic && selected_points.first().size() == points.size(),
|
||||
curves_added);
|
||||
}
|
||||
|
||||
static void append_point_knots(const Span<IndexRange> src_ranges,
|
||||
const OffsetIndices<int> dst_offsets,
|
||||
const Span<int> dst_to_src_curve,
|
||||
const bke::CurvesGeometry &src_curves,
|
||||
bke::CurvesGeometry &curves)
|
||||
{
|
||||
curves.nurbs_custom_knots_update_size();
|
||||
|
||||
const Span<int> src_points_by_curve = src_curves.points_by_curve().data();
|
||||
const Span<int> src_knots_by_curve = src_curves.nurbs_custom_knots_by_curve().data();
|
||||
const VArray<int8_t> src_orders = src_curves.nurbs_orders();
|
||||
const VArray<int8_t> knot_modes = curves.nurbs_knots_modes();
|
||||
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||
const OffsetIndices<int> knots_by_curve = curves.nurbs_custom_knots_by_curve();
|
||||
MutableSpan<float> dst_knots = curves.nurbs_custom_knots_for_write();
|
||||
/* Source knots must be defined after destination knots, because when `src_curves` == `curves`
|
||||
* call to `nurbs_custom_knots_for_write()` might invalidate the result of previously called
|
||||
* `nurbs_custom_knots()`. */
|
||||
const Span<float> src_knots = src_curves.nurbs_custom_knots();
|
||||
|
||||
const int old_curves_num = curves.curves_num() - dst_to_src_curve.size();
|
||||
|
||||
int range = 0;
|
||||
for (const int appended_curve : dst_to_src_curve.index_range()) {
|
||||
const int dst_curve = appended_curve + old_curves_num;
|
||||
if (knot_modes[dst_curve] != NURBS_KNOT_MODE_CUSTOM) {
|
||||
range++;
|
||||
continue;
|
||||
}
|
||||
const int src_curve = dst_to_src_curve[appended_curve];
|
||||
const int order = src_orders[src_curve];
|
||||
const int first_curve_point = src_points_by_curve[src_curve];
|
||||
const int first_curve_knot = src_knots_by_curve[src_curve];
|
||||
const int point_to_knot = -first_curve_point + first_curve_knot;
|
||||
const IndexRange src_range = src_ranges[range];
|
||||
const IndexRange src_knot_range = IndexRange::from_begin_size(
|
||||
src_range.first() + point_to_knot, src_range.size() + order);
|
||||
const IndexRange dst_knot_range = knots_by_curve[dst_curve];
|
||||
dst_knots.slice(dst_knot_range.take_front(src_knot_range.size()))
|
||||
.copy_from(src_knots.slice(src_knot_range));
|
||||
if (dst_offsets[range].size() != points_by_curve[dst_curve].size()) {
|
||||
range++;
|
||||
const IndexRange merged_tail = src_ranges[range];
|
||||
const IndexRange src_tail_knots = merged_tail.shift(point_to_knot + order);
|
||||
const IndexRange dst_tail_knots = dst_knot_range.take_back(src_tail_knots.size());
|
||||
const float knot_shift = dst_knots[dst_tail_knots.one_before_start()] -
|
||||
src_knots[src_tail_knots.one_before_start()];
|
||||
for (const int i : src_tail_knots.index_range()) {
|
||||
dst_knots[dst_tail_knots[i]] = src_knots[src_tail_knots[i]] + knot_shift;
|
||||
}
|
||||
}
|
||||
range++;
|
||||
}
|
||||
}
|
||||
|
||||
void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask)
|
||||
{
|
||||
if (curves.is_empty()) {
|
||||
return;
|
||||
}
|
||||
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||
const VArray<bool> src_cyclic = curves.cyclic();
|
||||
|
||||
Vector<int> dst_to_src_curve;
|
||||
Vector<int> new_curve_offsets({points_by_curve.data().last()});
|
||||
Vector<IndexRange> src_ranges;
|
||||
Vector<int> dst_offsets({0});
|
||||
Vector<bool> dst_cyclic;
|
||||
dst_to_src_curve.reserve(curves.curves_num());
|
||||
new_curve_offsets.reserve(curves.curves_num() + 1);
|
||||
src_ranges.reserve(curves.curves_num());
|
||||
dst_offsets.reserve(curves.curves_num() + 1);
|
||||
dst_cyclic.reserve(curves.curves_num());
|
||||
|
||||
/* Add the duplicated curves and points. */
|
||||
bke::curves::foreach_selected_point_ranges_per_curve(
|
||||
mask,
|
||||
points_by_curve,
|
||||
[&](const int curve, const IndexRange points, Span<IndexRange> ranges_to_duplicate) {
|
||||
curve_offsets_from_selection(ranges_to_duplicate,
|
||||
points,
|
||||
curve,
|
||||
src_cyclic[curve],
|
||||
new_curve_offsets,
|
||||
dst_cyclic,
|
||||
src_ranges,
|
||||
dst_offsets,
|
||||
dst_to_src_curve);
|
||||
});
|
||||
|
||||
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();
|
||||
const int num_points_to_add = mask.size();
|
||||
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
|
||||
/* Delete selection attribute so that it will not have to be resized. */
|
||||
remove_selection_attributes(attributes);
|
||||
|
||||
curves.resize(old_points_num + num_points_to_add, old_curves_num + num_curves_to_add);
|
||||
|
||||
array_utils::copy(new_curve_offsets.as_span(),
|
||||
curves.offsets_for_write().drop_front(old_curves_num));
|
||||
|
||||
/* Transfer curve and point attributes. */
|
||||
attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
|
||||
if (iter.storage_type == bke::AttrStorageType::Single) {
|
||||
return;
|
||||
}
|
||||
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(iter.name);
|
||||
if (!attribute) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (iter.domain) {
|
||||
case bke::AttrDomain::Curve: {
|
||||
if (iter.name == "cyclic") {
|
||||
attribute.finish();
|
||||
return;
|
||||
}
|
||||
bke::attribute_math::gather(
|
||||
attribute.span,
|
||||
dst_to_src_curve,
|
||||
attribute.span.slice(IndexRange(old_curves_num, num_curves_to_add)));
|
||||
break;
|
||||
}
|
||||
case bke::AttrDomain::Point: {
|
||||
bke::attribute_math::gather_ranges_to_groups(
|
||||
src_ranges.as_span(),
|
||||
dst_offsets.as_span(),
|
||||
attribute.span,
|
||||
attribute.span.slice(IndexRange(old_points_num, num_points_to_add)));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
attribute.finish();
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
attribute.finish();
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
if (curves.nurbs_has_custom_knots()) {
|
||||
append_point_knots(src_ranges, dst_offsets.as_span(), dst_to_src_curve, curves, curves);
|
||||
}
|
||||
|
||||
for (const StringRef selection_name : get_curves_selection_attribute_names(curves)) {
|
||||
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
|
||||
selection_name, bke::AttrDomain::Point);
|
||||
selection.span.take_back(num_points_to_add).fill(true);
|
||||
selection.finish();
|
||||
}
|
||||
}
|
||||
|
||||
static void append_curve_knots(const IndexMask &mask, bke::CurvesGeometry &curves)
|
||||
{
|
||||
curves.nurbs_custom_knots_update_size();
|
||||
const int old_curves_num = curves.curves_num() - mask.size();
|
||||
bke::curves::nurbs::gather_custom_knots(curves, mask, old_curves_num, curves);
|
||||
}
|
||||
|
||||
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. */
|
||||
remove_selection_attributes(attributes);
|
||||
|
||||
/* 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.foreach_attribute([&](const bke::AttributeIter &iter) {
|
||||
if (iter.storage_type == bke::AttrStorageType::Single) {
|
||||
return;
|
||||
}
|
||||
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(iter.name);
|
||||
switch (iter.domain) {
|
||||
case bke::AttrDomain::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 bke::AttrDomain::Curve:
|
||||
array_utils::gather(attribute.span, mask, attribute.span.take_back(mask.size()));
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return;
|
||||
}
|
||||
attribute.finish();
|
||||
});
|
||||
|
||||
curves.update_curve_types();
|
||||
curves.tag_topology_changed();
|
||||
|
||||
if (curves.nurbs_has_custom_knots()) {
|
||||
append_curve_knots(mask, curves);
|
||||
}
|
||||
|
||||
for (const StringRef selection_name : get_curves_selection_attribute_names(curves)) {
|
||||
bke::SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
|
||||
selection_name, bke::AttrDomain::Curve);
|
||||
selection.span.take_back(mask.size()).fill(true);
|
||||
selection.finish();
|
||||
}
|
||||
}
|
||||
|
||||
static void invert_ranges(const IndexRange universe,
|
||||
const Span<IndexRange> ranges,
|
||||
Array<IndexRange> &inverted)
|
||||
{
|
||||
const bool contains_first = ranges.first().first() == universe.first();
|
||||
const bool contains_last = ranges.last().last() == universe.last();
|
||||
inverted.reinitialize(ranges.size() - 1 + !contains_first + !contains_last);
|
||||
|
||||
int64_t start = contains_first ? ranges.first().one_after_last() : universe.first();
|
||||
int i = 0;
|
||||
for (const IndexRange range : ranges.drop_front(contains_first)) {
|
||||
inverted[i++] = IndexRange::from_begin_end(start, range.first());
|
||||
start = range.one_after_last();
|
||||
}
|
||||
if (!contains_last) {
|
||||
inverted.last() = IndexRange::from_begin_end(start, universe.one_after_last());
|
||||
}
|
||||
}
|
||||
|
||||
static IndexRange extend_range(const IndexRange range, const IndexRange universe)
|
||||
{
|
||||
return IndexRange::from_begin_end_inclusive(math::max(range.start() - 1, universe.start()),
|
||||
math::min(range.one_after_last(), universe.last()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends each range by one point at both ends of it. Merges adjacent ranges if intersections
|
||||
* occur.
|
||||
*/
|
||||
static void extend_range_by_1_within_bounds(const IndexRange universe,
|
||||
const bool cyclic,
|
||||
const Span<IndexRange> ranges,
|
||||
Vector<IndexRange> &extended_ranges)
|
||||
{
|
||||
extended_ranges.clear();
|
||||
if (ranges.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool first_match = ranges.first().first() == universe.first();
|
||||
const bool last_match = ranges.last().last() == universe.last();
|
||||
const bool add_first = cyclic && last_match && !first_match;
|
||||
const bool add_last = cyclic && first_match && !last_match;
|
||||
|
||||
IndexRange current = add_first ? IndexRange::from_single(universe.first()) :
|
||||
extend_range(ranges.first(), universe);
|
||||
for (const IndexRange range : ranges.drop_front(!add_first)) {
|
||||
const IndexRange extended = extend_range(range, universe);
|
||||
if (extended.first() <= current.last()) {
|
||||
current = IndexRange::from_begin_end_inclusive(current.start(), extended.last());
|
||||
}
|
||||
else {
|
||||
extended_ranges.append(current);
|
||||
current = extended;
|
||||
}
|
||||
}
|
||||
extended_ranges.append(current);
|
||||
if (add_last) {
|
||||
extended_ranges.append(IndexRange::from_single(universe.last()));
|
||||
}
|
||||
}
|
||||
|
||||
static bke::CurvesGeometry copy_data_to_geometry(const bke::CurvesGeometry &src_curves,
|
||||
const Span<int> dst_to_src_curve,
|
||||
const Span<int> offsets,
|
||||
const Span<bool> cyclic,
|
||||
const Span<IndexRange> src_ranges,
|
||||
const OffsetIndices<int> dst_offsets)
|
||||
{
|
||||
bke::CurvesGeometry dst_curves(offsets.last(), dst_to_src_curve.size());
|
||||
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &src_curves.vertex_group_names);
|
||||
|
||||
if (!dst_curves.is_empty()) {
|
||||
array_utils::copy(offsets, dst_curves.offsets_for_write());
|
||||
}
|
||||
dst_curves.cyclic_for_write().copy_from(cyclic);
|
||||
|
||||
const bke::AttributeAccessor src_attributes = src_curves.attributes();
|
||||
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
|
||||
|
||||
bke::gather_attributes(src_attributes,
|
||||
bke::AttrDomain::Curve,
|
||||
bke::AttrDomain::Curve,
|
||||
bke::attribute_filter_from_skip_ref({"cyclic"}),
|
||||
dst_to_src_curve,
|
||||
dst_attributes);
|
||||
|
||||
for (auto &attribute : bke::retrieve_attributes_for_transfer(
|
||||
src_attributes,
|
||||
dst_attributes,
|
||||
{bke::AttrDomain::Point},
|
||||
bke::attribute_filter_from_skip_ref(
|
||||
ed::curves::get_curves_selection_attribute_names(src_curves))))
|
||||
{
|
||||
bke::attribute_math::gather_ranges_to_groups(
|
||||
src_ranges, dst_offsets, attribute.src, attribute.dst.span);
|
||||
attribute.dst.finish();
|
||||
};
|
||||
|
||||
dst_curves.update_curve_types();
|
||||
|
||||
if (src_curves.nurbs_has_custom_knots()) {
|
||||
append_point_knots(src_ranges, dst_offsets, dst_to_src_curve, src_curves, dst_curves);
|
||||
}
|
||||
return dst_curves;
|
||||
}
|
||||
|
||||
bke::CurvesGeometry split_points(const bke::CurvesGeometry &curves,
|
||||
const IndexMask &points_to_split)
|
||||
{
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
const VArray<bool> cyclic = curves.cyclic();
|
||||
|
||||
Vector<int> curve_map;
|
||||
Vector<int> new_offsets({0});
|
||||
|
||||
Vector<IndexRange> src_ranges;
|
||||
Vector<int> dst_offsets({0});
|
||||
Vector<bool> new_cyclic;
|
||||
|
||||
Vector<IndexRange> deselect;
|
||||
|
||||
Array<IndexRange> unselected_curve_points;
|
||||
Vector<IndexRange> curve_points_to_preserve;
|
||||
|
||||
bke::curves::foreach_selected_point_ranges_per_curve(
|
||||
points_to_split,
|
||||
points_by_curve,
|
||||
[&](const int curve, const IndexRange points, const Span<IndexRange> selected_curve_points) {
|
||||
curve_offsets_from_selection(selected_curve_points,
|
||||
points,
|
||||
curve,
|
||||
cyclic[curve],
|
||||
new_offsets,
|
||||
new_cyclic,
|
||||
src_ranges,
|
||||
dst_offsets,
|
||||
curve_map);
|
||||
/* Invert ranges to get non selected points. */
|
||||
invert_ranges(points, selected_curve_points, unselected_curve_points);
|
||||
/* Extended every range to left and right by one point. Any resulting intersection is
|
||||
* merged. */
|
||||
extend_range_by_1_within_bounds(
|
||||
points, cyclic[curve], unselected_curve_points, curve_points_to_preserve);
|
||||
const int size_before = curve_map.size();
|
||||
/* Unselected part can contain all points from original curve, but have cuts. This happens
|
||||
* when pairs of adjacent points are selected. To prevent loop merge and result curve from
|
||||
* cyclic additional condition is checked. */
|
||||
const bool can_merge_loop = !unselected_curve_points.is_empty() &&
|
||||
(unselected_curve_points.first().first() == points.first() ||
|
||||
unselected_curve_points.last().last() == points.last());
|
||||
curve_offsets_from_selection(curve_points_to_preserve,
|
||||
points,
|
||||
curve,
|
||||
cyclic[curve] && can_merge_loop,
|
||||
new_offsets,
|
||||
new_cyclic,
|
||||
src_ranges,
|
||||
dst_offsets,
|
||||
curve_map);
|
||||
deselect.append(IndexRange::from_begin_end(size_before, curve_map.size()));
|
||||
},
|
||||
[&](const IndexRange curves, const IndexRange /*unselected_points*/) {
|
||||
deselect.append(IndexRange::from_begin_size(curve_map.size(), curves.size()));
|
||||
int last_offset = new_offsets.last();
|
||||
int last_dst_offset = dst_offsets.last();
|
||||
for (const int curve : curves) {
|
||||
/* Point ranges to `src_ranges` and `dst_offsets` have to be appended curve by curve to
|
||||
* ease custom knots copying. It gives better mapping between `src_ranges` and
|
||||
* `curve_map`. */
|
||||
const IndexRange points = points_by_curve[curve];
|
||||
src_ranges.append(points);
|
||||
last_dst_offset += points.size();
|
||||
dst_offsets.append(last_dst_offset);
|
||||
|
||||
last_offset += points.size();
|
||||
new_offsets.append(last_offset);
|
||||
curve_map.append(curve);
|
||||
new_cyclic.append(cyclic[curve]);
|
||||
}
|
||||
});
|
||||
|
||||
bke::CurvesGeometry new_curves = copy_data_to_geometry(
|
||||
curves, curve_map, new_offsets, new_cyclic, src_ranges, dst_offsets.as_span());
|
||||
|
||||
const OffsetIndices<int> new_points_by_curve = new_curves.points_by_curve();
|
||||
foreach_selection_attribute_writer(
|
||||
new_curves, bke::AttrDomain::Point, [&](bke::GSpanAttributeWriter &selection) {
|
||||
for (const IndexRange curves : deselect) {
|
||||
for (const int curve : curves) {
|
||||
fill_selection_false(selection.span.slice(new_points_by_curve[curve]));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return new_curves;
|
||||
}
|
||||
|
||||
void separate_points(const bke::CurvesGeometry &curves,
|
||||
const IndexMask &points_to_separate,
|
||||
bke::CurvesGeometry &separated,
|
||||
bke::CurvesGeometry &retained)
|
||||
{
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
const VArray<bool> cyclic = curves.cyclic();
|
||||
|
||||
Vector<int> separated_curve_map;
|
||||
Vector<int> separated_offsets({0});
|
||||
Vector<IndexRange> separated_src_ranges;
|
||||
Vector<int> separated_dst_offsets({0});
|
||||
Vector<bool> separated_cyclic;
|
||||
|
||||
Vector<int> retained_curve_map;
|
||||
Vector<int> retained_offsets({0});
|
||||
Vector<IndexRange> retained_src_ranges;
|
||||
Vector<int> retained_dst_offsets({0});
|
||||
Vector<bool> retained_cyclic;
|
||||
|
||||
Array<IndexRange> unselected_curve_points;
|
||||
Vector<IndexRange> curve_points_to_retain;
|
||||
|
||||
bke::curves::foreach_selected_point_ranges_per_curve(
|
||||
points_to_separate,
|
||||
points_by_curve,
|
||||
[&](const int curve, const IndexRange points, const Span<IndexRange> selected_curve_points) {
|
||||
curve_offsets_from_selection(selected_curve_points,
|
||||
points,
|
||||
curve,
|
||||
cyclic[curve],
|
||||
separated_offsets,
|
||||
separated_cyclic,
|
||||
separated_src_ranges,
|
||||
separated_dst_offsets,
|
||||
separated_curve_map);
|
||||
/* Invert ranges to get non selected points. */
|
||||
invert_ranges(points, selected_curve_points, unselected_curve_points);
|
||||
/* Extended every range to left and right by one point. Any resulting intersection is
|
||||
* merged. */
|
||||
extend_range_by_1_within_bounds(
|
||||
points, cyclic[curve], unselected_curve_points, curve_points_to_retain);
|
||||
/* Unselected part can contain all points from original curve, but have cuts. This happens
|
||||
* when pairs of adjacent points are selected. To prevent loop merge and result curve from
|
||||
* cyclic additional condition is checked. */
|
||||
const bool can_merge_loop = !unselected_curve_points.is_empty() &&
|
||||
(unselected_curve_points.first().first() == points.first() ||
|
||||
unselected_curve_points.last().last() == points.last());
|
||||
curve_offsets_from_selection(curve_points_to_retain,
|
||||
points,
|
||||
curve,
|
||||
cyclic[curve] && can_merge_loop,
|
||||
retained_offsets,
|
||||
retained_cyclic,
|
||||
retained_src_ranges,
|
||||
retained_dst_offsets,
|
||||
retained_curve_map);
|
||||
},
|
||||
[&](const IndexRange curves, const IndexRange /*unselected_points*/) {
|
||||
int last_offset = retained_offsets.last();
|
||||
int last_dst_offset = retained_dst_offsets.last();
|
||||
for (const int curve : curves) {
|
||||
/* Point ranges to `retained_src_ranges` and `retained_dst_offsets` have to be appended
|
||||
* curve by curve to ease custom knots copying. It gives better mapping between
|
||||
* `retained_src_ranges` and `retained_curve_map`. */
|
||||
const IndexRange points = points_by_curve[curve];
|
||||
retained_src_ranges.append(points);
|
||||
last_dst_offset += points.size();
|
||||
retained_dst_offsets.append(last_dst_offset);
|
||||
|
||||
last_offset += points.size();
|
||||
retained_offsets.append(last_offset);
|
||||
retained_curve_map.append(curve);
|
||||
retained_cyclic.append(cyclic[curve]);
|
||||
}
|
||||
});
|
||||
{
|
||||
bke::MutableAttributeAccessor attributes = separated.attributes_for_write();
|
||||
remove_selection_attributes(attributes);
|
||||
|
||||
separated = copy_data_to_geometry(curves,
|
||||
separated_curve_map,
|
||||
separated_offsets,
|
||||
separated_cyclic,
|
||||
separated_src_ranges,
|
||||
separated_dst_offsets.as_span());
|
||||
}
|
||||
{
|
||||
bke::MutableAttributeAccessor attributes = retained.attributes_for_write();
|
||||
remove_selection_attributes(attributes);
|
||||
|
||||
retained = copy_data_to_geometry(curves,
|
||||
retained_curve_map,
|
||||
retained_offsets,
|
||||
retained_cyclic,
|
||||
retained_src_ranges,
|
||||
retained_dst_offsets.as_span());
|
||||
}
|
||||
|
||||
foreach_selection_attribute_writer(
|
||||
retained, bke::AttrDomain::Point, [&](bke::GSpanAttributeWriter &selection) {
|
||||
fill_selection_false(selection.span);
|
||||
});
|
||||
}
|
||||
|
||||
void add_curves(bke::CurvesGeometry &curves, const Span<int> new_sizes)
|
||||
{
|
||||
const int orig_points_num = curves.points_num();
|
||||
const int orig_curves_num = curves.curves_num();
|
||||
curves.resize(orig_points_num, orig_curves_num + new_sizes.size());
|
||||
|
||||
/* Find the final number of points by accumulating the new */
|
||||
MutableSpan<int> new_offsets = curves.offsets_for_write().drop_front(orig_curves_num);
|
||||
new_offsets.drop_back(1).copy_from(new_sizes);
|
||||
offset_indices::accumulate_counts_to_offsets(new_offsets, orig_points_num);
|
||||
/* First, resize the curve domain. */
|
||||
curves.resize(curves.offsets().last(), curves.curves_num());
|
||||
|
||||
/* Initialize new attribute values, since #CurvesGeometry::resize() doesn't do that. */
|
||||
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
|
||||
bke::fill_attribute_range_default(
|
||||
attributes, bke::AttrDomain::Point, {}, curves.points_range().drop_front(orig_points_num));
|
||||
bke::fill_attribute_range_default(
|
||||
attributes, bke::AttrDomain::Curve, {}, curves.curves_range().drop_front(orig_curves_num));
|
||||
|
||||
curves.update_curve_types();
|
||||
}
|
||||
|
||||
void resize_curves(bke::CurvesGeometry &curves,
|
||||
const IndexMask &curves_to_resize,
|
||||
const Span<int> new_sizes)
|
||||
{
|
||||
if (curves_to_resize.is_empty()) {
|
||||
return;
|
||||
}
|
||||
BLI_assert(curves_to_resize.size() == new_sizes.size());
|
||||
bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(curves);
|
||||
|
||||
IndexMaskMemory memory;
|
||||
IndexMask curves_to_copy;
|
||||
std::optional<IndexRange> range = curves_to_resize.to_range();
|
||||
/* Check if we need to copy some curves over. Write the new sizes into the offsets. */
|
||||
if (range && curves.curves_range() == *range) {
|
||||
curves_to_copy = {};
|
||||
dst_curves.offsets_for_write().drop_back(1).copy_from(new_sizes);
|
||||
}
|
||||
else {
|
||||
curves_to_copy = curves_to_resize.complement(curves.curves_range(), memory);
|
||||
offset_indices::copy_group_sizes(
|
||||
curves.offsets(), curves_to_copy, dst_curves.offsets_for_write());
|
||||
array_utils::scatter(new_sizes, curves_to_resize, dst_curves.offsets_for_write());
|
||||
}
|
||||
/* Accumulate the sizes written from `new_sizes` into offsets. */
|
||||
offset_indices::accumulate_counts_to_offsets(dst_curves.offsets_for_write());
|
||||
|
||||
/* Resize the points domain. */
|
||||
dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num());
|
||||
|
||||
/* Copy point attributes and default initialize newly added point ranges. */
|
||||
const OffsetIndices<int> src_offsets = curves.points_by_curve();
|
||||
const OffsetIndices<int> dst_offsets = dst_curves.points_by_curve();
|
||||
const bke::AttributeAccessor src_attributes = curves.attributes();
|
||||
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
|
||||
src_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
|
||||
if (iter.domain != bke::AttrDomain::Point) {
|
||||
return;
|
||||
}
|
||||
const GVArray src = *iter.get();
|
||||
const CPPType &type = src.type();
|
||||
const CommonVArrayInfo info = src.common_info();
|
||||
if (info.type == CommonVArrayInfo::Type::Single) {
|
||||
const bke::AttributeInitValue init(GPointer(type, info.data));
|
||||
if (dst_attributes.add(iter.name, iter.domain, iter.data_type, init)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
|
||||
iter.name, iter.domain, iter.data_type);
|
||||
if (!dst) {
|
||||
return;
|
||||
}
|
||||
const GVArraySpan src_span(src);
|
||||
curves_to_resize.foreach_index(
|
||||
[&](const int curve_i) {
|
||||
const IndexRange src_points = src_offsets[curve_i];
|
||||
const IndexRange dst_points = dst_offsets[curve_i];
|
||||
if (dst_points.size() < src_points.size()) {
|
||||
const int src_excees = src_points.size() - dst_points.size();
|
||||
dst.span.slice(dst_points).copy_from(src_span.slice(src_points.drop_back(src_excees)));
|
||||
}
|
||||
else {
|
||||
const int dst_excees = dst_points.size() - src_points.size();
|
||||
dst.span.slice(dst_points.drop_back(dst_excees)).copy_from(src_span.slice(src_points));
|
||||
GMutableSpan dst_end_slice = dst.span.slice(dst_points.take_back(dst_excees));
|
||||
type.value_initialize_n(dst_end_slice.data(), dst_end_slice.size());
|
||||
}
|
||||
},
|
||||
exec_mode::grain_size(512));
|
||||
array_utils::copy_group_to_group(src_offsets, dst_offsets, curves_to_copy, src_span, dst.span);
|
||||
dst.finish();
|
||||
});
|
||||
|
||||
dst_curves.update_curve_types();
|
||||
if (dst_curves.nurbs_has_custom_knots()) {
|
||||
bke::curves::nurbs::update_custom_knot_modes(
|
||||
dst_curves.curves_range(), NURBS_KNOT_MODE_NORMAL, NURBS_KNOT_MODE_NORMAL, dst_curves);
|
||||
}
|
||||
|
||||
/* Move the result into `curves`. */
|
||||
curves = std::move(dst_curves);
|
||||
curves.tag_topology_changed();
|
||||
}
|
||||
|
||||
void reorder_curves(bke::CurvesGeometry &curves, const Span<int> old_by_new_indices_map)
|
||||
{
|
||||
curves = geometry::reorder_curves_geometry(curves, old_by_new_indices_map, {});
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves
|
||||
@@ -0,0 +1,372 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_curves_utils.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
|
||||
namespace blender::ed::curves {
|
||||
|
||||
/**
|
||||
* Merges copy intervals at curve endings to minimize number of copy operations.
|
||||
* For example given in function 'extrude_curves' intervals [0, 3, 4, 4, 4] became [0, 4, 4].
|
||||
* Leading to only two copy operations.
|
||||
*/
|
||||
static Span<int> compress_intervals(const OffsetIndices<int> intervals_by_curve,
|
||||
MutableSpan<int> intervals)
|
||||
{
|
||||
const int *src = intervals.data();
|
||||
/* Skip the first curve, as all the data stays in the same place.
|
||||
* -1 to drop index denoting curve's right endpoint.
|
||||
*/
|
||||
int *dst = intervals.data() + intervals_by_curve[0].size() - 1;
|
||||
|
||||
for (const int curve : intervals_by_curve.index_range().drop_front(1)) {
|
||||
const IndexRange range = intervals_by_curve[curve];
|
||||
/* -2 one to drop index denoting curve's beginning, second one for ending. */
|
||||
const int width = range.size() - 2;
|
||||
std::copy_n(src + range.first() + 1, width, dst);
|
||||
dst += width;
|
||||
}
|
||||
(*dst) = src[intervals_by_curve[intervals_by_curve.size() - 1].last()];
|
||||
return {intervals.data(), dst - intervals.data() + 1};
|
||||
}
|
||||
|
||||
static void calc_curves_extrusion(const IndexMask &selection,
|
||||
const OffsetIndices<int> points_by_curve,
|
||||
MutableSpan<int> copy_intervals,
|
||||
MutableSpan<int> curves_intervals_offsets,
|
||||
MutableSpan<bool> is_first_selected)
|
||||
{
|
||||
int current_endpoint_index = 0;
|
||||
curves_intervals_offsets.first() = 0;
|
||||
|
||||
bke::curves::foreach_selected_point_ranges_per_curve(
|
||||
selection,
|
||||
points_by_curve,
|
||||
[&](const int curve,
|
||||
const IndexRange curve_points,
|
||||
const Span<IndexRange> selected_point_ranges) {
|
||||
const IndexRange first_range = selected_point_ranges.first();
|
||||
is_first_selected[curve] = first_range.first() == curve_points.start() &&
|
||||
first_range.size() == 1 &&
|
||||
/* If single point curve is extruded we want the newly created
|
||||
* point to get selected. */
|
||||
curve_points.size() != 1;
|
||||
current_endpoint_index += !is_first_selected[curve];
|
||||
copy_intervals[curves_intervals_offsets[curve]] = curve_points.start();
|
||||
|
||||
for (const IndexRange range : selected_point_ranges) {
|
||||
copy_intervals[current_endpoint_index++] = range.first();
|
||||
copy_intervals[current_endpoint_index++] = range.last();
|
||||
}
|
||||
|
||||
const int last_interval_index = current_endpoint_index - 1;
|
||||
if (copy_intervals[last_interval_index] != curve_points.last() ||
|
||||
copy_intervals[last_interval_index - 1] != copy_intervals[last_interval_index])
|
||||
{
|
||||
/* Append last point of the current curve if it is not extruded or extruded together with
|
||||
* preceding points. */
|
||||
copy_intervals[current_endpoint_index++] = curve_points.last();
|
||||
}
|
||||
|
||||
curves_intervals_offsets[curve + 1] = current_endpoint_index;
|
||||
},
|
||||
[&](const IndexRange curves, [[maybe_unused]] const IndexRange unselected_points) {
|
||||
for (const int curve : curves) {
|
||||
const IndexRange curve_points = points_by_curve[curve];
|
||||
/* Setup interval to copy full curve. */
|
||||
is_first_selected[curve] = false;
|
||||
copy_intervals[current_endpoint_index++] = curve_points.first();
|
||||
copy_intervals[current_endpoint_index++] = curve_points.last();
|
||||
curves_intervals_offsets[curve + 1] = current_endpoint_index;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void calc_new_offsets(const Span<int> old_offsets,
|
||||
const Span<int> curves_intervals_offsets,
|
||||
MutableSpan<int> new_offsets)
|
||||
{
|
||||
new_offsets.first() = 0;
|
||||
const IndexRange range = old_offsets.index_range().drop_back(1).shift(1);
|
||||
threading::parallel_for(range, 256, [&](IndexRange index_range) {
|
||||
for (const int i : index_range) {
|
||||
/* -1 subtracts last interval endpoint and gives number of intervals.
|
||||
* Another -1 from number of intervals gives number of new points created for curve.
|
||||
* Multiplied by i because -2 are accumulated for each curve.
|
||||
*/
|
||||
new_offsets[i] = old_offsets[i] + curves_intervals_offsets[i] - 2 * i;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new index range with the same beginning but a shifted end.
|
||||
*/
|
||||
static IndexRange shift_end_by(const IndexRange &range, const int n)
|
||||
{
|
||||
return IndexRange::from_begin_size(range.start(), range.size() + n);
|
||||
}
|
||||
|
||||
static float clamp_to_zero(const float value)
|
||||
{
|
||||
return math::abs(value) < 0.00001 ? 0.0 : value;
|
||||
}
|
||||
|
||||
static void extrude_knots(const bke::CurvesGeometry &curves,
|
||||
const OffsetIndices<int> intervals_by_curve,
|
||||
const OffsetIndices<int> copy_intervals,
|
||||
const Span<bool> is_first_selected,
|
||||
bke::CurvesGeometry &dst_curves)
|
||||
{
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask custom_knot_curves = curves.nurbs_custom_knot_curves(memory);
|
||||
const Span<float> src_knots = curves.nurbs_custom_knots();
|
||||
const VArray<int8_t> orders = curves.nurbs_orders();
|
||||
const OffsetIndices<int> src_knots_by_curve = curves.nurbs_custom_knots_by_curve();
|
||||
|
||||
dst_curves.nurbs_custom_knots_update_size();
|
||||
MutableSpan<float> dst_knots = dst_curves.nurbs_custom_knots_for_write();
|
||||
|
||||
custom_knot_curves.foreach_index(
|
||||
[&](const int64_t curve) {
|
||||
const int order = orders[curve];
|
||||
const bool is_first_interval_selected = is_first_selected[curve];
|
||||
Span<float> src_curve_knots = src_knots.slice(src_knots_by_curve[curve]);
|
||||
|
||||
Array<float> curve_span_data(src_curve_knots.size() - 1);
|
||||
Array<int> span_multiplicity(curve_span_data.size(), 0);
|
||||
|
||||
int span = 0;
|
||||
curve_span_data[span] = clamp_to_zero(src_curve_knots[1] - src_curve_knots[0]);
|
||||
span_multiplicity[span] = 1;
|
||||
|
||||
for (const int i : src_curve_knots.index_range().drop_back(1).drop_front(1)) {
|
||||
const float span_value = clamp_to_zero(src_curve_knots[i + 1] - src_curve_knots[i]);
|
||||
const bool is_new = abs(curve_span_data[span] - span_value) >= 0.00001;
|
||||
span += is_new;
|
||||
curve_span_data[span] = span_value;
|
||||
span_multiplicity[span]++;
|
||||
}
|
||||
|
||||
MutableSpan<float> curve_spans = curve_span_data.as_mutable_span().slice(0, span + 1);
|
||||
|
||||
const IndexRange curve_intervals = intervals_by_curve[curve];
|
||||
const Span<int> duplicated_points =
|
||||
copy_intervals.data().slice(curve_intervals).drop_front(1).drop_back(1);
|
||||
const int first_curve_point = copy_intervals.data()[curve_intervals.first()];
|
||||
Vector<int> increase_span_multiplicity;
|
||||
increase_span_multiplicity.reserve(duplicated_points.size());
|
||||
int first_span_knot = 0;
|
||||
span = 0;
|
||||
|
||||
for (const int i : duplicated_points.index_range()) {
|
||||
const bool is_selected = bool(i % 2) != is_first_interval_selected;
|
||||
const int point = duplicated_points[i] - first_curve_point;
|
||||
while (first_span_knot + span_multiplicity[span] <= point) {
|
||||
first_span_knot += span_multiplicity[span];
|
||||
span++;
|
||||
}
|
||||
|
||||
int multiplicity = point - first_span_knot;
|
||||
int point_span = span;
|
||||
std::array<int, 2> side_spans{point_span, point_span};
|
||||
int side = 0;
|
||||
for ([[maybe_unused]] const int i : IndexRange(order)) {
|
||||
multiplicity++;
|
||||
if (multiplicity > span_multiplicity[point_span]) {
|
||||
point_span++;
|
||||
multiplicity = 1;
|
||||
}
|
||||
if (curve_spans[point_span] == 0.0) {
|
||||
continue;
|
||||
}
|
||||
side_spans[side] = point_span;
|
||||
side = 1;
|
||||
side_spans[side] = point_span;
|
||||
}
|
||||
increase_span_multiplicity.append(side_spans[is_selected]);
|
||||
}
|
||||
for (const int span : increase_span_multiplicity) {
|
||||
span_multiplicity[span]++;
|
||||
}
|
||||
|
||||
const OffsetIndices<int> dst_knots_by_curve = dst_curves.nurbs_custom_knots_by_curve();
|
||||
MutableSpan<float> dst_curve_knots = dst_knots.slice(dst_knots_by_curve[curve]);
|
||||
int knot = 0;
|
||||
float knot_value = src_curve_knots[knot];
|
||||
dst_curve_knots[knot++] = knot_value;
|
||||
for (const int span : curve_spans.index_range()) {
|
||||
for ([[maybe_unused]] const int k : IndexRange(span_multiplicity[span])) {
|
||||
knot_value += curve_spans[span];
|
||||
dst_curve_knots[knot++] = knot_value;
|
||||
}
|
||||
}
|
||||
},
|
||||
exec_mode::grain_size(64));
|
||||
}
|
||||
|
||||
static bke::CurvesGeometry extrude_curves(const bke::CurvesGeometry &curves,
|
||||
const IndexMask &extruded_points)
|
||||
{
|
||||
|
||||
bke::CurvesGeometry new_curves = bke::curves::copy_only_curve_domain(curves);
|
||||
|
||||
const int curves_num = curves.curves_num();
|
||||
|
||||
/* 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> copy_interval_offsets(extruded_points.size() * 2 + curves_num * 2);
|
||||
|
||||
/* Points to intervals for each curve in the copy_intervals array.
|
||||
* For example above value would be [0, 3, 5]. Meaning that [0 .. 2] are indices for curve 0 in
|
||||
* copy_intervals array, [3 .. 4] for curve 1. */
|
||||
Array<int> curves_intervals_offsets(curves_num + 1);
|
||||
|
||||
/* Per curve boolean indicating if first interval in a curve is selected.
|
||||
* Other can be calculated as in a curve two adjacent intervals can not have same selection
|
||||
* state. */
|
||||
Array<bool> is_first_selected(curves_num);
|
||||
|
||||
calc_curves_extrusion(extruded_points,
|
||||
curves.points_by_curve(),
|
||||
copy_interval_offsets,
|
||||
curves_intervals_offsets,
|
||||
is_first_selected);
|
||||
|
||||
MutableSpan<int> new_offsets = new_curves.offsets_for_write();
|
||||
calc_new_offsets(curves.offsets(), curves_intervals_offsets, new_offsets);
|
||||
new_curves.resize(new_offsets.last(), new_curves.curves_num());
|
||||
|
||||
const bke::AttributeAccessor src_attributes = curves.attributes();
|
||||
|
||||
std::array<GVArraySpan, 3> src_selection;
|
||||
std::array<bke::GSpanAttributeWriter, 3> dst_selections;
|
||||
|
||||
const Span<StringRef> selection_attr_names = get_curves_selection_attribute_names(curves);
|
||||
for (const int selection_i : selection_attr_names.index_range()) {
|
||||
const StringRef selection_name = selection_attr_names[selection_i];
|
||||
|
||||
GVArray src_selection_array = *src_attributes.lookup(selection_name, bke::AttrDomain::Point);
|
||||
if (!src_selection_array) {
|
||||
src_selection_array = VArray<bool>::from_single(true, curves.points_num());
|
||||
}
|
||||
|
||||
src_selection[selection_i] = src_selection_array;
|
||||
dst_selections[selection_i] = ensure_selection_attribute(
|
||||
new_curves,
|
||||
bke::AttrDomain::Point,
|
||||
src_selection_array.type().is<bool>() ? bke::AttrType::Bool : bke::AttrType::Float,
|
||||
selection_name);
|
||||
}
|
||||
|
||||
const OffsetIndices<int> intervals_by_curve = curves_intervals_offsets.as_span();
|
||||
const OffsetIndices<int> copy_intervals = copy_interval_offsets.as_span().slice(
|
||||
0, curves_intervals_offsets.last());
|
||||
|
||||
threading::parallel_for(curves.curves_range(), 256, [&](IndexRange curves_range) {
|
||||
for (const int curve : curves_range) {
|
||||
const int first_index = intervals_by_curve[curve].start();
|
||||
const int first_value = copy_intervals[first_index].start();
|
||||
const bool first_selected = is_first_selected[curve];
|
||||
|
||||
for (const int i : intervals_by_curve[curve].drop_back(1)) {
|
||||
const bool is_selected = bool((i - first_index) % 2) != first_selected;
|
||||
const IndexRange src = shift_end_by(copy_intervals[i], 1);
|
||||
const IndexRange dst = src.shift(new_offsets[curve] - first_value + i - first_index);
|
||||
|
||||
for (const int selection_i : selection_attr_names.index_range()) {
|
||||
GMutableSpan dst_span = dst_selections[selection_i].span.slice(dst);
|
||||
if (is_selected) {
|
||||
GSpan src_span = src_selection[selection_i].slice(src);
|
||||
src_selection[selection_i].type().copy_assign_n(
|
||||
src_span.data(), dst_span.data(), src.size());
|
||||
}
|
||||
else {
|
||||
fill_selection(dst_span, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (const int selection_i : selection_attr_names.index_range()) {
|
||||
dst_selections[selection_i].finish();
|
||||
}
|
||||
|
||||
if (curves.nurbs_has_custom_knots()) {
|
||||
extrude_knots(curves, intervals_by_curve, copy_intervals, is_first_selected, new_curves);
|
||||
}
|
||||
|
||||
const OffsetIndices<int> compact_intervals = compress_intervals(intervals_by_curve,
|
||||
copy_interval_offsets);
|
||||
|
||||
bke::MutableAttributeAccessor dst_attributes = new_curves.attributes_for_write();
|
||||
|
||||
for (auto &attribute : bke::retrieve_attributes_for_transfer(
|
||||
src_attributes,
|
||||
dst_attributes,
|
||||
{bke::AttrDomain::Point},
|
||||
bke::attribute_filter_from_skip_ref(selection_attr_names)))
|
||||
{
|
||||
const CPPType &type = attribute.src.type();
|
||||
threading::parallel_for(compact_intervals.index_range(), 512, [&](IndexRange range) {
|
||||
for (const int i : range) {
|
||||
const IndexRange src = shift_end_by(compact_intervals[i], 1);
|
||||
const IndexRange dst = src.shift(i);
|
||||
type.copy_assign_n(
|
||||
attribute.src.slice(src).data(), attribute.dst.span.slice(dst).data(), src.size());
|
||||
}
|
||||
});
|
||||
attribute.dst.finish();
|
||||
}
|
||||
return new_curves;
|
||||
}
|
||||
|
||||
static wmOperatorStatus curves_extrude_exec(bContext *C, wmOperator * /*op*/)
|
||||
{
|
||||
bool extruded = false;
|
||||
for (Curves *curves_id : get_unique_editable_curves(*C)) {
|
||||
const bke::AttrDomain selection_domain = bke::AttrDomain(curves_id->selection_domain);
|
||||
if (selection_domain != bke::AttrDomain::Point) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask extruded_points = retrieve_selected_points(curves, memory);
|
||||
if (extruded_points.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
curves_id->geometry.wrap() = extrude_curves(curves, extruded_points);
|
||||
DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
|
||||
extruded = true;
|
||||
}
|
||||
return extruded ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -0,0 +1,48 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup edcurves
|
||||
*/
|
||||
|
||||
#include "BLI_offset_indices.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
|
||||
namespace blender::ed::curves {
|
||||
|
||||
IndexMask end_points(const bke::CurvesGeometry &curves,
|
||||
const IndexMask &curves_mask,
|
||||
const int amount_start,
|
||||
const int amount_end,
|
||||
const bool inverted,
|
||||
IndexMaskMemory &memory)
|
||||
{
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
|
||||
Array<bool> end_points(curves.points_num(), inverted ? false : true);
|
||||
curves_mask.foreach_index(
|
||||
[&](const int64_t curve_i) {
|
||||
end_points.as_mutable_span()
|
||||
.slice(points_by_curve[curve_i].drop_front(amount_start).drop_back(amount_end))
|
||||
.fill(inverted ? true : false);
|
||||
},
|
||||
exec_mode::grain_size(512));
|
||||
|
||||
return IndexMask::from_bools(end_points, memory);
|
||||
}
|
||||
|
||||
IndexMask end_points(const bke::CurvesGeometry &curves,
|
||||
const int amount_start,
|
||||
const int amount_end,
|
||||
const bool inverted,
|
||||
IndexMaskMemory &memory)
|
||||
{
|
||||
return end_points(curves, curves.curves_range(), amount_start, amount_end, inverted, memory);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves
|
||||
1914
blender-5.2.0/source/blender/editors/curves/intern/curves_ops.cc
Normal file
1914
blender-5.2.0/source/blender/editors/curves/intern/curves_ops.cc
Normal file
File diff suppressed because it is too large
Load Diff
1536
blender-5.2.0/source/blender/editors/curves/intern/curves_pen.cc
Normal file
1536
blender-5.2.0/source/blender/editors/curves/intern/curves_pen.cc
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,153 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup edcurves
|
||||
*/
|
||||
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "BKE_undo_system.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
#include "ED_undo.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"undo.curves"};
|
||||
|
||||
namespace ed::curves {
|
||||
namespace undo {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Implements ED Undo System
|
||||
*
|
||||
* \note This is similar for all edit-mode types.
|
||||
* \{ */
|
||||
|
||||
struct StepObject {
|
||||
UndoRefID_Object obedit_ref = {};
|
||||
bke::CurvesGeometry geometry = {};
|
||||
};
|
||||
|
||||
struct CurvesUndoStep {
|
||||
UndoStep step;
|
||||
/** See #ED_undo_object_editmode_validate_scene_from_windows code comment for details. */
|
||||
UndoRefID_Scene scene_ref = {};
|
||||
Array<StepObject> objects;
|
||||
};
|
||||
|
||||
static bool step_encode(bContext *C, Main *bmain, UndoStep *us_p)
|
||||
{
|
||||
CurvesUndoStep *us = reinterpret_cast<CurvesUndoStep *>(us_p);
|
||||
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
Vector<Object *> objects = ED_undo_editmode_objects_from_view_layer(*bmain, scene, view_layer);
|
||||
|
||||
us->scene_ref.ptr = scene;
|
||||
new (&us->objects) Array<StepObject>(objects.size());
|
||||
|
||||
threading::parallel_for(us->objects.index_range(), 8, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
Object *ob = objects[i];
|
||||
const Curves &curves_id = *id_cast<Curves *>(ob->data);
|
||||
StepObject &object = us->objects[i];
|
||||
|
||||
object.obedit_ref.ptr = ob;
|
||||
object.geometry = curves_id.geometry.wrap();
|
||||
}
|
||||
});
|
||||
|
||||
bmain->is_memfile_undo_flush_needed = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void step_decode(
|
||||
bContext *C, Main *bmain, UndoStep *us_p, const eUndoStepDir /*dir*/, bool /*is_final*/)
|
||||
{
|
||||
CurvesUndoStep *us = reinterpret_cast<CurvesUndoStep *>(us_p);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
|
||||
ED_undo_object_editmode_validate_scene_from_windows(
|
||||
CTX_wm_manager(C), us->scene_ref.ptr, &scene, &view_layer);
|
||||
ED_undo_object_editmode_restore_helper(scene,
|
||||
view_layer,
|
||||
&us->objects.first().obedit_ref.ptr,
|
||||
us->objects.size(),
|
||||
sizeof(decltype(us->objects)::value_type));
|
||||
|
||||
BLI_assert(BKE_object_is_in_editmode(us->objects.first().obedit_ref.ptr));
|
||||
|
||||
for (const StepObject &object : us->objects) {
|
||||
Curves &curves_id = *id_cast<Curves *>(object.obedit_ref.ptr->data);
|
||||
|
||||
/* Overwrite the curves geometry. */
|
||||
curves_id.geometry.wrap() = object.geometry;
|
||||
|
||||
DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
|
||||
}
|
||||
|
||||
ED_undo_object_set_active_or_warn(
|
||||
*bmain, scene, view_layer, us->objects.first().obedit_ref.ptr, us_p->name, &LOG);
|
||||
|
||||
bmain->is_memfile_undo_flush_needed = true;
|
||||
|
||||
WM_event_add_notifier(C, NC_GEOM | ND_DATA, nullptr);
|
||||
}
|
||||
|
||||
static void step_free(UndoStep *us_p)
|
||||
{
|
||||
CurvesUndoStep *us = reinterpret_cast<CurvesUndoStep *>(us_p);
|
||||
us->objects.~Array();
|
||||
}
|
||||
|
||||
static void foreach_ID_ref(UndoStep *us_p,
|
||||
UndoTypeForEachIDRefFn foreach_ID_ref_fn,
|
||||
void *user_data)
|
||||
{
|
||||
CurvesUndoStep *us = reinterpret_cast<CurvesUndoStep *>(us_p);
|
||||
|
||||
foreach_ID_ref_fn(user_data, (reinterpret_cast<UndoRefID *>(&us->scene_ref)));
|
||||
for (const StepObject &object : us->objects) {
|
||||
foreach_ID_ref_fn(
|
||||
user_data,
|
||||
(reinterpret_cast<UndoRefID *>(const_cast<UndoRefID_Object *>(&object.obedit_ref))));
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace undo
|
||||
|
||||
void undosys_type_register(UndoType *ut)
|
||||
{
|
||||
ut->name = "Edit Curves";
|
||||
ut->poll = editable_curves_in_edit_mode_poll;
|
||||
ut->step_encode = undo::step_encode;
|
||||
ut->step_decode = undo::step_decode;
|
||||
ut->step_free = undo::step_free;
|
||||
|
||||
ut->step_foreach_ID_ref = undo::foreach_ID_ref;
|
||||
|
||||
ut->flags = UNDOTYPE_FLAG_NEED_CONTEXT_FOR_ENCODE;
|
||||
|
||||
ut->step_size = sizeof(undo::CurvesUndoStep);
|
||||
}
|
||||
|
||||
} // namespace ed::curves
|
||||
} // namespace blender
|
||||
94
blender-5.2.0/source/blender/editors/curves/intern/join.cc
Normal file
94
blender-5.2.0/source/blender/editors/curves/intern/join.cc
Normal file
@@ -0,0 +1,94 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_instances.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_build.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
#include "ED_object.hh"
|
||||
|
||||
#include "GEO_realize_instances.hh"
|
||||
|
||||
namespace blender::ed::curves {
|
||||
|
||||
wmOperatorStatus join_objects_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
Object *active_object = CTX_data_active_object(C);
|
||||
BLI_assert(active_object);
|
||||
BLI_assert(active_object->type == OB_CURVES);
|
||||
Curves &active_curves = *id_cast<Curves *>(active_object->data);
|
||||
const float4x4 &world_to_active = active_object->world_to_object();
|
||||
|
||||
Vector<Object *> objects{active_object};
|
||||
bool active_object_selected = false;
|
||||
CTX_DATA_BEGIN (C, Object *, object, selected_editable_objects) {
|
||||
if (object == active_object) {
|
||||
active_object_selected = true;
|
||||
continue;
|
||||
}
|
||||
if (object->type != OB_CURVES) {
|
||||
continue;
|
||||
}
|
||||
objects.append(object);
|
||||
}
|
||||
CTX_DATA_END;
|
||||
|
||||
if (!active_object_selected) {
|
||||
BKE_report(op->reports, RPT_WARNING, "Active object is not a selected curves object");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
bke::Instances instances;
|
||||
instances.resize(objects.size());
|
||||
MutableSpan<float4x4> transforms = instances.transforms_for_write();
|
||||
MutableSpan<int> references = instances.reference_handles_for_write();
|
||||
Map<const Curves *, int> reference_by_orig_curves;
|
||||
for (const int i : objects.index_range()) {
|
||||
transforms[i] = world_to_active * objects[i]->object_to_world();
|
||||
const Curves *orig_curves = id_cast<const Curves *>(objects[i]->data);
|
||||
references[i] = reference_by_orig_curves.lookup_or_add_cb(orig_curves, [&]() {
|
||||
auto geometry = bke::GeometrySet::from_curves(BKE_curves_copy_for_eval(orig_curves));
|
||||
return instances.add_new_reference(std::move(geometry));
|
||||
});
|
||||
}
|
||||
|
||||
bke::GeometrySet realized_geometry = geometry::realize_instances(
|
||||
bke::GeometrySet::from_instances(
|
||||
&instances, bke::GeometryOwnershipType::ReadOnly),
|
||||
geometry::RealizeInstancesOptions())
|
||||
.geometry;
|
||||
|
||||
if (!realized_geometry.has_curves()) {
|
||||
BKE_report(op->reports, RPT_WARNING, "No curves data to join");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
Curves *realized_curves = realized_geometry.get_curves_for_write();
|
||||
active_curves.geometry.wrap() = std::move(realized_curves->geometry.wrap());
|
||||
|
||||
for (Object *object : objects.as_span().drop_front(1)) {
|
||||
object::base_free_and_unlink(bmain, scene, object);
|
||||
}
|
||||
|
||||
DEG_relations_tag_update(bmain);
|
||||
DEG_id_tag_update(&active_object->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
|
||||
DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
|
||||
WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene);
|
||||
WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves
|
||||
@@ -0,0 +1,145 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_layer.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_define.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
#include "ED_view3d.hh"
|
||||
|
||||
namespace blender::ed::curves {
|
||||
|
||||
struct ClosestCurveDataBlock {
|
||||
Curves *curves_id = nullptr;
|
||||
FindClosestData elem;
|
||||
};
|
||||
static ClosestCurveDataBlock find_closest_curve(const Depsgraph &depsgraph,
|
||||
const ViewContext &vc,
|
||||
const Span<Base *> bases,
|
||||
const int2 &mval)
|
||||
{
|
||||
return threading::parallel_reduce(
|
||||
bases.index_range(),
|
||||
1L,
|
||||
ClosestCurveDataBlock(),
|
||||
[&](const IndexRange range, const ClosestCurveDataBlock &init) {
|
||||
ClosestCurveDataBlock new_closest = init;
|
||||
for (Base *base : bases.slice(range)) {
|
||||
Object &curves_ob = *base->object;
|
||||
Curves &curves_id = *id_cast<Curves *>(curves_ob.data);
|
||||
bke::crazyspace::GeometryDeformation deformation =
|
||||
bke::crazyspace::get_evaluated_curves_deformation(depsgraph, curves_ob);
|
||||
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
|
||||
const float4x4 projection = ED_view3d_ob_project_mat_get(vc.rv3d, &curves_ob);
|
||||
foreach_selectable_curve_range(
|
||||
curves,
|
||||
deformation,
|
||||
eHandleDisplay(vc.v3d->overlay.handle_display),
|
||||
[&](IndexRange range, Span<float3> positions, StringRef /*selection_name*/) {
|
||||
std::optional<FindClosestData> new_closest_elem = closest_elem_find_screen_space(
|
||||
vc,
|
||||
curves.points_by_curve(),
|
||||
positions,
|
||||
curves.cyclic(),
|
||||
projection,
|
||||
range,
|
||||
bke::AttrDomain::Curve,
|
||||
mval,
|
||||
new_closest.elem);
|
||||
if (new_closest_elem) {
|
||||
new_closest.elem = *new_closest_elem;
|
||||
new_closest.curves_id = &curves_id;
|
||||
}
|
||||
});
|
||||
}
|
||||
return new_closest;
|
||||
},
|
||||
[](const ClosestCurveDataBlock &a, const ClosestCurveDataBlock &b) {
|
||||
return (a.elem.distance_sq < b.elem.distance_sq) ? a : b;
|
||||
});
|
||||
}
|
||||
|
||||
static bool select_linked_pick(bContext &C, const int2 &mval, const SelectPick_Params ¶ms)
|
||||
{
|
||||
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(&C);
|
||||
const ViewContext vc = ED_view3d_viewcontext_init(&C, depsgraph);
|
||||
const Vector<Base *> bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data(
|
||||
*vc.bmain, vc.scene, vc.view_layer, vc.v3d);
|
||||
|
||||
const ClosestCurveDataBlock closest = find_closest_curve(*depsgraph, vc, bases, mval);
|
||||
if (!closest.curves_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bke::CurvesGeometry &closest_curves = closest.curves_id->geometry.wrap();
|
||||
const bke::AttrDomain selection_domain = bke::AttrDomain(closest.curves_id->selection_domain);
|
||||
|
||||
if (selection_domain == bke::AttrDomain::Point) {
|
||||
const OffsetIndices points_by_curve = closest_curves.points_by_curve();
|
||||
foreach_selection_attribute_writer(
|
||||
closest_curves, bke::AttrDomain::Point, [&](bke::GSpanAttributeWriter &selection) {
|
||||
for (const int point : points_by_curve[closest.elem.index]) {
|
||||
apply_selection_operation_at_index(selection.span, point, params.sel_op);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (selection_domain == bke::AttrDomain::Curve) {
|
||||
bke::GSpanAttributeWriter selection = ensure_selection_attribute(
|
||||
closest_curves, bke::AttrDomain::Curve, bke::AttrType::Bool);
|
||||
apply_selection_operation_at_index(selection.span, closest.elem.index, params.sel_op);
|
||||
selection.finish();
|
||||
}
|
||||
|
||||
/* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a
|
||||
* generic attribute for now. */
|
||||
DEG_id_tag_update(&closest.curves_id->id, ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(&C, NC_GEOM | ND_DATA, closest.curves_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static wmOperatorStatus select_linked_pick_invoke(bContext *C,
|
||||
wmOperator *op,
|
||||
const wmEvent *event)
|
||||
{
|
||||
SelectPick_Params params{};
|
||||
params.sel_op = RNA_boolean_get(op->ptr, "deselect") ? SEL_OP_SUB : SEL_OP_ADD;
|
||||
params.deselect_all = false;
|
||||
params.select_passthrough = false;
|
||||
|
||||
if (!select_linked_pick(*C, event->mval, params)) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void CURVES_OT_select_linked_pick(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Select Linked";
|
||||
ot->idname = "CURVES_OT_select_linked_pick";
|
||||
ot->description = "Select all points in the curve under the cursor";
|
||||
|
||||
ot->invoke = select_linked_pick_invoke;
|
||||
ot->poll = editable_curves_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
RNA_def_boolean(ot->srna,
|
||||
"deselect",
|
||||
false,
|
||||
"Deselect",
|
||||
"Deselect linked control points rather than selecting them");
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves
|
||||
135
blender-5.2.0/source/blender/editors/curves/intern/separate.cc
Normal file
135
blender-5.2.0/source/blender/editors/curves/intern/separate.cc
Normal file
@@ -0,0 +1,135 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_index_mask.hh"
|
||||
#include "BLI_task.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_layer.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
#include "ED_object.hh"
|
||||
|
||||
#include "DNA_layer_types.h"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_build.hh"
|
||||
|
||||
#include "GEO_curves_remove_and_split.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
|
||||
namespace blender::ed::curves {
|
||||
|
||||
static wmOperatorStatus separate_exec(bContext *C, wmOperator * /*op*/)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
ViewLayer *view_layer = CTX_data_view_layer(C);
|
||||
|
||||
Vector<Base *> bases = BKE_view_layer_array_from_bases_in_edit_mode(
|
||||
*bmain, scene, view_layer, CTX_wm_view3d(C));
|
||||
|
||||
VectorSet<Curves *> src_curves;
|
||||
for (Base *base_src : bases) {
|
||||
src_curves.add(id_cast<Curves *>(base_src->object->data));
|
||||
}
|
||||
|
||||
/* Modify new curves and generate new curves in parallel. */
|
||||
Array<std::optional<bke::CurvesGeometry>> dst_geometry(src_curves.size());
|
||||
threading::parallel_for(dst_geometry.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
Curves &src = *src_curves[i];
|
||||
IndexMaskMemory memory;
|
||||
switch (bke::AttrDomain(src.selection_domain)) {
|
||||
case bke::AttrDomain::Point: {
|
||||
const IndexMask selection = retrieve_selected_points(src, memory);
|
||||
if (selection.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
bke::CurvesGeometry separated;
|
||||
bke::CurvesGeometry retained;
|
||||
separate_points(src.geometry.wrap(), selection, separated, retained);
|
||||
|
||||
separated.calculate_bezier_auto_handles();
|
||||
retained.calculate_bezier_auto_handles();
|
||||
|
||||
dst_geometry[i] = std::move(separated);
|
||||
src.geometry.wrap() = std::move(retained);
|
||||
break;
|
||||
}
|
||||
case bke::AttrDomain::Curve: {
|
||||
const IndexMask selection = retrieve_selected_curves(src, memory);
|
||||
if (selection.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
dst_geometry[i] = bke::curves_copy_curve_selection(src.geometry.wrap(), selection, {});
|
||||
src.geometry.wrap().remove_curves(selection, {});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Move new curves into main data-base. */
|
||||
Array<Curves *> dst_curves(src_curves.size(), nullptr);
|
||||
for (const int i : dst_curves.index_range()) {
|
||||
if (std::optional<bke::CurvesGeometry> &dst = dst_geometry[i]) {
|
||||
dst_curves[i] = BKE_curves_add(bmain, BKE_id_name(src_curves[i]->id));
|
||||
dst_curves[i]->geometry.wrap() = std::move(*dst);
|
||||
bke::curves_copy_parameters(*src_curves[i], *dst_curves[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Skip processing objects with no selected elements. */
|
||||
bases.remove_if([&](Base *base) {
|
||||
Curves *curves = id_cast<Curves *>(base->object->data);
|
||||
return dst_curves[src_curves.index_of(curves)] == nullptr;
|
||||
});
|
||||
|
||||
if (bases.is_empty()) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
/* Add new objects for the new curves. */
|
||||
for (Base *base_src : bases) {
|
||||
Curves *src = id_cast<Curves *>(base_src->object->data);
|
||||
Curves *dst = dst_curves[src_curves.index_of(src)];
|
||||
|
||||
Base *base_dst = object::add_duplicate(
|
||||
bmain, scene, view_layer, base_src, eDupli_ID_Flags(U.dupflag) & USER_DUP_ACT);
|
||||
Object *object_dst = base_dst->object;
|
||||
object_dst->mode = OB_MODE_OBJECT;
|
||||
object_dst->data = id_cast<ID *>(dst);
|
||||
|
||||
DEG_id_tag_update(&src->id, ID_RECALC_GEOMETRY);
|
||||
DEG_id_tag_update(&dst->id, ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, base_src->object);
|
||||
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, object_dst);
|
||||
}
|
||||
|
||||
DEG_relations_tag_update(bmain);
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void CURVES_OT_separate(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Separate";
|
||||
ot->idname = "CURVES_OT_separate";
|
||||
ot->description = "Separate selected geometry into a new object";
|
||||
|
||||
ot->exec = separate_exec;
|
||||
ot->poll = editable_curves_in_edit_mode_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves
|
||||
@@ -0,0 +1,355 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include "ED_curves.hh"
|
||||
|
||||
#include "BKE_gtest_base.hh"
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
namespace blender::ed::curves::tests {
|
||||
|
||||
static bke::CurvesGeometry create_curves(const Span<Vector<float3>> all_positions,
|
||||
const int order,
|
||||
const Set<int> &is_cyclic)
|
||||
{
|
||||
Array<int> offsets(all_positions.size() + 1, 0);
|
||||
for (const int curve : all_positions.index_range()) {
|
||||
const Vector<float3> &curve_positions = all_positions[curve];
|
||||
offsets[curve + 1] = offsets[curve] + curve_positions.size();
|
||||
}
|
||||
|
||||
bke::CurvesGeometry curves(offsets.last(), all_positions.size());
|
||||
|
||||
curves.offsets_for_write().copy_from(offsets);
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
MutableSpan<float3> positions = curves.positions_for_write();
|
||||
MutableSpan<bool> cyclic = curves.cyclic_for_write();
|
||||
MutableSpan<int8_t> orders = curves.nurbs_orders_for_write();
|
||||
|
||||
for (const int curve : all_positions.index_range()) {
|
||||
positions.slice(points_by_curve[curve]).copy_from(all_positions[curve]);
|
||||
cyclic[curve] = is_cyclic.contains(curve);
|
||||
orders[curve] = order;
|
||||
}
|
||||
|
||||
curves.tag_topology_changed();
|
||||
return curves;
|
||||
}
|
||||
|
||||
static bke::CurvesGeometry create_curves(const Vector<float3> positions,
|
||||
const int order,
|
||||
const Set<int> &is_cyclic)
|
||||
{
|
||||
return create_curves(Span<Vector<float3>>(&positions, 1), order, is_cyclic);
|
||||
}
|
||||
|
||||
static void validate_positions(const Span<Vector<float3>> expected_positions,
|
||||
const OffsetIndices<int> points_by_curve,
|
||||
const Span<float3> positions)
|
||||
{
|
||||
for (const int curve : expected_positions.index_range()) {
|
||||
const Span<float3> expected_curve_positions = expected_positions[curve];
|
||||
const IndexRange points = points_by_curve[curve];
|
||||
for (const int point : expected_curve_positions.index_range()) {
|
||||
EXPECT_EQ(positions[points[point]], expected_curve_positions[point]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CurvesEditorsTest : public bke::BlenderGTestBase {};
|
||||
|
||||
TEST_F(CurvesEditorsTest, DuplicatePointsTwoSingle)
|
||||
{
|
||||
/* Two points from single curve. */
|
||||
const Vector<float3> expected_positions = {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}};
|
||||
|
||||
bke::CurvesGeometry curves = create_curves(expected_positions, 4, {});
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices(Array<int>{1, 2}.as_span(), memory);
|
||||
|
||||
duplicate_points(curves, mask);
|
||||
|
||||
EXPECT_TRUE(curves.curves_num() == 2);
|
||||
|
||||
const Span<float3> positions = curves.positions();
|
||||
|
||||
for (const int point : expected_positions.index_range()) {
|
||||
EXPECT_TRUE(positions[point] == expected_positions[point]);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(positions[4] == expected_positions[1]);
|
||||
EXPECT_TRUE(positions[5] == expected_positions[2]);
|
||||
}
|
||||
|
||||
TEST_F(CurvesEditorsTest, DuplicatePointsFourThree)
|
||||
{
|
||||
/* Four points from three curves. One curve has one point. */
|
||||
const Vector<Vector<float3>> expected_positions = {
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
|
||||
{{0, 0, 0}},
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
bke::CurvesGeometry curves = create_curves(expected_positions, 4, {});
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices(Array<int>{0, 1, 4, 9}.as_span(), memory);
|
||||
|
||||
duplicate_points(curves, mask);
|
||||
|
||||
EXPECT_TRUE(curves.curves_num() == expected_positions.size() + 3);
|
||||
|
||||
const Span<float3> positions = curves.positions();
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
|
||||
for (const int curve : expected_positions.index_range()) {
|
||||
const Span<float3> expected_curve_positions = expected_positions[curve];
|
||||
const IndexRange points = points_by_curve[curve];
|
||||
for (const int point : expected_curve_positions.index_range()) {
|
||||
EXPECT_TRUE(positions[points[point]] == expected_curve_positions[point]);
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(positions[10] == expected_positions[0][0]);
|
||||
EXPECT_TRUE(positions[11] == expected_positions[0][1]);
|
||||
EXPECT_TRUE(positions[12] == expected_positions[1][0]);
|
||||
EXPECT_TRUE(positions[13] == expected_positions[2][4]);
|
||||
}
|
||||
|
||||
TEST_F(CurvesEditorsTest, DuplicatePointsTwoCyclic)
|
||||
{
|
||||
/* Two points from cyclic curve. Points are on cycle. */
|
||||
const Vector<Vector<float3>> expected_positions = {
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
|
||||
{{0, 0, 0}},
|
||||
{{1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {-1, 1, 0}},
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
bke::CurvesGeometry curves = create_curves(expected_positions, 4, {2});
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices(Array<int>{5, 8}.as_span(), memory);
|
||||
|
||||
duplicate_points(curves, mask);
|
||||
|
||||
EXPECT_TRUE(curves.curves_num() == expected_positions.size() + 1);
|
||||
|
||||
const Span<float3> positions = curves.positions();
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
|
||||
for (const int curve : expected_positions.index_range()) {
|
||||
const Span<float3> expected_curve_positions = expected_positions[curve];
|
||||
const IndexRange points = points_by_curve[curve];
|
||||
for (const int point : expected_curve_positions.index_range()) {
|
||||
EXPECT_TRUE(positions[points[point]] == expected_curve_positions[point]);
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(positions[14] == expected_positions[2][3]);
|
||||
EXPECT_TRUE(positions[15] == expected_positions[2][0]);
|
||||
}
|
||||
|
||||
TEST_F(CurvesEditorsTest, SplitPointsTwoSingle)
|
||||
{
|
||||
/* Split two points from single curve. */
|
||||
const Vector<float3> positions = {{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}};
|
||||
|
||||
bke::CurvesGeometry curves = create_curves(positions, 4, {});
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices<int>({1, 2}, memory);
|
||||
|
||||
bke::CurvesGeometry new_curves = split_points(curves, mask);
|
||||
|
||||
const Vector<Vector<float3>> expected_positions = {
|
||||
{{-1, 1, 0}, {1, 1, 0}}, {{-1.5, 0, 0}, {-1, 1, 0}}, {{1, 1, 0}, {1.5, 0, 0}}};
|
||||
|
||||
GTEST_ASSERT_EQ(new_curves.curves_num(), expected_positions.size());
|
||||
validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
|
||||
}
|
||||
|
||||
TEST_F(CurvesEditorsTest, SplitPointsFourThree)
|
||||
{
|
||||
/* Four points from three curves. One curve has one point. */
|
||||
const Vector<Vector<float3>> positions = {
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
|
||||
{{0, 0, 0}},
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
bke::CurvesGeometry curves = create_curves(positions, 4, {});
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices(Array<int>{0, 1, 4, 9}.as_span(), memory);
|
||||
|
||||
bke::CurvesGeometry new_curves = split_points(curves, mask);
|
||||
|
||||
const Vector<Vector<float3>> expected_positions = {
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}},
|
||||
{{-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
|
||||
{{0, 0, 0}},
|
||||
{{1, -1, 0}},
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
GTEST_ASSERT_EQ(new_curves.curves_num(), expected_positions.size());
|
||||
validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
|
||||
}
|
||||
|
||||
TEST_F(CurvesEditorsTest, SplitPointsTwoCyclic)
|
||||
{
|
||||
/* Two points from cyclic curve. Points are on cycle. */
|
||||
const Vector<Vector<float3>> positions = {
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
|
||||
{{0, 0, 0}},
|
||||
{{1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {-1, 1, 0}},
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
bke::CurvesGeometry curves = create_curves(positions, 4, {2});
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices(Array<int>{5, 8}.as_span(), memory);
|
||||
|
||||
bke::CurvesGeometry new_curves = split_points(curves, mask);
|
||||
|
||||
const Vector<Vector<float3>> expected_positions = {
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
|
||||
{{0, 0, 0}},
|
||||
{{-1, 1, 0}, {1, 1, 0}},
|
||||
{{1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {-1, 1, 0}},
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
GTEST_ASSERT_EQ(new_curves.curves_num(), expected_positions.size());
|
||||
validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
|
||||
Array<bool> expected_cyclic = {false, false, false, false, false};
|
||||
VArray<bool> cyclic = new_curves.cyclic();
|
||||
for (const int i : expected_cyclic.index_range()) {
|
||||
EXPECT_EQ(expected_cyclic[i], cyclic[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CurvesEditorsTest, SplitPointsTwoTouchCyclic)
|
||||
{
|
||||
/* Two points from cyclic curve. Points are touching cycle. */
|
||||
const Vector<Vector<float3>> positions = {
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
|
||||
{{0, 0, 0}},
|
||||
{{1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {-1, 1, 0}},
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
bke::CurvesGeometry curves = create_curves(positions, 4, {2});
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices(Array<int>{5, 6}.as_span(), memory);
|
||||
|
||||
bke::CurvesGeometry new_curves = split_points(curves, mask);
|
||||
|
||||
const Vector<Vector<float3>> expected_positions = {
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}},
|
||||
{{0, 0, 0}},
|
||||
{{1, 1, 0}, {1, -1, 0}},
|
||||
{{1, -1, 0}, {-1, -1, 0}, {-1, 1, 0}, {1, 1, 0}},
|
||||
{{-1.5, 0, 0}, {-1, 1, 0}, {1, 1, 0}, {1.5, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
GTEST_ASSERT_EQ(new_curves.curves_num(), expected_positions.size());
|
||||
validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
|
||||
}
|
||||
|
||||
TEST_F(CurvesEditorsTest, SplitEverySecondCyclic)
|
||||
{
|
||||
/* Split every second point in cyclic curve. Expected result all selected points
|
||||
* as separate curves and original curve. */
|
||||
const Vector<Vector<float3>> positions = {{{0, -1, 0},
|
||||
{-1, -1, 0},
|
||||
{-1, 0, 0},
|
||||
{-1, 1, 0},
|
||||
{0, 1, 0},
|
||||
{1, 1, 0},
|
||||
{1, 0, 0},
|
||||
{1, -1, 0}}};
|
||||
|
||||
bke::CurvesGeometry curves = create_curves(positions, 4, {0});
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices(Array<int>{0, 2, 4, 6}.as_span(), memory);
|
||||
|
||||
bke::CurvesGeometry new_curves = split_points(curves, mask);
|
||||
|
||||
const Vector<Vector<float3>> expected_positions = {{{0, -1, 0}},
|
||||
{{-1, 0, 0}},
|
||||
{{0, 1, 0}},
|
||||
{{1, 0, 0}},
|
||||
{{0, -1, 0},
|
||||
{-1, -1, 0},
|
||||
{-1, 0, 0},
|
||||
{-1, 1, 0},
|
||||
{0, 1, 0},
|
||||
{1, 1, 0},
|
||||
{1, 0, 0},
|
||||
{1, -1, 0}}};
|
||||
|
||||
GTEST_ASSERT_EQ(new_curves.curves_num(), expected_positions.size());
|
||||
validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
|
||||
}
|
||||
|
||||
TEST_F(CurvesEditorsTest, SplitAllSelectedButFirstCyclic)
|
||||
{
|
||||
/* Split all except first points in cyclic curve. Expected result two curves. One from selected
|
||||
* points another from first, second and last. Both not cyclic. */
|
||||
const Vector<Vector<float3>> positions = {{{0, -1, 0},
|
||||
{-1, -1, 0},
|
||||
{-1, 0, 0},
|
||||
{-1, 1, 0},
|
||||
{0, 1, 0},
|
||||
{1, 1, 0},
|
||||
{1, 0, 0},
|
||||
{1, -1, 0}}};
|
||||
|
||||
bke::CurvesGeometry curves = create_curves(positions, 4, {0});
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices(Array<int>{1, 2, 3, 4, 5, 6, 7}.as_span(),
|
||||
memory);
|
||||
|
||||
bke::CurvesGeometry new_curves = split_points(curves, mask);
|
||||
|
||||
const Vector<Vector<float3>> expected_positions = {
|
||||
{{-1, -1, 0}, {-1, 0, 0}, {-1, 1, 0}, {0, 1, 0}, {1, 1, 0}, {1, 0, 0}, {1, -1, 0}},
|
||||
{{1, -1, 0}, {0, -1, 0}, {-1, -1, 0}},
|
||||
};
|
||||
|
||||
GTEST_ASSERT_EQ(new_curves.curves_num(), expected_positions.size());
|
||||
validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
|
||||
EXPECT_EQ(new_curves.curves_num(), expected_positions.size());
|
||||
EXPECT_FALSE(new_curves.cyclic()[0]);
|
||||
EXPECT_FALSE(new_curves.cyclic()[1]);
|
||||
}
|
||||
|
||||
TEST_F(CurvesEditorsTest, SplitTwoOnSeamAndExtraCyclic)
|
||||
{
|
||||
/* Split first, last and pair in the middle. Expected result four non cyclic curves. */
|
||||
const Vector<Vector<float3>> positions = {{{0, -1, 0},
|
||||
{-1, -1, 0},
|
||||
{-1, 0, 0},
|
||||
{-1, 1, 0},
|
||||
{0, 1, 0},
|
||||
{1, 1, 0},
|
||||
{1, 0, 0},
|
||||
{1, -1, 0}}};
|
||||
|
||||
bke::CurvesGeometry curves = create_curves(positions, 4, {0});
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask mask = IndexMask::from_indices(Array<int>{0, 3, 4, 7}.as_span(), memory);
|
||||
|
||||
bke::CurvesGeometry new_curves = split_points(curves, mask);
|
||||
|
||||
const Vector<Vector<float3>> expected_positions = {
|
||||
{{-1, 1, 0}, {0, 1, 0}},
|
||||
{{1, -1, 0}, {0, -1, 0}},
|
||||
{{0, -1, 0}, {-1, -1, 0}, {-1, 0, 0}, {-1, 1, 0}},
|
||||
{{0, 1, 0}, {1, 1, 0}, {1, 0, 0}, {1, -1, 0}}};
|
||||
|
||||
GTEST_ASSERT_EQ(new_curves.curves_num(), expected_positions.size());
|
||||
validate_positions(expected_positions, new_curves.points_by_curve(), new_curves.positions());
|
||||
EXPECT_FALSE(new_curves.cyclic()[0]);
|
||||
EXPECT_FALSE(new_curves.cyclic()[1]);
|
||||
EXPECT_FALSE(new_curves.cyclic()[2]);
|
||||
EXPECT_FALSE(new_curves.cyclic()[3]);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::curves::tests
|
||||
Reference in New Issue
Block a user