GPv3: Implement edit mode undo #117072

Merged
Bastien Montagne merged 3 commits from mont29/blender:gp_undo into main 2024-02-15 10:20:03 +01:00
8 changed files with 480 additions and 0 deletions

View File

@ -513,6 +513,8 @@ class LayerGroup : public ::GreasePencilLayerTreeGroup {
LayerGroup(const LayerGroup &other);
~LayerGroup();
LayerGroup &operator=(const LayerGroup &other);
public:
/* Define the common functions for #TreeNode. */
TREENODE_COMMON_METHODS;

View File

@ -17,6 +17,7 @@ struct UndoType;
struct bContext;
/* IDs */
struct GreasePencil;
struct Main;
struct Mesh;
struct Object;
@ -33,6 +34,7 @@ struct UndoRefID {
struct ptr_ty *ptr; \
char name[MAX_ID_NAME]; \
}
UNDO_REF_ID_TYPE(GreasePencil);
UNDO_REF_ID_TYPE(Mesh);
UNDO_REF_ID_TYPE(Object);
UNDO_REF_ID_TYPE(Scene);

View File

@ -1065,6 +1065,18 @@ LayerGroup::~LayerGroup()
this->runtime = nullptr;
}
LayerGroup &LayerGroup::operator=(const LayerGroup &other)
{
if (this == &other) {
return *this;
}
this->~LayerGroup();
new (this) LayerGroup(other);
return *this;
}
Layer &LayerGroup::add_layer(StringRefNull name)
{
Layer *new_layer = MEM_new<Layer>(__func__, name);
@ -1715,6 +1727,32 @@ blender::MutableSpan<GreasePencilDrawingBase *> GreasePencil::drawings()
this->drawing_array_num};
}
void GreasePencil::resize_drawings(const int new_num)
{
using namespace blender;
BLI_assert(new_num > 0);
const int prev_num = int(this->drawings().size());
if (new_num == prev_num) {
return;
}
if (new_num > prev_num) {
const int add_num = new_num - prev_num;
grow_array<GreasePencilDrawingBase *>(&this->drawing_array, &this->drawing_array_num, add_num);
}
else { /* if (new_num < prev_num) */
const int shrink_num = prev_num - new_num;
MutableSpan<GreasePencilDrawingBase *> old_drawings = this->drawings().drop_front(new_num);
for (const int64_t i : old_drawings.index_range()) {
if (old_drawings[i]) {
MEM_delete(old_drawings[i]);
}
}
shrink_array<GreasePencilDrawingBase *>(
&this->drawing_array, &this->drawing_array_num, shrink_num);
}
}
void GreasePencil::add_empty_drawings(const int add_num)
{
using namespace blender;

View File

@ -29,6 +29,7 @@ set(SRC
intern/grease_pencil_material.cc
intern/grease_pencil_ops.cc
intern/grease_pencil_select.cc
intern/grease_pencil_undo.cc
intern/grease_pencil_utils.cc
)
@ -38,6 +39,7 @@ set(LIB
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::intern::guardedalloc
PRIVATE bf::intern::clog
extern_curve_fit_nd
)

View File

@ -0,0 +1,424 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edgrease_pencil
*/
#include "BLI_string.h"
#include "BLI_task.hh"
#include "BKE_context.hh"
#include "BKE_curves.hh"
#include "BKE_customdata.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_object.hh"
#include "BKE_undo_system.hh"
#include "CLG_log.h"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "ED_grease_pencil.hh"
#include "ED_undo.hh"
#include "MEM_guardedalloc.h"
#include "WM_api.hh"
#include "WM_types.hh"
#include <iostream>
#include <ostream>
static CLG_LogRef LOG = {"ed.undo.greasepencil"};
namespace blender::ed::greasepencil::undo {
/* -------------------------------------------------------------------- */
mont29 marked this conversation as resolved Outdated

bke::CurvesGeometry already has a default constructor, so the = {} is unnecessary here

`bke::CurvesGeometry` already has a default constructor, so the `= {}` is unnecessary here
/** \name Implements ED Undo System
mont29 marked this conversation as resolved Outdated

Might as well use int like the style guide recommends

Might as well use `int` like the style guide recommends
*
* \note This is similar for all edit-mode types.
* \{ */
/**
* Store all drawings, layers and layers data, in each undo step.
*
* Each drawing type has its own array in the undo #StepObject data.
*
* NOTE: Storing Reference drawings is also needed, since drawings can be added or removed, data
mont29 marked this conversation as resolved Outdated

Hm is this still true? When e.g. deleting a keyframe, or deleting a layer, the order of the drawings in the array changes.

Hm is this still true? When e.g. deleting a keyframe, or deleting a layer, the order of the drawings in the array changes.

Yes, but I indeed was suspecting this assumption was not correct. Will fix, thx.

Yes, but I indeed was suspecting this assumption was not correct. Will fix, thx.
* from Reference ones also needs to be stored.
*/
/* Store contextual data and status info during undo step encoding or decoding. */
struct StepEncodeStatus {};
struct StepDecodeStatus {
/**
* In case some reference drawing needs to be re-created, the GreasePencil ID gets a new
* relation to another GreasePencil ID.
*/
bool needs_relationships_update = false;
};
class StepDrawingGeometryBase {
protected:
/* Index of this drawing in the original combined array of all drawings in GreasePencil ID. */
int index_;
mont29 marked this conversation as resolved Outdated

I think this also needs to encode:

  • Layer tree
  • Layer attributes (GreasePencil.layers_data)
I think this also needs to encode: * Layer tree * Layer attributes (`GreasePencil.layers_data`)

But, I thought you told me that the layer tree could not be modified in Edit mode?

But, I thought you told me that the layer tree could not be modified in Edit mode?

We may have missunderstood each other then. In edit mode, the user should be able to freely edit the layers and the keyframes (stored on the layer frames map).

We may have missunderstood each other then. In edit mode, the user should be able to freely edit the layers and the keyframes (stored on the layer frames map).
/* Data from #GreasePencilDrawingBase that needs to be saved in udo steps. */
uint32_t flag_;
mont29 marked this conversation as resolved Outdated

uint index -> int index

`uint index` -> `int index`

Can you explain why you are so strong about using int everywhere?

In this case, the value should never ever be negative, so using uint makes more sense to me...

Can you explain why you are so strong about using `int` everywhere? In this case, the value should never ever be negative, so using `uint` makes more sense to me...

See https://developer.blender.org/docs/handbook/guidelines/c_cpp/#integer-types

Indices should be signed, especially since the drawing API uses signed integers everywhere else.

See https://developer.blender.org/docs/handbook/guidelines/c_cpp/#integer-types Indices should be signed, especially since the drawing API uses signed integers everywhere else.
protected:
/* Ensures that the drawing from the given array at the current index exists, and has the propoer
* type.
*
* Non-existing drawings can happen after extenting the drawings array.
*
* Mismatch in drawing types can happen when some drawings have been deleted between the undo
* step storage, and the current state of the GreasePencil data. */
void decode_valid_drawingtype_at_index_ensure(MutableSpan<GreasePencilDrawingBase *> &drawings,
const GreasePencilDrawingType drawing_type) const

Can use int here too

Can use `int` here too

index_range generates int64_t, would rather keep the conversion to int as close to the actual assignment as possible. Also allows for the assert that given value is actually within valid positive int range.

`index_range` generates `int64_t`, would rather keep the conversion to `int` as close to the actual assignment as possible. Also allows for the assert that given value is actually within valid positive `int` range.
{
/* TODO: Maybe that code should rather be part of GreasePencil:: API, together with
* `add_empty_drawings` and such? */
GreasePencilDrawingBase *drawing = drawings[index_];
if (drawing != nullptr) {
if (drawing->type == drawing_type) {
return;
}
switch (drawing->type) {
case GP_DRAWING:
MEM_delete(&reinterpret_cast<GreasePencilDrawing *>(drawing)->wrap());
break;
case GP_DRAWING_REFERENCE:
mont29 marked this conversation as resolved Outdated

Use this->index

Use `this->index`
MEM_delete(&reinterpret_cast<GreasePencilDrawingReference *>(drawing)->wrap());
mont29 marked this conversation as resolved Outdated

These two arrays are already default constructed when their owner StepObject was constructed. The only reason placement new was used there is that the GreasePencilUndoStep is allocated by C code that doesn't properly initialize its members. So GreasePencilUndoStep is still uninitialized but allocated memory, meaning we have to construct the StepObject array in place. That doesn't apply here. So it should be fine to use this->drawings_geometry.reinitialize(drawings.size())

These two arrays are already default constructed when their owner `StepObject` was constructed. The only reason placement new was used there is that the `GreasePencilUndoStep` is allocated by C code that doesn't properly initialize its members. So `GreasePencilUndoStep` is still uninitialized but allocated memory, meaning we have to construct the `StepObject` array in place. That doesn't apply here. So it should be fine to use `this->drawings_geometry.reinitialize(drawings.size())`
break;
mont29 marked this conversation as resolved Outdated

We're already in the blender namespace here, no need to specify that again. Same below

We're already in the `blender` namespace here, no need to specify that again. Same below
}
drawing = nullptr;
}
if (drawing == nullptr) {
mont29 marked this conversation as resolved Outdated

Seems preferable to use the bke wrapped grease pencil types. Then this loop can be written like:

const Span<const GreasePencilDrawingBase *> drawings = grease_pencil.drawings();
for (const int i : drawings.index_range()) {
Seems preferable to use the bke wrapped grease pencil types. Then this loop can be written like: ``` const Span<const GreasePencilDrawingBase *> drawings = grease_pencil.drawings(); for (const int i : drawings.index_range()) { ```
switch (drawing_type) {
case GP_DRAWING:
drawings[index_] = reinterpret_cast<GreasePencilDrawingBase *>(
MEM_new<bke::greasepencil::Drawing>(__func__));
break;
case GP_DRAWING_REFERENCE:
drawings[index_] = reinterpret_cast<GreasePencilDrawingBase *>(
MEM_new<bke::greasepencil::DrawingReference>(__func__));
break;
}
}
}
};
mont29 marked this conversation as resolved Outdated

Class members are private by default, no need to specify it here

Class members are private by default, no need to specify it here
class StepDrawingGeometry : public StepDrawingGeometryBase {
bke::CurvesGeometry geometry_;
public:
void encode(const GreasePencilDrawing &drawing_geometry,
const int64_t drawing_index,
StepEncodeStatus & /* encode_status */)
{
BLI_assert(drawing_index >= 0 && drawing_index < INT32_MAX);
index_ = int(drawing_index);
flag_ = drawing_geometry.base.flag;
geometry_ = drawing_geometry.geometry.wrap();
}
void decode(GreasePencil &grease_pencil, StepDecodeStatus & /*decode_status*/) const
{
MutableSpan<GreasePencilDrawingBase *> drawings = grease_pencil.drawings();
this->decode_valid_drawingtype_at_index_ensure(drawings, GP_DRAWING);
BLI_assert(drawings[index_]->type == GP_DRAWING);
GreasePencilDrawing &drawing_geometry = *reinterpret_cast<GreasePencilDrawing *>(
drawings[index_]);
drawing_geometry.base.flag = flag_;
drawing_geometry.geometry.wrap() = geometry_;

The copy-assignment constructor will use the copy constructor which will copy the shared caches. These caches remain valid (because any other user that tags them dirty will make a copy for itself), so we shouldn't tag them here.

The copy-assignment constructor will use the copy constructor which will copy the shared caches. These caches remain valid (because any other user that tags them dirty will make a copy for itself), so we shouldn't tag them here.

I had to add this to avoid crashes in drawing code, especially when deleting some points in a curve. As far as I understand, it's because here we copy only the geometry part, not the whole Drawing data, so the runtime data stays in its previous state, and may not be compatible with current data in geometry anymore?

I had to add this to avoid crashes in drawing code, especially when deleting some points in a curve. As far as I understand, it's because here we copy only the `geometry` part, not the whole `Drawing` data, so the runtime data stays in its previous state, and may not be compatible with current data in `geometry` anymore?

Ah, sorry, I missread the code.
Maybe it would be better to just store the bke::greasepencil::Drawing then?
Otherwise, we do have to tag everything, yes.

Ah, sorry, I missread the code. Maybe it would be better to just store the `bke::greasepencil::Drawing` then? Otherwise, we do have to tag everything, yes.

Since runtime caches are shared, could be an option... Although it would still end up having a significant impact on memory used by undo of greasepencil imho?

Since runtime caches are shared, could be an option... Although it would still end up having a significant impact on memory used by undo of greasepencil imho?

Hm, the drawing is basically just the curves geometry. And the caches will be mostly shared. So I don't think so? We could do some measurements if it's a concern.

Hm, the drawing is basically just the curves geometry. And the caches will be mostly shared. So I don't think so? We could do some measurements if it's a concern.

The cache won't be shared anymore once user edit the geometry. This means that it can end up, for the edited drawing, with copies of all caches in undo steps that are only relevant for the undo steps.

Indeed measurement is needed, I suspect the overhead will vary widely depending on the use case (one giant single drawing vs. hundreds of simpler drawings, where only one drawing is edited at a time).

The cache won't be shared anymore once user edit the geometry. This means that it can end up, for the edited drawing, with copies of all caches in undo steps that are only relevant for the undo steps. Indeed measurement is needed, I suspect the overhead will vary widely depending on the use case (one giant single drawing vs. hundreds of simpler drawings, where only one drawing is edited at a time).

There could always be some code that manually frees up memory in undo steps by clearing caches that aren't shared with currently visible geometry. That would be nice for other undo systems as well.

There could always be some code that manually frees up memory in undo steps by clearing caches that aren't shared with currently visible geometry. That would be nice for other undo systems as well.
/* TODO: Check if there is a way to tell if both stored and current geometry are still the
* same, to avoid recomputing the cache all the time for all drawings? */
drawing_geometry.runtime->triangles_cache.tag_dirty();
}
};
class StepDrawingReference : public StepDrawingGeometryBase {
UndoRefID_GreasePencil grease_pencil_ref_ = {};
public:
void encode(const GreasePencilDrawingReference &drawing_reference,
const int64_t drawing_index,
StepEncodeStatus & /* encode_status */)
{
BLI_assert(drawing_index >= 0 && drawing_index < INT32_MAX);
index_ = int(drawing_index);
flag_ = drawing_reference.base.flag;
grease_pencil_ref_.ptr = drawing_reference.id_reference;
}
void decode(GreasePencil &grease_pencil, StepDecodeStatus &decode_status) const
{
MutableSpan<GreasePencilDrawingBase *> drawings = grease_pencil.drawings();
this->decode_valid_drawingtype_at_index_ensure(drawings, GP_DRAWING_REFERENCE);
BLI_assert(drawings[index_]->type == GP_DRAWING_REFERENCE);
GreasePencilDrawingReference &drawing_reference =
*reinterpret_cast<GreasePencilDrawingReference *>(drawings[index_]);
drawing_reference.base.flag = flag_;
if (drawing_reference.id_reference != grease_pencil_ref_.ptr) {
id_us_min(reinterpret_cast<ID *>(drawing_reference.id_reference));
drawing_reference.id_reference = grease_pencil_ref_.ptr;
id_us_plus(reinterpret_cast<ID *>(drawing_reference.id_reference));
mont29 marked this conversation as resolved Outdated

uint -> int

`uint` -> `int`
decode_status.needs_relationships_update = true;
}
}
void foreach_id_ref(UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data)
{
foreach_ID_ref_fn(user_data, reinterpret_cast<UndoRefID *>(&grease_pencil_ref_));
}
};
class StepObject {
public:
UndoRefID_Object obedit_ref = {};
private:
Array<StepDrawingGeometry> drawings_geometry_;
Array<StepDrawingReference> drawings_reference_;
int layers_num_ = 0;
bke::greasepencil::LayerGroup root_group_;
std::string active_layer_name_;
CustomData layers_data_ = {};
private:
void encode_drawings(const GreasePencil &grease_pencil, StepEncodeStatus &encode_status)
{
const Span<const GreasePencilDrawingBase *> drawings = grease_pencil.drawings();
int64_t drawings_geometry_num = 0;
int64_t drawings_reference_num = 0;
for (const int64_t idx : drawings.index_range()) {
const GreasePencilDrawingBase &drawing = *drawings[idx];
switch (drawing.type) {
case GP_DRAWING:
mont29 marked this conversation as resolved Outdated

Use this->, same for other occurences.

Use `this->`, same for other occurences.
drawings_geometry_num++;
break;
case GP_DRAWING_REFERENCE:
drawings_reference_num++;
break;
}
}
drawings_geometry_.reinitialize(drawings_geometry_num);
drawings_reference_.reinitialize(drawings_reference_num);
int drawings_geometry_idx = 0;
int drawings_reference_idx = 0;
for (const int64_t idx : drawings.index_range()) {
const GreasePencilDrawingBase &drawing = *drawings[idx];
switch (drawing.type) {
case GP_DRAWING:
drawings_geometry_[drawings_geometry_idx++].encode(
reinterpret_cast<const GreasePencilDrawing &>(drawing), idx, encode_status);
break;
case GP_DRAWING_REFERENCE:
drawings_reference_[drawings_reference_idx++].encode(
reinterpret_cast<const GreasePencilDrawingReference &>(drawing), idx, encode_status);
break;
}
}
}
void decode_drawings(GreasePencil &grease_pencil, StepDecodeStatus &decode_status) const
{
const int drawing_array_num = int(drawings_geometry_.size() + drawings_reference_.size());
grease_pencil.resize_drawings(drawing_array_num);
for (const StepDrawingGeometry &drawing : drawings_geometry_) {
drawing.decode(grease_pencil, decode_status);
}
for (const StepDrawingReference &drawing : drawings_reference_) {
drawing.decode(grease_pencil, decode_status);
}
}
void encode_layers(const GreasePencil &grease_pencil, StepEncodeStatus & /*encode_status*/)
{
layers_num_ = int(grease_pencil.layers().size());
CustomData_copy(
&grease_pencil.layers_data, &layers_data_, eCustomDataMask(CD_MASK_ALL), layers_num_);
if (grease_pencil.has_active_layer()) {
active_layer_name_ = grease_pencil.get_active_layer()->name();
}
root_group_ = grease_pencil.root_group();
}
void decode_layers(GreasePencil &grease_pencil, StepDecodeStatus & /*decode_status*/) const
{
if (grease_pencil.root_group_ptr) {
MEM_delete(&grease_pencil.root_group());
}
grease_pencil.root_group_ptr = MEM_new<bke::greasepencil::LayerGroup>(__func__, root_group_);
BLI_assert(layers_num_ == grease_pencil.layers().size());
if (!active_layer_name_.empty()) {
const bke::greasepencil::TreeNode *active_node =
grease_pencil.root_group().find_node_by_name(active_layer_name_);
if (active_node && active_node->is_layer()) {
mont29 marked this conversation as resolved Outdated

Would be good to use BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(objects); }); here. And remove the free further down.

Would be good to use `BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(objects); });` here. And remove the free further down.
grease_pencil.set_active_layer(&active_node->as_layer());
}
}
mont29 marked this conversation as resolved Outdated

I think you can just use us->objects.reinitialize(objects_num)

I think you can just use `us->objects.reinitialize(objects_num)`

From what I understood (hopefully @HooglyBoogly can confirm or tell me am wrong ;)), this is not possible here because the GreasePencilUndoStep is created from a C-type allocation (malloc), so no constructor is called for us->objects.

At least Curves undo code also does the same 'placement new' (see curves_undo.cc#L61).

This is also why we need an explicit call the this array's destructor in the free callback below.

From what I understood (hopefully @HooglyBoogly can confirm or tell me am wrong ;)), this is not possible here because the `GreasePencilUndoStep` is created from a C-type allocation (`malloc`), so no constructor is called for `us->objects`. At least Curves undo code also does the same 'placement new' (see [curves_undo.cc#L61]( https://projects.blender.org/blender/blender/src/commit/801e24379eec1305daafa5cb8a63e0bb2cc5e3c3/source/blender/editors/curves/intern/curves_undo.cc#L61)). This is also why we need an explicit call the this array's destructor in the `free` callback below.

Yes, you're right. I assumed the constructor was called.

Yes, you're right. I assumed the constructor was called.
CustomData_copy(
&layers_data_, &grease_pencil.layers_data, eCustomDataMask(CD_MASK_ALL), layers_num_);
}
public:
void encode(Object *ob, StepEncodeStatus &encode_status)
{
const GreasePencil &grease_pencil = *static_cast<GreasePencil *>(ob->data);
this->obedit_ref.ptr = ob;
this->encode_drawings(grease_pencil, encode_status);
this->encode_layers(grease_pencil, encode_status);
}
void decode(StepDecodeStatus &decode_status) const
{
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(this->obedit_ref.ptr->data);
this->decode_drawings(grease_pencil, decode_status);
this->decode_layers(grease_pencil, decode_status);
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
}
void foreach_id_ref(UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data)
{
foreach_ID_ref_fn(user_data, reinterpret_cast<UndoRefID *>(&this->obedit_ref));
for (StepDrawingReference &drawing_ref : drawings_reference_) {
drawing_ref.foreach_id_ref(foreach_ID_ref_fn, user_data);
}
}
};
struct GreasePencilUndoStep {
UndoStep step;
/** See #ED_undo_object_editmode_validate_scene_from_windows code comment for details. */
UndoRefID_Scene scene_ref = {};
Array<StepObject> objects;
};
static bool step_encode(bContext *C, Main *bmain, UndoStep *us_p)
{
GreasePencilUndoStep *us = reinterpret_cast<GreasePencilUndoStep *>(us_p);
StepEncodeStatus encode_status;
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
uint objects_num = 0;
Object **objects = ED_undo_editmode_objects_from_view_layer(scene, view_layer, &objects_num);
BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(objects); })
us->scene_ref.ptr = scene;
new (&us->objects) Array<StepObject>(objects_num);
threading::parallel_for(us->objects.index_range(), 8, [&](const IndexRange range) {
for (const int64_t i : range) {
Object *ob = objects[i];
us->objects[i].encode(ob, encode_status);
}
});
bmain->is_memfile_undo_flush_needed = true;
return true;
}
static void step_decode(
bContext *C, Main *bmain, UndoStep *us_p, const eUndoStepDir /*dir*/, bool /*is_final*/)
{
GreasePencilUndoStep *us = reinterpret_cast<GreasePencilUndoStep *>(us_p);
StepDecodeStatus decode_status;
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
ED_undo_object_editmode_validate_scene_from_windows(
CTX_wm_manager(C), us->scene_ref.ptr, &scene, &view_layer);
ED_undo_object_editmode_restore_helper(scene,
view_layer,
&us->objects.first().obedit_ref.ptr,
uint(us->objects.size()),
sizeof(decltype(us->objects)::value_type));
BLI_assert(BKE_object_is_in_editmode(us->objects.first().obedit_ref.ptr));
for (const StepObject &step_object : us->objects) {
step_object.decode(decode_status);
}
if (decode_status.needs_relationships_update) {
DEG_relations_tag_update(bmain);
}
ED_undo_object_set_active_or_warn(
scene, view_layer, us->objects.first().obedit_ref.ptr, us_p->name, &LOG);
bmain->is_memfile_undo_flush_needed = true;
WM_event_add_notifier(C, NC_GEOM | ND_DATA, nullptr);
}
static void step_free(UndoStep *us_p)
{
GreasePencilUndoStep *us = reinterpret_cast<GreasePencilUndoStep *>(us_p);
us->objects.~Array();
}
static void foreach_ID_ref(UndoStep *us_p,
UndoTypeForEachIDRefFn foreach_ID_ref_fn,
void *user_data)
{
GreasePencilUndoStep *us = reinterpret_cast<GreasePencilUndoStep *>(us_p);
foreach_ID_ref_fn(user_data, reinterpret_cast<UndoRefID *>(&us->scene_ref));
for (StepObject &object : us->objects) {
object.foreach_id_ref(foreach_ID_ref_fn, user_data);
}
}
/** \} */
} // namespace blender::ed::greasepencil::undo
void ED_undosys_type_grease_pencil(UndoType *ut)
{
using namespace blender::ed;
ut->name = "Edit GreasePencil";
ut->poll = greasepencil::editable_grease_pencil_poll;
ut->step_encode = greasepencil::undo::step_encode;
ut->step_decode = greasepencil::undo::step_decode;
ut->step_free = greasepencil::undo::step_free;
ut->step_foreach_ID_ref = greasepencil::undo::foreach_ID_ref;
ut->flags = UNDOTYPE_FLAG_NEED_CONTEXT_FOR_ENCODE;
ut->step_size = sizeof(greasepencil::undo::GreasePencilUndoStep);
}

