Initial Grease Pencil 3.0 stage #106848

Merged
Falk David merged 224 commits from filedescriptor/blender:grease-pencil-v3 into main 2023-05-30 11:14:22 +02:00
4 changed files with 166 additions and 175 deletions
Showing only changes of commit 96307ec7c9 - Show all commits

View File

@ -30,6 +30,7 @@ class Layer;
* children if it is a group.
*/
class TreeNode : public ::GreasePencilLayerTreeNode, NonMovable {
friend class LayerGroup;
public:
explicit TreeNode(GreasePencilLayerTreeNodeType type);
explicit TreeNode(GreasePencilLayerTreeNodeType type, StringRefNull name);
@ -37,8 +38,9 @@ class TreeNode : public ::GreasePencilLayerTreeNode, NonMovable {
TreeNode &operator=(const TreeNode &other) = delete;
virtual ~TreeNode();

Is there a particular reason you remove copy assignment but keep copy construction defined?

Is there a particular reason you remove copy assignment but keep copy construction defined?

Just because copying should be explicit. And removing the copy assignment constructor avoids errors.

Just because copying should be explicit. And removing the copy assignment constructor avoids errors.
public:
Vector<std::unique_ptr<TreeNode>> children;
private:
std::unique_ptr<LayerGroup> parent_ = nullptr;
Vector<std::unique_ptr<TreeNode>> children_;
public:
/**
@ -158,7 +160,7 @@ class Layer : public TreeNode, public ::GreasePencilLayer {
*/
bool is_visible() const;
/**
* \return true if the layer is locked.
* \return true if the layer is locked.

Put out of line.

Put out of line.
*/
bool is_locked() const;

The function is "is_locked" and the comment says "return if it is locked". This sort of comment doesn't add anything IMO, just wastes space. If there is a comment, maybe it should use a different word besides "locked" to describe the state. Or there doesn't need to be a comment at all.

The function is "is_locked" and the comment says "return if it is locked". This sort of comment doesn't add anything IMO, just wastes space. If there is a comment, maybe it should use a different word besides "locked" to describe the state. Or there doesn't need to be a comment at all.
@ -198,16 +200,16 @@ class Layer : public TreeNode, public ::GreasePencilLayer {
* A LayerGroup is a grouping of zero or more Layers.

Put out of line.

Put out of line.
*/
class LayerGroup : public TreeNode {
using TreeNodeIterFn = FunctionRef<void(TreeNode &)>;
using TreeNodeIndexIterFn = FunctionRef<void(int64_t, TreeNode &)>;
using LayerIterFn = FunctionRef<void(Layer &)>;
using LayerIndexIterFn = FunctionRef<void(int64_t, Layer &)>;
public:
LayerGroup() : TreeNode(GP_LAYER_TREE_GROUP) {}
explicit LayerGroup(const StringRefNull name) : TreeNode(GP_LAYER_TREE_GROUP, name) {}
LayerGroup(const LayerGroup &other);
private:

Put out of line.

Put out of line.
mutable CacheMutex children_cache_mutex_;
mutable Vector<TreeNode *> children_cache_;

Not sure what is a 'pre-order vector'? or is a typo? Like pre-ordered vector? Same below.

Not sure what is a 'pre-order vector'? or is a typo? Like `pre-ordered vector`? Same below.
mutable Vector<Layer *> layer_cache_;
public:
/**
* Adds a group at the end of this group.
@ -236,21 +238,23 @@ class LayerGroup : public TreeNode {
*/
void remove_child(int64_t index);
/**
* Calls \a function on every `TreeNode` in this group.
*/
void foreach_children_pre_order(TreeNodeIterFn function);
void foreach_children_with_index_pre_order(TreeNodeIndexIterFn function);
/**
* Returns a `Vector` of pointers to all the `TreeNode`s in this group.
*/
Vector<TreeNode *> children_in_pre_order() const;
Span<const TreeNode *> children() const;
Span<TreeNode *> children_for_write();
/**
* Returns a `Vector` of pointers to all the `Layers`s in this group.
*/
filedescriptor marked this conversation as resolved Outdated

Since the map will have to change to store more than just an index as the value, make sure to update the comment too.

Since the map will have to change to store more than just an index as the value, make sure to update the comment too.
Vector<Layer *> layers_in_pre_order() const;
Span<const Layer *> layers() const;
Span<Layer *> layers_for_write();
void print_children(StringRefNull header) const;
private:
void ensure_children_cache() const;
void tag_children_cache_dirty() const;
};
namespace convert {
@ -299,9 +303,6 @@ class GreasePencilDrawingRuntime {
};

Looks like this should be private maybe? It has a _ suffix.

Looks like this should be private maybe? It has a `_` suffix.
class GreasePencilRuntime {
private:
mutable SharedCache<Vector<greasepencil::Layer *>> layer_cache_;
public:
void *batch_cache = nullptr;
@ -325,13 +326,6 @@ class GreasePencilRuntime {
greasepencil::Layer &active_layer_for_write() const;

These two functions should never have to be called outside of the ID callbacks in grease_pencil.cc; IMO they make more sense as static functions there. Probably better to keep that storage for DNA thing as localized as possible.

These two functions should never have to be called outside of the `ID` callbacks in `grease_pencil.cc`; IMO they make more sense as static functions there. Probably better to keep that storage for DNA thing as localized as possible.
void set_active_layer_index(int index);
int active_layer_index() const;
void ensure_layer_cache() const;
void tag_layer_tree_topology_changed();
private:
greasepencil::Layer *get_active_layer_from_index(int index) const;
};
} // namespace blender::bke

Member variables come before functions https://wiki.blender.org/wiki/Style_Guide/C_Cpp#Class_Layout

Member variables come before functions https://wiki.blender.org/wiki/Style_Guide/C_Cpp#Class_Layout

View File

@ -431,7 +431,7 @@ Span<int> Layer::sorted_keys() const
}
std::sort(r_data.begin(), r_data.end());
});
return this->sorted_keys_cache_.data().as_span();
return this->sorted_keys_cache_.data();

.as_span() shouldn't be necessary here.

`.as_span()` shouldn't be necessary here.
}
int Layer::drawing_index_at(int frame) const

