Blender V5.0
annotate_paint.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2008-2018 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <cmath>
10#include <cstdio>
11#include <cstdlib>
12#include <cstring>
13
14#include "MEM_guardedalloc.h"
15
16#include "BLI_listbase.h"
17#include "BLI_math_geom.h"
18#include "BLI_math_matrix.h"
19#include "BLI_time.h"
20#include "BLI_utildefines.h"
21
22#include "BLT_translation.hh"
23
24#include "BKE_callbacks.hh"
25#include "BKE_context.hh"
27#include "BKE_gpencil_legacy.h"
28#include "BKE_report.hh"
29#include "BKE_screen.hh"
30#include "BKE_tracking.h"
31
33#include "DNA_scene_types.h"
35
36#include "UI_resources.hh"
37#include "UI_view2d.hh"
38
39#include "ED_clip.hh"
40#include "ED_gpencil_legacy.hh"
41#include "ED_screen.hh"
42#include "ED_view3d.hh"
43
44#include "GPU_immediate.hh"
45#include "GPU_immediate_util.hh"
46#include "GPU_state.hh"
47
48#include "RNA_access.hh"
49#include "RNA_define.hh"
50#include "RNA_prototypes.hh"
51
52#include "WM_api.hh"
53#include "WM_types.hh"
54
55#include "gpencil_intern.hh"
56
57/* ******************************************* */
58/* 'Globals' and Defines */
59
60#define DEPTH_INVALID 1.0f
61
62/* values for tGPsdata->status */
64 GP_STATUS_IDLING = 0, /* stroke isn't in progress yet */
65 GP_STATUS_PAINTING, /* a stroke is in progress */
66 GP_STATUS_ERROR, /* something wasn't correctly set up */
67 GP_STATUS_DONE, /* painting done */
68 GP_STATUS_CAPTURE /* capture event, but cancel */
69};
70
71/* Return flags for adding points to stroke buffer */
73 GP_STROKEADD_INVALID = -2, /* error occurred - insufficient info to do so */
74 GP_STROKEADD_OVERFLOW = -1, /* error occurred - cannot fit any more points */
75 GP_STROKEADD_NORMAL, /* point was successfully added */
76 GP_STROKEADD_FULL, /* cannot add any more points to buffer */
77};
78
79/* Runtime flags */
81 GP_PAINTFLAG_FIRSTRUN = (1 << 0), /* operator just started */
85 /* Flags used to indicate if stabilization is being used. */
88};
90
91/* Temporary 'Stroke' Operation data
92 * "p" = op->customdata
93 */
94struct tGPsdata {
98 Depsgraph *depsgraph;
99
111 const rctf *subrect;
113
116
125
128
135
137 short radius;
138
139 /* Stabilizer. */
143
145 float mval[2];
147 float mvalo[2];
148
150 float pressure;
153
154 /* These need to be doubles, as (at least under unix) they are in seconds since epoch,
155 * float (and its 7 digits precision) is definitively not enough here!
156 * double, with its 15 digits precision,
157 * ensures us millisecond precision for a few centuries at least.
158 */
160 double inittime;
162 double curtime;
164 double ocurtime;
165
168 float imat[4][4];
169 float mat[4][4];
170
172 float custom_color[4];
173
176
178 short straight[2];
179
182};
183
184/* ------ */
185
186/* Macros for accessing sensitivity thresholds... */
187/* minimum number of pixels mouse should move before new point created */
188#define MIN_MANHATTAN_PX (U.gp_manhattandist)
189/* minimum length of new segment before new point can be added */
190#define MIN_EUCLIDEAN_PX (U.gp_euclideandist)
191
193{
194 return (p->gpf && p->gpf->strokes.last && p->flags & GP_PAINTFLAG_STROKEADDED);
195}
196
198{
199 BLI_assert(p->gpf->strokes.last != nullptr);
201}
202
203/* ------ */
204/* Forward defines for some functions... */
205
207
208/* ******************************************* */
209/* Context Wrangling... */
210
211/* check if context is suitable for drawing */
213{
215 /* check if current context can support GPencil data */
216 if (ED_annotation_data_get_pointers(C, nullptr) != nullptr) {
217 return true;
218 }
219 else {
220 CTX_wm_operator_poll_msg_set(C, "Failed to find Annotation data to draw into");
221 }
222 }
223 else {
224 CTX_wm_operator_poll_msg_set(C, "Active region not set");
225 }
226
227 return false;
228}
229
230/* check if projecting strokes into 3d-geometry in the 3D-View */
232{
233 bGPdata *gpd = p->gpd;
234 return ((gpd->runtime.sbuffer_sflag & GP_STROKE_3DSPACE) &&
236}
237
238/* ******************************************* */
239/* Calculations/Conversions */
240
241/* Utilities --------------------------------- */
242
243/* get the reference point for stroke-point conversions */
244static void annotation_get_3d_reference(tGPsdata *p, float vec[3])
245{
246 const float *fp = p->scene->cursor.location;
247
248 /* use 3D-cursor */
249 copy_v3_v3(vec, fp);
250}
251
252/* Stroke Editing ---------------------------- */
253
254/* check if the current mouse position is suitable for adding a new point */
255static bool annotation_stroke_filtermval(tGPsdata *p, const float mval[2], const float pmval[2])
256{
257 int dx = int(fabsf(mval[0] - pmval[0]));
258 int dy = int(fabsf(mval[1] - pmval[1]));
259
260 /* if buffer is empty, just let this go through (i.e. so that dots will work) */
261 if (p->gpd->runtime.sbuffer_used == 0) {
262 return true;
263 }
264
265 /* check if mouse moved at least certain distance on both axes (best case)
266 * - aims to eliminate some jitter-noise from input when trying to draw straight lines freehand
267 */
268
269 /* If lazy mouse, check minimum distance. */
271 if ((dx * dx + dy * dy) > (p->stabilizer_radius * p->stabilizer_radius)) {
272 return true;
273 }
274
275 /* If the mouse is moving within the radius of the last move,
276 * don't update the mouse position. This allows sharp turns. */
277 copy_v2_v2(p->mval, p->mvalo);
278 return false;
279 }
280
281 if ((dx > MIN_MANHATTAN_PX) && (dy > MIN_MANHATTAN_PX)) {
282 return true;
283 }
284
285 /* Check if the distance since the last point is significant enough:
286 * - Prevents points being added too densely
287 * - Distance here doesn't use `sqrt` to prevent slowness.
288 * We should still be safe from overflows though.
289 */
290 if ((dx * dx + dy * dy) > MIN_EUCLIDEAN_PX * MIN_EUCLIDEAN_PX) {
291 return true;
292 }
293
294 /* mouse 'didn't move' */
295 return false;
296}
297
298/* convert screen-coordinates to buffer-coordinates */
300 const blender::float2 mval,
301 float out[3],
302 const float *depth)
303{
304 bGPdata *gpd = p->gpd;
305 if (depth && (*depth == DEPTH_INVALID)) {
306 depth = nullptr;
307 }
308
309 /* in 3d-space - pt->x/y/z are 3 side-by-side floats */
311 blender::int2 mval_i = blender::int2(mval);
312 if (annotation_project_check(p) && ED_view3d_autodist_simple(p->region, mval_i, out, 0, depth))
313 {
314 /* projecting onto 3D-Geometry
315 * - nothing more needs to be done here, since view_autodist_simple() has already done it
316 */
317 }
318 else {
319 float mval_prj[2];
320 float rvec[3];
321
322 /* Current method just converts each point in screen-coordinates to
323 * 3D-coordinates using the 3D-cursor as reference. In general, this
324 * works OK, but it could of course be improved.
325 *
326 * TODO:
327 * - investigate using nearest point(s) on a previous stroke as
328 * reference point instead or as offset, for easier stroke matching
329 */
330
332 const float zfac = ED_view3d_calc_zfac(
333 static_cast<const RegionView3D *>(p->region->regiondata), rvec);
334
337 {
338 float dvec[3];
339 float xy_delta[2];
340 sub_v2_v2v2(xy_delta, mval_prj, mval);
341 ED_view3d_win_to_delta(p->region, xy_delta, zfac, dvec);
342 sub_v3_v3v3(out, rvec, dvec);
343 }
344 else {
345 zero_v3(out);
346 }
347 }
348 }
349
350 /* 2d - on 'canvas' (assume that p->v2d is set) */
351 else if ((gpd->runtime.sbuffer_sflag & GP_STROKE_2DSPACE) && (p->v2d)) {
352 UI_view2d_region_to_view(p->v2d, mval[0], mval[1], &out[0], &out[1]);
353 mul_v3_m4v3(out, p->imat, out);
354 }
355
356 /* 2d - relative to screen (viewport area) */
357 else {
358 if (p->subrect == nullptr) { /* normal 3D view */
359 out[0] = float(mval[0]) / float(p->region->winx) * 100;
360 out[1] = float(mval[1]) / float(p->region->winy) * 100;
361 }
362 else { /* camera view, use subrect */
363 out[0] = ((mval[0] - p->subrect->xmin) / BLI_rctf_size_x(p->subrect)) * 100;
364 out[1] = ((mval[1] - p->subrect->ymin) / BLI_rctf_size_y(p->subrect)) * 100;
365 }
366 }
367}
368
379static void annotation_smooth_buffer(tGPsdata *p, float inf, int idx)
380{
381 bGPdata *gpd = p->gpd;
382 short num_points = gpd->runtime.sbuffer_used;
383
384 /* Do nothing if not enough points to smooth out */
385 if ((num_points < 3) || (idx < 3) || (inf == 0.0f)) {
386 return;
387 }
388
389 tGPspoint *points = (tGPspoint *)gpd->runtime.sbuffer;
390 float steps = 4.0f;
391 if (idx < 4) {
392 steps--;
393 }
394
395 tGPspoint *pta = idx >= 4 ? &points[idx - 4] : nullptr;
396 tGPspoint *ptb = idx >= 3 ? &points[idx - 3] : nullptr;
397 tGPspoint *ptc = idx >= 2 ? &points[idx - 2] : nullptr;
398 tGPspoint *ptd = &points[idx - 1];
399
400 float sco[2] = {0.0f};
401 float a[2], b[2], c[2], d[2];
402 const float average_fac = 1.0f / steps;
403
404 /* Compute smoothed coordinate by taking the ones nearby */
405 if (pta) {
406 copy_v2_v2(a, pta->m_xy);
407 madd_v2_v2fl(sco, a, average_fac);
408 }
409 if (ptb) {
410 copy_v2_v2(b, ptb->m_xy);
411 madd_v2_v2fl(sco, b, average_fac);
412 }
413 if (ptc) {
414 copy_v2_v2(c, ptc->m_xy);
415 madd_v2_v2fl(sco, c, average_fac);
416 }
417 if (ptd) {
418 copy_v2_v2(d, ptd->m_xy);
419 madd_v2_v2fl(sco, d, average_fac);
420 }
421
422 /* Based on influence factor, blend between original and optimal smoothed coordinate */
423 interp_v2_v2v2(c, c, sco, inf);
424 copy_v2_v2(ptc->m_xy, c);
425}
426
427static void annotation_stroke_arrow_calc_points_segment(float stroke_points[8],
428 const float ref_point[2],
429 const float dir_cw[2],
430 const float dir_ccw[2],
431 const float length,
432 const float sign)
433{
434 stroke_points[0] = ref_point[0] + dir_cw[0] * length * sign;
435 stroke_points[1] = ref_point[1] + dir_cw[1] * length * sign;
436 stroke_points[2] = ref_point[0] + dir_ccw[0] * length * sign;
437 stroke_points[3] = ref_point[1] + dir_ccw[1] * length * sign;
438}
439
441 const float stroke_dir[2],
442 float corner[2],
443 float stroke_points[8],
444 const int arrow_style)
445{
446 const int arrow_length = 8;
447 float norm_dir[2];
448 copy_v2_v2(norm_dir, stroke_dir);
449 normalize_v2(norm_dir);
450 const float inv_norm_dir_clockwise[2] = {norm_dir[1], -norm_dir[0]};
451 const float inv_norm_dir_counterclockwise[2] = {-norm_dir[1], norm_dir[0]};
452
453 switch (arrow_style) {
455 mul_v2_fl(norm_dir, arrow_length);
456 stroke_points[0] = corner[0] + inv_norm_dir_clockwise[0] * arrow_length + norm_dir[0];
457 stroke_points[1] = corner[1] + inv_norm_dir_clockwise[1] * arrow_length + norm_dir[1];
458 stroke_points[2] = corner[0] + inv_norm_dir_counterclockwise[0] * arrow_length + norm_dir[0];
459 stroke_points[3] = corner[1] + inv_norm_dir_counterclockwise[1] * arrow_length + norm_dir[1];
460 break;
463 corner,
464 inv_norm_dir_clockwise,
465 inv_norm_dir_counterclockwise,
466 arrow_length,
467 1.0f);
468 break;
470 mul_v2_fl(norm_dir, arrow_length);
471 if (point != nullptr) {
472 add_v2_v2(point->m_xy, norm_dir);
473 copy_v2_v2(corner, point->m_xy);
474 }
476 corner,
477 inv_norm_dir_clockwise,
478 inv_norm_dir_counterclockwise,
479 arrow_length,
480 -1.0f);
481 stroke_points[4] = corner[0] - norm_dir[0];
482 stroke_points[5] = corner[1] - norm_dir[1];
483 break;
485 mul_v2_fl(norm_dir, arrow_length * 1.5f);
486 if (point != nullptr) {
487 add_v2_v2(point->m_xy, norm_dir);
488 copy_v2_v2(corner, point->m_xy);
489 }
491 corner,
492 inv_norm_dir_clockwise,
493 inv_norm_dir_counterclockwise,
494 arrow_length * 0.75f,
495 -1.0f);
496 stroke_points[4] = stroke_points[0] - norm_dir[0];
497 stroke_points[5] = stroke_points[1] - norm_dir[1];
498 stroke_points[6] = stroke_points[2] - norm_dir[0];
499 stroke_points[7] = stroke_points[3] - norm_dir[1];
500 break;
501 default:
502 break;
503 }
504}
505
506/* add current stroke-point to buffer (returns whether point was successfully added) */
508 const float mval[2],
509 float pressure,
510 double curtime)
511{
512 bGPdata *gpd = p->gpd;
513 tGPspoint *pt;
515
516 /* check painting mode */
518 /* straight lines only - i.e. only store start and end point in buffer */
519 if (gpd->runtime.sbuffer_used == 0) {
520 /* first point in buffer (start point) */
521 pt = (tGPspoint *)(gpd->runtime.sbuffer);
522
523 /* store settings */
524 copy_v2_v2(pt->m_xy, mval);
525 /* Pressure values are unreliable, so ignore for now, see #44932. */
526 pt->pressure = 1.0f;
527 pt->strength = 1.0f;
528 pt->time = float(curtime - p->inittime);
529
530 /* increment buffer size */
531 gpd->runtime.sbuffer_used++;
532 }
533 else {
534 /* just reset the endpoint to the latest value
535 * - assume that pointers for this are always valid...
536 */
537 pt = ((tGPspoint *)(gpd->runtime.sbuffer) + 1);
538
539 /* store settings */
540 copy_v2_v2(pt->m_xy, mval);
541 /* Pressure values are unreliable, so ignore for now, see #44932. */
542 pt->pressure = 1.0f;
543 pt->strength = 1.0f;
544 pt->time = float(curtime - p->inittime);
545
546 /* now the buffer has 2 points (and shouldn't be allowed to get any larger) */
547 gpd->runtime.sbuffer_used = 2;
548
549 /* Arrows. */
551 /* Store start and end point coords for arrows. */
552 float end[2];
553 copy_v2_v2(end, pt->m_xy);
554 pt = ((tGPspoint *)(gpd->runtime.sbuffer));
555 float start[2];
556 copy_v2_v2(start, pt->m_xy);
557
558 /* Arrow end corner. */
560 pt++;
561 const float e_heading[2] = {start[0] - end[0], start[1] - end[1]};
562 /* Calculate points for ending arrow. */
564 pt, e_heading, end, gpd->runtime.arrow_end, gpd->runtime.arrow_end_style);
565 }
566 /* Arrow start corner. */
568 const float s_heading[2] = {end[0] - start[0], end[1] - start[1]};
569 /* Calculate points for starting arrow. */
571 nullptr, s_heading, start, gpd->runtime.arrow_start, gpd->runtime.arrow_start_style);
572 }
573 }
574 }
575
576 /* can keep carrying on this way :) */
577 return GP_STROKEADD_NORMAL;
578 }
579
580 if (p->paintmode == GP_PAINTMODE_DRAW) { /* normal drawing */
581 /* check if still room in buffer or add more */
583 static_cast<tGPspoint *>(gpd->runtime.sbuffer),
584 &gpd->runtime.sbuffer_size,
585 &gpd->runtime.sbuffer_used,
586 false);
587
588 /* get pointer to destination point */
589 pt = ((tGPspoint *)(gpd->runtime.sbuffer) + gpd->runtime.sbuffer_used);
590
591 /* store settings */
592 copy_v2_v2(pt->m_xy, mval);
593 pt->pressure = pressure;
594 /* Unused for annotations, but initialize for easier conversions to GP Object. */
595 pt->strength = 1.0f;
596
597 /* point time */
598 pt->time = float(curtime - p->inittime);
599
600 /* increment counters */
601 gpd->runtime.sbuffer_used++;
602
603 /* Don't smooth if stabilizer is on. */
604 if ((p->flags & GP_PAINTFLAG_USE_STABILIZER_TEMP) == 0) {
605 /* smooth while drawing previous points with a reduction factor for previous */
606 for (int s = 0; s < 3; s++) {
607 annotation_smooth_buffer(p, 0.5f * ((3.0f - s) / 3.0f), gpd->runtime.sbuffer_used - s);
608 }
609 }
610
611 return GP_STROKEADD_NORMAL;
612 }
613
615 /* get pointer to destination point */
616 pt = (tGPspoint *)gpd->runtime.sbuffer;
617
618 /* store settings */
619 copy_v2_v2(pt->m_xy, mval);
620 /* Pressure values are unreliable, so ignore for now, see #44932. */
621 pt->pressure = 1.0f;
622 pt->strength = 1.0f;
623 pt->time = float(curtime - p->inittime);
624
625 /* if there's stroke for this poly line session add (or replace last) point
626 * to stroke. This allows to draw lines more interactively (see new segment
627 * during mouse slide, e.g.)
628 */
630 bGPDstroke *gps = static_cast<bGPDstroke *>(p->gpf->strokes.last);
631 bGPDspoint *pts;
632
633 /* first time point is adding to temporary buffer -- need to allocate new point in stroke */
634 if (gpd->runtime.sbuffer_used == 0) {
635 gps->points = static_cast<bGPDspoint *>(
636 MEM_reallocN(gps->points, sizeof(bGPDspoint) * (gps->totpoints + 1)));
637 gps->totpoints++;
638 }
639
640 pts = &gps->points[gps->totpoints - 1];
641
642 /* special case for poly lines: normally,
643 * depth is needed only when creating new stroke from buffer,
644 * but poly lines are converting to stroke instantly,
645 * so initialize depth buffer before converting coordinates
646 */
648 View3D *v3d = static_cast<View3D *>(p->area->spacedata.first);
649
651
655 }
656 else {
657 mode = V3D_DEPTH_ALL;
658 }
659 }
660
662 ED_view3d_depth_override(p->depsgraph, p->region, v3d, nullptr, mode, false, nullptr);
663 }
664
665 /* convert screen-coordinates to appropriate coordinates (and store them) */
666 annotation_stroke_convertcoords(p, pt->m_xy, &pts->x, nullptr);
667
668 /* copy pressure and time */
669 pts->pressure = pt->pressure;
670 pts->strength = pt->strength;
671 pts->time = pt->time;
672 gps->tot_triangles = 0;
673 }
674
675 /* increment counters */
676 if (gpd->runtime.sbuffer_used == 0) {
677 gpd->runtime.sbuffer_used++;
678 }
679
680 return GP_STROKEADD_NORMAL;
681 }
682
683 /* return invalid state for now... */
685}
686
688{
689 pt->pressure = 1.0f;
690 pt->strength = 1.0f;
691 pt->time = 1.0f;
692}
693
694static void annotation_stroke_arrow_init_conv_point(bGPDspoint *pt, const float point[3])
695{
696 copy_v3_v3(&pt->x, point);
698}
699
701 tGPsdata *p, tGPspoint *ptc, bGPDspoint *pt, const float co[8], const int co_idx)
702{
703 /* NOTE: provided co_idx should be always pair number as it's [x1, y1, x2, y2, x3, y3]. */
704 const float real_co[2] = {co[co_idx], co[co_idx + 1]};
705 copy_v2_v2(ptc->m_xy, real_co);
706 annotation_stroke_convertcoords(p, ptc->m_xy, &pt->x, nullptr);
708}
709
710static void annotation_stroke_arrow_allocate(bGPDstroke *gps, const int totpoints)
711{
712 /* Copy appropriate settings for stroke. */
713 gps->totpoints = totpoints;
714 /* Allocate enough memory for a continuous array for storage points. */
715 gps->points = MEM_calloc_arrayN<bGPDspoint>(gps->totpoints, "annotation_stroke_points");
716}
717
719 tGPspoint *ptc,
720 bGPDspoint *pt,
721 const float corner_point[3],
722 const float arrow_points[8])
723{
724 annotation_stroke_arrow_init_point(p, ptc, pt, arrow_points, 0);
725 pt++;
727 pt++;
728 annotation_stroke_arrow_init_point(p, ptc, pt, arrow_points, 2);
729}
730
732 tGPspoint *ptc,
733 bGPDspoint *pt,
734 const float arrow_points[8])
735{
736 annotation_stroke_arrow_init_point(p, ptc, pt, arrow_points, 0);
737 pt++;
738 annotation_stroke_arrow_init_point(p, ptc, pt, arrow_points, 2);
739}
740
742 tGPspoint *ptc,
743 bGPDspoint *pt,
744 const float arrow_points[8])
745{
746 annotation_stroke_arrow_init_point(p, ptc, pt, arrow_points, 0);
747 pt++;
748 annotation_stroke_arrow_init_point(p, ptc, pt, arrow_points, 2);
749 pt++;
750 annotation_stroke_arrow_init_point(p, ptc, pt, arrow_points, 4);
751 pt++;
752 annotation_stroke_arrow_init_point(p, ptc, pt, arrow_points, 0);
753}
754
756 tGPspoint *ptc,
757 bGPDspoint *pt,
758 const float corner_point[3],
759 const float arrow_points[8])
760{
762 pt++;
763 annotation_stroke_arrow_init_point(p, ptc, pt, arrow_points, 0);
764 pt++;
765 annotation_stroke_arrow_init_point(p, ptc, pt, arrow_points, 4);
766 pt++;
767 annotation_stroke_arrow_init_point(p, ptc, pt, arrow_points, 6);
768 pt++;
769 annotation_stroke_arrow_init_point(p, ptc, pt, arrow_points, 2);
770 pt++;
772}
773
775 tGPspoint *ptc,
776 bGPDspoint *pt,
777 bGPDstroke *arrow_stroke,
778 const float arrow_points[8],
779 const int style)
780{
781 float corner_conv[3];
782 copy_v3_v3(corner_conv, &pt->x);
783
784 switch (style) {
786 annotation_arrow_create_segm(p, ptc, pt, arrow_points);
787 break;
789 annotation_arrow_create_closed(p, ptc, pt, arrow_points);
790 break;
792 annotation_arrow_create_open(p, ptc, pt, corner_conv, arrow_points);
793 break;
795 annotation_arrow_create_square(p, ptc, pt, corner_conv, arrow_points);
796 break;
797 default:
798 break;
799 }
800 /* Link stroke to frame. */
801 BLI_addtail(&p->gpf->strokes, arrow_stroke);
802}
803
804/* make a new stroke from the buffer data */
806{
807 bGPdata *gpd = p->gpd;
808 bGPDlayer *gpl = p->gpl;
809 bGPDstroke *gps;
810 bGPDspoint *pt;
811 tGPspoint *ptc;
813
814 int i, totelem;
815 /* Since strokes are so fine, when using their depth we need a margin
816 * otherwise they might get missed. */
817 int depth_margin = (ts->annotate_v3d_align & GP_PROJECT_DEPTH_STROKE) ? 4 : 0;
818
819 /* get total number of points to allocate space for
820 * - drawing straight-lines only requires the endpoints
821 */
823 totelem = (gpd->runtime.sbuffer_used >= 2) ? 2 : gpd->runtime.sbuffer_used;
824 }
825 else {
826 totelem = gpd->runtime.sbuffer_used;
827 }
828
829 /* exit with error if no valid points from this stroke */
830 if (totelem == 0) {
831 return;
832 }
833
834 /* special case for poly line -- for already added stroke during session
835 * coordinates are getting added to stroke immediately to allow more
836 * interactive behavior
837 */
840 return;
841 }
842 }
843
844 /* allocate memory for a new stroke */
845 gps = MEM_callocN<bGPDstroke>("annotation_stroke");
846
847 /* copy appropriate settings for stroke */
848 gps->totpoints = totelem;
849 gps->thickness = gpl->thickness;
850 gps->fill_opacity_fac = 1.0f;
851 gps->hardness = 1.0f;
852 copy_v2_fl(gps->aspect_ratio, 1.0f);
853 gps->uv_scale = 1.0f;
854 gps->flag = gpd->runtime.sbuffer_sflag;
855 gps->inittime = p->inittime;
856 gps->tot_triangles = 0;
857
858 /* allocate enough memory for a continuous array for storage points */
859 gps->points = MEM_calloc_arrayN<bGPDspoint>(gps->totpoints, "annotation_stroke_points");
860 gps->tot_triangles = 0;
861
862 /* set pointer to first non-initialized point */
863 pt = gps->points + (gps->totpoints - totelem);
864
865 /* copy points from the buffer to the stroke */
867 /* straight lines only -> only endpoints */
868 {
869 /* first point */
870 ptc = static_cast<tGPspoint *>(gpd->runtime.sbuffer);
871
872 /* convert screen-coordinates to appropriate coordinates (and store them) */
873 annotation_stroke_convertcoords(p, ptc->m_xy, &pt->x, nullptr);
874
875 /* copy pressure and time */
876 pt->pressure = ptc->pressure;
877 pt->strength = ptc->strength;
879 pt->time = ptc->time;
880
881 pt++;
882 }
883
884 if (totelem == 2) {
885 bGPdata_Runtime runtime = blender::dna::shallow_copy(gpd->runtime);
886
887 /* Last point if applicable. */
888 ptc = ((tGPspoint *)runtime.sbuffer) + (runtime.sbuffer_used - 1);
889
890 /* Convert screen-coordinates to appropriate coordinates (and store them). */
891 annotation_stroke_convertcoords(p, ptc->m_xy, &pt->x, nullptr);
892
893 /* Copy pressure and time. */
894 pt->pressure = ptc->pressure;
895 pt->strength = ptc->strength;
897 pt->time = ptc->time;
898
900 /* End arrow stroke. */
901 if ((runtime.sbuffer_sflag & GP_STROKE_USE_ARROW_END) &&
903 {
904 int totarrowpoints = runtime.arrow_end_style;
905
906 /* Setting up arrow stroke. */
907 bGPDstroke *e_arrow_gps = BKE_gpencil_stroke_duplicate(gps, false, false);
908 annotation_stroke_arrow_allocate(e_arrow_gps, totarrowpoints);
909
910 /* Set pointer to first non-initialized point. */
911 pt = e_arrow_gps->points + (e_arrow_gps->totpoints - totarrowpoints);
912
913 /* End point. */
914 ptc = ((tGPspoint *)runtime.sbuffer) + (runtime.sbuffer_used - 1);
915 annotation_stroke_convertcoords(p, ptc->m_xy, &pt->x, nullptr);
917
918 /* Fill and convert arrow points to create arrow shape. */
920 p, ptc, pt, e_arrow_gps, runtime.arrow_end, runtime.arrow_end_style);
921 }
922 /* Start arrow stroke. */
925 {
926 int totarrowpoints = runtime.arrow_start_style;
927
928 /* Setting up arrow stroke. */
929 bGPDstroke *s_arrow_gps = BKE_gpencil_stroke_duplicate(gps, false, false);
930 annotation_stroke_arrow_allocate(s_arrow_gps, totarrowpoints);
931
932 /* Set pointer to first non-initialized point. */
933 pt = s_arrow_gps->points + (s_arrow_gps->totpoints - totarrowpoints);
934
935 /* Start point. */
936 ptc = static_cast<tGPspoint *>(runtime.sbuffer);
937 annotation_stroke_convertcoords(p, ptc->m_xy, &pt->x, nullptr);
939
940 /* Fill and convert arrow points to create arrow shape. */
942 p, ptc, pt, s_arrow_gps, runtime.arrow_start, runtime.arrow_start_style);
943 }
944 }
945 }
946 else if (p->paintmode == GP_PAINTMODE_DRAW_POLY) {
947 /* first point */
948 ptc = static_cast<tGPspoint *>(gpd->runtime.sbuffer);
949
950 /* convert screen-coordinates to appropriate coordinates (and store them) */
951 annotation_stroke_convertcoords(p, ptc->m_xy, &pt->x, nullptr);
952
953 /* copy pressure and time */
954 pt->pressure = ptc->pressure;
955 pt->strength = ptc->strength;
956 pt->time = ptc->time;
957 }
958 else {
959 float *depth_arr = nullptr;
960
961 /* get an array of depths, far depths are blended */
963 blender::int2 mval_i, mval_prev = {0, 0};
964 int interp_depth = 0;
965 int found_depth = 0;
966
967 depth_arr = MEM_malloc_arrayN<float>(gpd->runtime.sbuffer_used, "depth_points");
968
969 const ViewDepths *depths = p->depths;
970 for (i = 0, ptc = static_cast<tGPspoint *>(gpd->runtime.sbuffer);
971 i < gpd->runtime.sbuffer_used;
972 i++, ptc++, pt++)
973 {
974 mval_i = blender::int2(ptc->m_xy);
975 if ((ED_view3d_depth_read_cached(depths, mval_i, depth_margin, depth_arr + i) == 0) &&
977 depths, mval_i, mval_prev, depth_margin + 1, depth_arr + i) == 0)))
978 {
979 interp_depth = true;
980 }
981 else {
982 found_depth = true;
983 }
984
985 copy_v2_v2_int(mval_prev, mval_i);
986 }
987
988 if (found_depth == false) {
989 /* Unfortunately there is not much we can do when the depth isn't found,
990 * ignore depth in this case, use the 3D cursor. */
991 for (i = gpd->runtime.sbuffer_used - 1; i >= 0; i--) {
992 depth_arr[i] = 0.9999f;
993 }
994 }
995 else {
997 /* remove all info between the valid endpoints */
998 int first_valid = 0;
999 int last_valid = 0;
1000
1001 for (i = 0; i < gpd->runtime.sbuffer_used; i++) {
1002 if (depth_arr[i] != DEPTH_INVALID) {
1003 break;
1004 }
1005 }
1006 first_valid = i;
1007
1008 for (i = gpd->runtime.sbuffer_used - 1; i >= 0; i--) {
1009 if (depth_arr[i] != DEPTH_INVALID) {
1010 break;
1011 }
1012 }
1013 last_valid = i;
1014
1015 /* invalidate non-endpoints, so only blend between first and last */
1016 for (i = first_valid + 1; i < last_valid; i++) {
1017 depth_arr[i] = DEPTH_INVALID;
1018 }
1019
1020 interp_depth = true;
1021 }
1022
1023 if (interp_depth) {
1025 }
1026 }
1027 }
1028
1029 pt = gps->points;
1030
1031 /* convert all points (normal behavior) */
1032 for (i = 0, ptc = static_cast<tGPspoint *>(gpd->runtime.sbuffer);
1033 i < gpd->runtime.sbuffer_used && ptc;
1034 i++, ptc++, pt++)
1035 {
1036 /* convert screen-coordinates to appropriate coordinates (and store them) */
1037 annotation_stroke_convertcoords(p, ptc->m_xy, &pt->x, depth_arr ? depth_arr + i : nullptr);
1038
1039 /* copy pressure and time */
1040 pt->pressure = ptc->pressure;
1041 pt->strength = ptc->strength;
1043 pt->time = ptc->time;
1044 }
1045
1046 if (depth_arr) {
1047 MEM_freeN(depth_arr);
1048 }
1049 }
1050
1051 /* add stroke to frame */
1052 BLI_addtail(&p->gpf->strokes, gps);
1054}
1055
1056/* --- 'Eraser' for 'Paint' Tool ------ */
1057
1058/* helper to free a stroke
1059 * NOTE: gps->dvert and gps->triangles should be nullptr, but check anyway for good measure
1060 */
1062{
1063 if (gps->points) {
1064 MEM_freeN(gps->points);
1065 }
1066
1067 if (gps->dvert) {
1069 MEM_freeN(static_cast<void *>(gps->dvert));
1070 }
1071
1072 if (gps->triangles) {
1073 MEM_freeN(gps->triangles);
1074 }
1075
1076 BLI_freelinkN(&gpf->strokes, gps);
1077}
1078
1079/* only erase stroke points that are visible (3d view) */
1081 const bGPDspoint *pt,
1082 const int x,
1083 const int y)
1084{
1086 RegionView3D *rv3d = static_cast<RegionView3D *>(p->region->regiondata);
1087 const int mval_i[2] = {x, y};
1088 float mval_3d[3];
1089
1090 float p_depth;
1091 if (ED_view3d_depth_read_cached(p->depths, mval_i, 0, &p_depth)) {
1092 ED_view3d_depth_unproject_v3(p->region, mval_i, double(p_depth), mval_3d);
1093
1094 const float depth_mval = ED_view3d_calc_depth_for_comparison(rv3d, mval_3d);
1095 const float depth_pt = ED_view3d_calc_depth_for_comparison(rv3d, &pt->x);
1096
1097 if (depth_pt > depth_mval) {
1098 return true;
1099 }
1100 }
1101 }
1102 return false;
1103}
1104
1105/* Eraser tool - evaluation per stroke. */
1107 bGPDframe *gpf,
1108 bGPDstroke *gps,
1109 const float mval[2],
1110 const int radius,
1111 const rcti *rect)
1112{
1113 bGPDspoint *pt1, *pt2;
1114 int pc1[2] = {0};
1115 int pc2[2] = {0};
1116 blender::int2 mval_i = blender::int2(mval);
1117
1118 if (gps->totpoints == 0) {
1119 /* just free stroke */
1120 annotation_free_stroke(gpf, gps);
1121 }
1122 else if (gps->totpoints == 1) {
1123 /* only process if it hasn't been masked out... */
1124 if (!(p->flags & GP_PAINTFLAG_SELECTMASK) || (gps->points->flag & GP_SPOINT_SELECT)) {
1125 gpencil_point_to_xy(&p->gsc, gps, gps->points, &pc1[0], &pc1[1]);
1126
1127 /* Do bound-box check first. */
1128 if (!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1]) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) {
1129 /* only check if point is inside */
1130 if (len_v2v2_int(mval_i, pc1) <= radius) {
1131 /* free stroke */
1132 annotation_free_stroke(gpf, gps);
1133 }
1134 }
1135 }
1136 }
1137 else {
1138 /* Perform culling? */
1139 bool do_cull = false;
1140
1141 /* Clear Tags
1142 *
1143 * NOTE: It's better this way, as we are sure that
1144 * we don't miss anything, though things will be
1145 * slightly slower as a result
1146 */
1147 for (int i = 0; i < gps->totpoints; i++) {
1148 bGPDspoint *pt = &gps->points[i];
1149 pt->flag &= ~GP_SPOINT_TAG;
1150 }
1151
1152 /* First Pass: Loop over the points in the stroke
1153 * 1) Thin out parts of the stroke under the brush
1154 * 2) Tag "too thin" parts for removal (in second pass)
1155 */
1156 for (int i = 0; (i + 1) < gps->totpoints; i++) {
1157 /* get points to work with */
1158 pt1 = gps->points + i;
1159 pt2 = gps->points + i + 1;
1160
1161 /* only process if it hasn't been masked out... */
1162 if ((p->flags & GP_PAINTFLAG_SELECTMASK) && !(gps->points->flag & GP_SPOINT_SELECT)) {
1163 continue;
1164 }
1165
1166 gpencil_point_to_xy(&p->gsc, gps, pt1, &pc1[0], &pc1[1]);
1167 gpencil_point_to_xy(&p->gsc, gps, pt2, &pc2[0], &pc2[1]);
1168
1169 /* Check that point segment of the bound-box of the eraser stroke. */
1170 if ((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1]) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) ||
1171 (!ELEM(V2D_IS_CLIPPED, pc2[0], pc2[1]) && BLI_rcti_isect_pt(rect, pc2[0], pc2[1])))
1172 {
1173 /* Check if point segment of stroke had anything to do with
1174 * eraser region (either within stroke painted, or on its lines)
1175 * - this assumes that line-width is irrelevant.
1176 */
1177 if (gpencil_stroke_inside_circle(mval, radius, pc1[0], pc1[1], pc2[0], pc2[1])) {
1178 if ((annotation_stroke_eraser_is_occluded(p, pt1, pc1[0], pc1[1]) == false) ||
1179 (annotation_stroke_eraser_is_occluded(p, pt2, pc2[0], pc2[1]) == false))
1180 {
1181 /* Edge is affected - Check individual points now */
1182 if (len_v2v2_int(mval_i, pc1) <= radius) {
1183 pt1->flag |= GP_SPOINT_TAG;
1184 }
1185 if (len_v2v2_int(mval_i, pc2) <= radius) {
1186 pt2->flag |= GP_SPOINT_TAG;
1187 }
1188 do_cull = true;
1189 }
1190 }
1191 }
1192 }
1193
1194 /* Second Pass: Remove any points that are tagged */
1195 if (do_cull) {
1197 p->gpd, gpf, gps, gps->next, GP_SPOINT_TAG, false, false, 0);
1198 }
1199 }
1200}
1201
1202/* erase strokes which fall under the eraser strokes */
1204{
1205 bGPDframe *gpf = p->gpf;
1206 bGPDstroke *gps, *gpn;
1207 rcti rect;
1208
1209 /* rect is rectangle of eraser */
1210 rect.xmin = p->mval[0] - p->radius;
1211 rect.ymin = p->mval[1] - p->radius;
1212 rect.xmax = p->mval[0] + p->radius;
1213 rect.ymax = p->mval[1] + p->radius;
1214
1215 if (p->area->spacetype == SPACE_VIEW3D) {
1217 View3D *v3d = static_cast<View3D *>(p->area->spacedata.first);
1220 p->depsgraph, p->region, v3d, nullptr, V3D_DEPTH_NO_GPENCIL, false, &p->depths);
1221 }
1222 }
1223
1224 /* loop over strokes of active layer only (session init already took care of ensuring validity),
1225 * checking segments for intersections to remove
1226 */
1227 for (gps = static_cast<bGPDstroke *>(gpf->strokes.first); gps; gps = gpn) {
1228 gpn = gps->next;
1229 /* Not all strokes in the datablock may be valid in the current editor/context
1230 * (e.g. 2D space strokes in the 3D view, if the same datablock is shared)
1231 */
1233 annotation_stroke_eraser_dostroke(p, gpf, gps, p->mval, p->radius, &rect);
1234 }
1235 }
1236}
1237
1238/* ******************************************* */
1239/* Sketching Operator */
1240
1241/* clear the session buffers (call this before AND after a paint operation) */
1243{
1244 bGPdata *gpd = p->gpd;
1245
1247 &gpd->runtime.sbuffer_size,
1248 &gpd->runtime.sbuffer_used,
1249 true);
1250
1251 /* reset flags */
1252 gpd->runtime.sbuffer_sflag = 0;
1253
1254 /* reset inittime */
1255 p->inittime = 0.0;
1256}
1257
1258/* (re)init new painting data */
1260{
1261 Main *bmain = CTX_data_main(C);
1262 bGPdata **gpd_ptr = nullptr;
1263 ScrArea *curarea = CTX_wm_area(C);
1264 ARegion *region = CTX_wm_region(C);
1266
1267 /* make sure the active view (at the starting time) is a 3d-view */
1268 if (curarea == nullptr) {
1270 return false;
1271 }
1272
1273 /* pass on current scene and window */
1274 p->bmain = CTX_data_main(C);
1275 p->scene = CTX_data_scene(C);
1277 p->win = CTX_wm_window(C);
1278
1279 unit_m4(p->imat);
1280 unit_m4(p->mat);
1281
1282 switch (curarea->spacetype) {
1283 /* supported views first */
1284 case SPACE_VIEW3D: {
1285 // View3D *v3d = curarea->spacedata.first;
1286 // RegionView3D *rv3d = region->regiondata;
1287
1288 /* set current area
1289 * - must verify that region data is 3D-view (and not something else)
1290 */
1291 /* CAUTION: If this is the "toolbar", then this will change on the first stroke */
1292 p->area = curarea;
1293 p->region = region;
1295
1296 if (region->regiondata == nullptr) {
1298 return false;
1299 }
1300 break;
1301 }
1302 case SPACE_NODE: {
1303 // SpaceNode *snode = curarea->spacedata.first;
1304
1305 /* set current area */
1306 p->area = curarea;
1307 p->region = region;
1308 p->v2d = &region->v2d;
1309 p->align_flag = &ts->gpencil_v2d_align;
1310 break;
1311 }
1312 case SPACE_SEQ: {
1313 SpaceSeq *sseq = static_cast<SpaceSeq *>(curarea->spacedata.first);
1314
1315 /* set current area */
1316 p->area = curarea;
1317 p->region = region;
1318 p->v2d = &region->v2d;
1319 p->align_flag = &ts->gpencil_v2d_align;
1320
1321 /* check that gpencil data is allowed to be drawn */
1322 if (!((sseq->mainb == SEQ_DRAW_IMG_IMBUF) && (region->regiontype == RGN_TYPE_PREVIEW))) {
1324 return false;
1325 }
1326 break;
1327 }
1328 case SPACE_IMAGE: {
1329 // SpaceImage *sima = curarea->spacedata.first;
1330
1331 /* set the current area */
1332 p->area = curarea;
1333 p->region = region;
1334 p->v2d = &region->v2d;
1335 p->align_flag = &ts->gpencil_v2d_align;
1336 break;
1337 }
1338 case SPACE_CLIP: {
1339 SpaceClip *sc = static_cast<SpaceClip *>(curarea->spacedata.first);
1341
1342 if (clip == nullptr) {
1344 return false;
1345 }
1346
1347 /* set the current area */
1348 p->area = curarea;
1349 p->region = region;
1350 p->v2d = &region->v2d;
1351 p->align_flag = &ts->gpencil_v2d_align;
1352
1353 invert_m4_m4(p->imat, sc->unistabmat);
1354
1355 /* custom color for new layer */
1356 p->custom_color[0] = 1.0f;
1357 p->custom_color[1] = 0.0f;
1358 p->custom_color[2] = 0.5f;
1359 p->custom_color[3] = 0.9f;
1360
1361 if (sc->gpencil_src == SC_GPENCIL_SRC_TRACK) {
1362 int framenr = ED_space_clip_get_clip_frame_number(sc);
1363 const MovieTrackingObject *tracking_object = BKE_tracking_object_get_active(
1364 &clip->tracking);
1365 MovieTrackingTrack *track = tracking_object->active_track;
1366 MovieTrackingMarker *marker = track ? BKE_tracking_marker_get(track, framenr) : nullptr;
1367
1368 if (marker) {
1369 p->imat[3][0] -= marker->pos[0];
1370 p->imat[3][1] -= marker->pos[1];
1371 }
1372 else {
1374 return false;
1375 }
1376 }
1377
1378 invert_m4_m4(p->mat, p->imat);
1379 copy_m4_m4(p->gsc.mat, p->mat);
1380 break;
1381 }
1382 /* unsupported views */
1383 default: {
1385 return false;
1386 }
1387 }
1388
1389 /* get gp-data */
1391 if (gpd_ptr == nullptr) {
1393 return false;
1394 }
1395
1396 /* if no existing GPencil block exists, add one */
1397 if (*gpd_ptr == nullptr) {
1398 bGPdata *gpd = BKE_gpencil_data_addnew(bmain, "Annotations");
1399 *gpd_ptr = gpd;
1400
1401 /* mark datablock as being used for annotations */
1402 gpd->flag |= GP_DATA_ANNOTATIONS;
1403 }
1404 p->gpd = *gpd_ptr;
1405
1406 /* clear out buffer (stored in gp-data), in case something contaminated it */
1408
1409 return true;
1410}
1411
1412/* Enable the annotations in the current space. */
1414{
1415 ScrArea *area = p->area;
1416 switch (area->spacetype) {
1417 case SPACE_VIEW3D: {
1418 View3D *v3d = (View3D *)area->spacedata.first;
1419 v3d->flag2 |= V3D_SHOW_ANNOTATION;
1420 break;
1421 }
1422 case SPACE_SEQ: {
1423 SpaceSeq *sseq = (SpaceSeq *)area->spacedata.first;
1425 break;
1426 }
1427 case SPACE_IMAGE: {
1428 SpaceImage *sima = (SpaceImage *)area->spacedata.first;
1429 sima->flag |= SI_SHOW_GPENCIL;
1430 break;
1431 }
1432 case SPACE_NODE: {
1433 SpaceNode *snode = (SpaceNode *)area->spacedata.first;
1434 snode->flag |= SNODE_SHOW_GPENCIL;
1435 break;
1436 }
1437 case SPACE_CLIP: {
1438 SpaceClip *sclip = (SpaceClip *)area->spacedata.first;
1439 sclip->flag |= SC_SHOW_ANNOTATION;
1440 break;
1441 }
1442 default:
1443 break;
1444 }
1445}
1446
1447/* init new painting session */
1449{
1450 tGPsdata *p = nullptr;
1451
1452 /* create new context data */
1453 p = MEM_new<tGPsdata>("Annotation Drawing Data");
1454
1455 /* Try to initialize context data
1456 * WARNING: This may not always succeed (e.g. using GP in an annotation-only context)
1457 */
1458 if (annotation_session_initdata(C, p) == 0) {
1459 /* Invalid state - Exit
1460 * NOTE: It should be safe to just free the data, since failing context checks should
1461 * only happen when no data has been allocated.
1462 */
1463 MEM_delete(p);
1464 return nullptr;
1465 }
1466
1467 /* Radius for eraser circle is defined in user-preferences. */
1468 /* NOTE: we do this here, so that if we exit immediately,
1469 * erase size won't get lost
1470 */
1471 p->radius = U.gp_eraser;
1472
1473 /* Annotations must be always visible when use it. */
1475
1476 /* return context data for running paint operator */
1477 return p;
1478}
1479
1480/* cleanup after a painting session */
1482{
1483 bGPdata *gpd = (p) ? p->gpd : nullptr;
1484
1485 /* error checking */
1486 if (gpd == nullptr) {
1487 return;
1488 }
1489
1490 /* free stroke buffer */
1491 if (gpd->runtime.sbuffer) {
1493 gpd->runtime.sbuffer = nullptr;
1494 }
1495
1496 /* clear flags */
1497 gpd->runtime.sbuffer_used = 0;
1498 gpd->runtime.sbuffer_size = 0;
1499 gpd->runtime.sbuffer_sflag = 0;
1500 p->inittime = 0.0;
1501}
1502
1504{
1505 if (p->depths) {
1507 }
1508 MEM_delete(p);
1509}
1510
1511/* init new stroke */
1513 eGPencil_PaintModes paintmode,
1514 Depsgraph *depsgraph)
1515{
1516 Scene *scene = p->scene;
1517 ToolSettings *ts = scene->toolsettings;
1518
1519 /* Call to the annotation pre handler to notify python the annotation starts. */
1521
1522 /* get active layer (or add a new one if non-existent) */
1524 if (p->gpl == nullptr) {
1525 /* tag for annotations */
1527 p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("Note"), true, false);
1528
1529 if (p->custom_color[3]) {
1531 }
1532 }
1533 if (p->gpl->flag & GP_LAYER_LOCKED) {
1535 return;
1536 }
1537
1538 /* get active frame (add a new one if not matching frame) */
1539 if (paintmode == GP_PAINTMODE_ERASER) {
1540 /* Eraser mode:
1541 * 1) Only allow erasing on the active layer (unlike for 3d-art Grease Pencil),
1542 * since we won't be exposing layer locking in the UI
1543 * 2) Ensure that p->gpf refers to the frame used for the active layer
1544 * (to avoid problems with other tools which expect it to exist)
1545 */
1546 bool has_layer_to_erase = false;
1547
1549 /* Ensure that there's stuff to erase here (not including selection mask below)... */
1550 if (p->gpl->actframe && p->gpl->actframe->strokes.first) {
1551 has_layer_to_erase = true;
1552 }
1553 }
1554
1555 /* Ensure active frame is set correctly... */
1556 p->gpf = p->gpl->actframe;
1557
1558 if (has_layer_to_erase == false) {
1560 printf("Error: Eraser will not be affecting anything (gpencil_paint_init)\n");
1561 return;
1562 }
1563 }
1564 else {
1565 /* Drawing Modes - Add a new frame if needed on the active layer */
1566 short add_frame_mode = GP_GETFRAME_ADD_NEW;
1567
1569 add_frame_mode = GP_GETFRAME_ADD_COPY;
1570 }
1571 else {
1572 add_frame_mode = GP_GETFRAME_ADD_NEW;
1573 }
1574
1575 p->gpf = BKE_gpencil_layer_frame_get(p->gpl, scene->r.cfra, eGP_GetFrame_Mode(add_frame_mode));
1576
1577 if (p->gpf == nullptr) {
1579 return;
1580 }
1581
1582 p->gpf->flag |= GP_FRAME_PAINT;
1583 }
1584
1585 /* set 'eraser' for this stroke if using eraser */
1586 p->paintmode = paintmode;
1587 if (p->paintmode == GP_PAINTMODE_ERASER) {
1589
1590 /* check if we should respect depth while erasing */
1591 if (p->area->spacetype == SPACE_VIEW3D) {
1592 if (p->gpl->flag & GP_LAYER_NO_XRAY) {
1594 }
1595 }
1596 }
1597 else {
1598 /* disable eraser flags - so that we can switch modes during a session */
1600
1601 if (p->area->spacetype == SPACE_VIEW3D) {
1602 if (p->gpl->flag & GP_LAYER_NO_XRAY) {
1604 }
1605 }
1606 }
1607
1608 /* set 'initial run' flag, which is only used to denote when a new stroke is starting */
1610
1611 /* when drawing in the camera view, in 2D space, set the subrect */
1612 p->subrect = nullptr;
1613 if ((*p->align_flag & GP_PROJECT_VIEWSPACE) == 0) {
1614 if (p->area->spacetype == SPACE_VIEW3D) {
1615 View3D *v3d = static_cast<View3D *>(p->area->spacedata.first);
1616 RegionView3D *rv3d = static_cast<RegionView3D *>(p->region->regiondata);
1617
1618 /* for camera view set the subrect */
1619 if (rv3d->persp == RV3D_CAMOB) {
1620 /* no shift */
1622 p->scene, depsgraph, p->region, v3d, rv3d, true, &p->subrect_data);
1623 p->subrect = &p->subrect_data;
1624 }
1625 }
1626 }
1627
1628 /* init stroke point space-conversion settings... */
1629 p->gsc.gpd = p->gpd;
1630 p->gsc.gpl = p->gpl;
1631
1632 p->gsc.area = p->area;
1633 p->gsc.region = p->region;
1634 p->gsc.v2d = p->v2d;
1635
1637 p->gsc.subrect = p->subrect;
1638
1639 copy_m4_m4(p->gsc.mat, p->mat);
1640
1641 /* check if points will need to be made in view-aligned space */
1642 if (*p->align_flag & GP_PROJECT_VIEWSPACE) {
1643 switch (p->area->spacetype) {
1644 case SPACE_VIEW3D: {
1646 break;
1647 }
1648 case SPACE_NODE:
1649 case SPACE_SEQ:
1650 case SPACE_IMAGE:
1651 case SPACE_CLIP: {
1653 break;
1654 }
1655 }
1656 }
1657}
1658
1659/* finish off a stroke (clears buffer, but doesn't finish the paint operation) */
1661{
1663 const bool is_eraser = (p->gpd->runtime.sbuffer_sflag & GP_STROKE_ERASER) != 0;
1664 /* for surface sketching, need to set the right OpenGL context stuff so that
1665 * the conversions will project the values correctly...
1666 */
1667 if (annotation_project_check(p)) {
1668 View3D *v3d = static_cast<View3D *>(p->area->spacedata.first);
1669
1671
1675 }
1676 else {
1677 mode = V3D_DEPTH_ALL;
1678 }
1679 }
1680 /* need to restore the original projection settings before packing up */
1683 p->depsgraph, p->region, v3d, nullptr, mode, false, is_eraser ? nullptr : &p->depths);
1684 }
1685
1686 /* check if doing eraser or not */
1687 if (!is_eraser) {
1688 /* transfer stroke to frame */
1690 }
1691
1692 /* Call to the annotation post handler to notify python the annotation is done. */
1694
1695 /* clean up buffer now */
1697}
1698
1699/* finish off stroke painting operation */
1701{
1702 /* p->gpd==nullptr happens when stroke failed to initialize,
1703 * for example when GP is hidden in current space (sergey)
1704 */
1705 if (p->gpd) {
1706 /* finish off a stroke */
1708 }
1709
1710 /* "unlock" frame */
1711 if (p->gpf) {
1712 p->gpf->flag &= ~GP_FRAME_PAINT;
1713 }
1714}
1715
1716/* ------------------------------- */
1717
1718/* Helper callback for drawing the cursor itself */
1720 const blender::int2 &xy,
1721 const blender::float2 & /*tilt*/,
1722 void *p_ptr)
1723{
1724 tGPsdata *p = (tGPsdata *)p_ptr;
1725
1726 if (p->paintmode == GP_PAINTMODE_ERASER) {
1728 const uint shdr_pos = GPU_vertformat_attr_add(
1729 format, "pos", blender::gpu::VertAttrType::SFLOAT_32_32);
1731
1732 GPU_line_smooth(true);
1734
1735 immUniformColor4ub(255, 100, 100, 20);
1736 imm_draw_circle_fill_2d(shdr_pos, xy[0], xy[1], p->radius, 40);
1737
1739
1741
1742 float viewport_size[4];
1743 GPU_viewport_size_get_f(viewport_size);
1744 immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
1745
1746 immUniformColor4f(1.0f, 0.39f, 0.39f, 0.78f);
1747 immUniform1i("colors_len", 0); /* "simple" mode */
1748 immUniform1f("dash_width", 12.0f);
1749 immUniform1f("udash_factor", 0.5f);
1750
1751 imm_draw_circle_wire_2d(shdr_pos,
1752 xy.x,
1753 xy.y,
1754 p->radius,
1755 /* XXX Dashed shader gives bad results with sets of small segments
1756 * currently, temp hack around the issue. :( */
1757 max_ii(8, p->radius / 2)); /* was fixed 40 */
1758
1760
1762 GPU_line_smooth(false);
1763 }
1764}
1765
1766/* Turn brush cursor in 3D view on/off */
1768{
1769 if (p->erasercursor && !enable) {
1770 /* clear cursor */
1772 p->erasercursor = nullptr;
1773 }
1774 else if (enable && !p->erasercursor) {
1775 /* enable cursor */
1778 nullptr, /* XXX */
1780 p);
1781 }
1782}
1784 const blender::int2 &xy,
1785 const blender::float2 & /*tilt*/,
1786 void *p_ptr)
1787{
1788 ARegion *region = CTX_wm_region(C);
1789 tGPsdata *p = (tGPsdata *)p_ptr;
1790 bGPdata_Runtime runtime = blender::dna::shallow_copy(p->gpd->runtime);
1791 const tGPspoint *points = static_cast<const tGPspoint *>(runtime.sbuffer);
1792 int totpoints = runtime.sbuffer_used;
1793 if (totpoints < 2) {
1794 return;
1795 }
1796 const tGPspoint *pt = &points[totpoints - 1];
1797
1799 uint pos = GPU_vertformat_attr_add(format, "pos", blender::gpu::VertAttrType::SFLOAT_32_32);
1801 GPU_line_smooth(true);
1803 GPU_line_width(1.25f);
1804 const float color[3] = {1.0f, 0.39f, 0.39f};
1805
1806 /* default radius and color */
1807 float darkcolor[3];
1808 const float radius = 4.0f;
1809
1810 /* Inner Ring: Color from UI panel */
1811 immUniformColor4f(color[0], color[1], color[2], 0.8f);
1812 imm_draw_circle_wire_2d(pos, xy.x, xy.y, radius, 40);
1813
1814 /* Outer Ring: Dark color for contrast on light backgrounds (e.g. gray on white) */
1815 mul_v3_v3fl(darkcolor, color, 0.40f);
1816 immUniformColor4f(darkcolor[0], darkcolor[1], darkcolor[2], 0.8f);
1817 imm_draw_circle_wire_2d(pos, xy.x, xy.y, radius + 1, 40);
1818
1819 /* Rope Simple. */
1820 immUniformColor4f(color[0], color[1], color[2], 0.8f);
1822 immVertex2f(pos, pt->m_xy[0] + region->winrct.xmin, pt->m_xy[1] + region->winrct.ymin);
1824 immEnd();
1825
1826 /* Returns back all GPU settings */
1828 GPU_line_smooth(false);
1829
1831}
1832
1833/* Turn *stabilizer* brush cursor in 3D view on/off */
1835{
1836 if (p->stabilizer_cursor && !enable) {
1837 /* clear cursor */
1839 p->stabilizer_cursor = nullptr;
1840 }
1841 else if (enable && !p->stabilizer_cursor) {
1842 /* enable cursor */
1845 }
1846}
1847
1848/* Check if tablet eraser is being used (when processing events) */
1850{
1851 return (event->tablet.active == EVT_TABLET_ERASER);
1852}
1853
1854/* ------------------------------- */
1855
1857{
1858 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
1859
1860 /* restore cursor to indicate end of drawing */
1862
1863 /* don't assume that operator data exists at all */
1864 if (p) {
1865 /* check size of buffer before cleanup, to determine if anything happened here */
1866 if (p->paintmode == GP_PAINTMODE_ERASER) {
1867 /* turn off radial brush cursor */
1869 }
1870 else if (p->paintmode == GP_PAINTMODE_DRAW) {
1872 }
1873
1874 /* always store the new eraser size to be used again next time
1875 * NOTE: Do this even when not in eraser mode, as eraser may
1876 * have been toggled at some point.
1877 */
1878 U.gp_eraser = p->radius;
1879
1880 /* cleanup */
1884 p = nullptr;
1885 }
1886
1887 op->customdata = nullptr;
1888}
1889
1891{
1892 /* this is just a wrapper around exit() */
1894}
1895
1896/* ------------------------------- */
1897
1898static int annotation_draw_init(bContext *C, wmOperator *op, const wmEvent *event)
1899{
1900 tGPsdata *p;
1901 eGPencil_PaintModes paintmode = eGPencil_PaintModes(RNA_enum_get(op->ptr, "mode"));
1902
1903 /* check context */
1904 p = static_cast<tGPsdata *>(op->customdata = annotation_session_initpaint(C));
1905 if ((p == nullptr) || (p->status == GP_STATUS_ERROR)) {
1906 /* something wasn't set correctly in context */
1908 return 0;
1909 }
1910
1911 /* init painting data */
1913 if (p->status == GP_STATUS_ERROR) {
1915 return 0;
1916 }
1917
1918 if (event != nullptr) {
1919 p->keymodifier = event->keymodifier;
1920 }
1921 else {
1922 p->keymodifier = -1;
1923 }
1924
1925 /* everything is now setup ok */
1926 return 1;
1927}
1928
1929/* ------------------------------- */
1930
1931/* ensure that the correct cursor icon is set */
1933{
1934 if (p->paintmode == GP_PAINTMODE_ERASER) {
1936 }
1937 else {
1939 }
1940}
1941
1942/* update UI indicators of status, including cursor and header prints */
1944{
1946
1947 /* header prints */
1948 switch (p->status) {
1949 case GP_STATUS_PAINTING:
1950 switch (p->paintmode) {
1952 status.item(IFACE_("End"), ICON_EVENT_ESC);
1953 status.item(IFACE_("Place Next Stroke Vertex"), ICON_MOUSE_LMB);
1954 break;
1956 status.item(IFACE_("End"), ICON_EVENT_ESC);
1957 status.item(IFACE_("Erase"), ICON_MOUSE_LMB);
1958 break;
1960 status.item(IFACE_("End"), ICON_EVENT_ESC);
1961 status.item(IFACE_("Draw"), ICON_MOUSE_LMB);
1962 break;
1963 case GP_PAINTMODE_DRAW:
1964 status.item(IFACE_("End"), ICON_EVENT_ESC);
1965 status.item(IFACE_("Draw"), ICON_MOUSE_LMB);
1966 break;
1967
1968 default: /* unhandled future cases */
1969 status.item(IFACE_("End"), ICON_EVENT_ESC);
1970 break;
1971 }
1972 break;
1973
1974 case GP_STATUS_IDLING:
1975 case GP_STATUS_ERROR:
1976 case GP_STATUS_DONE:
1977 case GP_STATUS_CAPTURE:
1978 break;
1979 }
1980}
1981
1982/* ------------------------------- */
1983
1984/* create a new stroke point at the point indicated by the painting context */
1985static void annotation_draw_apply(wmOperator *op, tGPsdata *p, Depsgraph *depsgraph)
1986{
1987 /* handle drawing/erasing -> test for erasing first */
1988 if (p->paintmode == GP_PAINTMODE_ERASER) {
1989 /* do 'live' erasing now */
1991
1992 /* store used values */
1993 p->mvalo[0] = p->mval[0];
1994 p->mvalo[1] = p->mval[1];
1995 p->opressure = p->pressure;
1996 }
1997 /* Only add current point to buffer if mouse moved
1998 * (even though we got an event, it might be just noise). */
1999 else if (annotation_stroke_filtermval(p, p->mval, p->mvalo)) {
2000 /* If lazy mouse, interpolate the last and current mouse positions. */
2002 float now_mouse[2];
2003 float last_mouse[2];
2004 copy_v2_v2(now_mouse, p->mval);
2005 copy_v2_v2(last_mouse, p->mvalo);
2006 interp_v2_v2v2(now_mouse, now_mouse, last_mouse, min_ff(p->stabilizer_factor, .995f));
2007 copy_v2_v2(p->mval, now_mouse);
2008 }
2009
2010 /* try to add point */
2011 short ok = annotation_stroke_addpoint(p, p->mval, p->pressure, p->curtime);
2012
2013 /* handle errors while adding point */
2015 /* finish off old stroke */
2017 /* And start a new one!!! Else, projection errors! */
2019
2020 /* start a new stroke, starting from previous point */
2021 if (ok == GP_STROKEADD_OVERFLOW) {
2022 p->inittime = p->ocurtime;
2024 }
2025 else {
2026 p->inittime = p->curtime;
2027 }
2029 }
2030 else if (ok == GP_STROKEADD_INVALID) {
2031 /* the painting operation cannot continue... */
2032 BKE_report(op->reports, RPT_ERROR, "Cannot paint stroke");
2034
2035 return;
2036 }
2037
2038 /* store used values */
2039 p->mvalo[0] = p->mval[0];
2040 p->mvalo[1] = p->mval[1];
2041 p->opressure = p->pressure;
2042 p->ocurtime = p->curtime;
2043 }
2044}
2045
2046/* handle draw event */
2048 wmOperator *op, const wmEvent *event, Depsgraph *depsgraph, float x, float y)
2049{
2050 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2051 PointerRNA itemptr;
2052 float mousef[2];
2053
2054 /* Convert from window-space to area-space mouse coordinates
2055 * add any x,y override position for fake events. */
2056 if (p->flags & GP_PAINTFLAG_FIRSTRUN) {
2057 /* The first run may be a drag event, see: #99368. */
2059 p->mval[0] -= x;
2060 p->mval[1] -= y;
2061 }
2062 else {
2063 p->mval[0] = float(event->mval[0]) - x;
2064 p->mval[1] = float(event->mval[1]) - y;
2065 }
2066
2067 /* Key to toggle stabilization. */
2068 if ((event->modifier & KM_SHIFT) && (p->paintmode == GP_PAINTMODE_DRAW)) {
2069 /* Using permanent stabilization, shift will deactivate the flag. */
2074 }
2075 }
2076 /* Not using any stabilization flag. Activate temporal one. */
2077 else if ((p->flags & GP_PAINTFLAG_USE_STABILIZER_TEMP) == 0) {
2080 }
2081 }
2082 /* verify key status for straight lines */
2083 else if (event->modifier & (KM_CTRL | KM_ALT)) {
2084 if (p->straight[0] == 0) {
2085 int dx = abs(int(p->mval[0] - p->mvalo[0]));
2086 int dy = abs(int(p->mval[1] - p->mvalo[1]));
2087 if ((dx > 0) || (dy > 0)) {
2088 /* check mouse direction to replace the other coordinate with previous values */
2089 if (dx >= dy) {
2090 /* horizontal */
2091 p->straight[0] = 1;
2092 p->straight[1] = p->mval[1]; /* save y */
2093 }
2094 else {
2095 /* vertical */
2096 p->straight[0] = 2;
2097 p->straight[1] = p->mval[0]; /* save x */
2098 }
2099 }
2100 }
2101 }
2102 else {
2103 p->straight[0] = 0;
2104 /* We were using shift while having permanent stabilization active,
2105 * so activate the temp flag back again. */
2107 if ((p->flags & GP_PAINTFLAG_USE_STABILIZER_TEMP) == 0) {
2110 }
2111 }
2112 /* We are using the temporal stabilizer flag at the moment,
2113 * but shift is not pressed as well as the permanent flag is not used,
2114 * so we don't need the cursor anymore. */
2116 /* Reset temporal stabilizer flag and remove cursor. */
2119 }
2120 }
2121
2123
2124 /* handle pressure sensitivity (which is supplied by tablets or otherwise 1.0) */
2125 p->pressure = event->tablet.pressure;
2126
2127 /* Hack for pressure sensitive eraser on D+RMB when using a tablet:
2128 * The pen has to float over the tablet surface, resulting in
2129 * zero pressure (#47101). Ignore pressure values if floating
2130 * (i.e. "effectively zero" pressure), and only when the "active"
2131 * end is the stylus (i.e. the default when not eraser)
2132 */
2133 if (p->paintmode == GP_PAINTMODE_ERASER) {
2134 if ((event->tablet.active != EVT_TABLET_ERASER) && (p->pressure < 0.001f)) {
2135 p->pressure = 1.0f;
2136 }
2137 }
2138
2139 /* special exception for start of strokes (i.e. maybe for just a dot) */
2140 if (p->flags & GP_PAINTFLAG_FIRSTRUN) {
2142
2143 p->mvalo[0] = p->mval[0];
2144 p->mvalo[1] = p->mval[1];
2145 p->opressure = p->pressure;
2146 p->inittime = p->ocurtime = p->curtime;
2147 p->straight[0] = 0;
2148 p->straight[1] = 0;
2149
2150 /* special exception here for too high pressure values on first touch in
2151 * windows for some tablets, then we just skip first touch...
2152 */
2153 if ((event->tablet.active != EVT_TABLET_NONE) && (p->pressure >= 0.99f)) {
2154 return;
2155 }
2156 }
2157
2158 /* check if alt key is pressed and limit to straight lines */
2159 if ((p->paintmode != GP_PAINTMODE_ERASER) && (p->straight[0] != 0)) {
2160 if (p->straight[0] == 1) {
2161 /* horizontal */
2162 p->mval[1] = p->straight[1]; /* replace y */
2163 }
2164 else {
2165 /* vertical */
2166 p->mval[0] = p->straight[1]; /* replace x */
2167 }
2168 }
2169
2170 /* fill in stroke data (not actually used directly by gpencil_draw_apply) */
2171 RNA_collection_add(op->ptr, "stroke", &itemptr);
2172
2173 mousef[0] = p->mval[0];
2174 mousef[1] = p->mval[1];
2175 RNA_float_set_array(&itemptr, "mouse", mousef);
2176 RNA_float_set(&itemptr, "pressure", p->pressure);
2177 RNA_boolean_set(&itemptr, "is_start", (p->flags & GP_PAINTFLAG_FIRSTRUN) != 0);
2178
2179 RNA_float_set(&itemptr, "time", p->curtime - p->inittime);
2180
2181 /* apply the current latest drawing point */
2183
2184 /* force refresh */
2185 /* just active area for now, since doing whole screen is too slow */
2187}
2188
2189/* ------------------------------- */
2190
2191/* operator 'redo' (i.e. after changing some properties, but also for repeat last) */
2193{
2195
2196 /* try to initialize context data needed while drawing */
2197 if (!annotation_draw_init(C, op, nullptr)) {
2198 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2199 MEM_delete(p);
2200 op->customdata = nullptr;
2201 return OPERATOR_CANCELLED;
2202 }
2203
2204 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2205
2206 /* loop over the stroke RNA elements recorded (i.e. progress of mouse movement),
2207 * setting the relevant values in context at each step, then applying
2208 */
2209 RNA_BEGIN (op->ptr, itemptr, "stroke") {
2210 float mousef[2];
2211
2212 /* get relevant data for this point from stroke */
2213 RNA_float_get_array(&itemptr, "mouse", mousef);
2214 p->mval[0] = int(mousef[0]);
2215 p->mval[1] = int(mousef[1]);
2216 p->pressure = RNA_float_get(&itemptr, "pressure");
2217 p->curtime = double(RNA_float_get(&itemptr, "time")) + p->inittime;
2218
2219 if (RNA_boolean_get(&itemptr, "is_start")) {
2220 /* if first-run flag isn't set already (i.e. not true first stroke),
2221 * then we must terminate the previous one first before continuing
2222 */
2223 if ((p->flags & GP_PAINTFLAG_FIRSTRUN) == 0) {
2224 /* TODO: both of these ops can set error-status, but we probably don't need to worry */
2227 }
2228 }
2229
2230 /* if first run, set previous data too */
2231 if (p->flags & GP_PAINTFLAG_FIRSTRUN) {
2233
2234 p->mvalo[0] = p->mval[0];
2235 p->mvalo[1] = p->mval[1];
2236 p->opressure = p->pressure;
2237 p->ocurtime = p->curtime;
2238 }
2239
2240 /* apply this data as necessary now (as per usual) */
2242 }
2243 RNA_END;
2244
2245 /* cleanup */
2247
2248 /* refreshes */
2250
2251 /* done */
2252 return OPERATOR_FINISHED;
2253}
2254
2255/* ------------------------------- */
2256
2257/* start of interactive drawing part of operator */
2259{
2260 /* support for tablets eraser pen */
2262 RNA_enum_set(op->ptr, "mode", GP_PAINTMODE_ERASER);
2263 }
2264
2265 /* try to initialize context data needed while drawing */
2266 if (!annotation_draw_init(C, op, event)) {
2267 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2268 MEM_delete(p);
2269 op->customdata = nullptr;
2270 return OPERATOR_CANCELLED;
2271 }
2272
2273 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2274
2275 /* if empty erase capture and finish */
2276 if (p->status == GP_STATUS_CAPTURE) {
2278
2279 BKE_report(op->reports, RPT_ERROR, "Nothing to erase");
2280 return OPERATOR_FINISHED;
2281 }
2282
2283 /* if eraser is on, draw radial aid */
2284 if (p->paintmode == GP_PAINTMODE_ERASER) {
2286 }
2287 else if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) {
2288 if (RNA_enum_get(op->ptr, "arrowstyle_start") != GP_STROKE_ARROWSTYLE_NONE) {
2290 p->gpd->runtime.arrow_start_style = RNA_enum_get(op->ptr, "arrowstyle_start");
2291 }
2292 if (RNA_enum_get(op->ptr, "arrowstyle_end") != GP_STROKE_ARROWSTYLE_NONE) {
2294 p->gpd->runtime.arrow_end_style = RNA_enum_get(op->ptr, "arrowstyle_end");
2295 }
2296 }
2297 else if (p->paintmode == GP_PAINTMODE_DRAW) {
2298 p->stabilizer_factor = RNA_float_get(op->ptr, "stabilizer_factor");
2299 p->stabilizer_radius = RNA_int_get(op->ptr, "stabilizer_radius");
2300 if (RNA_boolean_get(op->ptr, "use_stabilizer")) {
2303 }
2304 else if (event->modifier & KM_SHIFT) {
2307 }
2308 }
2309 /* set cursor
2310 * NOTE: This may change later (i.e. intentionally via brush toggle,
2311 * or unintentionally if the user scrolls outside the area)...
2312 */
2314
2315 /* only start drawing immediately if we're allowed to do so... */
2316 if (RNA_boolean_get(op->ptr, "wait_for_input") == false) {
2317 /* hotkey invoked - start drawing */
2319
2320 /* handle the initial drawing - i.e. for just doing a simple dot */
2323 }
2324 else {
2325 /* toolbar invoked - don't start drawing yet... */
2327 }
2328
2330 /* add a modal handler for this operator, so that we can then draw continuous strokes */
2333}
2334
2335/* gpencil modal operator stores area, which can be removed while using it (like full-screen). */
2336static bool annotation_area_exists(bContext *C, ScrArea *area_test)
2337{
2338 bScreen *screen = CTX_wm_screen(C);
2339 return (BLI_findindex(&screen->areabase, area_test) != -1);
2340}
2341
2343{
2344 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2345
2346 /* we must check that we're still within the area that we're set up to work from
2347 * otherwise we could crash (see bug #20586)
2348 */
2349 if (CTX_wm_area(C) != p->area) {
2350 printf("\t\t\tGP - wrong area execution abort!\n");
2352 }
2353
2354 /* we may need to set up paint env again if we're resuming */
2355 /* XXX: watch it with the paintmode! in future,
2356 * it'd be nice to allow changing paint-mode when in sketching-sessions */
2357
2360 }
2361
2362 if (p->status != GP_STATUS_ERROR) {
2365 }
2366
2367 return static_cast<tGPsdata *>(op->customdata);
2368}
2369
2371{
2372 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2373
2375
2377
2380
2381 p->gpd = nullptr;
2382 p->gpl = nullptr;
2383 p->gpf = nullptr;
2384}
2385
2386/* add events for missing mouse movements when the artist draw very fast */
2388 wmOperator *op,
2389 const wmEvent *event,
2390 tGPsdata *p)
2391{
2393 float pt[2], a[2], b[2];
2394 float factor = 10.0f;
2395
2396 copy_v2_v2(a, p->mvalo);
2397 b[0] = float(event->mval[0]) + 1.0f;
2398 b[1] = float(event->mval[1]) + 1.0f;
2399
2400 /* get distance in pixels */
2401 float dist = len_v2v2(a, b);
2402
2403 /* for very small distances, add a half way point */
2404 if (dist <= 2.0f) {
2405 interp_v2_v2v2(pt, a, b, 0.5f);
2406 sub_v2_v2v2(pt, b, pt);
2407 /* create fake event */
2408 annotation_draw_apply_event(op, event, depsgraph, pt[0], pt[1]);
2409 }
2410 else if (dist >= factor) {
2411 int slices = 2 + int((dist - 1.0) / factor);
2412 float n = 1.0f / slices;
2413 for (int i = 1; i < slices; i++) {
2414 interp_v2_v2v2(pt, a, b, n * i);
2415 sub_v2_v2v2(pt, b, pt);
2416 /* create fake event */
2417 annotation_draw_apply_event(op, event, depsgraph, pt[0], pt[1]);
2418 }
2419 }
2420}
2421
2422/* events handling during interactive drawing part of operator */
2424{
2425 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2426 /* Default exit state - pass through to support MMB view navigation, etc. */
2428
2429/* NOTE(mike erwin): Not quite what I was looking for, but a good start!
2430 * grease-pencil continues to draw on the screen while the 3D mouse moves the viewpoint.
2431 * Problem is that the stroke is converted to 3D only after it is finished.
2432 * This approach should work better in tools that immediately apply in 3D space. */
2433#if 0
2434 if (event->type == NDOF_MOTION) {
2435 return OPERATOR_PASS_THROUGH;
2436 }
2437#endif
2438
2439 if (p->status == GP_STATUS_IDLING) {
2440 ARegion *region = CTX_wm_region(C);
2441 p->region = region;
2442 }
2443
2444 /* We don't pass on key events, GP is used with key-modifiers -
2445 * prevents D-key to insert drivers. */
2446 if (ISKEYBOARD(event->type)) {
2447 if (ELEM(event->type,
2452 EVT_ZKEY))
2453 {
2454 /* allow some keys:
2455 * - For frame changing #33412.
2456 * - For undo (during sketching sessions).
2457 */
2458 }
2459 else if (ELEM(event->type,
2460 EVT_PAD0,
2461 EVT_PAD1,
2462 EVT_PAD2,
2463 EVT_PAD3,
2464 EVT_PAD4,
2465 EVT_PAD5,
2466 EVT_PAD6,
2467 EVT_PAD7,
2468 EVT_PAD8,
2469 EVT_PAD9))
2470 {
2471 /* Allow numpad keys so that camera/view manipulations can still take place
2472 * - #EVT_PAD0 in particular is really important for Grease Pencil drawing,
2473 * as animators may be working "to camera", so having this working
2474 * is essential for ensuring that they can quickly return to that view.
2475 */
2476 }
2477 else if ((event->type == EVT_BKEY) && (event->val == KM_RELEASE)) {
2478 /* Add Blank Frame
2479 * - Since this operator is non-modal, we can just call it here, and keep going...
2480 * - This operator is especially useful when animating
2481 */
2483 "GPENCIL_OT_layer_annotation_add",
2485 nullptr,
2486 event);
2487 estate = OPERATOR_RUNNING_MODAL;
2488 }
2489 else {
2490 estate = OPERATOR_RUNNING_MODAL;
2491 }
2492 }
2493
2494 /* Exit painting mode (and/or end current stroke)
2495 *
2496 * NOTE: cannot do RIGHTMOUSE (as is standard for canceling)
2497 * as that would break polyline #32647.
2498 */
2499 if (event->val == KM_PRESS &&
2501 {
2502 /* exit() ends the current stroke before cleaning up */
2504 estate = OPERATOR_FINISHED;
2505 }
2506
2507 /* Toggle painting mode upon mouse-button movement
2508 * - #RIGHTMOUSE: eraser (all).
2509 * (Disabling #RIGHTMOUSE case here results in bugs like #32647)
2510 * - Others (typically LMB): standard drawing (all) / straight line drawing (all).
2511 * Also making sure we have a valid event value, to not exit too early. */
2512
2513 if (ISMOUSE_BUTTON(event->type) && ELEM(event->val, KM_PRESS, KM_RELEASE)) {
2514 /* if painting, end stroke */
2515 if (p->status == GP_STATUS_PAINTING) {
2516 int sketch = 0;
2517
2518 /* basically, this should be mouse-button up = end stroke
2519 * BUT, polyline drawing is an exception -- all knots should be added during one session
2520 */
2521 sketch |= (p->paintmode == GP_PAINTMODE_DRAW_POLY);
2522
2523 if (sketch) {
2524 /* end stroke only, and then wait to resume painting soon */
2526
2527 /* If eraser mode is on, turn it off after the stroke finishes
2528 * NOTE: This just makes it nicer to work with drawing sessions
2529 */
2530 if (p->paintmode == GP_PAINTMODE_ERASER) {
2531 p->paintmode = eGPencil_PaintModes(RNA_enum_get(op->ptr, "mode"));
2532
2533 /* if the original mode was *still* eraser,
2534 * we'll let it say for now, since this gives
2535 * users an opportunity to have visual feedback
2536 * when adjusting eraser size
2537 */
2538 if (p->paintmode != GP_PAINTMODE_ERASER) {
2539 /* turn off cursor...
2540 * NOTE: this should be enough for now
2541 * Just hiding this makes it seem like
2542 * you can paint again...
2543 */
2545 }
2546 }
2547
2548 /* we've just entered idling state, so this event was processed (but no others yet) */
2549 estate = OPERATOR_RUNNING_MODAL;
2550
2551 /* stroke could be smoothed, send notifier to refresh screen */
2553 }
2554 else {
2556 estate = OPERATOR_FINISHED;
2557 }
2558 }
2559 else if (event->val == KM_PRESS) {
2560 bool in_bounds = false;
2561
2562 /* Check if we're outside the bounds of the active region
2563 * NOTE: An exception here is that if launched from the toolbar,
2564 * whatever region we're now in should become the new region
2565 */
2566 if ((p->region) && (p->region->regiontype == RGN_TYPE_TOOLS)) {
2567 /* Change to whatever region is now under the mouse */
2568 ARegion *current_region = BKE_area_find_region_xy(p->area, RGN_TYPE_ANY, event->xy);
2569
2570 if (current_region) {
2571 /* Assume that since we found the cursor in here, it is in bounds
2572 * and that this should be the region that we begin drawing in
2573 */
2574 p->region = current_region;
2575 in_bounds = true;
2576 }
2577 else {
2578 /* Out of bounds, or invalid in some other way */
2580 estate = OPERATOR_CANCELLED;
2581 }
2582 }
2583 else if (p->region) {
2584 /* Perform bounds check. */
2585 const rcti *region_rect = ED_region_visible_rect(p->region);
2586 in_bounds = BLI_rcti_isect_pt_v(region_rect, event->mval);
2587 }
2588 else {
2589 /* No region */
2591 estate = OPERATOR_CANCELLED;
2592 }
2593
2594 if (in_bounds) {
2595 /* Switch paintmode (temporarily if need be) based on which button was used
2596 * NOTE: This is to make it more convenient to erase strokes when using drawing sessions
2597 */
2598 if ((event->type == RIGHTMOUSE) || annotation_is_tablet_eraser_active(event)) {
2599 /* turn on eraser */
2601 }
2602 else { /* Any mouse button besides right. */
2603 /* restore drawmode to default */
2604 p->paintmode = eGPencil_PaintModes(RNA_enum_get(op->ptr, "mode"));
2605 }
2606
2608
2609 /* not painting, so start stroke (this should be mouse-button down) */
2610 p = annotation_stroke_begin(C, op);
2611
2612 if (p->status == GP_STATUS_ERROR) {
2613 estate = OPERATOR_CANCELLED;
2614 }
2615 }
2616 else if (p->status != GP_STATUS_ERROR) {
2617 /* User clicked outside bounds of window while idling, so exit paintmode
2618 * NOTE: Don't enter this case if an error occurred while finding the
2619 * region (as above)
2620 */
2622 estate = OPERATOR_FINISHED;
2623 }
2624 }
2625 else if (event->val == KM_RELEASE) {
2628 }
2629 }
2630
2631 /* handle mode-specific events */
2632 if (p->status == GP_STATUS_PAINTING) {
2633 /* handle painting mouse-movements? */
2634 if (ISMOUSE_MOTION(event->type) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) {
2635 /* handle drawing event */
2636 if ((p->flags & GP_PAINTFLAG_FIRSTRUN) == 0) {
2637 annotation_add_missing_events(C, op, event, p);
2638 }
2639
2640 /* TODO(sergey): Possibly evaluating dependency graph from modal operator? */
2642
2643 /* finish painting operation if anything went wrong just now */
2644 if (p->status == GP_STATUS_ERROR) {
2645 printf("\t\t\t\tGP - add error done!\n");
2646 estate = OPERATOR_CANCELLED;
2647 }
2648 else {
2649 /* event handled, so just tag as running modal */
2650 estate = OPERATOR_RUNNING_MODAL;
2651 }
2652 }
2653 /* eraser size */
2654 else if ((p->paintmode == GP_PAINTMODE_ERASER) &&
2656 {
2657 /* just resize the brush (local version)
2658 * TODO: fix the hardcoded size jumps (set to make a visible difference) and hardcoded keys
2659 */
2660 switch (event->type) {
2661 case WHEELDOWNMOUSE: /* larger */
2662 case EVT_PADPLUSKEY:
2663 p->radius += 5;
2664 break;
2665
2666 case WHEELUPMOUSE: /* smaller */
2667 case EVT_PADMINUS:
2668 p->radius -= 5;
2669
2670 if (p->radius <= 0) {
2671 p->radius = 1;
2672 }
2673 break;
2674 default: {
2675 break;
2676 }
2677 }
2678
2679 /* force refresh */
2680 /* just active area for now, since doing whole screen is too slow */
2682
2683 /* event handled, so just tag as running modal */
2684 estate = OPERATOR_RUNNING_MODAL;
2685 }
2686 /* there shouldn't be any other events, but just in case there are, let's swallow them
2687 * (i.e. to prevent problems with undo)
2688 */
2689 else {
2690 /* swallow event to save ourselves trouble */
2691 estate = OPERATOR_RUNNING_MODAL;
2692 }
2693 }
2694
2695 /* gpencil modal operator stores area, which can be removed while using it (like full-screen). */
2696 if (0 == annotation_area_exists(C, p->area)) {
2697 estate = OPERATOR_CANCELLED;
2698 }
2699 else {
2700 /* update status indicators - cursor, header, etc. */
2702 /* cursor may have changed outside our control - #44084 */
2704 }
2705
2706 /* process last operations before exiting */
2707 switch (estate) {
2708 case OPERATOR_FINISHED:
2709 /* one last flush before we're done */
2712 break;
2713
2714 case OPERATOR_CANCELLED:
2716 break;
2717
2718 /* Event doesn't need to be handled. */
2719 /* `OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH` */
2720 default:
2721 /* Quiet warnings. */
2722 break;
2723 }
2724
2725 /* return status code */
2726 return estate;
2727}
2728
2729/* ------------------------------- */
2730
2732 {GP_PAINTMODE_DRAW, "DRAW", 0, "Draw Freehand", "Draw freehand stroke(s)"},
2734 "DRAW_STRAIGHT",
2735 0,
2736 "Draw Straight Lines",
2737 "Draw straight line segment(s)"},
2739 "DRAW_POLY",
2740 0,
2741 "Draw Poly Line",
2742 "Click to place endpoints of straight line segments (connected)"},
2743 {GP_PAINTMODE_ERASER, "ERASER", 0, "Eraser", "Erase Annotation strokes"},
2744 {0, nullptr, 0, nullptr, nullptr},
2745};
2746
2748 {GP_STROKE_ARROWSTYLE_NONE, "NONE", 0, "None", "Don't use any arrow/style in corner"},
2749 {GP_STROKE_ARROWSTYLE_CLOSED, "ARROW", 0, "Arrow", "Use closed arrow style"},
2750 {GP_STROKE_ARROWSTYLE_OPEN, "ARROW_OPEN", 0, "Open Arrow", "Use open arrow style"},
2752 "ARROW_OPEN_INVERTED",
2753 0,
2754 "Segment",
2755 "Use perpendicular segment style"},
2756 {GP_STROKE_ARROWSTYLE_SQUARE, "DIAMOND", 0, "Square", "Use square style"},
2757 {0, nullptr, 0, nullptr, nullptr},
2758};
2759
2761{
2762 PropertyRNA *prop;
2763
2764 /* identifiers */
2765 ot->name = "Annotation Draw";
2766 ot->idname = "GPENCIL_OT_annotate";
2767 ot->description = "Make annotations on the active data";
2768
2769 /* API callbacks. */
2770 ot->exec = annotation_draw_exec;
2771 ot->invoke = annotation_draw_invoke;
2772 ot->modal = annotation_draw_modal;
2773 ot->cancel = annotation_draw_cancel;
2774 ot->poll = annotation_draw_poll;
2775
2776 /* flags */
2777 ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING;
2778
2779 /* settings for drawing */
2780 ot->prop = RNA_def_enum(
2781 ot->srna, "mode", prop_gpencil_drawmodes, 0, "Mode", "Way to interpret mouse movements");
2782
2783 /* properties */
2784 prop = RNA_def_enum(
2785 ot->srna, "arrowstyle_start", arrow_types, 0, "Start Arrow Style", "Stroke start style");
2786 prop = RNA_def_enum(
2787 ot->srna, "arrowstyle_end", arrow_types, 0, "End Arrow Style", "Stroke end style");
2788 prop = RNA_def_boolean(ot->srna,
2789 "use_stabilizer",
2790 false,
2791 "Stabilize Stroke",
2792 "Helper to draw smooth and clean lines. Press Shift for an invert effect "
2793 "(even if this option is not active)");
2794 prop = RNA_def_float(ot->srna,
2795 "stabilizer_factor",
2796 0.75f,
2797 0.0f,
2798 1.0f,
2799 "Stabilizer Stroke Factor",
2800 "Higher values gives a smoother stroke",
2801 0.0f,
2802 1.0f);
2803 prop = RNA_def_int(ot->srna,
2804 "stabilizer_radius",
2805 35,
2806 0,
2807 200,
2808 "Stabilizer Stroke Radius",
2809 "Minimum distance from last point before stroke continues",
2810 1,
2811 100);
2813
2814 prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
2816
2817 /* NOTE: wait for input is enabled by default,
2818 * so that all UI code can work properly without needing users to know about this */
2819 prop = RNA_def_boolean(ot->srna,
2820 "wait_for_input",
2821 true,
2822 "Wait for Input",
2823 "Wait for first click instead of painting immediately");
2825}
@ BKE_CB_EVT_ANNOTATION_POST
@ BKE_CB_EVT_ANNOTATION_PRE
void BKE_callback_exec_id_depsgraph(Main *bmain, ID *id, Depsgraph *depsgraph, eCbEvent evt)
Definition callbacks.cc:52
bScreen * CTX_wm_screen(const bContext *C)
void CTX_wm_operator_poll_msg_set(bContext *C, const char *msg)
Depsgraph * CTX_data_ensure_evaluated_depsgraph(const bContext *C)
ScrArea * CTX_wm_area(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Main * CTX_data_main(const bContext *C)
ToolSettings * CTX_data_tool_settings(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
struct bGPDstroke * BKE_gpencil_stroke_delete_tagged_points(struct bGPdata *gpd, struct bGPDframe *gpf, struct bGPDstroke *gps, struct bGPDstroke *next_stroke, int tag_flags, bool select, bool flat_cap, int limit)
void BKE_gpencil_free_stroke_weights(struct bGPDstroke *gps)
struct bGPDlayer * BKE_gpencil_layer_addnew(struct bGPdata *gpd, const char *name, bool setactive, bool add_to_header)
bool BKE_gpencil_layer_is_editable(const struct bGPDlayer *gpl)
struct bGPDframe * BKE_gpencil_layer_frame_get(struct bGPDlayer *gpl, int cframe, eGP_GetFrame_Mode addnew)
struct bGPDlayer * BKE_gpencil_layer_active_get(struct bGPdata *gpd)
struct bGPDstroke * BKE_gpencil_stroke_duplicate(struct bGPDstroke *gps_src, bool dup_points, bool dup_curve)
#define GPENCIL_STRENGTH_MIN
struct bGPdata * BKE_gpencil_data_addnew(struct Main *bmain, const char name[])
eGP_GetFrame_Mode
@ GP_GETFRAME_ADD_NEW
@ GP_GETFRAME_ADD_COPY
@ RPT_ERROR
Definition BKE_report.hh:39
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
ARegion * BKE_area_find_region_xy(const ScrArea *area, int regiontype, const int xy[2]) ATTR_NONNULL(3)
Definition screen.cc:875
struct MovieTrackingMarker * BKE_tracking_marker_get(struct MovieTrackingTrack *track, int framenr)
Definition tracking.cc:1358
struct MovieTrackingObject * BKE_tracking_object_get_active(const struct MovieTracking *tracking)
#define BLI_assert(a)
Definition BLI_assert.h:46
int BLI_findindex(const ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:586
void BLI_freelinkN(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:270
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
MINLINE float min_ff(float a, float b)
MINLINE int max_ii(int a, int b)
int interp_sparse_array(float *array, int list_size, float skipval)
void copy_m4_m4(float m1[4][4], const float m2[4][4])
void mul_v3_m4v3(float r[3], const float mat[4][4], const float vec[3])
bool invert_m4_m4(float inverse[4][4], const float mat[4][4])
void unit_m4(float m[4][4])
MINLINE void madd_v2_v2fl(float r[2], const float a[2], float f)
MINLINE float len_v2v2_int(const int v1[2], const int v2[2])
MINLINE void sub_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE float len_v2v2(const float v1[2], const float v2[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void mul_v2_fl(float r[2], float f)
void interp_v2_v2v2(float r[2], const float a[2], const float b[2], float t)
MINLINE void copy_v2_v2(float r[2], const float a[2])
MINLINE void copy_v2_v2_int(int r[2], const int a[2])
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 sub_v2_v2v2(float r[2], const float a[2], const float b[2])
MINLINE float normalize_v2(float n[2])
MINLINE void zero_v3(float r[3])
MINLINE void mul_v3_v3fl(float r[3], const float a[3], float f)
MINLINE void copy_v2_fl(float r[2], float f)
bool BLI_rcti_isect_pt_v(const struct rcti *rect, const int xy[2])
bool BLI_rcti_isect_pt(const struct rcti *rect, int x, int y)
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition BLI_rect.h:202
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:206
unsigned int uint
Platform independent time functions.
double BLI_time_now_seconds(void)
Definition time.cc:113
#define CLAMP(a, b, c)
#define ENUM_OPERATORS(_type, _max)
#define ELEM(...)
#define IFACE_(msgid)
#define DATA_(msgid)
@ GP_STROKE_ARROWSTYLE_NONE
@ GP_STROKE_ARROWSTYLE_SEGMENT
@ GP_STROKE_ARROWSTYLE_CLOSED
@ GP_STROKE_ARROWSTYLE_OPEN
@ GP_STROKE_ARROWSTYLE_SQUARE
@ GP_STROKE_USE_ARROW_END
@ GP_STROKE_USE_ARROW_START
@ GP_PROJECT_VIEWSPACE
@ GP_PROJECT_DEPTH_VIEW
@ GP_PROJECT_DEPTH_STROKE_ENDPOINTS
@ GP_PROJECT_DEPTH_STROKE
@ GP_PROJECT_DEPTH_ONLY_SELECTED
@ GP_TOOL_FLAG_RETAIN_LAST
@ RGN_TYPE_PREVIEW
@ RGN_TYPE_TOOLS
#define RGN_TYPE_ANY
@ SI_SHOW_GPENCIL
@ SNODE_SHOW_GPENCIL
@ SPACE_CLIP
@ SPACE_NODE
@ SPACE_SEQ
@ SPACE_IMAGE
@ SPACE_VIEW3D
@ SEQ_PREVIEW_SHOW_GPENCIL
@ SC_SHOW_ANNOTATION
@ SEQ_DRAW_IMG_IMBUF
@ SC_GPENCIL_SRC_TRACK
#define SPACE_TYPE_ANY
@ V3D_SHOW_ANNOTATION
@ RV3D_CAMOB
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
@ OPERATOR_RUNNING_MODAL
@ OPERATOR_PASS_THROUGH
@ OP_IS_MODAL_CURSOR_REGION
MovieClip * ED_space_clip_get_clip(const SpaceClip *sc)
int ED_space_clip_get_clip_frame_number(const SpaceClip *sc)
bool ED_operator_regionactive(bContext *C)
const rcti * ED_region_visible_rect(ARegion *region)
Definition area.cc:4301
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:618
bool ED_view3d_depth_read_cached(const ViewDepths *vd, const int mval[2], int margin, float *r_depth)
void ED_view3d_win_to_delta(const ARegion *region, const float xy_delta[2], float zfac, float r_out[3], bool precise=false)
bool ED_view3d_depth_read_cached_seg(const ViewDepths *vd, const int mval_sta[2], const int mval_end[2], int margin, float *r_depth)
@ V3D_PROJ_TEST_NOP
Definition ED_view3d.hh:279
void ED_view3d_depth_override(Depsgraph *depsgraph, ARegion *region, View3D *v3d, Object *obact, eV3DDepthOverrideMode mode, bool use_overlay, ViewDepths **r_depths)
float ED_view3d_calc_depth_for_comparison(const RegionView3D *rv3d, const float co[3])
@ V3D_PROJ_RET_OK
Definition ED_view3d.hh:256
void view3d_region_operator_needs_gpu(ARegion *region)
float ED_view3d_calc_zfac(const RegionView3D *rv3d, const float co[3])
void ED_view3d_depths_free(ViewDepths *depths)
eV3DDepthOverrideMode
Definition ED_view3d.hh:188
@ V3D_DEPTH_ALL
Definition ED_view3d.hh:190
@ V3D_DEPTH_SELECTED_ONLY
Definition ED_view3d.hh:198
@ V3D_DEPTH_NO_GPENCIL
Definition ED_view3d.hh:192
@ V3D_DEPTH_GPENCIL_ONLY
Definition ED_view3d.hh:194
bool ED_view3d_autodist_simple(ARegion *region, const int mval[2], float mouse_worldloc[3], int margin, const float *force_depth)
void ED_view3d_calc_camera_border(const Scene *scene, const Depsgraph *depsgraph, const ARegion *region, const View3D *v3d, const RegionView3D *rv3d, bool no_shift, rctf *r_viewborder)
bool ED_view3d_depth_unproject_v3(const ARegion *region, const int mval[2], double depth, float r_location_world[3])
eV3DProjStatus ED_view3d_project_float_global(const ARegion *region, const float co[3], float r_co[2], eV3DProjTest flag)
void immEnd()
void immUnbindProgram()
void immBindBuiltinProgram(GPUBuiltinShader shader_id)
void immUniform2f(const char *name, float x, float y)
void immUniformColor4f(float r, float g, float b, float a)
void immVertex2f(uint attr_id, float x, float y)
void immVertex2fv(uint attr_id, const float data[2])
void immUniform1i(const char *name, int x)
void immUniform1f(const char *name, float x)
void immUniformColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
GPUVertFormat * immVertexFormat()
void immBegin(GPUPrimType, uint vertex_len)
void imm_draw_circle_fill_2d(uint shdr_pos, float x, float y, float radius, int nsegments)
void imm_draw_circle_wire_2d(uint shdr_pos, float x, float y, float radius, int nsegments)
@ GPU_PRIM_LINES
@ GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR
@ GPU_SHADER_3D_UNIFORM_COLOR
void GPU_line_width(float width)
Definition gpu_state.cc:166
void GPU_line_smooth(bool enable)
Definition gpu_state.cc:78
@ GPU_BLEND_NONE
Definition GPU_state.hh:85
@ GPU_BLEND_ALPHA
Definition GPU_state.hh:87
void GPU_blend(GPUBlend blend)
Definition gpu_state.cc:42
void GPU_viewport_size_get_f(float coords[4])
Definition gpu_state.cc:273
uint GPU_vertformat_attr_add(GPUVertFormat *format, blender::StringRef name, blender::gpu::VertAttrType type)
Read Guarded memory(de)allocation.
#define MEM_reallocN(vmemh, len)
#define RNA_BEGIN(sptr, itemptr, propname)
#define RNA_END
@ PROP_SKIP_SAVE
Definition RNA_types.hh:344
@ PROP_HIDDEN
Definition RNA_types.hh:338
@ PROP_PIXEL
Definition RNA_types.hh:248
#define C
Definition RandGen.cpp:29
void UI_view2d_region_to_view(const View2D *v2d, float x, float y, float *r_view_x, float *r_view_y) ATTR_NONNULL()
Definition view2d.cc:1668
#define V2D_IS_CLIPPED
Definition UI_view2d.hh:21
@ KM_CTRL
Definition WM_types.hh:279
@ KM_ALT
Definition WM_types.hh:280
@ KM_SHIFT
Definition WM_types.hh:278
@ KM_PRESS
Definition WM_types.hh:311
@ KM_RELEASE
Definition WM_types.hh:312
@ OPTYPE_BLOCKING
Definition WM_types.hh:184
@ OPTYPE_UNDO
Definition WM_types.hh:182
#define NA_EDITED
Definition WM_types.hh:584
#define NC_GPENCIL
Definition WM_types.hh:399
void GPENCIL_OT_annotate(wmOperatorType *ot)
eGPencil_PaintStatus
@ GP_STATUS_PAINTING
@ GP_STATUS_IDLING
@ GP_STATUS_CAPTURE
@ GP_STATUS_ERROR
@ GP_STATUS_DONE
static void annotation_stroke_arrow_calc_points(tGPspoint *point, const float stroke_dir[2], float corner[2], float stroke_points[8], const int arrow_style)
static short annotation_stroke_addpoint(tGPsdata *p, const float mval[2], float pressure, double curtime)
static void annotation_arrow_create(tGPsdata *p, tGPspoint *ptc, bGPDspoint *pt, bGPDstroke *arrow_stroke, const float arrow_points[8], const int style)
static void annotation_arrow_create_square(tGPsdata *p, tGPspoint *ptc, bGPDspoint *pt, const float corner_point[3], const float arrow_points[8])
static bool annotation_stroke_eraser_is_occluded(tGPsdata *p, const bGPDspoint *pt, const int x, const int y)
static void annotation_stroke_eraser_dostroke(tGPsdata *p, bGPDframe *gpf, bGPDstroke *gps, const float mval[2], const int radius, const rcti *rect)
static void annotation_get_3d_reference(tGPsdata *p, float vec[3])
static void annotation_smooth_buffer(tGPsdata *p, float inf, int idx)
static bool annotation_session_initdata(bContext *C, tGPsdata *p)
eGPencil_PaintFlags
@ GP_PAINTFLAG_SELECTMASK
@ GP_PAINTFLAG_USE_STABILIZER
@ GP_PAINTFLAG_FIRSTRUN
@ GP_PAINTFLAG_STROKEADDED
@ GP_PAINTFLAG_V3D_ERASER_DEPTH
@ GP_PAINTFLAG_USE_STABILIZER_TEMP
static void annotation_stroke_end(wmOperator *op)
static void annotation_stroke_newfrombuffer(tGPsdata *p)
static wmOperatorStatus annotation_draw_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static bool annotation_draw_poll(bContext *C)
static bool annotation_stroke_filtermval(tGPsdata *p, const float mval[2], const float pmval[2])
static void annotation_arrow_create_segm(tGPsdata *p, tGPspoint *ptc, bGPDspoint *pt, const float arrow_points[8])
static void annotation_stroke_added_enable(tGPsdata *p)
static bool annotation_area_exists(bContext *C, ScrArea *area_test)
static void annotation_draw_eraser(bContext *, const blender::int2 &xy, const blender::float2 &, void *p_ptr)
static void annotation_add_missing_events(bContext *C, wmOperator *op, const wmEvent *event, tGPsdata *p)
static void annotation_paint_initstroke(tGPsdata *p, eGPencil_PaintModes paintmode, Depsgraph *depsgraph)
static void annotation_stroke_arrow_init_point(tGPsdata *p, tGPspoint *ptc, bGPDspoint *pt, const float co[8], const int co_idx)
static void annotation_draw_exit(bContext *C, wmOperator *op)
static void annotation_draw_stabilizer(bContext *C, const blender::int2 &xy, const blender::float2 &, void *p_ptr)
static bool annotation_stroke_added_check(tGPsdata *p)
static void annotation_stroke_arrow_allocate(bGPDstroke *gps, const int totpoints)
static void annotation_stroke_arrow_calc_points_segment(float stroke_points[8], const float ref_point[2], const float dir_cw[2], const float dir_ccw[2], const float length, const float sign)
static void annotation_paint_strokeend(tGPsdata *p)
static void annotation_draw_toggle_stabilizer_cursor(tGPsdata *p, short enable)
static bool annotation_project_check(tGPsdata *p)
static bool annotation_is_tablet_eraser_active(const wmEvent *event)
static void annotation_draw_apply(wmOperator *op, tGPsdata *p, Depsgraph *depsgraph)
static wmOperatorStatus annotation_draw_modal(bContext *C, wmOperator *op, const wmEvent *event)
static int annotation_draw_init(bContext *C, wmOperator *op, const wmEvent *event)
static void annotation_session_cleanup(tGPsdata *p)
static void annotation_draw_apply_event(wmOperator *op, const wmEvent *event, Depsgraph *depsgraph, float x, float y)
static void annotation_arrow_create_closed(tGPsdata *p, tGPspoint *ptc, bGPDspoint *pt, const float arrow_points[8])
static tGPsdata * annotation_stroke_begin(bContext *C, wmOperator *op)
#define MIN_EUCLIDEAN_PX
static void annotation_draw_cancel(bContext *C, wmOperator *op)
static const EnumPropertyItem arrow_types[]
static void annotation_draw_cursor_set(tGPsdata *p)
static void annotation_draw_status_indicators(bContext *C, tGPsdata *p)
static void annotation_visible_on_space(tGPsdata *p)
#define MIN_MANHATTAN_PX
static void annotation_arrow_create_open(tGPsdata *p, tGPspoint *ptc, bGPDspoint *pt, const float corner_point[3], const float arrow_points[8])
eGP_StrokeAdd_Result
@ GP_STROKEADD_INVALID
@ GP_STROKEADD_FULL
@ GP_STROKEADD_NORMAL
@ GP_STROKEADD_OVERFLOW
static void annotation_session_validatebuffer(tGPsdata *p)
static void annotation_stroke_doeraser(tGPsdata *p)
static void annotation_draw_toggle_eraser_cursor(tGPsdata *p, short enable)
static tGPsdata * annotation_session_initpaint(bContext *C)
static void annotation_session_free(tGPsdata *p)
#define DEPTH_INVALID
static void annotation_stroke_arrow_init_point_default(bGPDspoint *pt)
static const EnumPropertyItem prop_gpencil_drawmodes[]
static void annotation_free_stroke(bGPDframe *gpf, bGPDstroke *gps)
static void annotation_paint_cleanup(tGPsdata *p)
static wmOperatorStatus annotation_draw_exec(bContext *C, wmOperator *op)
static void annotation_stroke_arrow_init_conv_point(bGPDspoint *pt, const float point[3])
static void annotation_stroke_convertcoords(tGPsdata *p, const blender::float2 mval, float out[3], const float *depth)
#define U
BPy_StructRNA * depsgraph
nullptr float
eGPencil_PaintModes
@ GP_PAINTMODE_DRAW_POLY
@ GP_PAINTMODE_ERASER
@ GP_PAINTMODE_DRAW
@ GP_PAINTMODE_DRAW_STRAIGHT
void gpencil_point_to_xy(const GP_SpaceConversion *gsc, const bGPDstroke *gps, const bGPDspoint *pt, int *r_x, int *r_y)
bool gpencil_stroke_inside_circle(const float mval[2], int rad, int x0, int y0, int x1, int y1)
bool ED_gpencil_stroke_can_use_direct(const ScrArea *area, const bGPDstroke *gps)
tGPspoint * ED_gpencil_sbuffer_ensure(tGPspoint *buffer_array, int *buffer_size, int *buffer_used, const bool clear)
bGPdata ** ED_annotation_data_get_pointers(const bContext *C, PointerRNA *r_ptr)
uint pos
#define out
#define printf(...)
constexpr T sign(T) RET
#define abs
float length(VecOp< float, D >) RET
format
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_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
const int status
#define fabsf
void RNA_boolean_set(PointerRNA *ptr, const char *name, bool value)
void RNA_float_get_array(PointerRNA *ptr, const char *name, float *values)
void RNA_collection_add(PointerRNA *ptr, const char *name, PointerRNA *r_value)
int RNA_int_get(PointerRNA *ptr, const char *name)
float RNA_float_get(PointerRNA *ptr, const char *name)
void RNA_float_set(PointerRNA *ptr, const char *name, float value)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
void RNA_enum_set(PointerRNA *ptr, const char *name, int value)
void RNA_float_set_array(PointerRNA *ptr, const char *name, const float *values)
int RNA_enum_get(PointerRNA *ptr, const char *name)
PropertyRNA * RNA_def_float(StructOrFunctionRNA *cont_, const char *identifier, const float default_value, const float hardmin, const float hardmax, const char *ui_name, const char *ui_description, const float softmin, const float softmax)
PropertyRNA * RNA_def_enum(StructOrFunctionRNA *cont_, const char *identifier, const EnumPropertyItem *items, const int default_value, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_collection_runtime(StructOrFunctionRNA *cont_, const char *identifier, StructRNA *type, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_boolean(StructOrFunctionRNA *cont_, const char *identifier, const bool default_value, const char *ui_name, const char *ui_description)
void RNA_def_property_flag(PropertyRNA *prop, PropertyFlag flag)
void RNA_def_property_subtype(PropertyRNA *prop, PropertySubType subtype)
PropertyRNA * RNA_def_int(StructOrFunctionRNA *cont_, const char *identifier, const int default_value, const int hardmin, const int hardmax, const char *ui_name, const char *ui_description, const int softmin, const int softmax)
void * regiondata
void * last
void * first
struct MovieTracking tracking
MovieTrackingTrack * active_track
struct ToolSettings * toolsettings
struct RenderData r
View3DCursor cursor
ListBase spacedata
float unistabmat[4][4]
bGPDtriangle * triangles
struct MDeformVert * dvert
struct bGPDstroke * next
bGPdata_Runtime runtime
ListBase areabase
float xmin
float ymin
int ymin
int ymax
int xmin
int xmax
bGPDframe * gpf
float mat[4][4]
ScrArea * area
short straight[2]
float stabilizer_factor
Scene * scene
PointerRNA ownerPtr
wmWindow * win
eGPencil_PaintStatus status
Depsgraph * depsgraph
eGPencil_PaintModes paintmode
short keymodifier
bGPDlayer * gpl
const rctf * subrect
double inittime
View2D * v2d
eGPencil_PaintFlags flags
float custom_color[4]
double ocurtime
ARegion * region
ViewDepths * depths
GP_SpaceConversion gsc
char * align_flag
float mval[2]
bGPdata * gpd
char stabilizer_radius
float mvalo[2]
void * stabilizer_cursor
float imat[4][4]
void * erasercursor
wmEventModifierFlag modifier
Definition WM_types.hh:774
wmEventType type
Definition WM_types.hh:757
short val
Definition WM_types.hh:759
int xy[2]
Definition WM_types.hh:761
int mval[2]
Definition WM_types.hh:763
wmTabletData tablet
Definition WM_types.hh:786
struct ReportList * reports
struct PointerRNA * ptr
i
Definition text_draw.cc:230
void WM_cursor_modal_set(wmWindow *win, int val)
void WM_cursor_modal_restore(wmWindow *win)
@ WM_CURSOR_PAINT_BRUSH
Definition wm_cursors.hh:34
@ WM_CURSOR_ERASER
Definition wm_cursors.hh:35
int xy[2]
Definition wm_draw.cc:178
void WM_event_drag_start_mval_fl(const wmEvent *event, const ARegion *region, float r_mval[2])
wmEventHandler_Op * WM_event_add_modal_handler(bContext *C, wmOperator *op)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
wmOperatorStatus WM_operator_name_call(bContext *C, const char *opstring, blender::wm::OpCallContext context, PointerRNA *properties, const wmEvent *event)
#define ISMOUSE_BUTTON(event_type)
@ EVT_TABLET_NONE
@ EVT_TABLET_ERASER
#define ISMOUSE_MOTION(event_type)
@ EVT_PAD8
@ EVT_PAD2
@ RIGHTMOUSE
@ EVT_EKEY
@ EVT_PAD4
@ EVT_PAD0
@ EVT_PAD9
@ EVT_DOWNARROWKEY
@ EVT_PAD3
@ WHEELUPMOUSE
@ EVT_RIGHTARROWKEY
@ EVT_PADENTER
@ EVT_SPACEKEY
@ WHEELDOWNMOUSE
@ EVT_PAD6
@ EVT_PAD5
@ EVT_PADMINUS
@ EVT_UPARROWKEY
@ EVT_LEFTARROWKEY
@ NDOF_MOTION
@ EVT_ZKEY
@ EVT_ESCKEY
@ EVT_PAD1
@ EVT_PAD7
@ EVT_PADPLUSKEY
@ EVT_BKEY
@ EVT_RETKEY
#define ISKEYBOARD(event_type)
wmOperatorType * ot
Definition wm_files.cc:4237
bool WM_paint_cursor_end(wmPaintCursor *handle)
wmPaintCursor * WM_paint_cursor_activate(short space_type, short region_type, bool(*poll)(bContext *C), wmPaintCursorDraw draw, void *customdata)