GPv3: Port frame_clean_duplicate operator

Port the `frame_clean_duplicate` operator to GPv3.
Resolves #114182.

Pull Request: https://projects.blender.org/blender/blender/pulls/116655
This commit is contained in:
Abdelrahman 2024-07-05 09:48:20 +02:00 committed by Falk David
parent 169ad993f5
commit 789bcaae78
2 changed files with 180 additions and 0 deletions

@ -6084,6 +6084,7 @@ class VIEW3D_MT_edit_greasepencil_cleanup(Menu):
layout = self.layout
layout.operator("grease_pencil.clean_loose")
layout.operator("grease_pencil.frame_clean_duplicate")
if ob.mode != 'PAINT_GREASE_PENCIL':
layout.operator("grease_pencil.stroke_merge_by_distance", text="Merge by Distance")

@ -6,6 +6,7 @@
* \ingroup edgreasepencil
*/
#include "BKE_curves.hh"
#include "BLI_map.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_utildefines.h"
@ -413,6 +414,162 @@ static int insert_blank_frame_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static bool attributes_varrays_not_equal(const bke::GAttributeReader &attrs_a,
const bke::GAttributeReader &attrs_b)
{
return (attrs_a.varray.size() != attrs_b.varray.size() ||
attrs_a.varray.type() != attrs_b.varray.type());
}
static bool attributes_varrays_span_data_equal(const bke::GAttributeReader &attrs_a,
const bke::GAttributeReader &attrs_b)
{
if (attrs_a.varray.is_span() && attrs_b.varray.is_span()) {
const GSpan attrs_span_a = attrs_a.varray.get_internal_span();
const GSpan attrs_span_b = attrs_b.varray.get_internal_span();
if (attrs_span_a.data() == attrs_span_b.data()) {
return true;
}
}
return false;
}
template<typename T>
static bool attributes_elements_are_equal(const VArray<T> &attributes_a,
const VArray<T> &attributes_b)
{
const std::optional<T> value_a = attributes_a.get_if_single();
const std::optional<T> value_b = attributes_b.get_if_single();
if (value_a.has_value() && value_b.has_value()) {
return value_a.value() == value_b.value();
}
const VArraySpan attrs_span_a = attributes_a;
const VArraySpan attrs_span_b = attributes_b;
return std::equal(
attrs_span_a.begin(), attrs_span_a.end(), attrs_span_b.begin(), attrs_span_b.end());
}
static bool curves_geometry_is_equal(const bke::CurvesGeometry &curves_a,
const bke::CurvesGeometry &curves_b)
{
using namespace blender::bke;
if (curves_a.points_num() == 0 && curves_b.points_num() == 0) {
return true;
}
if (curves_a.curves_num() != curves_b.curves_num() ||
curves_a.points_num() != curves_b.points_num() || curves_a.offsets() != curves_b.offsets())
{
return false;
}
const AttributeAccessor attributes_a = curves_a.attributes();
const AttributeAccessor attributes_b = curves_b.attributes();
const Set<AttributeIDRef> ids_a = attributes_a.all_ids();
const Set<AttributeIDRef> ids_b = attributes_b.all_ids();
if (ids_a != ids_b) {
return false;
}
for (const AttributeIDRef &id : ids_a) {
GAttributeReader attrs_a = attributes_a.lookup(id);
GAttributeReader attrs_b = attributes_b.lookup(id);
if (attributes_varrays_not_equal(attrs_a, attrs_b)) {
return false;
}
if (attributes_varrays_span_data_equal(attrs_a, attrs_b)) {
return true;
}
bool attributes_are_equal = true;
attribute_math::convert_to_static_type(attrs_a.varray.type(), [&](auto dummy) {
using T = decltype(dummy);
const VArray attributes_a = attrs_a.varray.typed<T>();
const VArray attributes_b = attrs_b.varray.typed<T>();
attributes_are_equal = attributes_elements_are_equal(attributes_a, attributes_b);
});
if (!attributes_are_equal) {
return false;
}
}
return true;
}
static int frame_clean_duplicate_exec(bContext *C, wmOperator *op)
{
using namespace blender::bke::greasepencil;
Object *object = CTX_data_active_object(C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
const bool selected = RNA_boolean_get(op->ptr, "selected");
bool changed = false;
for (Layer *layer : grease_pencil.layers_for_write()) {
if (!layer->is_editable()) {
continue;
}
Vector<int> start_frame_numbers;
for (const FramesMapKeyT key : layer->sorted_keys()) {
const GreasePencilFrame *frame = layer->frames().lookup_ptr(key);
if (selected && !frame->is_selected()) {
continue;
}
if (frame->is_end()) {
continue;
}
start_frame_numbers.append(int(key));
}
Vector<int> frame_numbers_to_delete;
for (const int i : start_frame_numbers.index_range().drop_back(1)) {
const int current = start_frame_numbers[i];
const int next = start_frame_numbers[i + 1];
Drawing *drawing = grease_pencil.get_drawing_at(*layer, current);
Drawing *drawing_next = grease_pencil.get_drawing_at(*layer, next);
if (!drawing || !drawing_next) {
continue;
}
bke::CurvesGeometry &curves = drawing->strokes_for_write();
bke::CurvesGeometry &curves_next = drawing_next->strokes_for_write();
if (!curves_geometry_is_equal(curves, curves_next)) {
continue;
}
frame_numbers_to_delete.append(next);
}
grease_pencil.remove_frames(*layer, frame_numbers_to_delete.as_span());
changed = true;
}
if (changed) {
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr);
}
return OPERATOR_FINISHED;
}
static void GREASE_PENCIL_OT_insert_blank_frame(wmOperatorType *ot)
{
PropertyRNA *prop;
@ -435,6 +592,27 @@ static void GREASE_PENCIL_OT_insert_blank_frame(wmOperatorType *ot)
RNA_def_int(ot->srna, "duration", 0, 0, MAXFRAME, "Duration", "", 0, 100);
}
static void GREASE_PENCIL_OT_frame_clean_duplicate(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Delete Duplicate Frames";
ot->idname = "GREASE_PENCIL_OT_frame_clean_duplicate";
ot->description = "Remove any keyframe that is a duplicate of the previous one";
/* callbacks */
ot->exec = frame_clean_duplicate_exec;
ot->poll = active_grease_pencil_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
prop = RNA_def_boolean(
ot->srna, "selected", false, "Selected", "Only delete selected keyframes");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
bool grease_pencil_copy_keyframes(bAnimContext *ac, KeyframeClipboard &clipboard)
{
using namespace bke::greasepencil;
@ -635,4 +813,5 @@ void ED_operatortypes_grease_pencil_frames()
{
using namespace blender::ed::greasepencil;
WM_operatortype_append(GREASE_PENCIL_OT_insert_blank_frame);
WM_operatortype_append(GREASE_PENCIL_OT_frame_clean_duplicate);
}