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:
parent
169ad993f5
commit
789bcaae78
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user