GPv3: Merge Layers Operator

This adds a function to merge layers in original Grease Pencil geometry.
It also adds an operator to merge layers as well as some tests for the `merge_layers` function.

The operator has 3 modes:
* `Merge Down`: Combine the active layer with the layer just below (if there is one).
* `Merge Group`: Combine all the layers in a group into one single layer and remove
  the group. Can be accessed in the right-click menu of groups.
* `Merge All`: Combine all the layers of the object into one single layer.

All of these can be accessed in the `Extras` menu next to the layer tree.

Pull Request: https://projects.blender.org/blender/blender/pulls/128201
This commit is contained in:
Falk David
2024-10-02 18:28:30 +02:00
committed by Falk David
parent 76e7770bc9
commit 1829982d3b
9 changed files with 828 additions and 9 deletions

View File

@@ -215,6 +215,11 @@ class GREASE_PENCIL_MT_grease_pencil_add_layer_extra(Menu):
if layer:
layout.prop(layer, "ignore_locked_materials")
layout.separator()
layout.operator("grease_pencil.layer_merge", text="Merge Down").mode = 'ACTIVE'
layout.operator("grease_pencil.layer_merge", text="Merge Group").mode = 'GROUP'
layout.operator("grease_pencil.layer_merge", text="Merge All").mode = 'ALL'
layout.separator()
layout.operator("grease_pencil.layer_duplicate_object", text="Copy Layer to Selected").only_active = True
layout.operator("grease_pencil.layer_duplicate_object", text="Copy All Layers to Selected").only_active = False
@@ -227,6 +232,7 @@ class GREASE_PENCIL_MT_group_context_menu(Menu):
layout = self.layout
layout.operator("grease_pencil.layer_group_remove", text="Delete Group").keep_children = False
layout.operator("grease_pencil.layer_group_remove", text="Ungroup").keep_children = True
layout.operator("grease_pencil.layer_merge", text="Merge Group").mode = 'GROUP'
layout.separator()
row = layout.row(align=True)

View File

