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:
@@ -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)
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user