Blender V5.0
keyframes_general.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2008 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <algorithm>
10#include <cfloat>
11#include <cmath>
12#include <cstdlib>
13#include <cstring>
14#include <iostream>
15
16#include "MEM_guardedalloc.h"
17
18#include "BLI_listbase.h"
19#include "BLI_math_vector.h"
20#include "BLI_string.h"
21#include "BLI_string_utils.hh"
22#include "BLI_utildefines.h"
23
24#include "DNA_anim_types.h"
25#include "DNA_object_types.h"
26#include "DNA_scene_types.h"
27#include "DNA_space_types.h"
28
29#include "BKE_action.hh"
30#include "BKE_curve.hh"
31#include "BKE_fcurve.hh"
32#include "BKE_main.hh"
33#include "BKE_scene.hh"
34
35#include "RNA_access.hh"
36#include "RNA_enum_types.hh"
37#include "RNA_path.hh"
38
39#include "ED_keyframes_edit.hh"
40
41#include "ANIM_action.hh"
42#include "ANIM_animdata.hh"
43#include "ANIM_fcurve.hh"
44
46
47/* This file contains code for various keyframe-editing tools which are 'destructive'
48 * (i.e. they will modify the order of the keyframes, and change the size of the array).
49 * While some of these tools may eventually be moved out into blenkernel, for now, it is
50 * fine to have these calls here.
51 *
52 * There are also a few tools here which cannot be easily coded for in the other system (yet).
53 * These may also be moved around at some point, but for now, they are best added here.
54 *
55 * - Joshua Leung, Dec 2008
56 */
57
58/* **************************************************** */
59
61{
62 bool changed = false;
63
64 /* this can only work when there is an F-Curve, and also when there are some BezTriples */
65 if (ELEM(nullptr, fcu, fcu->bezt)) {
66 return changed;
67 }
68
69 for (int i = 0; i < fcu->totvert; i++) {
70 /* If a key is selected */
71 if (fcu->bezt[i].f2 & SELECT) {
72 /* Expand the list */
73 BezTriple *newbezt = MEM_calloc_arrayN<BezTriple>((fcu->totvert + 1), "beztriple");
74
75 memcpy(newbezt, fcu->bezt, sizeof(BezTriple) * (i + 1));
76 memcpy(newbezt + i + 1, fcu->bezt + i, sizeof(BezTriple));
77 memcpy(newbezt + i + 2, fcu->bezt + i + 1, sizeof(BezTriple) * (fcu->totvert - (i + 1)));
78 fcu->totvert++;
79 changed = true;
80 /* reassign pointers... (free old, and add new) */
81 MEM_freeN(fcu->bezt);
82 fcu->bezt = newbezt;
83
84 /* Unselect the current key */
85 BEZT_DESEL_ALL(&fcu->bezt[i]);
86 i++;
87
88 /* Select the copied key */
89 BEZT_SEL_ALL(&fcu->bezt[i]);
90 }
91 }
92 return changed;
93}
94
95/* -------------------------------------------------------------------- */
98
100 float thresh,
101 bool cleardefault,
102 const bool only_selected_keys)
103{
104 FCurve *fcu = static_cast<FCurve *>(ale->key_data);
105 BezTriple *old_bezts, *bezt, *beztn;
106 BezTriple *lastb;
107 int totCount, i;
108
109 /* Check if any points. */
110 if ((fcu == nullptr) || (fcu->bezt == nullptr) || (fcu->totvert == 0) ||
111 (!cleardefault && fcu->totvert == 1))
112 {
113 return;
114 }
115
116 /* make a copy of the old BezTriples, and clear F-Curve */
117 old_bezts = fcu->bezt;
118 totCount = fcu->totvert;
119 fcu->bezt = nullptr;
120 fcu->totvert = 0;
121
122 /* now insert first keyframe, as it should be ok */
123 bezt = old_bezts;
125 if (!(bezt->f2 & SELECT)) {
126 lastb = fcu->bezt;
127 lastb->f1 = lastb->f2 = lastb->f3 = 0;
128 }
129
130 /* Loop through BezTriples, comparing them. Skip any that do
131 * not fit the criteria for "ok" points.
132 */
133 for (i = 1; i < totCount; i++) {
134 float prev[2], cur[2], next[2];
135
136 /* get BezTriples and their values */
137 if (i < (totCount - 1)) {
138 beztn = (old_bezts + (i + 1));
139 next[0] = beztn->vec[1][0];
140 next[1] = beztn->vec[1][1];
141 }
142 else {
143 beztn = nullptr;
144 next[0] = next[1] = 0.0f;
145 }
146 lastb = (fcu->bezt + (fcu->totvert - 1));
147 bezt = (old_bezts + i);
148
149 /* get references for quicker access */
150 prev[0] = lastb->vec[1][0];
151 prev[1] = lastb->vec[1][1];
152 cur[0] = bezt->vec[1][0];
153 cur[1] = bezt->vec[1][1];
154
155 if (only_selected_keys && !(bezt->f2 & SELECT)) {
157 lastb = (fcu->bezt + (fcu->totvert - 1));
158 lastb->f1 = lastb->f2 = lastb->f3 = 0;
159 continue;
160 }
161
162 /* check if current bezt occurs at same time as last ok */
163 if (IS_EQT(cur[0], prev[0], thresh)) {
164 /* If there is a next beztriple, and if occurs at the same time, only insert
165 * if there is a considerable distance between the points, and also if the
166 * current is further away than the next one is to the previous.
167 */
168 if (beztn && IS_EQT(cur[0], next[0], thresh) && (IS_EQT(next[1], prev[1], thresh) == 0)) {
169 /* only add if current is further away from previous */
170 if (cur[1] > next[1]) {
171 if (IS_EQT(cur[1], prev[1], thresh) == 0) {
172 /* add new keyframe */
174 }
175 }
176 }
177 else {
178 /* only add if values are a considerable distance apart */
179 if (IS_EQT(cur[1], prev[1], thresh) == 0) {
180 /* add new keyframe */
182 }
183 }
184 }
185 else {
186 /* checks required are dependent on whether this is last keyframe or not */
187 if (beztn) {
188 /* does current have same value as previous and next? */
189 if (IS_EQT(cur[1], prev[1], thresh) == 0) {
190 /* add new keyframe */
192 }
193 else if (IS_EQT(cur[1], next[1], thresh) == 0) {
194 /* add new keyframe */
196 }
197 }
198 else {
199 /* add if value doesn't equal that of previous */
200 if (IS_EQT(cur[1], prev[1], thresh) == 0) {
201 /* add new keyframe */
203 }
204 }
205 }
206 }
207
208 /* now free the memory used by the old BezTriples */
209 if (old_bezts) {
210 MEM_freeN(old_bezts);
211 }
212
213 /* final step, if there is just one key in fcurve, check if it's
214 * the default value and if is, remove fcurve completely. */
215 if (cleardefault && fcu->totvert == 1) {
216 float default_value = 0.0f;
218 PropertyRNA *prop;
219 PointerRNA id_ptr = RNA_id_pointer_create(ale->id);
220
221 /* get property to read from, and get value as appropriate */
222 if (RNA_path_resolve_property(&id_ptr, fcu->rna_path, &ptr, &prop)) {
223 if (RNA_property_type(prop) == PROP_FLOAT) {
224 default_value = RNA_property_float_get_default_index(&ptr, prop, fcu->array_index);
225 }
226 }
227
228 if (fcu->bezt->vec[1][1] == default_value) {
230
231 /* check if curve is really unused and if it is, return signal for deletion */
232 if (BKE_fcurve_is_empty(fcu)) {
233 AnimData *adt = ale->adt;
235 ale->key_data = nullptr;
236 }
237 }
238 }
239}
240
249 const int start_index,
250 int *r_segment_start_idx,
251 int *r_segment_len)
252{
253 *r_segment_start_idx = 0;
254 *r_segment_len = 0;
255
256 bool in_segment = false;
257
258 for (int i = start_index; i < fcu->totvert; i++) {
259 const bool point_is_selected = fcu->bezt[i].f2 & SELECT;
260 const bool point_is_ignored = fcu->bezt[i].f2 & BEZT_FLAG_IGNORE_TAG;
261
262 if (point_is_selected && !point_is_ignored) {
263 if (!in_segment) {
264 *r_segment_start_idx = i;
265 in_segment = true;
266 }
267 (*r_segment_len)++;
268 }
269 else if (in_segment) {
270 /* If the curve point is not selected then we have reached the end of the selected curve
271 * segment. */
272 return true; /* Segment found. */
273 }
274 }
275
276 /* If the last curve point was in the segment, `r_segment_len` and `r_segment_start_idx`
277 * are already updated and true is returned. */
278 return in_segment;
279}
280
282{
283 ListBase segments = {nullptr, nullptr};
284
285 /* Ignore baked curves. */
286 if (!fcu->bezt) {
287 return segments;
288 }
289
290 int segment_start_idx = 0;
291 int segment_len = 0;
292 int current_index = 0;
293
294 while (find_fcurve_segment(fcu, current_index, &segment_start_idx, &segment_len)) {
295 FCurveSegment *segment;
296 segment = MEM_callocN<FCurveSegment>("FCurveSegment");
297 segment->start_index = segment_start_idx;
298 segment->length = segment_len;
299 BLI_addtail(&segments, segment);
300 current_index = segment_start_idx + segment_len;
301 }
302 return segments;
303}
304
305static const BezTriple *fcurve_segment_start_get(FCurve *fcu, int index)
306{
307 const BezTriple *start_bezt = index - 1 >= 0 ? &fcu->bezt[index - 1] : &fcu->bezt[index];
308 return start_bezt;
309}
310
311static const BezTriple *fcurve_segment_end_get(FCurve *fcu, int index)
312{
313 const BezTriple *end_bezt = index < fcu->totvert ? &fcu->bezt[index] : &fcu->bezt[index - 1];
314 return end_bezt;
315}
316
317/* ---------------- */
318
319void blend_to_neighbor_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
320{
321 const BezTriple *target_bezt;
322 /* Find which key to blend towards. */
323 if (factor < 0) {
324 target_bezt = fcurve_segment_start_get(fcu, segment->start_index);
325 }
326 else {
327 target_bezt = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
328 }
329 const float lerp_factor = fabs(factor);
330 /* Blend each key individually. */
331 for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
332 const float key_y_value = interpf(target_bezt->vec[1][1], fcu->bezt[i].vec[1][1], lerp_factor);
334 }
335}
336
337/* ---------------- */
338
340{
341 const int len = RNA_property_array_length(ptr, prop);
342
343 float default_value = 0;
344 /* Find the default value of that property. */
345 switch (RNA_property_type(prop)) {
346 case PROP_BOOLEAN:
347 if (len) {
348 default_value = float(RNA_property_boolean_get_default_index(ptr, prop, fcu->array_index));
349 }
350 else {
351 default_value = float(RNA_property_boolean_get_default(ptr, prop));
352 }
353 break;
354 case PROP_INT:
355 if (len) {
356 default_value = RNA_property_int_get_default_index(ptr, prop, fcu->array_index);
357 }
358 else {
359 default_value = RNA_property_int_get_default(ptr, prop);
360 }
361 break;
362 case PROP_FLOAT:
363 if (len) {
364 default_value = RNA_property_float_get_default_index(ptr, prop, fcu->array_index);
365 }
366 else {
367 default_value = RNA_property_float_get_default(ptr, prop);
368 }
369 break;
370
371 default:
372 break;
373 }
374 return default_value;
375}
376
377void blend_to_default_fcurve(PointerRNA *id_ptr, FCurve *fcu, const float factor)
378{
380 PropertyRNA *prop;
381
382 /* Check if path is valid. */
383 if (!RNA_path_resolve_property(id_ptr, fcu->rna_path, &ptr, &prop)) {
384 return;
385 }
386
387 const float default_value = get_default_rna_value(fcu, prop, &ptr);
388
389 /* Blend selected keys to default. */
390 for (int i = 0; i < fcu->totvert; i++) {
391 if (!(fcu->bezt[i].f2 & SELECT)) {
392 continue;
393 }
394 const float key_y_value = interpf(default_value, fcu->bezt[i].vec[1][1], factor);
396 }
397}
398
399/* ---------------- */
400
401void scale_average_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
402{
403 float y = 0;
404
405 /* Find first the average of the y values to then use it in the final calculation. */
406 for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
407 y += fcu->bezt[i].vec[1][1];
408 }
409
410 const float y_average = y / segment->length;
411
412 for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
413 const float key_y_value = interpf(y_average, fcu->bezt[i].vec[1][1], 1 - factor);
415 }
416}
417
418/* ---------------- */
419
421 double *A, *d1, *d2;
423};
424
426{
428 "Butterworth Coefficients");
429 bw_coeff->filter_order = filter_order;
430 bw_coeff->d1 = MEM_calloc_arrayN<double>(filter_order, "coeff filtered");
431 bw_coeff->d2 = MEM_calloc_arrayN<double>(filter_order, "coeff samples");
432 bw_coeff->A = MEM_calloc_arrayN<double>(filter_order, "Butterworth A");
433 return bw_coeff;
434}
435
437{
438 MEM_freeN(bw_coeff->d1);
439 MEM_freeN(bw_coeff->d2);
440 MEM_freeN(bw_coeff->A);
441 MEM_freeN(bw_coeff);
442}
443
444void ED_anim_calculate_butterworth_coefficients(const float cutoff_frequency,
445 const float sampling_frequency,
446 ButterworthCoefficients *bw_coeff)
447{
448 double s = double(sampling_frequency);
449 const double a = tan(M_PI * cutoff_frequency / s);
450 const double a2 = a * a;
451 double r;
452 for (int i = 0; i < bw_coeff->filter_order; ++i) {
453 r = sin(M_PI * (2.0 * i + 1.0) / (4.0 * bw_coeff->filter_order));
454 s = a2 + 2.0 * a * r + 1.0;
455 bw_coeff->A[i] = a2 / s;
456 bw_coeff->d1[i] = 2.0 * (1 - a2) / s;
457 bw_coeff->d2[i] = -(a2 - 2.0 * a * r + 1.0) / s;
458 }
459}
460
462 double x, double *w0, double *w1, double *w2, ButterworthCoefficients *bw_coeff)
463{
464 for (int i = 0; i < bw_coeff->filter_order; i++) {
465 w0[i] = bw_coeff->d1[i] * w1[i] + bw_coeff->d2[i] * w2[i] + x;
466 x = bw_coeff->A[i] * (w0[i] + 2.0 * w1[i] + w2[i]);
467 w2[i] = w1[i];
468 w1[i] = w0[i];
469 }
470 return x;
471}
472
473static float butterworth_calculate_blend_value(float *samples,
474 const float *filtered_values,
475 const int start_index,
476 const int end_index,
477 const int sample_index,
478 const int blend_in_out)
479{
480 if (start_index == end_index || blend_in_out == 0) {
481 return samples[start_index];
482 }
483
484 const float blend_in_y_samples = samples[start_index];
485 const float blend_out_y_samples = samples[end_index];
486
487 const float blend_in_y_filtered = filtered_values[start_index + blend_in_out];
488 const float blend_out_y_filtered = filtered_values[end_index - blend_in_out];
489
490 const float slope_in_samples = samples[start_index] - samples[start_index - 1];
491 const float slope_out_samples = samples[end_index] - samples[end_index + 1];
492 const float slope_in_filtered = filtered_values[start_index + blend_in_out - 1] -
493 filtered_values[start_index + blend_in_out];
494 const float slope_out_filtered = filtered_values[end_index - blend_in_out] -
495 filtered_values[end_index - blend_in_out - 1];
496
497 if (sample_index - start_index <= blend_in_out) {
498 const int blend_index = sample_index - start_index;
499 const float blend_in_out_factor = clamp_f(float(blend_index) / blend_in_out, 0.0f, 1.0f);
500 const float blend_value = interpf(blend_in_y_filtered +
501 slope_in_filtered * (blend_in_out - blend_index),
502 blend_in_y_samples + slope_in_samples * blend_index,
503 blend_in_out_factor);
504 return blend_value;
505 }
506 if (end_index - sample_index <= blend_in_out) {
507 const int blend_index = end_index - sample_index;
508 const float blend_in_out_factor = clamp_f(float(blend_index) / blend_in_out, 0.0f, 1.0f);
509 const float blend_value = interpf(blend_out_y_filtered +
510 slope_out_filtered * (blend_in_out - blend_index),
511 blend_out_y_samples + slope_out_samples * blend_index,
512 blend_in_out_factor);
513 return blend_value;
514 }
515 return 0;
516}
517
519 FCurveSegment *segment,
520 float *samples,
521 const int sample_count,
522 const float factor,
523 const int blend_in_out,
524 const int sample_rate,
525 ButterworthCoefficients *bw_coeff)
526{
527 const int filter_order = bw_coeff->filter_order;
528
529 float *filtered_values = MEM_calloc_arrayN<float>(sample_count,
530 "Butterworth Filtered FCurve Values");
531
532 double *w0 = MEM_calloc_arrayN<double>(filter_order, "w0");
533 double *w1 = MEM_calloc_arrayN<double>(filter_order, "w1");
534 double *w2 = MEM_calloc_arrayN<double>(filter_order, "w2");
535
536 /* The values need to be offset so the first sample starts at 0. This avoids oscillations at the
537 * start and end of the curve. */
538 const float fwd_offset = samples[0];
539
540 for (int i = 0; i < sample_count; i++) {
541 const double x = double(samples[i] - fwd_offset);
542 const double filtered_value = butterworth_filter_value(x, w0, w1, w2, bw_coeff);
543 filtered_values[i] = float(filtered_value) + fwd_offset;
544 }
545
546 for (int i = 0; i < filter_order; i++) {
547 w0[i] = 0.0;
548 w1[i] = 0.0;
549 w2[i] = 0.0;
550 }
551
552 const float bwd_offset = filtered_values[sample_count - 1];
553
554 /* Run the filter backwards as well to remove phase offset. */
555 for (int i = sample_count - 1; i >= 0; i--) {
556 const double x = double(filtered_values[i] - bwd_offset);
557 const double filtered_value = butterworth_filter_value(x, w0, w1, w2, bw_coeff);
558 filtered_values[i] = float(filtered_value) + bwd_offset;
559 }
560
561 const int segment_end_index = segment->start_index + segment->length;
562 BezTriple left_bezt = fcu->bezt[segment->start_index];
563 BezTriple right_bezt = fcu->bezt[segment_end_index - 1];
564
565 const int samples_start_index = filter_order * sample_rate;
566 const int samples_end_index = int(right_bezt.vec[1][0] - left_bezt.vec[1][0] + filter_order) *
567 sample_rate;
568
569 const int blend_in_out_clamped = min_ii(blend_in_out,
570 (samples_end_index - samples_start_index) / 2);
571
572 for (int i = segment->start_index; i < segment_end_index; i++) {
573 float blend_in_out_factor;
574 if (blend_in_out_clamped == 0) {
575 blend_in_out_factor = 1;
576 }
577 else if (i < segment->start_index + segment->length / 2) {
578 blend_in_out_factor = min_ff(float(i - segment->start_index) / blend_in_out_clamped, 1.0f);
579 }
580 else {
581 blend_in_out_factor = min_ff(float(segment_end_index - i - 1) / blend_in_out_clamped, 1.0f);
582 }
583
584 const float x_delta = fcu->bezt[i].vec[1][0] - left_bezt.vec[1][0] + filter_order;
585 /* Using round() instead of casting to int. Casting would introduce a stepping issue when the
586 * x-value is just below a full frame. */
587 const int filter_index = round(x_delta * sample_rate);
588 const float blend_value = butterworth_calculate_blend_value(samples,
589 filtered_values,
590 samples_start_index,
591 samples_end_index,
592 filter_index,
593 blend_in_out_clamped);
594
595 const float blended_value = interpf(
596 filtered_values[filter_index], blend_value, blend_in_out_factor);
597 const float key_y_value = interpf(blended_value, samples[filter_index], factor);
598
600 }
601
602 MEM_freeN(filtered_values);
603 MEM_freeN(w0);
604 MEM_freeN(w1);
605 MEM_freeN(w2);
606}
607
608/* ---------------- */
609
610void ED_ANIM_get_1d_gauss_kernel(const float sigma, const int kernel_size, double *r_kernel)
611{
612 BLI_assert(sigma > 0.0f);
613 BLI_assert(kernel_size > 0);
614 const double sigma_sq = 2.0 * sigma * sigma;
615 double sum = 0.0;
616
617 for (int i = 0; i < kernel_size; i++) {
618 const double normalized_index = double(i) / (kernel_size - 1);
619 r_kernel[i] = exp(-normalized_index * normalized_index / sigma_sq);
620 if (i == 0) {
621 sum += r_kernel[i];
622 }
623 else {
624 /* We only calculate half the kernel,
625 * the normalization needs to take that into account. */
626 sum += r_kernel[i] * 2;
627 }
628 }
629
630 /* Normalize kernel values. */
631 for (int i = 0; i < kernel_size; i++) {
632 r_kernel[i] /= sum;
633 }
634}
635
637 FCurveSegment *segment,
638 const float *original_values,
639 float *samples,
640 const int sample_count,
641 const float factor,
642 const int kernel_size,
643 const double *kernel)
644{
645 const int segment_end_index = segment->start_index + segment->length;
646 const float segment_start_x = fcu->bezt[segment->start_index].vec[1][0];
647 float *filtered_samples = static_cast<float *>(MEM_dupallocN(samples));
648 for (int i = kernel_size; i < sample_count - kernel_size; i++) {
649 /* Apply the kernel. */
650 double filter_result = samples[i] * kernel[0];
651 for (int j = 1; j <= kernel_size; j++) {
652 const double kernel_value = kernel[j];
653 filter_result += samples[i + j] * kernel_value;
654 filter_result += samples[i - j] * kernel_value;
655 }
656 filtered_samples[i] = filter_result;
657 }
658
659 for (int i = segment->start_index; i < segment_end_index; i++) {
660 const float sample_index_f = (fcu->bezt[i].vec[1][0] - segment_start_x) + kernel_size;
661 /* Using round() instead of (int). The latter would create stepping on x-values that are just
662 * below a full frame. */
663 const int sample_index = round(sample_index_f);
664 /* Sampling the two closest indices to support subframe keys. This can end up being the same
665 * index as sample_index, in which case the interpolation will happen between two identical
666 * values. */
667 const int secondary_index = clamp_i(
668 sample_index + signum_i(sample_index_f - sample_index), 0, sample_count - 1);
669
670 const float filter_result = interpf(filtered_samples[secondary_index],
671 filtered_samples[sample_index],
672 std::abs(sample_index_f - sample_index));
673 const float key_y_value = interpf(
674 filter_result, original_values[i - segment->start_index], factor);
676 }
677 MEM_freeN(filtered_samples);
678}
679/* ---------------- */
680
681static float ease_sigmoid_function(const float x, const float width, const float shift)
682{
683 const float x_shift = (x - shift) * width;
684 const float y = x_shift / sqrt(1 + pow2f(x_shift));
685 /* Normalize result to 0-1. */
686 return (y + 1) * 0.5f;
687}
688
690 FCurveSegment *segment,
691 const float factor,
692 const float width)
693{
694 const BezTriple *left_key = fcurve_segment_start_get(fcu, segment->start_index);
695 const BezTriple *right_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
696
697 const float key_x_range = right_key->vec[1][0] - left_key->vec[1][0];
698 const float key_y_range = right_key->vec[1][1] - left_key->vec[1][1];
699
700 /* Happens if there is only 1 key on the FCurve. Needs to be skipped because it
701 * would be a divide by 0. */
702 if (IS_EQF(key_x_range, 0.0f)) {
703 return;
704 }
705
706 /* Using the factor on the X-shift we are basically moving the curve horizontally. */
707 const float shift = -factor;
708 const float y_min = ease_sigmoid_function(-1, width, shift);
709 const float y_max = ease_sigmoid_function(1, width, shift);
710
711 for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
712 /* Mapping the x-location of the key within the segment to a -1/1 range. */
713 const float x = ((fcu->bezt[i].vec[1][0] - left_key->vec[1][0]) / key_x_range) * 2 - 1;
714 const float y = ease_sigmoid_function(x, width, shift);
715 /* Normalizing the y value to the min and max to ensure that the keys at the end are not
716 * detached from the rest of the animation. */
717 const float blend = (y - y_min) * (1 / (y_max - y_min));
718
719 const float key_y_value = left_key->vec[1][1] + key_y_range * blend;
721 }
722}
723
724/* ---------------- */
725
726void blend_offset_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
727{
728 const BezTriple *left_key = fcurve_segment_start_get(fcu, segment->start_index);
729 const BezTriple *right_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
730
731 float y_delta;
732
733 if (factor > 0) {
734 const BezTriple segment_last_key = fcu->bezt[segment->start_index + segment->length - 1];
735 y_delta = right_key->vec[1][1] - segment_last_key.vec[1][1];
736 }
737 else {
738 const BezTriple segment_first_key = fcu->bezt[segment->start_index];
739 y_delta = left_key->vec[1][1] - segment_first_key.vec[1][1];
740 }
741
742 const float offset_value = y_delta * fabs(factor);
743 for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
744 const float key_y_value = fcu->bezt[i].vec[1][1] + offset_value;
746 }
747}
748
749/* ---------------- */
750
751static float s_curve(float x, float slope, float width, float height, float xshift, float yshift)
752{
753 /* Formula for 'S' curve we use for the "ease" sliders.
754 * The shift values move the curve vertically or horizontally.
755 * The range of the curve used is from 0 to 1 on "x" and "y"
756 * so we can scale it (width and height) and move it (`xshift` and y `yshift`)
757 * to crop the part of the curve we need. Slope determines how curvy the shape is. */
758 float y = height * pow((x - xshift), slope) /
759 (pow((x - xshift), slope) + pow((width - (x - xshift)), slope)) +
760 yshift;
761
762 /* The curve doesn't do what we want beyond our margins so we clamp the values. */
763 if (x > xshift + width) {
764 y = height + yshift;
765 }
766 else if (x < xshift) {
767 y = yshift;
768 }
769 return y;
770}
771
772void blend_to_ease_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
773{
774 const BezTriple *left_key = fcurve_segment_start_get(fcu, segment->start_index);
775 const BezTriple *right_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
776
777 const float key_x_range = right_key->vec[1][0] - left_key->vec[1][0];
778 const float key_y_range = right_key->vec[1][1] - left_key->vec[1][1];
779
780 /* Happens if there is only 1 key on the FCurve. Needs to be skipped because it
781 * would be a divide by 0. */
782 if (IS_EQF(key_x_range, 0.0f)) {
783 return;
784 }
785
786 const float slope = 3.0;
787 /* By doubling the size of the "S" curve we just one side of it, a "C" shape. */
788 const float width = 2.0;
789 const float height = 2.0;
790 float xy_shift;
791
792 /* Shifting the x and y values we can decide what side of the "S" shape to use. */
793 if (factor > 0) {
794 xy_shift = -1.0;
795 }
796 else {
797 xy_shift = 0.0;
798 }
799
800 for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
801
802 const float x = (fcu->bezt[i].vec[1][0] - left_key->vec[1][0]) / key_x_range;
803 const float ease = s_curve(x, slope, width, height, xy_shift, xy_shift);
804 const float base = left_key->vec[1][1] + key_y_range * ease;
805
806 float y_delta;
807 if (factor > 0) {
808 y_delta = base - fcu->bezt[i].vec[1][1];
809 }
810 else {
811 y_delta = fcu->bezt[i].vec[1][1] - base;
812 }
813
814 const float key_y_value = fcu->bezt[i].vec[1][1] + y_delta * factor;
816 }
817}
818
819/* ---------------- */
820
821bool match_slope_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
822{
823 const BezTriple *left_key = fcurve_segment_start_get(fcu, segment->start_index);
824 const BezTriple *right_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
825
826 BezTriple beyond_key;
827 const BezTriple *reference_key;
828
829 if (factor >= 0) {
830 /* Stop the function if there is no key beyond the right neighboring one. */
831 if (segment->start_index + segment->length >= fcu->totvert - 1) {
832 return false;
833 }
834 reference_key = right_key;
835 beyond_key = fcu->bezt[segment->start_index + segment->length + 1];
836 }
837 else {
838 /* Stop the function if there is no key beyond the left neighboring one. */
839 if (segment->start_index <= 1) {
840 return false;
841 }
842 reference_key = left_key;
843 beyond_key = fcu->bezt[segment->start_index - 2];
844 }
845
846 /* This delta values are used to get the relationship between the bookend keys and the
847 * reference keys beyond those. */
848 const float y_delta = beyond_key.vec[1][1] - reference_key->vec[1][1];
849 const float x_delta = beyond_key.vec[1][0] - reference_key->vec[1][0];
850
851 /* Avoids dividing by 0. */
852 if (x_delta == 0) {
853 return false;
854 }
855
856 for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
857
858 /* These new deltas are used to determine the relationship between the current key and the
859 * bookend ones. */
860 const float new_x_delta = fcu->bezt[i].vec[1][0] - reference_key->vec[1][0];
861 const float new_y_delta = new_x_delta * y_delta / x_delta;
862
863 const float delta = reference_key->vec[1][1] + new_y_delta - fcu->bezt[i].vec[1][1];
864
865 const float key_y_value = fcu->bezt[i].vec[1][1] + delta * fabs(factor);
867 }
868 return true;
869}
870
871/* ---------------- */
872
874 FCurveSegment *segment,
875 const float factor,
876 tShearDirection direction)
877{
878 const BezTriple *left_key = fcurve_segment_start_get(fcu, segment->start_index);
879 const BezTriple *right_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
880
881 const float key_x_range = right_key->vec[1][0] - left_key->vec[1][0];
882 const float key_y_range = right_key->vec[1][1] - left_key->vec[1][1];
883
884 /* Happens if there is only 1 key on the FCurve. Needs to be skipped because it
885 * would be a divide by 0. */
886 if (IS_EQF(key_x_range, 0.0f)) {
887 return;
888 }
889
890 for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
891 /* For easy calculation of the curve, the values are normalized. */
892 float normalized_x;
893 if (direction == SHEAR_FROM_LEFT) {
894 normalized_x = (fcu->bezt[i].vec[1][0] - left_key->vec[1][0]) / key_x_range;
895 }
896 else {
897 normalized_x = (right_key->vec[1][0] - fcu->bezt[i].vec[1][0]) / key_x_range;
898 }
899
900 const float y_delta = key_y_range * normalized_x;
901
902 const float key_y_value = fcu->bezt[i].vec[1][1] + y_delta * factor;
904 }
905}
906
907/* ---------------- */
908
909void push_pull_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
910{
911 const BezTriple *left_key = fcurve_segment_start_get(fcu, segment->start_index);
912 const BezTriple *right_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
913
914 const float key_x_range = right_key->vec[1][0] - left_key->vec[1][0];
915 const float key_y_range = right_key->vec[1][1] - left_key->vec[1][1];
916
917 /* Happens if there is only 1 key on the FCurve. Needs to be skipped because it
918 * would be a divide by 0. */
919 if (IS_EQF(key_x_range, 0.0f)) {
920 return;
921 }
922
923 for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
924 /* For easy calculation of the curve, the values are normalized. */
925 const float normalized_x = (fcu->bezt[i].vec[1][0] - left_key->vec[1][0]) / key_x_range;
926
927 const float linear = left_key->vec[1][1] + key_y_range * normalized_x;
928
929 const float delta = fcu->bezt[i].vec[1][1] - linear;
930
931 const float key_y_value = linear + delta * factor;
933 }
934}
935
936/* ---------------- */
937
938void time_offset_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float frame_offset)
939{
940 /* Two bookend keys of the fcurve are needed to be able to cycle the values. */
941 const BezTriple *last_key = &fcu->bezt[fcu->totvert - 1];
942 const BezTriple *first_key = &fcu->bezt[0];
943
944 const float fcu_x_range = last_key->vec[1][0] - first_key->vec[1][0];
945 const float fcu_y_range = last_key->vec[1][1] - first_key->vec[1][1];
946
947 const float first_key_x = first_key->vec[1][0];
948
949 /* If we operate directly on the fcurve there will be a feedback loop
950 * so we need to capture the "y" values on an array to then apply them on a second loop. */
951 float *y_values = MEM_calloc_arrayN<float>(segment->length, "Time Offset Samples");
952
953 for (int i = 0; i < segment->length; i++) {
954 /* This simulates the fcu curve moving in time. */
955 const float time = fcu->bezt[segment->start_index + i].vec[1][0] + frame_offset;
956 /* Need to normalize time to first_key to specify that as the wrapping point. */
957 const float wrapped_time = floored_fmod(time - first_key_x, fcu_x_range) + first_key_x;
958 const float delta_y = fcu_y_range * floorf((time - first_key_x) / fcu_x_range);
959
960 const float key_y_value = evaluate_fcurve(fcu, wrapped_time) + delta_y;
961 y_values[i] = key_y_value;
962 }
963
964 for (int i = 0; i < segment->length; i++) {
965 BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[segment->start_index + i], y_values[i]);
966 }
967 MEM_freeN(y_values);
968}
969
970/* ---------------- */
971
973 FCurveSegment *segment,
974 const float factor,
975 const FCurveSegmentAnchor anchor)
976{
977 const BezTriple *reference_key;
978 switch (anchor) {
980 reference_key = fcurve_segment_start_get(fcu, segment->start_index);
981 break;
983 reference_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
984 break;
985 }
986
987 for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
988 const float key_y_value = interpf(fcu->bezt[i].vec[1][1], reference_key->vec[1][1], factor);
990 }
991}
992
993/* ---------------- */
994
995void breakdown_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
996{
997 const BezTriple *left_bezt = fcurve_segment_start_get(fcu, segment->start_index);
998 const BezTriple *right_bezt = fcurve_segment_end_get(fcu,
999 segment->start_index + segment->length);
1000
1001 const float lerp_factor = (factor + 1) / 2;
1002 for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
1003 const float key_y_value = interpf(right_bezt->vec[1][1], left_bezt->vec[1][1], lerp_factor);
1005 }
1006}
1007
1009
1010/* -------------------------------------------------------------------- */
1013
1014/* Check if the keyframe interpolation type is supported */
1015static bool prepare_for_decimate(FCurve *fcu, int i)
1016{
1017 switch (fcu->bezt[i].ipo) {
1018 case BEZT_IPO_BEZ:
1019 /* We do not need to do anything here as the keyframe already has the required setting.
1020 */
1021 return true;
1022 case BEZT_IPO_LIN:
1023 /* Convert to a linear bezt curve to be able to use the decimation algorithm. */
1024 fcu->bezt[i].ipo = BEZT_IPO_BEZ;
1025 fcu->bezt[i].h1 = HD_FREE;
1026 fcu->bezt[i].h2 = HD_FREE;
1027
1028 if (i != 0) {
1029 float h1[3];
1030 sub_v3_v3v3(h1, fcu->bezt[i - 1].vec[1], fcu->bezt[i].vec[1]);
1031 mul_v3_fl(h1, 1.0f / 3.0f);
1032 add_v3_v3(h1, fcu->bezt[i].vec[1]);
1033 copy_v3_v3(fcu->bezt[i].vec[0], h1);
1034 }
1035
1036 if (i + 1 != fcu->totvert) {
1037 float h2[3];
1038 sub_v3_v3v3(h2, fcu->bezt[i + 1].vec[1], fcu->bezt[i].vec[1]);
1039 mul_v3_fl(h2, 1.0f / 3.0f);
1040 add_v3_v3(h2, fcu->bezt[i].vec[1]);
1041 copy_v3_v3(fcu->bezt[i].vec[2], h2);
1042 }
1043 return true;
1044 default:
1045 /* These are unsupported. */
1046 return false;
1047 }
1048}
1049
1050/* Decimate the given curve segment. */
1052 int bezt_segment_start_idx,
1053 int bezt_segment_len,
1054 float remove_ratio,
1055 float error_sq_max)
1056{
1057 int selected_len = bezt_segment_len;
1058
1059 /* Make sure that we can remove the start/end point of the segment if they
1060 * are not the start/end point of the curve. BKE_curve_decimate_bezt_array
1061 * has a check that prevents removal of the first and last index in the
1062 * passed array. */
1063 if (bezt_segment_len + bezt_segment_start_idx != fcu->totvert &&
1064 prepare_for_decimate(fcu, bezt_segment_len + bezt_segment_start_idx))
1065 {
1066 bezt_segment_len++;
1067 }
1068 if (bezt_segment_start_idx != 0 && prepare_for_decimate(fcu, bezt_segment_start_idx - 1)) {
1069 bezt_segment_start_idx--;
1070 bezt_segment_len++;
1071 }
1072
1073 const int target_fcurve_verts = ceil(bezt_segment_len - selected_len * remove_ratio);
1074
1075 BKE_curve_decimate_bezt_array(&fcu->bezt[bezt_segment_start_idx],
1076 bezt_segment_len,
1077 12, /* The actual resolution displayed in the viewport is dynamic
1078 * so we just pick a value that preserves the curve shape. */
1079 false,
1080 SELECT,
1082 error_sq_max,
1083 target_fcurve_verts);
1084}
1085
1086bool decimate_fcurve(bAnimListElem *ale, float remove_ratio, float error_sq_max)
1087{
1088 FCurve *fcu = static_cast<FCurve *>(ale->key_data);
1089 /* Check if the curve actually has any points. */
1090 if (fcu == nullptr || fcu->bezt == nullptr || fcu->totvert == 0) {
1091 return true;
1092 }
1093
1094 BezTriple *old_bezts = fcu->bezt;
1095
1096 bool can_decimate_all_selected = true;
1097
1098 for (int i = 0; i < fcu->totvert; i++) {
1099 /* Ignore keyframes that are not supported. */
1100 if (!prepare_for_decimate(fcu, i)) {
1101 can_decimate_all_selected = false;
1102 fcu->bezt[i].f2 |= BEZT_FLAG_IGNORE_TAG;
1103 }
1104 /* Make sure that the temp flag is unset as we use it to determine what to remove. */
1105 fcu->bezt[i].f2 &= ~BEZT_FLAG_TEMP_TAG;
1106 }
1107
1108 ListBase segments = find_fcurve_segments(fcu);
1109 LISTBASE_FOREACH (FCurveSegment *, segment, &segments) {
1111 fcu, segment->start_index, segment->length, remove_ratio, error_sq_max);
1112 }
1113 BLI_freelistN(&segments);
1114
1115 uint old_totvert = fcu->totvert;
1116 fcu->bezt = nullptr;
1117 fcu->totvert = 0;
1118
1119 for (int i = 0; i < old_totvert; i++) {
1120 BezTriple *bezt = (old_bezts + i);
1121 bezt->f2 &= ~BEZT_FLAG_IGNORE_TAG;
1122 if ((bezt->f2 & BEZT_FLAG_TEMP_TAG) == 0) {
1124 }
1125 }
1126 /* now free the memory used by the old BezTriples */
1127 if (old_bezts) {
1128 MEM_freeN(old_bezts);
1129 }
1130
1131 return can_decimate_all_selected;
1132}
1133
1135
1136/* -------------------------------------------------------------------- */
1139
1140/* temp struct used for smooth_fcurve */
1142 float *h1, *h2, *h3; /* bezt->vec[0,1,2][1] */
1143 float y1, y2, y3; /* averaged before/new/after y-values */
1144};
1145
1147{
1148 int totSel = 0;
1149
1150 if (fcu->bezt == nullptr) {
1151 return;
1152 }
1153
1154 /* first loop through - count how many verts are selected */
1155 BezTriple *bezt = fcu->bezt;
1156 for (int i = 0; i < fcu->totvert; i++, bezt++) {
1157 if (BEZT_ISSEL_ANY(bezt)) {
1158 totSel++;
1159 }
1160 }
1161
1162 /* if any points were selected, allocate tSmooth_Bezt points to work on */
1163 if (totSel >= 3) {
1164 tSmooth_Bezt *tarray, *tsb;
1165
1166 /* allocate memory in one go */
1167 tsb = tarray = MEM_calloc_arrayN<tSmooth_Bezt>(totSel, "tSmooth_Bezt Array");
1168
1169 /* populate tarray with data of selected points */
1170 bezt = fcu->bezt;
1171 for (int i = 0, x = 0; (i < fcu->totvert) && (x < totSel); i++, bezt++) {
1172 if (BEZT_ISSEL_ANY(bezt)) {
1173 /* tsb simply needs pointer to vec, and index */
1174 tsb->h1 = &bezt->vec[0][1];
1175 tsb->h2 = &bezt->vec[1][1];
1176 tsb->h3 = &bezt->vec[2][1];
1177
1178 /* advance to the next tsb to populate */
1179 if (x < totSel - 1) {
1180 tsb++;
1181 }
1182 else {
1183 break;
1184 }
1185 }
1186 }
1187
1188 /* calculate the new smoothed F-Curve's with weighted averages:
1189 * - this is done with two passes to avoid progressive corruption errors
1190 * - uses 5 points for each operation (which stores in the relevant handles)
1191 * - previous: w/a ratio = 3:5:2:1:1
1192 * - next: w/a ratio = 1:1:2:5:3
1193 */
1194
1195 /* round 1: calculate smoothing deltas and new values */
1196 tsb = tarray;
1197 for (int i = 0; i < totSel; i++, tsb++) {
1198 /* Don't touch end points (otherwise, curves slowly explode,
1199 * as we don't have enough data there). */
1200 if (ELEM(i, 0, (totSel - 1)) == 0) {
1201 const tSmooth_Bezt *tP1 = tsb - 1;
1202 const tSmooth_Bezt *tP2 = (i - 2 > 0) ? (tsb - 2) : (nullptr);
1203 const tSmooth_Bezt *tN1 = tsb + 1;
1204 const tSmooth_Bezt *tN2 = (i + 2 < totSel) ? (tsb + 2) : (nullptr);
1205
1206 const float p1 = *tP1->h2;
1207 const float p2 = (tP2) ? (*tP2->h2) : (*tP1->h2);
1208 const float c1 = *tsb->h2;
1209 const float n1 = *tN1->h2;
1210 const float n2 = (tN2) ? (*tN2->h2) : (*tN1->h2);
1211
1212 /* calculate previous and next, then new position by averaging these */
1213 tsb->y1 = (3 * p2 + 5 * p1 + 2 * c1 + n1 + n2) / 12;
1214 tsb->y3 = (p2 + p1 + 2 * c1 + 5 * n1 + 3 * n2) / 12;
1215
1216 tsb->y2 = (tsb->y1 + tsb->y3) / 2;
1217 }
1218 }
1219
1220 /* round 2: apply new values */
1221 tsb = tarray;
1222 for (int i = 0; i < totSel; i++, tsb++) {
1223 /* don't touch end points, as their values weren't touched above */
1224 if (ELEM(i, 0, (totSel - 1)) == 0) {
1225 /* y2 takes the average of the 2 points */
1226 *tsb->h2 = tsb->y2;
1227
1228 /* handles are weighted between their original values and the averaged values */
1229 *tsb->h1 = ((*tsb->h1) * 0.7f) + (tsb->y1 * 0.3f);
1230 *tsb->h3 = ((*tsb->h3) * 0.7f) + (tsb->y3 * 0.3f);
1231 }
1232 }
1233
1234 /* free memory required for tarray */
1235 MEM_freeN(tarray);
1236 }
1237
1238 /* recalculate handles */
1240}
1241
1243
1244/* -------------------------------------------------------------------- */
1254
1255namespace blender::ed::animation {
1256
1258
1260{
1261 /* No need to check the channelbags for having F-Curves, as they are only
1262 * added when an F-Curve needs to be stored. */
1263 return this->keyframe_data.channelbags().is_empty();
1264}
1265
1267{
1268 if (this->keyframe_data.channelbags().size() != 1) {
1269 return false;
1270 }
1271
1272 const animrig::Channelbag *channelbag = this->keyframe_data.channelbag(0);
1273 return channelbag->fcurves().size() == 1;
1274}
1275
1276bool KeyframeCopyBuffer::is_bone(const FCurve &fcurve) const
1277{
1278 return this->bone_fcurves.contains(&fcurve);
1279}
1280
1282{
1283 /* The number of slots can be taken from any of these properties, so just assert that they are
1284 * consistent with each other. */
1285 BLI_assert(this->keyframe_data.channelbags().size() == this->slot_identifiers.size());
1286
1287 /* Return the number of channelbags, as this is actually storing data; the `slot_identifiers`
1288 * field is more cache-like. Even though they should produce the same value, I (Sybren) feel more
1289 * comfortable using the channelbag count, as channelbags are defined as per-slot F-Curve
1290 * storage. */
1291 return this->keyframe_data.channelbags().size();
1292}
1293
1295{
1296 /* TODO: use a nicer data structure so this loop isn't necessary any more. Or use a vector, in
1297 * which case we always need to loop, but it'll be small and maybe the lower overhead of Vector
1298 * (vs Map) will be worth it anyway. */
1299 for (const auto [handle, identifier] : this->slot_identifiers.items()) {
1300 if (identifier == slot_identifier) {
1301 return this->keyframe_data.channelbag_for_slot(handle);
1302 }
1303 }
1304 return nullptr;
1305}
1306
1308{
1309 using namespace blender::animrig;
1310
1311 std::cout << "KeyframeCopyBuffer contents:" << std::endl;
1312 std::cout << " frame range: " << this->first_frame << "-" << this->last_frame << std::endl;
1313 std::cout << " scene frame: " << this->current_frame << std::endl;
1314
1315 if (is_empty()) {
1316 std::cout << " buffer is empty" << std::endl;
1317 }
1318
1319 if (is_single_fcurve()) {
1320 std::cout << " buffer has single F-Curve" << std::endl;
1321 }
1322
1323 const StripKeyframeData &keyframe_data = this->keyframe_data;
1324 std::cout << " channelbags: " << keyframe_data.channelbags().size() << std::endl;
1325 for (const Channelbag *channelbag : keyframe_data.channelbags()) {
1326
1327 const std::string &slot_identifier = this->slot_identifiers.lookup(channelbag->slot_handle);
1328 if (slot_identifier == KeyframeCopyBuffer::SLOTLESS_SLOT_IDENTIFIER) {
1329 std::cout << " - Channelbag for slotless F-Curves:" << std::endl;
1330 }
1331 else {
1332 std::cout << " - Channelbag for slot \"" << slot_identifier << "\":" << std::endl;
1333 }
1334 for (const FCurve *fcurve : channelbag->fcurves()) {
1335 std::cout << " " << fcurve->rna_path << "[" << fcurve->array_index << "]";
1336 if (this->is_bone(*fcurve)) {
1337 std::cout << " (bone)";
1338 }
1339 std::cout << std::endl;
1340 }
1341 }
1342}
1343
1344} // namespace blender::ed::animation
1345
1347{
1348 using namespace blender::ed::animation;
1350 keyframe_copy_buffer = MEM_new<KeyframeCopyBuffer>(__func__);
1351}
1352
1354{
1355 using namespace blender::ed::animation;
1357 MEM_delete(keyframe_copy_buffer);
1358 }
1359}
1360
1361/* ------------------- */
1362
1363static bool is_animating_bone(const bAnimListElem *ale)
1364{
1366
1367 if (!ale->id || GS(ale->id->name) != ID_OB) {
1368 return false;
1369 }
1370
1371 Object *ob = reinterpret_cast<Object *>(ale->id);
1372 if (ob->type != OB_ARMATURE) {
1373 return false;
1374 }
1375
1376 FCurve *fcurve = static_cast<FCurve *>(ale->key_data);
1377 if (!fcurve->rna_path) {
1378 return false;
1379 }
1380
1381 char bone_name[sizeof(bPoseChannel::name)];
1382 if (!BLI_str_quoted_substr(fcurve->rna_path, "pose.bones[", bone_name, sizeof(bone_name))) {
1383 return false;
1384 }
1385
1386 bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
1387 return pchan != nullptr;
1388};
1389
1390namespace {
1391
1392using namespace blender;
1393using namespace blender::animrig;
1394using namespace blender::ed::animation;
1395
1409class SlotMapper {
1410 public:
1411 KeyframeCopyBuffer &buffer;
1412
1414 Map<std::pair<const Action *, slot_handle_t>, slot_handle_t> orig_to_buffer_slots;
1415
1426 static constexpr animrig::slot_handle_t SLOTLESS_SLOT_HANDLE = 0;
1427
1431 Channelbag &channelbag_for_ale(const bAnimListElem *ale)
1432 {
1433 /* Slots really only exist with F-Curves from Actions. */
1434 const Action *map_key_action;
1435 slot_handle_t map_key_handle;
1436 if (GS(ale->fcurve_owner_id->name) == ID_AC) {
1437 map_key_action = &reinterpret_cast<bAction *>(ale->fcurve_owner_id)->wrap();
1438 map_key_handle = ale->slot_handle;
1439 }
1440 else {
1441 map_key_action = nullptr;
1442 map_key_handle = SLOTLESS_SLOT_HANDLE;
1443 }
1444
1445 const auto map_key = std::make_pair(map_key_action, map_key_handle);
1446
1447 if (const std::optional<slot_handle_t> opt_internal_slot_handle =
1448 this->orig_to_buffer_slots.lookup_try(map_key))
1449 {
1450 /* There already is a slot for this, and that means there is a channelbag too. */
1451 const slot_handle_t internal_slot_handle = *opt_internal_slot_handle;
1452 BLI_assert(this->buffer.slot_identifiers.contains(internal_slot_handle));
1453
1454 Channelbag *channelbag = this->buffer.keyframe_data.channelbag_for_slot(
1455 internal_slot_handle);
1456 BLI_assert_msg(channelbag, "If the slot exists, so should the channelbag");
1457
1458 return *channelbag;
1459 }
1460
1461 /* Create a new Channelbag for this F-Curve. */
1462 const slot_handle_t internal_slot_handle = ++this->buffer.last_used_slot_handle;
1463 Channelbag &channelbag = this->buffer.keyframe_data.channelbag_for_slot_add(
1464 internal_slot_handle);
1465 this->orig_to_buffer_slots.add_new(map_key, internal_slot_handle);
1466
1467 /* Determine the slot identifier. */
1468 StringRef slot_identifier;
1469 if (map_key_action) {
1470 const Slot *ale_slot = map_key_action->slot_for_handle(ale->slot_handle);
1471 BLI_assert_msg(ale_slot, "Slot for copied keyframes is expected to exist.");
1472 slot_identifier = ale_slot->identifier;
1473 }
1474 else {
1476 }
1477 this->buffer.slot_identifiers.add(internal_slot_handle, slot_identifier);
1478
1479 /* ale->id might be nullptr on unassigned slots. */
1480 this->buffer.slot_animated_ids.add_new(internal_slot_handle, ale->id);
1481
1482 return channelbag;
1483 }
1484};
1485
1486} // namespace
1487
1489{
1490 using namespace blender::ed::animation;
1491 using namespace blender::animrig;
1492
1494
1495 SlotMapper slot_mapper{*keyframe_copy_buffer};
1496
1497 LISTBASE_FOREACH (const bAnimListElem *, ale, anim_data) {
1499 const FCurve *fcu = static_cast<const FCurve *>(ale->key_data);
1500
1501 /* Firstly, check if F-Curve has any selected keyframes. Skip if no selected
1502 * keyframes found (so no need to create unnecessary copy-buffer data). This
1503 * check should also eliminate any problems associated with using
1504 * sample-data. */
1506 nullptr,
1507 /* The const-cast is because I (Sybren) want to have `fcu` as `const` in as much of
1508 * this LISTBASE_FOREACH as possible. The code is alternating between the to-be-copied
1509 * F-Curve and the copy, and I want the compiler to help distinguish those. */
1510 const_cast<FCurve *>(fcu),
1511 nullptr,
1513 nullptr) == 0)
1514 {
1515 continue;
1516 }
1517
1518 Channelbag &channelbag = slot_mapper.channelbag_for_ale(ale);
1519
1520 /* Create an F-Curve on this ChannelBag. */
1521 FCurve &fcurve_copy = *BKE_fcurve_create();
1522 fcurve_copy.rna_path = BLI_strdup(fcu->rna_path);
1523 fcurve_copy.array_index = fcu->array_index;
1524 channelbag.fcurve_append(fcurve_copy);
1525
1526 if (fcu->grp) {
1527 bActionGroup &group = channelbag.channel_group_ensure(fcu->grp->name);
1528 channelbag.fcurve_assign_to_channel_group(fcurve_copy, group);
1529 }
1530
1531 /* Detect if this is a bone. We do that here rather than during pasting
1532 * because ID pointers will get invalidated on undo / loading another file. */
1533 if (is_animating_bone(ale)) {
1534 keyframe_copy_buffer->bone_fcurves.add(&fcurve_copy);
1535 }
1536
1537 /* Add selected keyframes to the buffer F-Curve. */
1538 int bezt_index = 0;
1539 BezTriple *bezt = fcu->bezt;
1540 for (; bezt_index < fcu->totvert; bezt_index++, bezt++) {
1541 /* Don't copy if only a handle is selected. */
1542 if (!BEZT_ISSEL_IDX(bezt, 1)) {
1543 continue;
1544 }
1545
1546 /* Use INSERTKEY_FAST as that avoids recalculating handles. They should
1547 * remain as-is in the buffer. */
1549
1550 /* Keep track of the extremities. */
1551 const float bezt_frame = bezt->vec[1][0];
1552 keyframe_copy_buffer->first_frame = std::min(keyframe_copy_buffer->first_frame, bezt_frame);
1553 keyframe_copy_buffer->last_frame = std::max(keyframe_copy_buffer->last_frame, bezt_frame);
1554 }
1555 }
1556
1557 keyframe_copy_buffer->current_frame = ac->scene->r.cfra;
1558
1559#ifndef NDEBUG
1560 /* TODO: remove this call completely when slot-aware copy-pasting has been implemented. */
1561 keyframe_copy_buffer->debug_print();
1562#endif
1563
1564 return !keyframe_copy_buffer->is_empty();
1565}
1566
1567namespace blender::ed::animation {
1568
1569std::optional<std::string> flip_names(const blender::StringRefNull rna_path)
1570{
1571 int ofs_start, ofs_end;
1572 if (!BLI_str_quoted_substr_range(rna_path.c_str(), "pose.bones[", &ofs_start, &ofs_end)) {
1573 return {};
1574 }
1575
1576 /* NOTE: there is no need to un-escape the string to flip it.
1577 * However the buffer does need to be twice the size. */
1578 char bname_new[MAX_VGROUP_NAME * 2];
1579
1580 /* Take a copy so it's 0-terminated. */
1581 const std::string bone_name = rna_path.substr(ofs_start, ofs_end - ofs_start);
1582
1583 BLI_string_flip_side_name(bname_new, bone_name.c_str(), false, sizeof(bname_new));
1584
1585 return rna_path.substr(0, ofs_start) + bname_new + rna_path.substr(ofs_end);
1586}
1587
1588using pastebuf_match_func = bool (*)(Main *bmain,
1589 const FCurve &fcurve_to_match,
1590 const FCurve &fcurve_in_copy_buffer,
1591 blender::animrig::slot_handle_t slot_handle_in_copy_buffer,
1592 bool from_single,
1593 bool to_single,
1594 bool flip);
1595
1596namespace {
1597
1598using namespace blender::animrig;
1599
1600enum class SlotMatchMethod {
1602 NONE = 0,
1604 IDENTIFIER = 1,
1606 SELECTION = 2,
1608 SELECTION_AND_IDENTIFIER = 3,
1609};
1610
1611} // namespace
1612
1617static SlotMatchMethod get_slot_match_method(const bool from_single,
1618 const bool to_single,
1619 const KeyframePasteContext &paste_context)
1620{
1621 BLI_assert_msg(!(from_single && to_single),
1622 "The from-single-to-single case is expected to be implemented as a special case "
1623 "in `paste_animedit_keys()`");
1624 UNUSED_VARS_NDEBUG(from_single);
1625
1626 if (paste_context.num_fcurves_selected == 0) {
1627 /* No F-Curves selected to explicitly paste into. The names of the selected slots determine the
1628 * source Channelbag. */
1629
1630 if (paste_context.num_slots_selected == 0) {
1631 /* Since none of the slots were selected to paste into, just do a match by name and ignore
1632 * their selection state. */
1633 return SlotMatchMethod::IDENTIFIER;
1634 }
1635
1636 if (keyframe_copy_buffer->num_slots() == 1) {
1637 /* Copied from one slot, pasting into one or more selected slots. This should only look at
1638 * selection of the target slot, and ignore slot names. */
1639 return SlotMatchMethod::SELECTION;
1640 }
1641
1642 /* Copied from multiple slots, in which case slot names do matter, and the targets should be
1643 * limited by their slot selection (i.e. F-Curves from unselected slots should not be pasted
1644 * into). */
1645 return SlotMatchMethod::SELECTION_AND_IDENTIFIER;
1646 }
1647
1648 if (to_single) {
1649 /* Copying into a single F-Curve. Because the single-to-single case is handled somewhere else,
1650 * we know multiple F-Curves were copied. Slot names do not matter, matching is done purely on
1651 * RNA path + array index.
1652 *
1653 * TODO: slot names may matter here after all, when the copy buffer has multiple slots. In that
1654 * case the single F-Curve to paste into can still match multiple copied F-Curves. That's a
1655 * corner case to implement at some other time, though. */
1656 return SlotMatchMethod::NONE;
1657 }
1658
1659 /* Pasting into multiple F-Curves. Whether slot names matter depends on how many slots the
1660 * key were copied from:
1661 * - 0 slots: impossible, then there would not be any keys at all.
1662 * - 1 slot: slot names do not matter. This makes it possible to copy-paste between slots.
1663 * - 2+ slots: slot names matter, only paste within the same slot as the copied data.
1664 */
1665
1666 const int num_slots_copied = keyframe_copy_buffer->num_slots();
1667 BLI_assert_msg(num_slots_copied > 0,
1668 "If any keyframes were copied, they MUST have come from some slot.");
1669 if (num_slots_copied > 1) {
1670 /* Copied from multiple slots, so do name matching. */
1671 return SlotMatchMethod::IDENTIFIER;
1672 }
1673
1674 return SlotMatchMethod::NONE;
1675}
1676
1684 Main *bmain,
1685 const bAnimListElem &ale_to_paste_into,
1686 const bool from_single,
1687 const bool to_single,
1688 const KeyframePasteContext &paste_context)
1689{
1690 using namespace blender::animrig;
1691
1692 BLI_assert(ale_to_paste_into.datatype == ALE_FCURVE);
1693 const FCurve &fcurve_to_match = *static_cast<FCurve *>(ale_to_paste_into.data);
1694
1695 BLI_assert_msg(!(from_single && to_single),
1696 "The from-single-to-single case is expected to be implemented as a special case "
1697 "in `paste_animedit_keys()`");
1698
1699 /* Because `channelbags_to_paste_from` can reference `single_copy_buffer_channelbag`, the latter
1700 * has to live longer, hence it is declared first. */
1701 const Channelbag *single_copy_buffer_channelbag;
1702 Span<const Channelbag *> channelbags_to_paste_from;
1703
1704 /* Get the slot of this ALE, as some of the cases below need to query it. It might be slotless,
1705 * for example for NLA control curves. */
1706 const Slot *ale_slot = nullptr;
1707 const Action *ale_action = nullptr;
1708 if (GS(ale_to_paste_into.fcurve_owner_id->name) == ID_AC) {
1709 ale_action = &reinterpret_cast<bAction *>(ale_to_paste_into.fcurve_owner_id)->wrap();
1710 ale_slot = ale_action->slot_for_handle(ale_to_paste_into.slot_handle);
1711 BLI_assert(ale_slot);
1712 }
1713
1714 /* NASTINESS: this code shouldn't have to care about which slots are currently visible in
1715 * the channel list. But since selection state is only relevant when they CAN actually be
1716 * selected, it does matter. This code assumes:
1717 * 1. because SELECTION or SELECTION_AND_IDENTIFIER was returned, slot selection is a
1718 * thing in this mode,
1719 * 2. because slot selection is a thing, and this F-Curve is potentially getting pasted
1720 * into, its slot is visible too,
1721 * 3. and because of that, the selection state of this slot is enough to check here. */
1722
1723 const SlotMatchMethod slot_match = get_slot_match_method(from_single, to_single, paste_context);
1724 switch (slot_match) {
1725 case SlotMatchMethod::SELECTION:
1726 if (!ale_slot->is_selected()) {
1727 return nullptr;
1728 }
1730
1731 case SlotMatchMethod::NONE:
1732 /* Just search through all channelbags in the copy buffer. */
1733 channelbags_to_paste_from = keyframe_copy_buffer->keyframe_data.channelbags();
1734 break;
1735
1736 case SlotMatchMethod::SELECTION_AND_IDENTIFIER:
1737 if (!ale_slot->is_selected()) {
1738 return nullptr;
1739 }
1741
1742 case SlotMatchMethod::IDENTIFIER: {
1743 /* See if we copied from a slot whose identifier matches this ALE. */
1744 const std::string target_slot_identifier = ale_slot ?
1745 ale_slot->identifier :
1747
1748 single_copy_buffer_channelbag = keyframe_copy_buffer->channelbag_for_slot(
1749 target_slot_identifier);
1750 if (!single_copy_buffer_channelbag) {
1751 /* No data copied from a slot with this name. */
1752 return nullptr;
1753 }
1754
1755 /* Only consider the F-Curves from this channelbag. Because of channelbags_to_paste_from
1756 * referencing single_copy_buffer_channelbag, the latter has to live longer, hence it was
1757 * declared first. */
1758 channelbags_to_paste_from = Span<const Channelbag *>(&single_copy_buffer_channelbag, 1);
1759 break;
1760 }
1761 }
1762
1763 for (const Channelbag *channelbag : channelbags_to_paste_from) {
1764 for (const FCurve *fcurve : channelbag->fcurves()) {
1765 if (strategy(bmain,
1766 fcurve_to_match,
1767 *fcurve,
1768 channelbag->slot_handle,
1769 from_single,
1770 to_single,
1771 paste_context.flip))
1772 {
1773 return fcurve;
1774 }
1775 }
1776 }
1777
1778 return nullptr;
1779}
1780
1782 const FCurve &fcurve_to_match,
1783 const FCurve &fcurve_in_copy_buffer,
1784 blender::animrig::slot_handle_t /*slot_handle_in_copy_buffer*/,
1785 const bool from_single,
1786 const bool to_single,
1787 const bool flip)
1788{
1789 if (!fcurve_to_match.rna_path || !fcurve_in_copy_buffer.rna_path) {
1790 /* No paths to compare to, so only ok if pasting to a single F-Curve. */
1791 return to_single;
1792 }
1793
1794 /* The 'source' of the copy data is considered 'ok' if either we're copying a single F-Curve,
1795 * or the array index matches (so [location X,Y,Z] can be copied to a single 'scale' property,
1796 * and it'll pick up the right X/Y/Z component). */
1797 const bool is_source_ok = from_single ||
1798 fcurve_in_copy_buffer.array_index == fcurve_to_match.array_index;
1799 if (!is_source_ok) {
1800 return false;
1801 }
1802
1803 if (!to_single && flip && keyframe_copy_buffer->is_bone(fcurve_in_copy_buffer)) {
1804 const std::optional<std::string> with_flipped_name = blender::ed::animation::flip_names(
1805 fcurve_in_copy_buffer.rna_path);
1806 return with_flipped_name && with_flipped_name == fcurve_to_match.rna_path;
1807 }
1808
1809 return to_single || STREQ(fcurve_in_copy_buffer.rna_path, fcurve_to_match.rna_path);
1810}
1811
1813 const FCurve &fcurve_to_match,
1814 const FCurve &fcurve_in_copy_buffer,
1815 blender::animrig::slot_handle_t slot_handle_in_copy_buffer,
1816 const bool from_single,
1817 const bool /*to_single*/,
1818 const bool /*flip*/)
1819{
1820 if (!fcurve_in_copy_buffer.rna_path || !fcurve_to_match.rna_path) {
1821 return false;
1822 }
1823
1824 /* The 'source' of the copy data is considered 'ok' if either we're copying a single F-Curve,
1825 * or the array index matches (so [location X,Y,Z] can be copied to a single 'scale' property,
1826 * and it'll pick up the right X/Y/Z component). */
1827 const bool is_source_ok = from_single ||
1828 fcurve_in_copy_buffer.array_index == fcurve_to_match.array_index;
1829 if (!is_source_ok) {
1830 return false;
1831 }
1832
1833 /* find the property of the fcurve and compare against the end of the tAnimCopybufItem
1834 * more involved since it needs to do path lookups.
1835 * This is not 100% reliable since the user could be editing the curves on a path that won't
1836 * resolve, or a bone could be renamed after copying for eg. but in normal copy & paste
1837 * this should work out ok.
1838 */
1839 const std::optional<ID *> optional_id = keyframe_copy_buffer->slot_animated_ids.lookup_try(
1840 slot_handle_in_copy_buffer);
1841 if (!optional_id) {
1842 printf(
1843 "paste_animedit_keys: no idea which ID was animated by \"%s\" in slot \"%s\", so cannot "
1844 "match by property name\n",
1845 fcurve_in_copy_buffer.rna_path,
1846 keyframe_copy_buffer->slot_identifiers.lookup(slot_handle_in_copy_buffer).c_str());
1847 return false;
1848 }
1849 ID *animated_id = optional_id.value();
1850 if (BLI_findindex(which_libbase(bmain, GS(animated_id->name)), animated_id) == -1) {
1851 /* The ID could have been removed after copying the keys. This function
1852 * needs it to resolve the property & get the name. */
1853 printf("paste_animedit_keys: error ID has been removed!\n");
1854 return false;
1855 }
1856
1857 PointerRNA rptr;
1858 PropertyRNA *prop;
1859 PointerRNA id_ptr = RNA_id_pointer_create(animated_id);
1860
1861 if (!RNA_path_resolve_property(&id_ptr, fcurve_in_copy_buffer.rna_path, &rptr, &prop)) {
1862 printf("paste_animedit_keys: failed to resolve path id:%s, '%s'!\n",
1863 animated_id->name,
1864 fcurve_in_copy_buffer.rna_path);
1865 return false;
1866 }
1867
1868 const char *identifier = RNA_property_identifier(prop);
1869 /* NOTE: paths which end with "] will fail with this test - Animated ID Props. */
1870 return blender::StringRef(fcurve_to_match.rna_path).endswith(identifier);
1871}
1872
1874 const FCurve &fcurve_to_match,
1875 const FCurve &fcurve_in_copy_buffer,
1876 blender::animrig::slot_handle_t /* slot_handle_in_copy_buffer */,
1877 const bool from_single,
1878 const bool /*to_single*/,
1879 const bool /*flip*/)
1880{
1881 return from_single || fcurve_in_copy_buffer.array_index == fcurve_to_match.array_index;
1882}
1883
1884/* ................ */
1885
1886static void do_curve_mirror_flippping(const FCurve &fcurve, BezTriple &bezt)
1887{
1888 if (!keyframe_copy_buffer->is_bone(fcurve)) {
1889 return;
1890 }
1891
1892 /* TODO: pull the investigation of the RNA path out of this function, and out of the loop over
1893 * all keys of the F-Curve. It only has to be done once per F-Curve, and not for every single
1894 * key. */
1895 const size_t slength = strlen(fcurve.rna_path);
1896 bool flip = false;
1897 if (BLI_strn_endswith(fcurve.rna_path, "location", slength) && fcurve.array_index == 0) {
1898 flip = true;
1899 }
1900 else if (BLI_strn_endswith(fcurve.rna_path, "rotation_quaternion", slength) &&
1901 ELEM(fcurve.array_index, 2, 3))
1902 {
1903 flip = true;
1904 }
1905 else if (BLI_strn_endswith(fcurve.rna_path, "rotation_euler", slength) &&
1906 ELEM(fcurve.array_index, 1, 2))
1907 {
1908 flip = true;
1909 }
1910 else if (BLI_strn_endswith(fcurve.rna_path, "rotation_axis_angle", slength) &&
1911 ELEM(fcurve.array_index, 2, 3))
1912 {
1913 flip = true;
1914 }
1915
1916 if (flip) {
1917 bezt.vec[0][1] = -bezt.vec[0][1];
1918 bezt.vec[1][1] = -bezt.vec[1][1];
1919 bezt.vec[2][1] = -bezt.vec[2][1];
1920 }
1921}
1922
1923/* helper for paste_animedit_keys() - performs the actual pasting */
1925 const FCurve &fcurve_in_copy_buffer,
1926 float offset[2],
1927 const eKeyMergeMode merge_mode,
1928 bool flip)
1929{
1930 BezTriple *bezt;
1931 int i;
1932
1933 /* First de-select existing FCurve's keyframes */
1934 for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
1935 BEZT_DESEL_ALL(bezt);
1936 }
1937
1938 /* mix mode with existing data */
1939 switch (merge_mode) {
1941 /* do-nothing */
1942 break;
1943
1945 /* remove all keys */
1947 break;
1948
1951 float f_min;
1952 float f_max;
1953
1954 if (merge_mode == KEYFRAME_PASTE_MERGE_OVER_RANGE) {
1955 f_min = fcurve_in_copy_buffer.bezt[0].vec[1][0] + offset[0];
1956 f_max = fcurve_in_copy_buffer.bezt[fcurve_in_copy_buffer.totvert - 1].vec[1][0] +
1957 offset[0];
1958 }
1959 else { /* Entire Range */
1960 f_min = keyframe_copy_buffer->first_frame + offset[0];
1961 f_max = keyframe_copy_buffer->last_frame + offset[0];
1962 }
1963
1964 /* remove keys in range */
1965 if (f_min < f_max) {
1966 /* select verts in range for removal */
1967 for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
1968 if ((f_min < bezt[0].vec[1][0]) && (bezt[0].vec[1][0] < f_max)) {
1969 bezt->f2 |= SELECT;
1970 }
1971 }
1972
1973 /* remove frames in the range */
1975 }
1976 break;
1977 }
1978 }
1979
1980 /* just start pasting, with the first keyframe on the current frame, and so on */
1981 for (i = 0, bezt = fcurve_in_copy_buffer.bezt; i < fcurve_in_copy_buffer.totvert; i++, bezt++) {
1982 /* Create a copy to modify, before inserting it into the F-Curve. The
1983 * applied offset also determines the frame number of the pasted BezTriple.
1984 * If the insertion is done before the offset is applied, it will replace
1985 * the original key and _then_ move it to the new position. */
1986 BezTriple bezt_copy = *bezt;
1987
1988 if (flip) {
1989 do_curve_mirror_flippping(fcurve_in_copy_buffer, bezt_copy);
1990 }
1991
1992 add_v2_v2(bezt_copy.vec[0], offset);
1993 add_v2_v2(bezt_copy.vec[1], offset);
1994 add_v2_v2(bezt_copy.vec[2], offset);
1995
1996 /* Ensure that all pasted data is selected. */
1997 BEZT_SEL_ALL(&bezt_copy);
1998
1999 /* Only now that it has the right values, do the pasting into the F-Curve. */
2001 }
2002
2003 /* recalculate F-Curve's handles? */
2005}
2006} // namespace blender::ed::animation
2007
2010 "START",
2011 0,
2012 "Frame Start",
2013 "Paste keys starting at current frame"},
2014 {KEYFRAME_PASTE_OFFSET_CFRA_END, "END", 0, "Frame End", "Paste keys ending at current frame"},
2016 "RELATIVE",
2017 0,
2018 "Frame Relative",
2019 "Paste keys relative to the current frame when copying"},
2020 {KEYFRAME_PASTE_OFFSET_NONE, "NONE", 0, "No Offset", "Paste keys from original time"},
2021 {0, nullptr, 0, nullptr, nullptr},
2022};
2023
2026 "LEFT_KEY",
2027 0,
2028 "Left Key",
2029 "Paste keys with the first key matching the key left of the cursor"},
2031 "RIGHT_KEY",
2032 0,
2033 "Right Key",
2034 "Paste keys with the last key matching the key right of the cursor"},
2036 "CURRENT_FRAME",
2037 0,
2038 "Current Frame Value",
2039 "Paste keys relative to the value of the curve under the cursor"},
2041 "CURSOR_VALUE",
2042 0,
2043 "Cursor Value",
2044 "Paste keys relative to the Y-Position of the cursor"},
2046 "NONE",
2047 0,
2048 "No Offset",
2049 "Paste keys with the same value as they were copied"},
2050 {0, nullptr, 0, nullptr, nullptr},
2051};
2052
2054 {KEYFRAME_PASTE_MERGE_MIX, "MIX", 0, "Mix", "Overlay existing with new keys"},
2055 {KEYFRAME_PASTE_MERGE_OVER, "OVER_ALL", 0, "Overwrite All", "Replace all keys"},
2057 "OVER_RANGE",
2058 0,
2059 "Overwrite Range",
2060 "Overwrite keys in pasted range"},
2062 "OVER_RANGE_ALL",
2063 0,
2064 "Overwrite Entire Range",
2065 "Overwrite keys in pasted range, using the range of all copied keys"},
2066 {0, nullptr, 0, nullptr, nullptr},
2067};
2068
2069static float paste_get_y_offset(const bAnimContext *ac,
2070 const FCurve &fcurve_in_copy_buffer,
2071 const bAnimListElem *ale,
2072 const eKeyPasteValueOffset value_offset_mode)
2073{
2075 const FCurve *fcu = static_cast<const FCurve *>(ale->data);
2076 const float cfra = BKE_scene_frame_get(ac->scene);
2077
2078 switch (value_offset_mode) {
2080 const SpaceGraph *sipo = reinterpret_cast<SpaceGraph *>(ac->sl);
2081 const float offset = sipo->cursorVal - fcurve_in_copy_buffer.bezt[0].vec[1][1];
2082 return offset;
2083 }
2084
2086 const float cfra_y = evaluate_fcurve(fcu, cfra);
2087 const float offset = cfra_y - fcurve_in_copy_buffer.bezt[0].vec[1][1];
2088 return offset;
2089 }
2090
2092 bool replace;
2093 const int fcu_index = BKE_fcurve_bezt_binarysearch_index(
2094 fcu->bezt, cfra, fcu->totvert, &replace);
2095 const BezTriple left_key = fcu->bezt[max_ii(fcu_index - 1, 0)];
2096 const float offset = left_key.vec[1][1] - fcurve_in_copy_buffer.bezt[0].vec[1][1];
2097 return offset;
2098 }
2099
2101 bool replace;
2102 const int fcu_index = BKE_fcurve_bezt_binarysearch_index(
2103 fcu->bezt, cfra, fcu->totvert, &replace);
2104 const BezTriple right_key = fcu->bezt[min_ii(fcu_index, fcu->totvert - 1)];
2105 const float offset = right_key.vec[1][1] -
2106 fcurve_in_copy_buffer.bezt[fcurve_in_copy_buffer.totvert - 1].vec[1][1];
2107 return offset;
2108 }
2109
2111 break;
2112 }
2113
2114 return 0.0f;
2115}
2116
2118 ListBase *anim_data,
2119 const KeyframePasteContext &paste_context)
2120{
2121 using namespace blender::ed::animation;
2122
2123 if (!keyframe_copy_buffer || keyframe_copy_buffer->is_empty()) {
2125 }
2126 if (BLI_listbase_is_empty(anim_data)) {
2128 }
2129
2130 const Scene *scene = (ac->scene);
2131 const bool from_single = keyframe_copy_buffer->is_single_fcurve();
2132 const bool to_single = BLI_listbase_is_single(anim_data);
2133 float offset[2] = {0, 0};
2134
2135 /* methods of offset */
2136 switch (paste_context.offset_mode) {
2138 offset[0] = float(scene->r.cfra - keyframe_copy_buffer->first_frame);
2139 break;
2141 offset[0] = float(scene->r.cfra - keyframe_copy_buffer->last_frame);
2142 break;
2144 offset[0] = float(scene->r.cfra - keyframe_copy_buffer->current_frame);
2145 break;
2147 offset[0] = 0.0f;
2148 break;
2149 }
2150
2151 if (from_single && to_single) {
2152 /* 1:1 match, no tricky checking, just paste. */
2153 bAnimListElem *ale = static_cast<bAnimListElem *>(anim_data->first);
2154 FCurve *fcu = static_cast<FCurve *>(ale->data); /* destination F-Curve */
2155 const FCurve &fcurve_in_copy_buffer =
2156 *keyframe_copy_buffer->keyframe_data.channelbag(0)->fcurve(0);
2157
2158 offset[1] = paste_get_y_offset(
2159 ac, fcurve_in_copy_buffer, ale, paste_context.value_offset_mode);
2160
2161 ANIM_nla_mapping_apply_if_needed_fcurve(ale, fcu, false, false);
2163 fcu, fcurve_in_copy_buffer, offset, paste_context.merge_mode, false);
2164 ANIM_nla_mapping_apply_if_needed_fcurve(ale, fcu, true, false);
2165
2167
2168 ANIM_animdata_update(ac, anim_data);
2169
2170 return KEYFRAME_PASTE_OK;
2171 }
2172
2173 /* Try to find "matching" channels to paste keyframes into with increasingly
2174 * loose matching heuristics. The process finishes when at least one F-Curve
2175 * has been pasted into. */
2176 Vector<pastebuf_match_func> matchers = {
2178
2179 for (const pastebuf_match_func matcher : matchers) {
2180 bool found_match = false;
2181
2182 LISTBASE_FOREACH (bAnimListElem *, ale, anim_data) {
2183 /* See if there is an F-Curve in the copy buffer that matches this ALE. */
2184 const FCurve *fcurve_in_copy_buffer = pastebuf_find_matching_copybuf_item(
2185 matcher, ac->bmain, *ale, from_single, to_single, paste_context);
2186 if (!fcurve_in_copy_buffer) {
2187 continue;
2188 }
2189
2190 /* Copy the relevant data from the matching buffer curve. */
2191 offset[1] = paste_get_y_offset(
2192 ac, *fcurve_in_copy_buffer, ale, paste_context.value_offset_mode);
2193
2194 /* Do the actual pasting. */
2195 FCurve *fcurve_to_paste_into = static_cast<FCurve *>(ale->data);
2196 ANIM_nla_mapping_apply_if_needed_fcurve(ale, fcurve_to_paste_into, false, false);
2197 paste_animedit_keys_fcurve(fcurve_to_paste_into,
2198 *fcurve_in_copy_buffer,
2199 offset,
2200 paste_context.merge_mode,
2201 paste_context.flip);
2202 ANIM_nla_mapping_apply_if_needed_fcurve(ale, fcurve_to_paste_into, true, false);
2203
2204 found_match = true;
2206 }
2207
2208 /* Don't continue if some fcurves were pasted. */
2209 if (found_match) {
2210 break;
2211 }
2212 }
2213
2214 ANIM_animdata_update(ac, anim_data);
2215
2216 return KEYFRAME_PASTE_OK;
2217}
2218
Functions and classes to work with Actions.
Functions to work with AnimData.
Functions to modify FCurves.
Blender kernel action and pose functionality.
bPoseChannel * BKE_pose_channel_find_name(const bPose *pose, const char *name)
unsigned int BKE_curve_decimate_bezt_array(BezTriple *bezt_array, unsigned int bezt_array_len, unsigned int resolu, bool is_cyclic, char flag_test, char flag_set, float error_sq_max, unsigned int error_target_len)
FCurve * BKE_fcurve_create()
int BKE_fcurve_bezt_binarysearch_index(const BezTriple array[], float frame, int arraylen, bool *r_replace)
void BKE_fcurve_handles_recalc(FCurve *fcu)
bool BKE_fcurve_is_empty(const FCurve *fcu)
bool BKE_fcurve_delete_keys_selected(FCurve *fcu)
void BKE_fcurve_keyframe_move_value_with_handles(BezTriple *keyframe, float new_value)
float evaluate_fcurve(const FCurve *fcu, float evaltime)
void BKE_fcurve_delete_keys_all(FCurve *fcu)
ListBase * which_libbase(Main *bmain, short type)
Definition main.cc:902
float BKE_scene_frame_get(const Scene *scene)
Definition scene.cc:2384
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define ATTR_FALLTHROUGH
int BLI_findindex(const ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:586
#define LISTBASE_FOREACH(type, var, list)
BLI_INLINE bool BLI_listbase_is_empty(const ListBase *lb)
void void BLI_freelistN(ListBase *listbase) ATTR_NONNULL(1)
Definition listbase.cc:497
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
void void BLI_INLINE bool BLI_listbase_is_single(const ListBase *lb)
MINLINE int min_ii(int a, int b)
MINLINE float pow2f(float x)
MINLINE float clamp_f(float value, float min, float max)
MINLINE float min_ff(float a, float b)
MINLINE int max_ii(int a, int b)
MINLINE float floored_fmod(float f, float n)
MINLINE float interpf(float target, float origin, float t)
MINLINE int clamp_i(int value, int min, int max)
MINLINE int signum_i(float a)
#define M_PI
MINLINE void sub_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void mul_v3_fl(float r[3], float f)
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void add_v2_v2(float r[2], const float a[2])
MINLINE void add_v3_v3(float r[3], const float a[3])
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.cc:41
int bool bool bool BLI_strn_endswith(const char *__restrict str, const char *__restrict end, size_t str_len) ATTR_NONNULL(1
bool BLI_str_quoted_substr_range(const char *__restrict str, const char *__restrict prefix, int *__restrict r_start, int *__restrict r_end) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
bool bool BLI_str_quoted_substr(const char *__restrict str, const char *__restrict prefix, char *result, size_t result_maxncpy)
Definition string.cc:521
size_t BLI_string_flip_side_name(char *name_dst, const char *name_src, bool strip_number, size_t name_dst_maxncpy) ATTR_NONNULL(1
unsigned int uint
#define UNUSED_VARS_NDEBUG(...)
#define ELEM(...)
#define IS_EQF(a, b)
#define IS_EQT(a, b, c)
#define STREQ(a, b)
@ ID_AC
@ ID_OB
struct bAction bAction
eInsertKeyFlags
@ INSERTKEY_FAST
@ INSERTKEY_OVERWRITE_FULL
@ HD_FREE
@ BEZT_IPO_BEZ
@ BEZT_IPO_LIN
@ BEZT_FLAG_TEMP_TAG
@ BEZT_FLAG_IGNORE_TAG
#define BEZT_ISSEL_IDX(bezt, i)
#define BEZT_SEL_ALL(bezt)
#define BEZT_ISSEL_ANY(bezt)
#define BEZT_DESEL_ALL(bezt)
Object is a sort of wrapper for general info.
@ OB_ARMATURE
#define MAX_VGROUP_NAME
#define ANIM_UPDATE_DEFAULT
@ ALE_FCURVE
tShearDirection
@ SHEAR_FROM_LEFT
@ KEYFRAME_PASTE_MERGE_OVER_RANGE_ALL
@ KEYFRAME_PASTE_MERGE_OVER_RANGE
@ KEYFRAME_PASTE_MERGE_OVER
@ KEYFRAME_PASTE_MERGE_MIX
eKeyPasteValueOffset
@ KEYFRAME_PASTE_VALUE_OFFSET_RIGHT_KEY
@ KEYFRAME_PASTE_VALUE_OFFSET_NONE
@ KEYFRAME_PASTE_VALUE_OFFSET_CURSOR
@ KEYFRAME_PASTE_VALUE_OFFSET_CFRA
@ KEYFRAME_PASTE_VALUE_OFFSET_LEFT_KEY
@ KEYFRAME_PASTE_OFFSET_NONE
@ KEYFRAME_PASTE_OFFSET_CFRA_END
@ KEYFRAME_PASTE_OFFSET_CFRA_RELATIVE
@ KEYFRAME_PASTE_OFFSET_CFRA_START
@ BEZT_OK_SELECTED_KEY
@ KEYFRAME_PASTE_NOTHING_TO_PASTE
@ KEYFRAME_PASTE_OK
@ KEYFRAME_PASTE_NOWHERE_TO_PASTE
FCurveSegmentAnchor
Read Guarded memory(de)allocation.
@ PROP_FLOAT
Definition RNA_types.hh:164
@ PROP_BOOLEAN
Definition RNA_types.hh:162
@ PROP_INT
Definition RNA_types.hh:163
void ANIM_animdata_update(bAnimContext *ac, ListBase *anim_data)
Definition anim_deps.cc:356
void ANIM_nla_mapping_apply_if_needed_fcurve(bAnimListElem *ale, FCurve *fcu, const bool restore, const bool only_keys)
Definition anim_draw.cc:401
@ NONE
static T sum(const btAlignedObjectArray< T > &items)
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:265
bool contains(const Key &key) const
Definition BLI_map.hh:353
std::optional< Value > lookup_try(const Key &key) const
Definition BLI_map.hh:531
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:265
constexpr StringRef substr(int64_t start, int64_t size) const
constexpr bool endswith(StringRef suffix) const
constexpr const char * c_str() const
Slot * slot_for_handle(slot_handle_t handle)
bool fcurve_assign_to_channel_group(FCurve &fcurve, bActionGroup &to_group)
blender::Span< const FCurve * > fcurves() const
bActionGroup & channel_group_ensure(StringRefNull name)
const Channelbag * channelbag_for_slot(const Slot &slot) const
Channelbag & channelbag_for_slot_add(const Slot &slot)
nullptr float
#define SELECT
#define GS(x)
#define printf(...)
#define tan
#define sin
#define round
#define pow
#define exp
#define ceil
#define sqrt
short ANIM_fcurve_keyframes_loop(KeyframeEditData *ked, FCurve *fcu, KeyframeEditFunc key_ok, KeyframeEditFunc key_cb, FcuEditFunc fcu_cb)
KeyframeEditFunc ANIM_editkeyframes_ok(short mode)
void smooth_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float *original_values, float *samples, const int sample_count, const float factor, const int kernel_size, const double *kernel)
void ED_anim_calculate_butterworth_coefficients(const float cutoff_frequency, const float sampling_frequency, ButterworthCoefficients *bw_coeff)
static bool prepare_for_decimate(FCurve *fcu, int i)
static float butterworth_calculate_blend_value(float *samples, const float *filtered_values, const int start_index, const int end_index, const int sample_index, const int blend_in_out)
void butterworth_smooth_fcurve_segment(FCurve *fcu, FCurveSegment *segment, float *samples, const int sample_count, const float factor, const int blend_in_out, const int sample_rate, ButterworthCoefficients *bw_coeff)
const EnumPropertyItem rna_enum_keyframe_paste_offset_items[]
static const BezTriple * fcurve_segment_end_get(FCurve *fcu, int index)
static bool is_animating_bone(const bAnimListElem *ale)
float get_default_rna_value(const FCurve *fcu, PropertyRNA *prop, PointerRNA *ptr)
bool decimate_fcurve(bAnimListElem *ale, float remove_ratio, float error_sq_max)
void ANIM_fcurves_copybuf_reset()
void breakdown_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
static bool find_fcurve_segment(FCurve *fcu, const int start_index, int *r_segment_start_idx, int *r_segment_len)
const EnumPropertyItem rna_enum_keyframe_paste_merge_items[]
void ease_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor, const float width)
bool copy_animedit_keys(bAnimContext *ac, ListBase *anim_data)
ButterworthCoefficients * ED_anim_allocate_butterworth_coefficients(const int filter_order)
void time_offset_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float frame_offset)
void ED_ANIM_get_1d_gauss_kernel(const float sigma, const int kernel_size, double *r_kernel)
static float paste_get_y_offset(const bAnimContext *ac, const FCurve &fcurve_in_copy_buffer, const bAnimListElem *ale, const eKeyPasteValueOffset value_offset_mode)
void scale_average_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
void blend_offset_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
const EnumPropertyItem rna_enum_keyframe_paste_offset_value_items[]
void clean_fcurve(bAnimListElem *ale, float thresh, bool cleardefault, const bool only_selected_keys)
eKeyPasteError paste_animedit_keys(bAnimContext *ac, ListBase *anim_data, const KeyframePasteContext &paste_context)
void shear_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor, tShearDirection direction)
void ANIM_fcurves_copybuf_free()
static const BezTriple * fcurve_segment_start_get(FCurve *fcu, int index)
static float ease_sigmoid_function(const float x, const float width, const float shift)
void blend_to_ease_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
static void decimate_fcurve_segment(FCurve *fcu, int bezt_segment_start_idx, int bezt_segment_len, float remove_ratio, float error_sq_max)
static double butterworth_filter_value(double x, double *w0, double *w1, double *w2, ButterworthCoefficients *bw_coeff)
bool match_slope_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
void smooth_fcurve(FCurve *fcu)
void push_pull_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
void blend_to_default_fcurve(PointerRNA *id_ptr, FCurve *fcu, const float factor)
static float s_curve(float x, float slope, float width, float height, float xshift, float yshift)
ListBase find_fcurve_segments(FCurve *fcu)
bool duplicate_fcurve_keys(FCurve *fcu)
void scale_from_fcurve_segment_neighbor(FCurve *fcu, FCurveSegment *segment, const float factor, const FCurveSegmentAnchor anchor)
void blend_to_neighbor_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
void ED_anim_free_butterworth_coefficients(ButterworthCoefficients *bw_coeff)
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_dupallocN(const void *vmemh)
Definition mallocn.cc:143
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
ccl_device_inline float2 fabs(const float2 a)
static ulong * next
void animdata_fcurve_delete(AnimData *adt, FCurve *fcu)
Definition animdata.cc:251
decltype(::ActionSlot::handle) slot_handle_t
int insert_bezt_fcurve(FCurve *fcu, const BezTriple *bezt, eInsertKeyFlags flag)
Lesser Key-framing API call.
bool(*)(Main *bmain, const FCurve &fcurve_to_match, const FCurve &fcurve_in_copy_buffer, blender::animrig::slot_handle_t slot_handle_in_copy_buffer, bool from_single, bool to_single, bool flip) pastebuf_match_func
KeyframeCopyBuffer * keyframe_copy_buffer
static void paste_animedit_keys_fcurve(FCurve *fcu, const FCurve &fcurve_in_copy_buffer, float offset[2], const eKeyMergeMode merge_mode, bool flip)
bool pastebuf_match_index_only(Main *, const FCurve &fcurve_to_match, const FCurve &fcurve_in_copy_buffer, blender::animrig::slot_handle_t, const bool from_single, const bool, const bool)
static SlotMatchMethod get_slot_match_method(const bool from_single, const bool to_single, const KeyframePasteContext &paste_context)
static const FCurve * pastebuf_find_matching_copybuf_item(const pastebuf_match_func strategy, Main *bmain, const bAnimListElem &ale_to_paste_into, const bool from_single, const bool to_single, const KeyframePasteContext &paste_context)
bool pastebuf_match_path_full(Main *, const FCurve &fcurve_to_match, const FCurve &fcurve_in_copy_buffer, blender::animrig::slot_handle_t, const bool from_single, const bool to_single, const bool flip)
bool pastebuf_match_path_property(Main *bmain, const FCurve &fcurve_to_match, const FCurve &fcurve_in_copy_buffer, blender::animrig::slot_handle_t slot_handle_in_copy_buffer, const bool from_single, const bool, const bool)
std::optional< std::string > flip_names(const blender::StringRefNull rna_path)
static void do_curve_mirror_flippping(const FCurve &fcurve, BezTriple &bezt)
float wrap(float value, float max, float min)
Definition node_math.h:103
static bool point_is_selected(PTCacheEditPoint *point)
#define floorf
bool RNA_property_boolean_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int index)
int RNA_property_int_get_default(PointerRNA *ptr, PropertyRNA *prop)
float RNA_property_float_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int index)
PropertyType RNA_property_type(PropertyRNA *prop)
int RNA_property_int_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int index)
float RNA_property_float_get_default(PointerRNA *ptr, PropertyRNA *prop)
int RNA_property_array_length(PointerRNA *ptr, PropertyRNA *prop)
bool RNA_property_boolean_get_default(PointerRNA *ptr, PropertyRNA *prop)
const char * RNA_property_identifier(const PropertyRNA *prop)
PointerRNA RNA_id_pointer_create(ID *id)
bool RNA_path_resolve_property(const PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop)
Definition rna_path.cc:560
char identifier[258]
float vec[3][3]
bActionGroup * grp
char * rna_path
BezTriple * bezt
int array_index
unsigned int totvert
Definition DNA_ID.h:414
char name[258]
Definition DNA_ID.h:432
eKeyPasteValueOffset value_offset_mode
eKeyPasteOffset offset_mode
void * first
struct bPose * pose
struct RenderData r
SpaceLink * sl
int32_t slot_handle
AnimData * adt
eAnim_Update_Flags update
eAnim_KeyType datatype
Map< animrig::slot_handle_t, ID * > slot_animated_ids
bool is_bone(const FCurve &fcurve) const
Map< animrig::slot_handle_t, std::string > slot_identifiers
animrig::Channelbag * channelbag_for_slot(StringRef slot_identifier)
i
Definition text_draw.cc:230
static int blend(const Tex *tex, const float texvec[3], TexResult *texres)
uint len
PointerRNA * ptr
Definition wm_files.cc:4238