@@ -1040,6 +1040,7 @@ void BKE_grease_pencil_copy_layer_parameters(const blender::bke::greasepencil::L
void BKE_grease_pencil_copy_layer_group_parameters(
const blender::bke::greasepencil::LayerGroup &src,
blender::bke::greasepencil::LayerGroup &dst);
/**
* Move data from a grease pencil outside of the main data-base into a grease pencil in the
* data-base. Takes ownership of the source grease pencil. */

View File

@@ -3236,10 +3236,11 @@ static std::string unique_layer_group_name(const GreasePencil &grease_pencil,
return unique_node_name(grease_pencil, DATA_("Group"), name);
}
blender::bke::greasepencil::Layer &GreasePencil::add_layer(const blender::StringRefNull name)
blender::bke::greasepencil::Layer &GreasePencil::add_layer(const blender::StringRefNull name,
const bool check_name_is_unique)
{
using namespace blender;
std::string unique_name = unique_layer_name(*this, name);
std::string unique_name = check_name_is_unique ? unique_layer_name(*this, name) : name.c_str();
const int numLayers = layers().size();
CustomData_realloc(&layers_data, numLayers, numLayers + 1);
bke::greasepencil::Layer *new_layer = MEM_new<bke::greasepencil::Layer>(__func__, unique_name);
@@ -3258,10 +3259,12 @@ blender::bke::greasepencil::Layer &GreasePencil::add_layer(const blender::String
}
blender::bke::greasepencil::Layer &GreasePencil::add_layer(
blender::bke::greasepencil::LayerGroup &parent_group, const blender::StringRefNull name)
blender::bke::greasepencil::LayerGroup &parent_group,
const blender::StringRefNull name,
const bool check_name_is_unique)
{
using namespace blender;
blender::bke::greasepencil::Layer &new_layer = this->add_layer(name);
blender::bke::greasepencil::Layer &new_layer = this->add_layer(name, check_name_is_unique);
move_node_into(new_layer.as_node(), parent_group);
return new_layer;
}
@@ -3305,10 +3308,13 @@ blender::bke::greasepencil::Layer &GreasePencil::duplicate_layer(
}
blender::bke::greasepencil::LayerGroup &GreasePencil::add_layer_group(
blender::bke::greasepencil::LayerGroup &parent_group, const blender::StringRefNull name)
blender::bke::greasepencil::LayerGroup &parent_group,
const blender::StringRefNull name,
const bool check_name_is_unique)
{
using namespace blender;
std::string unique_name = unique_layer_group_name(*this, name);
std::string unique_name = check_name_is_unique ? unique_layer_group_name(*this, name) :
name.c_str();
bke::greasepencil::LayerGroup *new_group = MEM_new<bke::greasepencil::LayerGroup>(__func__,
unique_name);
/* Hide masks by default. */

View File

@@ -33,6 +33,7 @@ set(SRC
intern/grease_pencil_layers.cc
intern/grease_pencil_lineart.cc
intern/grease_pencil_material.cc
intern/grease_pencil_merge.cc
intern/grease_pencil_modes.cc
intern/grease_pencil_ops.cc
intern/grease_pencil_primitive.cc
@@ -58,3 +59,14 @@ set(LIB
blender_add_lib(bf_editor_grease_pencil "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
add_dependencies(bf_editor_curves bf_rna)
if(WITH_GTESTS)
set(TEST_SRC
tests/grease_pencil_merge_test.cc
)
set(TEST_INC
)
set(TEST_LIB
)
blender_add_test_suite_lib(editor_grease_pencil "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
endif()

View File

@@ -655,6 +655,155 @@ static void GREASE_PENCIL_OT_layer_duplicate(wmOperatorType *ot)
RNA_def_boolean(ot->srna, "empty_keyframes", false, "Empty Keyframes", "Add Empty Keyframes");
}
enum class MergeMode : int8_t {
Down = 0,
Group = 1,
All = 2,
};
static int grease_pencil_merge_layer_exec(bContext *C, wmOperator *op)
{
using namespace blender::bke::greasepencil;
Main *bmain = CTX_data_main(C);
Object *object = CTX_data_active_object(C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
const MergeMode mode = MergeMode(RNA_enum_get(op->ptr, "mode"));
Vector<Vector<int>> src_layer_indices_by_dst_layer;
std::string merged_layer_name;
if (mode == MergeMode::Down) {
if (!grease_pencil.has_active_layer()) {
BKE_report(op->reports, RPT_ERROR, "No active layer");
return OPERATOR_CANCELLED;
}
const Layer &active_layer = *grease_pencil.get_active_layer();
GreasePencilLayerTreeNode *prev_node = active_layer.as_node().prev;
if (prev_node == nullptr || !prev_node->wrap().is_layer()) {
/* No layer below the active one. */
return OPERATOR_CANCELLED;
}
const Layer &prev_layer = prev_node->wrap().as_layer();
/* Get the indices of the two layers to be merged. */
const int prev_layer_index = *grease_pencil.get_layer_index(prev_layer);
const int active_layer_index = *grease_pencil.get_layer_index(active_layer);
/* Map all the other layers to their own index. */
const Span<const Layer *> layers = grease_pencil.layers();
for (const int layer_i : layers.index_range()) {
if (layer_i != prev_layer_index && layer_i != active_layer_index) {
src_layer_indices_by_dst_layer.append({layer_i});
}
}
/* Map the two layers to one index so they will be merged. */
src_layer_indices_by_dst_layer.append({prev_layer_index, active_layer_index});
/* Store the name of the current active layer as the name of the merged layer. */
merged_layer_name = grease_pencil.layer(prev_layer_index).name();
}
else if (mode == MergeMode::Group) {
if (!grease_pencil.has_active_group()) {
BKE_report(op->reports, RPT_ERROR, "No active group");
return OPERATOR_CANCELLED;
}
LayerGroup &active_group = *grease_pencil.get_active_group();
/* Remove all sub groups of the active group since they won't be needed anymore, but keep the
* layers. */
Array<LayerGroup *> groups = active_group.groups_for_write();
for (LayerGroup *group : groups) {
grease_pencil.remove_group(*group, true);
}
const Span<const Layer *> layers = grease_pencil.layers();
Vector<int> indices;
for (const int layer_i : layers.index_range()) {
const Layer &layer = grease_pencil.layer(layer_i);
if (!layer.is_child_of(active_group)) {
src_layer_indices_by_dst_layer.append({layer_i});
}
else {
indices.append(layer_i);
}
}
src_layer_indices_by_dst_layer.append(indices);
/* Store the name of the group as the name of the merged layer. */
merged_layer_name = active_group.name();
/* Remove the active group. */
grease_pencil.remove_group(active_group, true);
/* Rename the first node so that the merged layer will have the name of the group. */
grease_pencil.rename_node(
*bmain, grease_pencil.layer(indices[0]).as_node(), merged_layer_name);
}
else if (mode == MergeMode::All) {
/* Remove all groups, keep the layers. */
Array<LayerGroup *> groups = grease_pencil.layer_groups_for_write();
for (LayerGroup *group : groups) {
grease_pencil.remove_group(*group, true);
}
Vector<int> indices;
for (const int layer_i : grease_pencil.layers().index_range()) {
indices.append(layer_i);
}
src_layer_indices_by_dst_layer.append(indices);
merged_layer_name = N_("Layer");
grease_pencil.rename_node(
*bmain, grease_pencil.layer(indices[0]).as_node(), merged_layer_name);
}
else {
BLI_assert_unreachable();
}
GreasePencil *merged_grease_pencil = BKE_grease_pencil_new_nomain();
ed::greasepencil::merge_layers(
grease_pencil, src_layer_indices_by_dst_layer, *merged_grease_pencil);
BKE_grease_pencil_nomain_to_grease_pencil(merged_grease_pencil, &grease_pencil);
/* Try to set the active (merged) layer. */
TreeNode *node = grease_pencil.find_node_by_name(merged_layer_name);
if (node && node->is_layer()) {
Layer &layer = node->as_layer();
grease_pencil.set_active_layer(&layer);
}
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_SELECTED, nullptr);
return OPERATOR_FINISHED;
}
static void GREASE_PENCIL_OT_layer_merge(wmOperatorType *ot)
{
static const EnumPropertyItem merge_modes[] = {
{int(MergeMode::Down),
"ACTIVE",
0,
"Active",
"Combine the active layer with the layer just below (if it exists)"},
{int(MergeMode::Group),
"GROUP",
0,
"Group",
"Combine layers in the active group into a single layer"},
{int(MergeMode::All), "ALL", 0, "All", "Combine all layers into a single layer"},
{0, nullptr, 0, nullptr, nullptr},
};
ot->name = "Merge";
ot->idname = "GREASE_PENCIL_OT_layer_merge";
ot->description = "Combine layers based on the mode into one layer";
ot->exec = grease_pencil_merge_layer_exec;
ot->poll = active_grease_pencil_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ot->prop = RNA_def_enum(ot->srna, "mode", merge_modes, int(MergeMode::Down), "Mode", "");
}
static int grease_pencil_layer_mask_add_exec(bContext *C, wmOperator *op)
{
using namespace blender::bke::greasepencil;
@@ -966,6 +1115,7 @@ void ED_operatortypes_grease_pencil_layers()
WM_operatortype_append(GREASE_PENCIL_OT_layer_isolate);
WM_operatortype_append(GREASE_PENCIL_OT_layer_lock_all);
WM_operatortype_append(GREASE_PENCIL_OT_layer_duplicate);
WM_operatortype_append(GREASE_PENCIL_OT_layer_merge);
WM_operatortype_append(GREASE_PENCIL_OT_layer_group_add);
WM_operatortype_append(GREASE_PENCIL_OT_layer_group_remove);

View File

@@ -0,0 +1,367 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edgreasepencil
*/
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "GEO_join_geometries.hh"
#include "ED_grease_pencil.hh"
namespace blender::ed::greasepencil {
using bke::greasepencil::Layer;
using bke::greasepencil::LayerGroup;
using bke::greasepencil::TreeNode;
static void copy_layer_groups_without_layers(GreasePencil &dst_grease_pencil,
const LayerGroup &src_parent,
LayerGroup &dst_parent)
{
using namespace bke::greasepencil;
/* Note: Don't loop over all children, just the direct children. */
LISTBASE_FOREACH (GreasePencilLayerTreeNode *, node, &src_parent.children) {
if (!node->wrap().is_group()) {
continue;
}
const LayerGroup &src_group = node->wrap().as_group();
LayerGroup &new_group = dst_grease_pencil.add_layer_group(dst_parent, src_group.name(), false);
BKE_grease_pencil_copy_layer_group_parameters(src_group, new_group);
/* Repeat recursively for groups in group. */
copy_layer_groups_without_layers(dst_grease_pencil, src_group, new_group);
}
}
static Vector<const LayerGroup *> get_sorted_layer_parents(const Layer &layer)
{
Vector<const LayerGroup *> parents;
const TreeNode *node = &layer.as_node();
while (node->parent_group()) {
parents.append(node->parent_group());
node = node->parent_node();
}
/* Reverse so that the root group is the first element. */
std::reverse(parents.begin(), parents.end());
return parents;
}
static const LayerGroup &find_lowest_common_ancestor(const GreasePencil &grease_pencil,
const Span<int> src_layer_indices)
{
using namespace bke::greasepencil;
BLI_assert(src_layer_indices.size() > 0);
const Span<const Layer *> layers = grease_pencil.layers();
if (src_layer_indices.size() == 1) {
return layers[src_layer_indices.first()]->parent_group();
}
Vector<const LayerGroup *> candidates = get_sorted_layer_parents(
*layers[src_layer_indices.first()]);
for (const int layer_i : src_layer_indices) {
const Layer &layer = *layers[layer_i];
const Vector<const LayerGroup *> parents = get_sorted_layer_parents(layer);
/* Possibly shrink set of candidates so that it only contains the parents common with the
* current layer. */
candidates.resize(std::min(candidates.size(), parents.size()));
for (const int i : candidates.index_range()) {
if (candidates[i] != parents[i]) {
candidates.resize(i);
break;
}
}
}
BLI_assert(!candidates.is_empty());
return *candidates.last();
}
static bke::CurvesGeometry join_curves(const GreasePencil &src_grease_pencil,
const Span<const bke::CurvesGeometry *> all_src_curves,
const Span<float4x4> transforms_to_apply)
{
BLI_assert(all_src_curves.size() == transforms_to_apply.size());
Vector<bke::GeometrySet> src_geometries(all_src_curves.size());
for (const int src_curves_i : all_src_curves.index_range()) {
bke::CurvesGeometry src_curves = *all_src_curves[src_curves_i];
if (src_curves.curves_num() == 0) {
continue;
}
const float4x4 &transform = transforms_to_apply[src_curves_i];
src_curves.transform(transform);
Curves *src_curves_id = bke::curves_new_nomain(std::move(src_curves));
src_curves_id->mat = static_cast<Material **>(MEM_dupallocN(src_grease_pencil.material_array));
src_curves_id->totcol = src_grease_pencil.material_array_num;
src_geometries[src_curves_i].replace_curves(src_curves_id);
}
bke::GeometrySet joined_geometry = geometry::join_geometries(src_geometries, {});
if (joined_geometry.has_curves()) {
return joined_geometry.get_curves()->geometry.wrap();
}
return {};
}
void merge_layers(const GreasePencil &src_grease_pencil,
const Span<Vector<int>> src_layer_indices_by_dst_layer,
GreasePencil &dst_grease_pencil)
{
using namespace bke::greasepencil;
const int num_dst_layers = src_layer_indices_by_dst_layer.size();
const Span<const Layer *> src_layers = src_grease_pencil.layers();
const Span<const LayerGroup *> src_groups = src_grease_pencil.layer_groups();
const Span<const GreasePencilDrawingBase *> src_drawings = src_grease_pencil.drawings();
/* Reconstruct the same layer tree structure from the source. */
copy_layer_groups_without_layers(
dst_grease_pencil, src_grease_pencil.root_group(), dst_grease_pencil.root_group());
BLI_assert(src_groups.size() == dst_grease_pencil.layer_groups().size());
/* Find the parent group indices for all the dst layers. */
Array<int> parent_group_index_by_dst_layer(num_dst_layers);
for (const int dst_layer_i : src_layer_indices_by_dst_layer.index_range()) {
const Span<int> src_layer_indices = src_layer_indices_by_dst_layer[dst_layer_i];
const LayerGroup &parent = find_lowest_common_ancestor(src_grease_pencil, src_layer_indices);
/* Note: For layers in the root group the index will be -1. */
parent_group_index_by_dst_layer[dst_layer_i] = src_groups.first_index_try(&parent);
}
/* Important: The cache for the groups changes when layers are added. We have to make a copy of
* all the pointers here. */
const Array<LayerGroup *> dst_groups = dst_grease_pencil.layer_groups_for_write();
/* Add all the layers in the destination under the right parent groups. */
int num_dst_drawings = 0;
Vector<Vector<int>> src_drawing_indices_by_dst_drawing;
Vector<Vector<float4x4>> src_transforms_by_dst_drawing;
Map<const Layer *, int> dst_layer_to_old_index_map;
for (const int dst_layer_i : src_layer_indices_by_dst_layer.index_range()) {
const Span<int> src_layer_indices = src_layer_indices_by_dst_layer[dst_layer_i];
const Layer &src_first = *src_layers[src_layer_indices.first()];
const int parent_index = parent_group_index_by_dst_layer[dst_layer_i];
/* If the parent index is -1 then the layer is added in the root group. */
LayerGroup &dst_parent = (parent_index == -1) ? dst_grease_pencil.root_group() :
*dst_groups[parent_index];
Layer &dst_layer = dst_grease_pencil.add_layer(dst_parent, src_first.name(), false);
/* Copy the layer parameters of the first source layer. */
BKE_grease_pencil_copy_layer_parameters(src_first, dst_layer);
dst_layer_to_old_index_map.add(&dst_layer, dst_layer_i);
const int dst_drawing_start_index = src_drawing_indices_by_dst_drawing.size();
const float4x4 dst_layer_transform = dst_layer.local_transform();
const float4x4 dst_layer_transform_inv = math::invert(dst_layer_transform);
if (src_layer_indices.size() == 1) {
const Map<FramesMapKeyT, GreasePencilFrame> &src_frames = src_first.frames();
Map<FramesMapKeyT, GreasePencilFrame> &dst_frames = dst_layer.frames_for_write();
VectorSet<int> unique_src_indices;
for (const FramesMapKeyT key : src_first.sorted_keys()) {
const GreasePencilFrame &src_frame = src_frames.lookup(key);
GreasePencilFrame &value = dst_frames.lookup_or_add(key, src_frame);
int index = unique_src_indices.index_of_try(src_frame.drawing_index);
if (index == -1) {
unique_src_indices.add_new(src_frame.drawing_index);
index = src_drawing_indices_by_dst_drawing.append_and_get_index(
{src_frame.drawing_index});
src_transforms_by_dst_drawing.append(
{dst_layer_transform_inv * src_first.local_transform()});
num_dst_drawings++;
}
else {
index = index + dst_drawing_start_index;
}
value.drawing_index = index;
}
dst_layer.tag_frames_map_changed();
continue;
}
struct InsertKeyframe {
GreasePencilFrame frame;
int duration = -1;
};
Map<FramesMapKeyT, InsertKeyframe> dst_frames;
for (const int src_layer_i : src_layer_indices) {
const Layer &src_layer = *src_layers[src_layer_i];
for (const auto &[key, value] : src_layer.frames().items()) {
if (value.is_end()) {
continue;
}
const int duration = src_layer.get_frame_duration_at(key);
BLI_assert(duration >= 0);
dst_frames.add_or_modify(
key,
[&](InsertKeyframe *frame) {
*frame = {value, duration};
},
[&](InsertKeyframe *frame) {
/* The destination frame is always an implicit hold if at least on of the source
* frame is an implicit hold. */
if (duration == 0) {
frame->duration = 0;
frame->frame.flag |= GP_FRAME_IMPLICIT_HOLD;
}
else if (frame->duration > 0) {
/* For fixed duration frames, use the longest duration of the source frames. */
frame->duration = std::max(frame->duration, duration);
}
});
}
}
Array<FramesMapKeyT> sorted_keys(dst_frames.size());
{
int i = 0;
for (const FramesMapKeyT key : dst_frames.keys()) {
sorted_keys[i++] = key;
}
std::sort(sorted_keys.begin(), sorted_keys.end());
}
Array<Vector<int>> src_drawing_indices_by_frame(sorted_keys.size());
Array<Vector<float4x4>> src_transforms_by_frame(sorted_keys.size());
for (const int src_layer_i : src_layer_indices) {
const Layer &src_layer = *src_layers[src_layer_i];
for (const int key_i : sorted_keys.index_range()) {
const FramesMapKeyT key = sorted_keys[key_i];
const int drawing_index = src_layer.drawing_index_at(key);
if (drawing_index != -1) {
src_drawing_indices_by_frame[key_i].append(drawing_index);
src_transforms_by_frame[key_i].append(dst_layer_transform_inv *
src_layer.local_transform());
}
}
}
/* Add all the destination frames. */
VectorSet<Vector<int>> unique_src_indices_per_drawing;
for (const int key_i : sorted_keys.index_range()) {
const FramesMapKeyT key = sorted_keys[key_i];
const InsertKeyframe value = dst_frames.lookup(key);
const Vector<int> &src_drawing_indices = src_drawing_indices_by_frame[key_i];
const Vector<float4x4> &src_transforms = src_transforms_by_frame[key_i];
GreasePencilFrame *frame = dst_layer.add_frame(key, value.duration);
/* Copy frame parameters. */
frame->flag = value.frame.flag;
frame->type = value.frame.type;
/* In case drawings are shared in the source, keep sharing the drawings if possible. */
int index = unique_src_indices_per_drawing.index_of_try(src_drawing_indices);
if (index == -1) {
unique_src_indices_per_drawing.add_new(src_drawing_indices);
index = src_drawing_indices_by_dst_drawing.append_and_get_index(src_drawing_indices);
src_transforms_by_dst_drawing.append(src_transforms);
num_dst_drawings++;
}
else {
index = index + dst_drawing_start_index;
}
frame->drawing_index = index;
}
dst_layer.tag_frames_map_changed();
}
/* The destination layers don't map to the order of elements in #src_layer_indices_by_dst_layer.
* This map maps between the old order and the final order in the destination Grease Pencil. */
Array<int> old_to_new_index_map(num_dst_layers);
for (const int layer_i : dst_grease_pencil.layers().index_range()) {
const Layer *layer = &dst_grease_pencil.layer(layer_i);
old_to_new_index_map[dst_layer_to_old_index_map.lookup(layer)] = layer_i;
}
/* Add all the drawings. */
if (num_dst_drawings > 0) {
dst_grease_pencil.add_empty_drawings(num_dst_drawings);
}
MutableSpan<GreasePencilDrawingBase *> dst_drawings = dst_grease_pencil.drawings();
threading::parallel_for(dst_drawings.index_range(), 32, [&](const IndexRange range) {
for (const int dst_drawing_i : range) {
const Span<int> src_drawing_indices = src_drawing_indices_by_dst_drawing[dst_drawing_i];
const Span<float4x4> src_transforms_to_apply = src_transforms_by_dst_drawing[dst_drawing_i];
const GreasePencilDrawingBase *src_first_base = src_drawings[src_drawing_indices.first()];
BLI_assert(src_first_base->type == GP_DRAWING);
GreasePencilDrawingBase *dst_base = dst_drawings[dst_drawing_i];
BLI_assert(dst_base->type == GP_DRAWING);
/* Copy the parameters of the first source drawing. */
dst_base->flag = src_first_base->flag;
Drawing &dst_drawing = reinterpret_cast<GreasePencilDrawing *>(dst_base)->wrap();
if (src_drawing_indices.size() == 1) {
const Drawing &src_drawing =
reinterpret_cast<const GreasePencilDrawing *>(src_first_base)->wrap();
dst_drawing.strokes_for_write() = src_drawing.strokes();
dst_drawing.tag_topology_changed();
continue;
}
/* Gather all the source curves to be merged. */
Vector<const bke::CurvesGeometry *> all_src_curves;
for (const int src_darwing_i : src_drawing_indices) {
const GreasePencilDrawingBase *src_base = src_drawings[src_darwing_i];
BLI_assert(src_base->type == GP_DRAWING);
const Drawing &src_drawing =
reinterpret_cast<const GreasePencilDrawing *>(src_base)->wrap();
all_src_curves.append(&src_drawing.strokes());
}
dst_drawing.strokes_for_write() = join_curves(
src_grease_pencil, all_src_curves, src_transforms_to_apply);
dst_drawing.tag_topology_changed();
}
});
/* Update the user count for all the drawings. */
for (const Layer *dst_layer : dst_grease_pencil.layers()) {
dst_grease_pencil.update_drawing_users_for_layer(*dst_layer);
}
/* Gather all the layer attributes. */
const bke::AttributeAccessor src_attributes = src_grease_pencil.attributes();
bke::MutableAttributeAccessor dst_attributes = dst_grease_pencil.attributes_for_write();
src_attributes.foreach_attribute([&](const blender::bke::AttributeIter &iter) {
if (iter.data_type == CD_PROP_STRING) {
return;
}
bke::GAttributeReader src_attribute = src_attributes.lookup(iter.name);
if (!src_attribute) {
return;
}
bke::GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, bke::AttrDomain::Layer, iter.data_type);
const CPPType &type = dst_attribute.span.type();
bke::attribute_math::convert_to_static_type(type, [&](auto type) {
using T = decltype(type);
const VArraySpan<T> src_span = src_attribute.varray.typed<T>();
MutableSpan<T> new_span = dst_attribute.span.typed<T>();
bke::attribute_math::DefaultMixer<T> mixer(new_span);
for (const int dst_layer_i : IndexRange(num_dst_layers)) {
const Span<int> src_layer_indices = src_layer_indices_by_dst_layer[dst_layer_i];
const int new_index = old_to_new_index_map[dst_layer_i];
for (const int src_layer_i : src_layer_indices) {
const T &src_value = src_span[src_layer_i];
mixer.mix_in(new_index, src_value);
}
}
mixer.finalize();
});
dst_attribute.finish();
});
}
} // namespace blender::ed::greasepencil

View File

@@ -0,0 +1,268 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "testing/testing.h"
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BLI_array.hh"
#include "BLI_vector.hh"
#include "BLI_virtual_array.hh"
#include "ED_grease_pencil.hh"
#include <iostream>
namespace blender::ed::greasepencil::tests {
struct GreasePencilIDTestContext {
Main *bmain = nullptr;
GreasePencil *grease_pencil = nullptr;
GreasePencilIDTestContext()
{
BKE_idtype_init();
this->bmain = BKE_main_new();
this->grease_pencil = static_cast<GreasePencil *>(BKE_id_new(this->bmain, ID_GP, "GP"));
}
~GreasePencilIDTestContext()
{
BKE_main_free(bmain);
}
};
TEST(grease_pencil_merge, merge_simple)
{
using namespace bke::greasepencil;
GreasePencilIDTestContext ctx;
GreasePencil &grease_pencil = *ctx.grease_pencil;
Layer &layer1 = grease_pencil.add_layer("Layer1");
Layer &layer2 = grease_pencil.add_layer("Layer2");
grease_pencil.insert_frame(layer1, 0);
grease_pencil.insert_frame(layer2, 0);
grease_pencil.insert_frame(layer2, 2);
GreasePencil *merged_grease_pencil = BKE_grease_pencil_new_nomain();
/* Merge Layer1 and Layer2. */
Array<Vector<int>> src_layer_indices_by_dst_layer({{0, 1}});
ed::greasepencil::merge_layers(
grease_pencil, src_layer_indices_by_dst_layer, *merged_grease_pencil);
EXPECT_EQ(merged_grease_pencil->layers().size(), 1);
EXPECT_EQ(merged_grease_pencil->layer(0).frames().size(), 2);
EXPECT_STREQ(merged_grease_pencil->layer(0).name().c_str(), "Layer1");
BKE_id_free(nullptr, merged_grease_pencil);
}
TEST(grease_pencil_merge, merge_in_same_group)
{
using namespace bke::greasepencil;
GreasePencilIDTestContext ctx;
GreasePencil &grease_pencil = *ctx.grease_pencil;
LayerGroup &group1 = grease_pencil.add_layer_group(grease_pencil.root_group(), "Group1");
LayerGroup &group2 = grease_pencil.add_layer_group(group1, "Group2");
LayerGroup &group3 = grease_pencil.add_layer_group(grease_pencil.root_group(), "Group3");
Layer &layer1 = grease_pencil.add_layer("Layer1");
Layer &layer2 = grease_pencil.add_layer(group1, "Layer2");
Layer &layer3 = grease_pencil.add_layer(group2, "Layer3");
Layer &layer4 = grease_pencil.add_layer(group2, "Layer4");
grease_pencil.add_layer(group3, "Layer5");
grease_pencil.insert_frame(layer1, 0);
grease_pencil.insert_frame(layer2, 0);
grease_pencil.insert_frame(layer2, 2);
grease_pencil.insert_frame(layer3, 0);
grease_pencil.insert_frame(layer3, 3);
grease_pencil.insert_frame(layer4, 1);
grease_pencil.insert_frame(layer4, 3);
GreasePencil *merged_grease_pencil = BKE_grease_pencil_new_nomain();
BKE_grease_pencil_copy_parameters(grease_pencil, *merged_grease_pencil);
/* Merge "Layer3" and "Layer4" */
Array<Vector<int>> src_layer_indices_by_dst_layer({{0, 1}, {2}, {3}, {4}});
ed::greasepencil::merge_layers(
grease_pencil, src_layer_indices_by_dst_layer, *merged_grease_pencil);
EXPECT_EQ(merged_grease_pencil->layers().size(), 4);
Array<std::string> expected_layer_names({"Layer3", "Layer2", "Layer5", "Layer1"});
for (const int i : merged_grease_pencil->layers().index_range()) {
const Layer &layer = merged_grease_pencil->layer(i);
EXPECT_STREQ(layer.name().c_str(), expected_layer_names[i].c_str());
}
Array<int> expected_layer3_keyframes({0, 1, 3});
for (const int i : merged_grease_pencil->layer(0).sorted_keys().index_range()) {
const int key = merged_grease_pencil->layer(0).sorted_keys()[i];
EXPECT_EQ(key, expected_layer3_keyframes[i]);
}
BKE_id_free(nullptr, merged_grease_pencil);
}
TEST(grease_pencil_merge, merge_in_different_group)
{
using namespace bke::greasepencil;
GreasePencilIDTestContext ctx;
GreasePencil &grease_pencil = *ctx.grease_pencil;
LayerGroup &group1 = grease_pencil.add_layer_group(grease_pencil.root_group(), "Group1");
LayerGroup &group2 = grease_pencil.add_layer_group(group1, "Group2");
LayerGroup &group3 = grease_pencil.add_layer_group(grease_pencil.root_group(), "Group3");
LayerGroup &group4 = grease_pencil.add_layer_group(group2, "Group4");
LayerGroup &group5 = grease_pencil.add_layer_group(group4, "Group5");
LayerGroup &group6 = grease_pencil.add_layer_group(group1, "Group6");
Layer &layer1 = grease_pencil.add_layer("Layer1");
Layer &layer2 = grease_pencil.add_layer(group6, "Layer2");
Layer &layer3 = grease_pencil.add_layer(group5, "Layer3");
Layer &layer4 = grease_pencil.add_layer(group2, "Layer4");
grease_pencil.add_layer(group3, "Layer5");
grease_pencil.insert_frame(layer1, 0);
grease_pencil.insert_frame(layer2, 0);
grease_pencil.insert_frame(layer2, 2);
grease_pencil.insert_frame(layer3, 0);
grease_pencil.insert_frame(layer3, 3);
grease_pencil.insert_frame(layer4, 1);
grease_pencil.insert_frame(layer4, 3);
GreasePencil *merged_grease_pencil = BKE_grease_pencil_new_nomain();
BKE_grease_pencil_copy_parameters(grease_pencil, *merged_grease_pencil);
Array<Vector<int>> src_layer_indices_by_dst_layer({{0, 2}, {1}, {3}, {4}});
ed::greasepencil::merge_layers(
grease_pencil, src_layer_indices_by_dst_layer, *merged_grease_pencil);
EXPECT_EQ(merged_grease_pencil->layers().size(), 4);
TreeNode *node = merged_grease_pencil->find_node_by_name("Layer3");
EXPECT_TRUE(node != nullptr);
EXPECT_TRUE(node->is_layer());
EXPECT_TRUE(node->parent_group() && node->parent_group()->name() == "Group1");
EXPECT_EQ(node->as_layer().frames().size(), 3);
Array<int> expected_layer3_keyframes({0, 2, 3});
for (const int i : node->as_layer().sorted_keys().index_range()) {
const int key = node->as_layer().sorted_keys()[i];
EXPECT_EQ(key, expected_layer3_keyframes[i]);
}
Array<std::string> expected_layer_names({"Layer4", "Layer3", "Layer5", "Layer1"});
for (const int i : merged_grease_pencil->layers().index_range()) {
const Layer &layer = merged_grease_pencil->layer(i);
EXPECT_STREQ(layer.name().c_str(), expected_layer_names[i].c_str());
}
BKE_id_free(nullptr, merged_grease_pencil);
}
TEST(grease_pencil_merge, merge_keyframes)
{
using namespace bke::greasepencil;
GreasePencilIDTestContext ctx;
GreasePencil &grease_pencil = *ctx.grease_pencil;
Layer &layer1 = grease_pencil.add_layer("Layer1");
Layer &layer2 = grease_pencil.add_layer("Layer2");
Layer &layer3 = grease_pencil.add_layer("Layer3");
Layer &layer4 = grease_pencil.add_layer("Layer4");
grease_pencil.add_layer("Layer5");
Drawing *drawing = grease_pencil.insert_frame(layer1, 0);
drawing->strokes_for_write().resize(10, 2);
drawing = grease_pencil.insert_frame(layer2, 0);
drawing->strokes_for_write().resize(20, 3);
drawing = grease_pencil.insert_frame(layer2, 2);
drawing->strokes_for_write().resize(30, 4);
drawing = grease_pencil.insert_frame(layer3, 0);
drawing->strokes_for_write().resize(40, 5);
drawing = grease_pencil.insert_frame(layer3, 3);
drawing->strokes_for_write().resize(50, 6);
drawing = grease_pencil.insert_frame(layer4, 1);
drawing->strokes_for_write().resize(60, 7);
drawing = grease_pencil.insert_frame(layer4, 3);
drawing->strokes_for_write().resize(70, 8);
GreasePencil *merged_grease_pencil = BKE_grease_pencil_new_nomain();
BKE_grease_pencil_copy_parameters(grease_pencil, *merged_grease_pencil);
Array<Vector<int>> src_layer_indices_by_dst_layer({{0}, {1, 2}, {3}, {4}});
ed::greasepencil::merge_layers(
grease_pencil, src_layer_indices_by_dst_layer, *merged_grease_pencil);
EXPECT_EQ(merged_grease_pencil->layers().size(), 4);
Layer &expected_layer_1 = merged_grease_pencil->find_node_by_name("Layer1")->as_layer();
EXPECT_EQ(merged_grease_pencil->get_drawing_at(expected_layer_1, 0)->strokes().points_num(), 10);
Layer &expected_layer_2 = merged_grease_pencil->find_node_by_name("Layer2")->as_layer();
EXPECT_EQ(merged_grease_pencil->get_drawing_at(expected_layer_2, 0)->strokes().points_num(), 60);
Layer &expected_layer_4 = merged_grease_pencil->find_node_by_name("Layer4")->as_layer();
EXPECT_EQ(merged_grease_pencil->get_drawing_at(expected_layer_4, 3)->strokes().points_num(), 70);
BKE_id_free(nullptr, merged_grease_pencil);
}
TEST(grease_pencil_merge, merge_layer_attributes)
{
using namespace bke;
using namespace bke::greasepencil;
GreasePencilIDTestContext ctx;
GreasePencil &grease_pencil = *ctx.grease_pencil;
grease_pencil.add_layer("Layer1");
grease_pencil.add_layer("Layer2");
grease_pencil.add_layer("Layer3");
Array<float> test_float_values({4.2f, 1.0f, -12.0f});
SpanAttributeWriter<float> test_attribute =
grease_pencil.attributes_for_write().lookup_or_add_for_write_only_span<float>(
"test", AttrDomain::Layer);
test_attribute.span.copy_from(test_float_values);
test_attribute.finish();
GreasePencil *merged_grease_pencil = BKE_grease_pencil_new_nomain();
/* Merge Layer1 and Layer2. */
Array<Vector<int>> src_layer_indices_by_dst_layer({{0, 1}, {2}});
ed::greasepencil::merge_layers(
grease_pencil, src_layer_indices_by_dst_layer, *merged_grease_pencil);
EXPECT_EQ(merged_grease_pencil->layers().size(), 2);
VArray<float> merged_values = *merged_grease_pencil->attributes().lookup<float>("test");
Array<float> expected_float_values({2.6, -12.0f});
for (const int i : merged_grease_pencil->layers().index_range()) {
EXPECT_FLOAT_EQ(merged_values[i], expected_float_values[i]);
}
BKE_id_free(nullptr, merged_grease_pencil);
}
} // namespace blender::ed::greasepencil::tests

View File

@@ -861,6 +861,10 @@ bke::CurvesGeometry trim_curve_segments(const bke::CurvesGeometry &src,
bool keep_caps);
}; // namespace trim
void merge_layers(const GreasePencil &src_grease_pencil,
const Span<Vector<int>> src_layer_indices_by_dst_layer,
GreasePencil &dst_grease_pencil);
/* Lineart */
/* Stores the maximum calculation range in the whole modifier stack for line art so the cache can

View File

@@ -543,10 +543,13 @@ typedef struct GreasePencil {
/* Adding layers and layer groups. */
/** Adds a new layer with the given name to the top of root group. */
blender::bke::greasepencil::Layer &add_layer(blender::StringRefNull name);
blender::bke::greasepencil::Layer &add_layer(blender::StringRefNull name,
bool check_name_is_unique = true);
/** Adds a new layer with the given name to the top of the given group. */
blender::bke::greasepencil::Layer &add_layer(
blender::bke::greasepencil::LayerGroup &parent_group, blender::StringRefNull name);
blender::bke::greasepencil::LayerGroup &parent_group,
blender::StringRefNull name,
bool check_name_is_unique = true);
/** Duplicates the given layer to the top of the root group. */
blender::bke::greasepencil::Layer &duplicate_layer(
const blender::bke::greasepencil::Layer &duplicate_layer);
@@ -555,7 +558,9 @@ typedef struct GreasePencil {
blender::bke::greasepencil::LayerGroup &parent_group,
const blender::bke::greasepencil::Layer &duplicate_layer);
blender::bke::greasepencil::LayerGroup &add_layer_group(
blender::bke::greasepencil::LayerGroup &parent_group, blender::StringRefNull name);
blender::bke::greasepencil::LayerGroup &parent_group,
blender::StringRefNull name,
bool check_name_is_unique = true);
/**
* Adds multiple layers with an empty name.