View File

@ -25,6 +25,7 @@ struct KeyframeEditData;
struct wmKeyConfig;
struct ToolSettings;
struct Scene;
struct UndoType;
struct ViewDepths;
struct View3D;
namespace blender {
@ -51,6 +52,8 @@ void ED_operatortypes_grease_pencil_edit();
void ED_operatortypes_grease_pencil_material();
void ED_operatormacros_grease_pencil();
void ED_keymap_grease_pencil(wmKeyConfig *keyconf);
void ED_undosys_type_grease_pencil(UndoType *undo_type);
/**
* Get the selection mode for Grease Pencil selection operators: point, stroke, segment.
*/

View File

@ -13,6 +13,7 @@
#include "ED_armature.hh"
#include "ED_curve.hh"
#include "ED_curves.hh"
#include "ED_grease_pencil.hh"
#include "ED_lattice.hh"
#include "ED_mball.hh"
#include "ED_mesh.hh"
@ -37,6 +38,7 @@ void ED_undosys_type_init()
BKE_undosys_type_append(ED_mball_undosys_type);
BKE_undosys_type_append(ED_mesh_undosys_type);
BKE_undosys_type_append(ED_curves_undosys_type);
BKE_undosys_type_append(ED_undosys_type_grease_pencil);
/* Paint Modes */
BKE_UNDOSYS_TYPE_IMAGE = BKE_undosys_type_append(ED_image_undosys_type);

View File

@ -529,6 +529,13 @@ typedef struct GreasePencil {
void remove_layer(blender::bke::greasepencil::Layer &layer);
/* Drawing API functions. */
/**
* Low-level resizing of drawings array. Only allocates new entries in the array, no drawings are
* created in case of size increase. In case of size decrease, the removed drawings are deleted.
*/
void resize_drawings(const int new_num);
/** Add `add_num` new empty geometry drawings. */
void add_empty_drawings(int add_num);
void add_duplicate_drawings(int duplicate_num,
const blender::bke::greasepencil::Drawing &drawing);