GPv3: Implement edit mode undo #117072
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
/** \name Implements ED Undo System
|
||||
mont29 marked this conversation as resolved
Outdated
Hans Goudey
commented
Might as well use 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
Falk David
commented
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.
Bastien Montagne
commented
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
Falk David
commented
I think this also needs to encode:
I think this also needs to encode:
* Layer tree
* Layer attributes (`GreasePencil.layers_data`)
Bastien Montagne
commented
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?
Falk David
commented
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
Hans Goudey
commented
`uint index` -> `int index`
Bastien Montagne
commented
Can you explain why you are so strong about using In this case, the value should never ever be negative, so using 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...
Falk David
commented
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
|
||||
Hans Goudey
commented
Can use Can use `int` here too
Bastien Montagne
commented
`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
Falk David
commented
Use Use `this->index`
|
||||
MEM_delete(&reinterpret_cast<GreasePencilDrawingReference *>(drawing)->wrap());
|
||||
mont29 marked this conversation as resolved
Outdated
Hans Goudey
commented
These two arrays are already default constructed when their owner 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
Hans Goudey
commented
We're already in the 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
Hans Goudey
commented
Seems preferable to use the bke wrapped grease pencil types. Then this loop can be written like:
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
Hans Goudey
commented
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_;
|
||||
|
||||
Falk David
commented
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.
Bastien Montagne
commented
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 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?
Falk David
commented
Ah, sorry, I missread the code. 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.
Bastien Montagne
commented
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?
Falk David
commented
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.
Bastien Montagne
commented
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).
Hans Goudey
commented
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
Hans Goudey
commented
`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
Falk David
commented
Use 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
Falk David
commented
Would be good to use 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
Falk David
commented
I think you can just use I think you can just use `us->objects.reinitialize(objects_num)`
Bastien Montagne
commented
From what I understood (hopefully @HooglyBoogly can confirm or tell me am wrong ;)), this is not possible here because the 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 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.
Falk David
commented
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);
|
||||
}
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
bke::CurvesGeometry
already has a default constructor, so the= {}
is unnecessary here