int frame -> const int frame

`int frame` -> `const int frame`
@ -464,123 +464,147 @@ void Layer::tag_frames_map_keys_changed()
LayerGroup::LayerGroup(const LayerGroup &other) : TreeNode(other)
{
this->children.reserve(other.children.size());
for (const std::unique_ptr<TreeNode> &elem : other.children) {
this->children_.reserve(other.children_.size());
for (const std::unique_ptr<TreeNode> &elem : other.children_) {
if (elem.get()->is_group()) {
this->children.append(std::make_unique<LayerGroup>(elem.get()->as_group()));
this->children_.append(std::make_unique<LayerGroup>(elem.get()->as_group()));
}
else if (elem.get()->is_layer()) {
this->children.append(std::make_unique<Layer>(elem.get()->as_layer()));
this->children_.append(std::make_unique<Layer>(elem.get()->as_layer()));
}
this->children_.last().get()->parent_.reset(this);
}
this->tag_children_cache_dirty();
}
void LayerGroup::add_group(LayerGroup &group)
{
this->children.append(std::make_unique<LayerGroup>(group));
this->children_.append(std::make_unique<LayerGroup>(group));
this->children_.last().get()->parent_.reset(this);
this->tag_children_cache_dirty();
}

I believe it's standard to std::move from a T &&

Is there a need for overloads for T & and T &&? What's the benefit of that?

I believe it's standard to `std::move` from a `T &&` Is there a need for overloads for `T &` and `T &&`? What's the benefit of that?
void LayerGroup::add_group(LayerGroup &&group)
{
this->children.append(std::make_unique<LayerGroup>(group));
this->children_.append(std::make_unique<LayerGroup>(group));
this->children_.last().get()->parent_.reset(this);
this->tag_children_cache_dirty();
}
Layer &LayerGroup::add_layer(Layer &layer)
{
int64_t index = children.append_and_get_index(std::make_unique<Layer>(layer));
return children[index].get()->as_layer_for_write();
children_.append(std::make_unique<Layer>(layer));
this->children_.last().get()->parent_.reset(this);
this->tag_children_cache_dirty();
return children_.last().get()->as_layer_for_write();
}
Layer &LayerGroup::add_layer(Layer &&layer)
{
int64_t index = children.append_and_get_index(std::make_unique<Layer>(layer));
return children[index].get()->as_layer_for_write();
children_.append(std::make_unique<Layer>(std::move(layer)));
this->children_.last().get()->parent_.reset(this);
this->tag_children_cache_dirty();
return children_.last().get()->as_layer_for_write();
}
int64_t LayerGroup::num_direct_children() const
{
return children.size();
return children_.size();
}
int64_t LayerGroup::num_children_total() const
{
int64_t total = 0;
Stack<TreeNode *> stack;
for (auto it = this->children.rbegin(); it != this->children.rend(); it++) {
stack.push((*it).get());
}
while (!stack.is_empty()) {
TreeNode &next_node = *stack.pop();
total++;
for (auto it = next_node.children.rbegin(); it != next_node.children.rend(); it++) {
stack.push((*it).get());
}
}
return total;
this->ensure_children_cache();
return this->children_cache_.size();
}
void LayerGroup::remove_child(int64_t index)
{
BLI_assert(index >= 0 && index < this->children.size());
this->children.remove(index);
BLI_assert(index >= 0 && index < this->children_.size());
this->children_.remove(index);
this->tag_children_cache_dirty();
}
void LayerGroup::foreach_children_pre_order(TreeNodeIterFn function)
Span<const TreeNode *> LayerGroup::children() const
{
for (auto &child : this->children) {
function(*child);
if (child->is_group()) {
child->as_group_for_write().foreach_children_pre_order(function);
this->ensure_children_cache();
return this->children_cache_.as_span();
}
Span<TreeNode *> LayerGroup::children_for_write()
{
this->ensure_children_cache();
return this->children_cache_.as_span();
}
Span<const Layer *> LayerGroup::layers() const
{
this->ensure_children_cache();
return this->layer_cache_.as_span();
}
Span<Layer *> LayerGroup::layers_for_write()
{
this->ensure_children_cache();
return this->layer_cache_.as_span();
}
void LayerGroup::print_children(StringRefNull header) const
{
std::cout << header << std::endl;
Stack<std::pair<int, TreeNode *>> next_node;
for (auto it = this->children_.rbegin(); it != this->children_.rend(); it++) {
next_node.push(std::make_pair(1, (*it).get()));
}
while (!next_node.is_empty()) {
auto [indent, node] = next_node.pop();
for (int i = 0; i < indent; i++) {
std::cout << " ";
}
}
}
void LayerGroup::foreach_children_with_index_pre_order(TreeNodeIndexIterFn function)
{
Vector<TreeNode *> children = this->children_in_pre_order();
for (const int64_t i : children.index_range()) {
function(i, *children[i]);
}
}
Vector<TreeNode *> LayerGroup::children_in_pre_order() const
{
Vector<TreeNode *> children;
Stack<TreeNode *> stack;
for (auto it = this->children.rbegin(); it != this->children.rend(); it++) {
stack.push((*it).get());
}
while (!stack.is_empty()) {
TreeNode &next_node = *stack.pop();
children.append(&next_node);
for (auto it = next_node.children.rbegin(); it != next_node.children.rend(); it++) {
stack.push((*it).get());
if (node->is_layer()) {
std::cout << node->name;
}
}
return children;
}
Vector<Layer *> LayerGroup::layers_in_pre_order() const
{
Vector<Layer *> layers;
Stack<TreeNode *> stack;
for (auto it = this->children.rbegin(); it != this->children.rend(); it++) {
stack.push((*it).get());
}
while (!stack.is_empty()) {
TreeNode &next_node = *stack.pop();
if (next_node.is_layer()) {
layers.append(&next_node.as_layer_for_write());
}
else {
for (auto it = next_node.children.rbegin(); it != next_node.children.rend(); it++) {
stack.push((*it).get());
else if (node->is_group()) {
std::cout << node->name << ": ";
for (auto it = node->children_.rbegin(); it != node->children_.rend(); it++) {
next_node.push(std::make_pair(indent + 1, (*it).get()));
}
}
std::cout << std::endl;
}
}
void LayerGroup::ensure_children_cache() const
{
this->children_cache_mutex_.ensure([&]() {
this->children_cache_.clear_and_shrink();
this->layer_cache_.clear_and_shrink();
Stack<TreeNode *> stack;
for (auto it = this->children_.rbegin(); it != this->children_.rend(); it++) {
stack.push((*it).get());
}
while (!stack.is_empty()) {
TreeNode *next_node = stack.pop();
this->children_cache_.append(next_node);
if (next_node->is_layer()) {
this->layer_cache_.append(&next_node->as_layer_for_write());
}
else if (next_node->is_group()) {
for (auto it = next_node->children_.rbegin(); it != next_node->children_.rend(); it++) {
stack.push((*it).get());
}
}

It probably shouldn't be a SharedCache then. Either that or it should cache indices rather than pointers?

It probably shouldn't be a `SharedCache` then. Either that or it should cache indices rather than pointers?

Ah yes, I don't see a way it can be a shared cache then.

Ah yes, I don't see a way it can be a shared cache then.
}
});
}
void LayerGroup::tag_children_cache_dirty() const
{
this->children_cache_mutex_.tag_dirty();
if (this->parent_) {
this->parent_.get()->tag_children_cache_dirty();
}
return layers;
}
} // namespace blender::bke::greasepencil
@ -594,8 +618,6 @@ namespace blender::bke {
GreasePencilRuntime::GreasePencilRuntime(const GreasePencilRuntime &other)
: root_group_(other.root_group_), active_layer_index_(other.active_layer_index_)
{
/* We cannot copy this cache, because the pointers in that cache become invalid. */
layer_cache_.tag_dirty();
}
const greasepencil::LayerGroup &GreasePencilRuntime::root_group() const
@ -610,12 +632,12 @@ greasepencil::LayerGroup &GreasePencilRuntime::root_group_for_write()
Span<const greasepencil::Layer *> GreasePencilRuntime::layers() const
{
return this->layer_cache_.data();
return this->root_group().layers();
}
Span<greasepencil::Layer *> GreasePencilRuntime::layers_for_write()
{
return this->layer_cache_.data();
return this->root_group_for_write().layers_for_write();
}
bool GreasePencilRuntime::has_active_layer() const
@ -626,13 +648,7 @@ bool GreasePencilRuntime::has_active_layer() const
const greasepencil::Layer &GreasePencilRuntime::active_layer() const
{
BLI_assert(this->active_layer_index_ >= 0);
return *get_active_layer_from_index(this->active_layer_index_);
}
greasepencil::Layer &GreasePencilRuntime::active_layer_for_write() const
{
BLI_assert(this->active_layer_index_ >= 0);
return *get_active_layer_from_index(this->active_layer_index_);
return *this->root_group().layers()[this->active_layer_index_];
}
void GreasePencilRuntime::set_active_layer_index(int index)
@ -645,24 +661,6 @@ int GreasePencilRuntime::active_layer_index() const
return this->active_layer_index_;
}
void GreasePencilRuntime::ensure_layer_cache() const
{
this->layer_cache_.ensure([this](Vector<greasepencil::Layer *> &data) {
data = this->root_group_.layers_in_pre_order();
});
}
void GreasePencilRuntime::tag_layer_tree_topology_changed()
{
this->layer_cache_.tag_dirty();
}
greasepencil::Layer *GreasePencilRuntime::get_active_layer_from_index(int index) const
{
this->ensure_layer_cache();
return this->layer_cache_.data()[index];
}
} // namespace blender::bke
/** \} */
@ -737,16 +735,12 @@ void BKE_grease_pencil_data_update(struct Depsgraph * /*depsgraph*/,
GreasePencil *grease_pencil = static_cast<GreasePencil *>(object->data);
/* Evaluate modifiers. */
/* TODO. */
/* TODO: modifiers. */
/* Assign evaluated object. */
/* TODO: Get eval from modifiers geometry set. */
GreasePencil *grease_pencil_eval = (GreasePencil *)BKE_id_copy_ex(
nullptr, &grease_pencil->id, nullptr, LIB_ID_COPY_LOCALIZE);
// if (grease_pencil_eval == nullptr) {
// grease_pencil_eval = BKE_grease_pencil_new_nomain();
// BKE_object_eval_assign_data(object, &grease_pencil_eval->id, true);
// }
BKE_object_eval_assign_data(object, &grease_pencil_eval->id, true);
}
@ -784,7 +778,7 @@ blender::Span<blender::uint3> GreasePencilDrawing::triangles() const
{
using namespace blender;
const bke::GreasePencilDrawingRuntime &runtime = *this->runtime;
runtime.triangles_cache.ensure([&](Vector<uint3> &cache) {
runtime.triangles_cache.ensure([&](Vector<uint3> &r_data) {
MemArena *pf_arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
const bke::CurvesGeometry &curves = this->geometry.wrap();
@ -792,17 +786,18 @@ blender::Span<blender::uint3> GreasePencilDrawing::triangles() const
const offset_indices::OffsetIndices<int> points_by_curve = curves.points_by_curve();
int total_triangles = 0;
Array<int> tris_offests(curves.curves_num());
for (int curve_i : curves.curves_range()) {

offset_indices::OffsetIndices -> OffsetIndices

Please search the patch for this, I mentioned it last time too

`offset_indices::OffsetIndices` -> `OffsetIndices` Please search the patch for this, I mentioned it last time too
IndexRange points = points_by_curve[curve_i];
if (points.size() > 2) {
tris_offests[curve_i] = total_triangles;
total_triangles += points.size() - 2;
}
}
cache.resize(total_triangles);
r_data.resize(total_triangles);
int t = 0;
for (int curve_i : curves.curves_range()) {
for (const int curve_i : curves.curves_range()) {
const IndexRange points = points_by_curve[curve_i];
if (points.size() < 3) {
@ -810,9 +805,9 @@ blender::Span<blender::uint3> GreasePencilDrawing::triangles() const
}
const int num_trinagles = points.size() - 2;
MutableSpan<uint3> r_tris = r_data.as_mutable_span().slice(tris_offests[curve_i],
num_trinagles);
uint(*tris)[3] = static_cast<uint(*)[3]>(
BLI_memarena_alloc(pf_arena, sizeof(*tris) * size_t(num_trinagles)));
float(*projverts)[2] = static_cast<float(*)[2]>(
BLI_memarena_alloc(pf_arena, sizeof(*projverts) * size_t(points.size())));
@ -824,13 +819,8 @@ blender::Span<blender::uint3> GreasePencilDrawing::triangles() const
mul_v2_m3v3(projverts[i], axis_mat.ptr(), positions[points[i]]);
}
BLI_polyfill_calc_arena(projverts, points.size(), 0, tris, pf_arena);
for (const int i : IndexRange(num_trinagles)) {
cache[t] = uint3(tris[i]);
t++;
}
BLI_polyfill_calc_arena(
projverts, points.size(), 0, reinterpret_cast<uint32_t(*)[3]>(r_tris.data()), pf_arena);
BLI_memarena_clear(pf_arena);
}
@ -1023,20 +1013,19 @@ blender::bke::greasepencil::LayerGroup &GreasePencil::root_group_for_write()
blender::Span<const blender::bke::greasepencil::Layer *> GreasePencil::layers() const
{
BLI_assert(this->runtime != nullptr);
this->runtime->ensure_layer_cache();
return this->runtime->layers();
return this->runtime->root_group().layers();
}
blender::Span<blender::bke::greasepencil::Layer *> GreasePencil::layers_for_write()
{
BLI_assert(this->runtime != nullptr);
this->runtime->ensure_layer_cache();
return this->runtime->layers_for_write();
return this->runtime->root_group_for_write().layers_for_write();
}
void GreasePencil::tag_layer_tree_topology_changed()
void GreasePencil::print_layer_tree()
{
this->runtime->tag_layer_tree_topology_changed();
using namespace blender::bke::greasepencil;
this->root_group().print_children("Layer Tree:");
}
/** \} */
@ -1281,29 +1270,28 @@ static void load_layer_tree_from_storage_ex(blender::bke::GreasePencilRuntime &r
}
BLI_assert(total_nodes_read + 1 == storage.nodes_num);
runtime.set_active_layer_index(storage.active_layer_index);
runtime.tag_layer_tree_topology_changed();
}
static void save_layer_tree_to_storage_ex(const blender::bke::GreasePencilRuntime &runtime,
GreasePencilLayerTreeStorage &storage)
{
using namespace blender::bke::greasepencil;
/* We always store the root group, so we have to add one here. */
int num_tree_nodes = runtime.root_group().num_children_total() + 1;
storage.nodes_num = num_tree_nodes;
storage.nodes = MEM_cnew_array<GreasePencilLayerTreeNode *>(num_tree_nodes, __func__);
save_layer_group_to_storage(runtime.root_group(), &storage.nodes[0]);

Looks like drawings are allocated with MEM_new above, which means they should be deallocated with MEM_delete.

Looks like drawings are allocated with `MEM_new` above, which means they should be deallocated with `MEM_delete`.
Span<const TreeNode *> children = runtime.root_group().children_in_pre_order();
Span<const TreeNode *> children = runtime.root_group().children();
for (const int node_i : children.index_range()) {
const TreeNode &node = *children[node_i];
const TreeNode *node = children[node_i];
GreasePencilLayerTreeNode **dst = &storage.nodes[node_i + 1];
if (node.is_group()) {
const LayerGroup &group = node.as_group();
if (node->is_group()) {
const LayerGroup &group = node->as_group();
save_layer_group_to_storage(group, dst);
}
else if (node.is_layer()) {
const Layer &layer = node.as_layer();
else if (node->is_layer()) {
const Layer &layer = node->as_layer();
save_layer_to_storage(layer, dst);
}
}

View File

@ -57,7 +57,6 @@ TEST(greasepencil, save_layer_tree_to_storage)
group.add_group(std::move(group2));
grease_pencil.root_group_for_write().add_group(std::move(group));
grease_pencil.root_group_for_write().add_layer(Layer(names[6]));
grease_pencil.tag_layer_tree_topology_changed();
/* Save to storage. */
grease_pencil.free_layer_tree_storage();
@ -68,8 +67,13 @@ TEST(greasepencil, save_layer_tree_to_storage)
grease_pencil.runtime = MEM_new<blender::bke::GreasePencilRuntime>(__func__);
grease_pencil.load_layer_tree_from_storage();
grease_pencil.root_group_for_write().foreach_children_with_index_pre_order(
[&](uint64_t i, TreeNode &child) { EXPECT_STREQ(child.name, names[i].data()); });
grease_pencil.print_layer_tree();
Span<const TreeNode *> children = grease_pencil.root_group().children();
for (const int i : children.index_range()) {
const TreeNode &child = *children[i];
EXPECT_STREQ(child.name, names[i].data());
}
}
TEST(greasepencil, set_active_layer_index)
@ -84,7 +88,6 @@ TEST(greasepencil, set_active_layer_index)
const Layer &layer1_ref = grease_pencil.root_group_for_write().add_layer(std::move(layer1));
const Layer &layer2_ref = grease_pencil.root_group_for_write().add_layer(std::move(layer2));
grease_pencil.tag_layer_tree_topology_changed();
grease_pencil.runtime->set_active_layer_index(0);
EXPECT_TRUE(grease_pencil.runtime->has_active_layer());
@ -142,7 +145,6 @@ TEST(greasepencil, remove_drawing)
grease_pencil.root_group_for_write().add_layer(std::move(layer1));
grease_pencil.root_group_for_write().add_layer(std::move(layer2));
grease_pencil.tag_layer_tree_topology_changed();
grease_pencil.remove_drawing(1);
EXPECT_EQ(grease_pencil.drawings().size(), 2);
@ -201,11 +203,15 @@ struct GreasePencilLayerTreeExample {
}
};
TEST(greasepencil, layer_tree_pre_order_iteration_callback)
TEST(greasepencil, layer_tree_pre_order_iteration)
{
GreasePencilLayerTreeExample ex;
ex.root.foreach_children_with_index_pre_order(
[&](uint64_t i, TreeNode &child) { EXPECT_EQ(child.name, ex.names[i]); });
Span<const TreeNode *> children = ex.root.children();
for (const int i : children.index_range()) {
const TreeNode &child = *children[i];
EXPECT_STREQ(child.name, ex.names[i].data());
}
}
TEST(greasepencil, layer_tree_total_size)
@ -217,10 +223,12 @@ TEST(greasepencil, layer_tree_total_size)
TEST(greasepencil, layer_tree_node_types)
{
GreasePencilLayerTreeExample ex;
ex.root.foreach_children_with_index_pre_order([&](uint64_t i, TreeNode &child) {
Span<const TreeNode *> children = ex.root.children();
for (const int i : children.index_range()) {
const TreeNode &child = *children[i];
EXPECT_EQ(child.is_layer(), ex.is_layer[i]);
EXPECT_EQ(child.is_group(), !ex.is_layer[i]);
});
}
}
} // namespace blender::bke::greasepencil::tests

View File

@ -456,7 +456,8 @@ typedef struct GreasePencil {
blender::bke::greasepencil::LayerGroup &root_group_for_write();
blender::Span<const blender::bke::greasepencil::Layer *> layers() const;
blender::Span<blender::bke::greasepencil::Layer *> layers_for_write();
void tag_layer_tree_topology_changed();
void print_layer_tree();
#endif
} GreasePencil;