Blender V4.5
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(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
1730
1731 GPU_line_smooth(true);
1733
1734 immUniformColor4ub(255, 100, 100, 20);
1735 imm_draw_circle_fill_2d(shdr_pos, xy[0], xy[1], p->radius, 40);
1736
1738
1740
1741 float viewport_size[4];
1742 GPU_viewport_size_get_f(viewport_size);
1743 immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
1744
1745 immUniformColor4f(1.0f, 0.39f, 0.39f, 0.78f);
1746 immUniform1i("colors_len", 0); /* "simple" mode */
1747 immUniform1f("dash_width", 12.0f);
1748 immUniform1f("udash_factor", 0.5f);
1749
1750 imm_draw_circle_wire_2d(shdr_pos,
1751 xy.x,
1752 xy.y,
1753 p->radius,
1754 /* XXX Dashed shader gives bad results with sets of small segments
1755 * currently, temp hack around the issue. :( */
1756 max_ii(8, p->radius / 2)); /* was fixed 40 */
1757
1759
1761 GPU_line_smooth(false);
1762 }
1763}
1764
1765/* Turn brush cursor in 3D view on/off */
1767{
1768 if (p->erasercursor && !enable) {
1769 /* clear cursor */
1771 p->erasercursor = nullptr;
1772 }
1773 else if (enable && !p->erasercursor) {
1774 /* enable cursor */
1777 nullptr, /* XXX */
1779 p);
1780 }
1781}
1783 const blender::int2 &xy,
1784 const blender::float2 & /*tilt*/,
1785 void *p_ptr)
1786{
1787 ARegion *region = CTX_wm_region(C);
1788 tGPsdata *p = (tGPsdata *)p_ptr;
1789 bGPdata_Runtime runtime = blender::dna::shallow_copy(p->gpd->runtime);
1790 const tGPspoint *points = static_cast<const tGPspoint *>(runtime.sbuffer);
1791 int totpoints = runtime.sbuffer_used;
1792 if (totpoints < 2) {
1793 return;
1794 }
1795 const tGPspoint *pt = &points[totpoints - 1];
1796
1800 GPU_line_smooth(true);
1802 GPU_line_width(1.25f);
1803 const float color[3] = {1.0f, 0.39f, 0.39f};
1804
1805 /* default radius and color */
1806 float darkcolor[3];
1807 const float radius = 4.0f;
1808
1809 /* Inner Ring: Color from UI panel */
1810 immUniformColor4f(color[0], color[1], color[2], 0.8f);
1811 imm_draw_circle_wire_2d(pos, xy.x, xy.y, radius, 40);
1812
1813 /* Outer Ring: Dark color for contrast on light backgrounds (e.g. gray on white) */
1814 mul_v3_v3fl(darkcolor, color, 0.40f);
1815 immUniformColor4f(darkcolor[0], darkcolor[1], darkcolor[2], 0.8f);
1816 imm_draw_circle_wire_2d(pos, xy.x, xy.y, radius + 1, 40);
1817
1818 /* Rope Simple. */
1819 immUniformColor4f(color[0], color[1], color[2], 0.8f);
1821 immVertex2f(pos, pt->m_xy[0] + region->winrct.xmin, pt->m_xy[1] + region->winrct.ymin);
1823 immEnd();
1824
1825 /* Returns back all GPU settings */
1827 GPU_line_smooth(false);
1828
1830}
1831
1832/* Turn *stabilizer* brush cursor in 3D view on/off */
1834{
1835 if (p->stabilizer_cursor && !enable) {
1836 /* clear cursor */
1838 p->stabilizer_cursor = nullptr;
1839 }
1840 else if (enable && !p->stabilizer_cursor) {
1841 /* enable cursor */
1844 }
1845}
1846
1847/* Check if tablet eraser is being used (when processing events) */
1849{
1850 return (event->tablet.active == EVT_TABLET_ERASER);
1851}
1852
1853/* ------------------------------- */
1854
1856{
1857 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
1858
1859 /* restore cursor to indicate end of drawing */
1861
1862 /* don't assume that operator data exists at all */
1863 if (p) {
1864 /* check size of buffer before cleanup, to determine if anything happened here */
1865 if (p->paintmode == GP_PAINTMODE_ERASER) {
1866 /* turn off radial brush cursor */
1868 }
1869 else if (p->paintmode == GP_PAINTMODE_DRAW) {
1871 }
1872
1873 /* always store the new eraser size to be used again next time
1874 * NOTE: Do this even when not in eraser mode, as eraser may
1875 * have been toggled at some point.
1876 */
1877 U.gp_eraser = p->radius;
1878
1879 /* cleanup */
1883 p = nullptr;
1884 }
1885
1886 op->customdata = nullptr;
1887}
1888
1890{
1891 /* this is just a wrapper around exit() */
1893}
1894
1895/* ------------------------------- */
1896
1897static int annotation_draw_init(bContext *C, wmOperator *op, const wmEvent *event)
1898{
1899 tGPsdata *p;
1900 eGPencil_PaintModes paintmode = eGPencil_PaintModes(RNA_enum_get(op->ptr, "mode"));
1901
1902 /* check context */
1903 p = static_cast<tGPsdata *>(op->customdata = annotation_session_initpaint(C));
1904 if ((p == nullptr) || (p->status == GP_STATUS_ERROR)) {
1905 /* something wasn't set correctly in context */
1907 return 0;
1908 }
1909
1910 /* init painting data */
1912 if (p->status == GP_STATUS_ERROR) {
1914 return 0;
1915 }
1916
1917 if (event != nullptr) {
1918 p->keymodifier = event->keymodifier;
1919 }
1920 else {
1921 p->keymodifier = -1;
1922 }
1923
1924 /* everything is now setup ok */
1925 return 1;
1926}
1927
1928/* ------------------------------- */
1929
1930/* ensure that the correct cursor icon is set */
1932{
1933 if (p->paintmode == GP_PAINTMODE_ERASER) {
1935 }
1936 else {
1938 }
1939}
1940
1941/* update UI indicators of status, including cursor and header prints */
1943{
1944 WorkspaceStatus status(C);
1945
1946 /* header prints */
1947 switch (p->status) {
1948 case GP_STATUS_PAINTING:
1949 switch (p->paintmode) {
1951 status.item(IFACE_("End"), ICON_EVENT_ESC);
1952 status.item(IFACE_("Place Next Stroke Vertex"), ICON_MOUSE_LMB);
1953 break;
1955 status.item(IFACE_("End"), ICON_EVENT_ESC);
1956 status.item(IFACE_("Erase"), ICON_MOUSE_LMB);
1957 break;
1959 status.item(IFACE_("End"), ICON_EVENT_ESC);
1960 status.item(IFACE_("Draw"), ICON_MOUSE_LMB);
1961 break;
1962 case GP_PAINTMODE_DRAW:
1963 status.item(IFACE_("End"), ICON_EVENT_ESC);
1964 status.item(IFACE_("Draw"), ICON_MOUSE_LMB);
1965 break;
1966
1967 default: /* unhandled future cases */
1968 status.item(IFACE_("End"), ICON_EVENT_ESC);
1969 break;
1970 }
1971 break;
1972
1973 case GP_STATUS_IDLING:
1974 case GP_STATUS_ERROR:
1975 case GP_STATUS_DONE:
1976 case GP_STATUS_CAPTURE:
1977 break;
1978 }
1979}
1980
1981/* ------------------------------- */
1982
1983/* create a new stroke point at the point indicated by the painting context */
1984static void annotation_draw_apply(wmOperator *op, tGPsdata *p, Depsgraph *depsgraph)
1985{
1986 /* handle drawing/erasing -> test for erasing first */
1987 if (p->paintmode == GP_PAINTMODE_ERASER) {
1988 /* do 'live' erasing now */
1990
1991 /* store used values */
1992 p->mvalo[0] = p->mval[0];
1993 p->mvalo[1] = p->mval[1];
1994 p->opressure = p->pressure;
1995 }
1996 /* Only add current point to buffer if mouse moved
1997 * (even though we got an event, it might be just noise). */
1998 else if (annotation_stroke_filtermval(p, p->mval, p->mvalo)) {
1999 /* If lazy mouse, interpolate the last and current mouse positions. */
2001 float now_mouse[2];
2002 float last_mouse[2];
2003 copy_v2_v2(now_mouse, p->mval);
2004 copy_v2_v2(last_mouse, p->mvalo);
2005 interp_v2_v2v2(now_mouse, now_mouse, last_mouse, min_ff(p->stabilizer_factor, .995f));
2006 copy_v2_v2(p->mval, now_mouse);
2007 }
2008
2009 /* try to add point */
2010 short ok = annotation_stroke_addpoint(p, p->mval, p->pressure, p->curtime);
2011
2012 /* handle errors while adding point */
2014 /* finish off old stroke */
2016 /* And start a new one!!! Else, projection errors! */
2018
2019 /* start a new stroke, starting from previous point */
2020 if (ok == GP_STROKEADD_OVERFLOW) {
2021 p->inittime = p->ocurtime;
2023 }
2024 else {
2025 p->inittime = p->curtime;
2026 }
2028 }
2029 else if (ok == GP_STROKEADD_INVALID) {
2030 /* the painting operation cannot continue... */
2031 BKE_report(op->reports, RPT_ERROR, "Cannot paint stroke");
2033
2034 return;
2035 }
2036
2037 /* store used values */
2038 p->mvalo[0] = p->mval[0];
2039 p->mvalo[1] = p->mval[1];
2040 p->opressure = p->pressure;
2041 p->ocurtime = p->curtime;
2042 }
2043}
2044
2045/* handle draw event */
2047 wmOperator *op, const wmEvent *event, Depsgraph *depsgraph, float x, float y)
2048{
2049 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2050 PointerRNA itemptr;
2051 float mousef[2];
2052
2053 /* Convert from window-space to area-space mouse coordinates
2054 * add any x,y override position for fake events. */
2055 if (p->flags & GP_PAINTFLAG_FIRSTRUN) {
2056 /* The first run may be a drag event, see: #99368. */
2058 p->mval[0] -= x;
2059 p->mval[1] -= y;
2060 }
2061 else {
2062 p->mval[0] = float(event->mval[0]) - x;
2063 p->mval[1] = float(event->mval[1]) - y;
2064 }
2065
2066 /* Key to toggle stabilization. */
2067 if ((event->modifier & KM_SHIFT) && (p->paintmode == GP_PAINTMODE_DRAW)) {
2068 /* Using permanent stabilization, shift will deactivate the flag. */
2073 }
2074 }
2075 /* Not using any stabilization flag. Activate temporal one. */
2076 else if ((p->flags & GP_PAINTFLAG_USE_STABILIZER_TEMP) == 0) {
2079 }
2080 }
2081 /* verify key status for straight lines */
2082 else if (event->modifier & (KM_CTRL | KM_ALT)) {
2083 if (p->straight[0] == 0) {
2084 int dx = abs(int(p->mval[0] - p->mvalo[0]));
2085 int dy = abs(int(p->mval[1] - p->mvalo[1]));
2086 if ((dx > 0) || (dy > 0)) {
2087 /* check mouse direction to replace the other coordinate with previous values */
2088 if (dx >= dy) {
2089 /* horizontal */
2090 p->straight[0] = 1;
2091 p->straight[1] = p->mval[1]; /* save y */
2092 }
2093 else {
2094 /* vertical */
2095 p->straight[0] = 2;
2096 p->straight[1] = p->mval[0]; /* save x */
2097 }
2098 }
2099 }
2100 }
2101 else {
2102 p->straight[0] = 0;
2103 /* We were using shift while having permanent stabilization active,
2104 * so activate the temp flag back again. */
2106 if ((p->flags & GP_PAINTFLAG_USE_STABILIZER_TEMP) == 0) {
2109 }
2110 }
2111 /* We are using the temporal stabilizer flag at the moment,
2112 * but shift is not pressed as well as the permanent flag is not used,
2113 * so we don't need the cursor anymore. */
2115 /* Reset temporal stabilizer flag and remove cursor. */
2118 }
2119 }
2120
2122
2123 /* handle pressure sensitivity (which is supplied by tablets or otherwise 1.0) */
2124 p->pressure = event->tablet.pressure;
2125
2126 /* Hack for pressure sensitive eraser on D+RMB when using a tablet:
2127 * The pen has to float over the tablet surface, resulting in
2128 * zero pressure (#47101). Ignore pressure values if floating
2129 * (i.e. "effectively zero" pressure), and only when the "active"
2130 * end is the stylus (i.e. the default when not eraser)
2131 */
2132 if (p->paintmode == GP_PAINTMODE_ERASER) {
2133 if ((event->tablet.active != EVT_TABLET_ERASER) && (p->pressure < 0.001f)) {
2134 p->pressure = 1.0f;
2135 }
2136 }
2137
2138 /* special exception for start of strokes (i.e. maybe for just a dot) */
2139 if (p->flags & GP_PAINTFLAG_FIRSTRUN) {
2141
2142 p->mvalo[0] = p->mval[0];
2143 p->mvalo[1] = p->mval[1];
2144 p->opressure = p->pressure;
2145 p->inittime = p->ocurtime = p->curtime;
2146 p->straight[0] = 0;
2147 p->straight[1] = 0;
2148
2149 /* special exception here for too high pressure values on first touch in
2150 * windows for some tablets, then we just skip first touch...
2151 */
2152 if ((event->tablet.active != EVT_TABLET_NONE) && (p->pressure >= 0.99f)) {
2153 return;
2154 }
2155 }
2156
2157 /* check if alt key is pressed and limit to straight lines */
2158 if ((p->paintmode != GP_PAINTMODE_ERASER) && (p->straight[0] != 0)) {
2159 if (p->straight[0] == 1) {
2160 /* horizontal */
2161 p->mval[1] = p->straight[1]; /* replace y */
2162 }
2163 else {
2164 /* vertical */
2165 p->mval[0] = p->straight[1]; /* replace x */
2166 }
2167 }
2168
2169 /* fill in stroke data (not actually used directly by gpencil_draw_apply) */
2170 RNA_collection_add(op->ptr, "stroke", &itemptr);
2171
2172 mousef[0] = p->mval[0];
2173 mousef[1] = p->mval[1];
2174 RNA_float_set_array(&itemptr, "mouse", mousef);
2175 RNA_float_set(&itemptr, "pressure", p->pressure);
2176 RNA_boolean_set(&itemptr, "is_start", (p->flags & GP_PAINTFLAG_FIRSTRUN) != 0);
2177
2178 RNA_float_set(&itemptr, "time", p->curtime - p->inittime);
2179
2180 /* apply the current latest drawing point */
2182
2183 /* force refresh */
2184 /* just active area for now, since doing whole screen is too slow */
2186}
2187
2188/* ------------------------------- */
2189
2190/* operator 'redo' (i.e. after changing some properties, but also for repeat last) */
2192{
2194
2195 /* try to initialize context data needed while drawing */
2196 if (!annotation_draw_init(C, op, nullptr)) {
2197 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2198 MEM_delete(p);
2199 op->customdata = nullptr;
2200 return OPERATOR_CANCELLED;
2201 }
2202
2203 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2204
2205 /* loop over the stroke RNA elements recorded (i.e. progress of mouse movement),
2206 * setting the relevant values in context at each step, then applying
2207 */
2208 RNA_BEGIN (op->ptr, itemptr, "stroke") {
2209 float mousef[2];
2210
2211 /* get relevant data for this point from stroke */
2212 RNA_float_get_array(&itemptr, "mouse", mousef);
2213 p->mval[0] = int(mousef[0]);
2214 p->mval[1] = int(mousef[1]);
2215 p->pressure = RNA_float_get(&itemptr, "pressure");
2216 p->curtime = double(RNA_float_get(&itemptr, "time")) + p->inittime;
2217
2218 if (RNA_boolean_get(&itemptr, "is_start")) {
2219 /* if first-run flag isn't set already (i.e. not true first stroke),
2220 * then we must terminate the previous one first before continuing
2221 */
2222 if ((p->flags & GP_PAINTFLAG_FIRSTRUN) == 0) {
2223 /* TODO: both of these ops can set error-status, but we probably don't need to worry */
2226 }
2227 }
2228
2229 /* if first run, set previous data too */
2230 if (p->flags & GP_PAINTFLAG_FIRSTRUN) {
2232
2233 p->mvalo[0] = p->mval[0];
2234 p->mvalo[1] = p->mval[1];
2235 p->opressure = p->pressure;
2236 p->ocurtime = p->curtime;
2237 }
2238
2239 /* apply this data as necessary now (as per usual) */
2241 }
2242 RNA_END;
2243
2244 /* cleanup */
2246
2247 /* refreshes */
2249
2250 /* done */
2251 return OPERATOR_FINISHED;
2252}
2253
2254/* ------------------------------- */
2255
2256/* start of interactive drawing part of operator */
2258{
2259 /* support for tablets eraser pen */
2261 RNA_enum_set(op->ptr, "mode", GP_PAINTMODE_ERASER);
2262 }
2263
2264 /* try to initialize context data needed while drawing */
2265 if (!annotation_draw_init(C, op, event)) {
2266 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2267 MEM_delete(p);
2268 op->customdata = nullptr;
2269 return OPERATOR_CANCELLED;
2270 }
2271
2272 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2273
2274 /* if empty erase capture and finish */
2275 if (p->status == GP_STATUS_CAPTURE) {
2277
2278 BKE_report(op->reports, RPT_ERROR, "Nothing to erase");
2279 return OPERATOR_FINISHED;
2280 }
2281
2282 /* if eraser is on, draw radial aid */
2283 if (p->paintmode == GP_PAINTMODE_ERASER) {
2285 }
2286 else if (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT) {
2287 if (RNA_enum_get(op->ptr, "arrowstyle_start") != GP_STROKE_ARROWSTYLE_NONE) {
2289 p->gpd->runtime.arrow_start_style = RNA_enum_get(op->ptr, "arrowstyle_start");
2290 }
2291 if (RNA_enum_get(op->ptr, "arrowstyle_end") != GP_STROKE_ARROWSTYLE_NONE) {
2293 p->gpd->runtime.arrow_end_style = RNA_enum_get(op->ptr, "arrowstyle_end");
2294 }
2295 }
2296 else if (p->paintmode == GP_PAINTMODE_DRAW) {
2297 p->stabilizer_factor = RNA_float_get(op->ptr, "stabilizer_factor");
2298 p->stabilizer_radius = RNA_int_get(op->ptr, "stabilizer_radius");
2299 if (RNA_boolean_get(op->ptr, "use_stabilizer")) {
2302 }
2303 else if (event->modifier & KM_SHIFT) {
2306 }
2307 }
2308 /* set cursor
2309 * NOTE: This may change later (i.e. intentionally via brush toggle,
2310 * or unintentionally if the user scrolls outside the area)...
2311 */
2313
2314 /* only start drawing immediately if we're allowed to do so... */
2315 if (RNA_boolean_get(op->ptr, "wait_for_input") == false) {
2316 /* hotkey invoked - start drawing */
2318
2319 /* handle the initial drawing - i.e. for just doing a simple dot */
2322 }
2323 else {
2324 /* toolbar invoked - don't start drawing yet... */
2326 }
2327
2329 /* add a modal handler for this operator, so that we can then draw continuous strokes */
2332}
2333
2334/* gpencil modal operator stores area, which can be removed while using it (like full-screen). */
2335static bool annotation_area_exists(bContext *C, ScrArea *area_test)
2336{
2337 bScreen *screen = CTX_wm_screen(C);
2338 return (BLI_findindex(&screen->areabase, area_test) != -1);
2339}
2340
2342{
2343 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2344
2345 /* we must check that we're still within the area that we're set up to work from
2346 * otherwise we could crash (see bug #20586)
2347 */
2348 if (CTX_wm_area(C) != p->area) {
2349 printf("\t\t\tGP - wrong area execution abort!\n");
2351 }
2352
2353 /* we may need to set up paint env again if we're resuming */
2354 /* XXX: watch it with the paintmode! in future,
2355 * it'd be nice to allow changing paint-mode when in sketching-sessions */
2356
2359 }
2360
2361 if (p->status != GP_STATUS_ERROR) {
2364 }
2365
2366 return static_cast<tGPsdata *>(op->customdata);
2367}
2368
2370{
2371 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2372
2374
2376
2379
2380 p->gpd = nullptr;
2381 p->gpl = nullptr;
2382 p->gpf = nullptr;
2383}
2384
2385/* add events for missing mouse movements when the artist draw very fast */
2387 wmOperator *op,
2388 const wmEvent *event,
2389 tGPsdata *p)
2390{
2392 float pt[2], a[2], b[2];
2393 float factor = 10.0f;
2394
2395 copy_v2_v2(a, p->mvalo);
2396 b[0] = float(event->mval[0]) + 1.0f;
2397 b[1] = float(event->mval[1]) + 1.0f;
2398
2399 /* get distance in pixels */
2400 float dist = len_v2v2(a, b);
2401
2402 /* for very small distances, add a half way point */
2403 if (dist <= 2.0f) {
2404 interp_v2_v2v2(pt, a, b, 0.5f);
2405 sub_v2_v2v2(pt, b, pt);
2406 /* create fake event */
2407 annotation_draw_apply_event(op, event, depsgraph, pt[0], pt[1]);
2408 }
2409 else if (dist >= factor) {
2410 int slices = 2 + int((dist - 1.0) / factor);
2411 float n = 1.0f / slices;
2412 for (int i = 1; i < slices; i++) {
2413 interp_v2_v2v2(pt, a, b, n * i);
2414 sub_v2_v2v2(pt, b, pt);
2415 /* create fake event */
2416 annotation_draw_apply_event(op, event, depsgraph, pt[0], pt[1]);
2417 }
2418 }
2419}
2420
2421/* events handling during interactive drawing part of operator */
2423{
2424 tGPsdata *p = static_cast<tGPsdata *>(op->customdata);
2425 /* Default exit state - pass through to support MMB view navigation, etc. */
2427
2428/* NOTE(mike erwin): Not quite what I was looking for, but a good start!
2429 * grease-pencil continues to draw on the screen while the 3D mouse moves the viewpoint.
2430 * Problem is that the stroke is converted to 3D only after it is finished.
2431 * This approach should work better in tools that immediately apply in 3D space. */
2432#if 0
2433 if (event->type == NDOF_MOTION) {
2434 return OPERATOR_PASS_THROUGH;
2435 }
2436#endif
2437
2438 if (p->status == GP_STATUS_IDLING) {
2439 ARegion *region = CTX_wm_region(C);
2440 p->region = region;
2441 }
2442
2443 /* We don't pass on key events, GP is used with key-modifiers -
2444 * prevents D-key to insert drivers. */
2445 if (ISKEYBOARD(event->type)) {
2446 if (ELEM(event->type,
2451 EVT_ZKEY))
2452 {
2453 /* allow some keys:
2454 * - For frame changing #33412.
2455 * - For undo (during sketching sessions).
2456 */
2457 }
2458 else if (ELEM(event->type,
2459 EVT_PAD0,
2460 EVT_PAD1,
2461 EVT_PAD2,
2462 EVT_PAD3,
2463 EVT_PAD4,
2464 EVT_PAD5,
2465 EVT_PAD6,
2466 EVT_PAD7,
2467 EVT_PAD8,
2468 EVT_PAD9))
2469 {
2470 /* Allow numpad keys so that camera/view manipulations can still take place
2471 * - #EVT_PAD0 in particular is really important for Grease Pencil drawing,
2472 * as animators may be working "to camera", so having this working
2473 * is essential for ensuring that they can quickly return to that view.
2474 */
2475 }
2476 else if ((event->type == EVT_BKEY) && (event->val == KM_RELEASE)) {
2477 /* Add Blank Frame
2478 * - Since this operator is non-modal, we can just call it here, and keep going...
2479 * - This operator is especially useful when animating
2480 */
2482 C, "GPENCIL_OT_layer_annotation_add", WM_OP_EXEC_DEFAULT, nullptr, event);
2483 estate = OPERATOR_RUNNING_MODAL;
2484 }
2485 else {
2486 estate = OPERATOR_RUNNING_MODAL;
2487 }
2488 }
2489
2490 /* Exit painting mode (and/or end current stroke)
2491 *
2492 * NOTE: cannot do RIGHTMOUSE (as is standard for canceling)
2493 * as that would break polyline #32647.
2494 */
2495 if (event->val == KM_PRESS &&
2497 {
2498 /* exit() ends the current stroke before cleaning up */
2500 estate = OPERATOR_FINISHED;
2501 }
2502
2503 /* Toggle painting mode upon mouse-button movement
2504 * - #RIGHTMOUSE: eraser (all).
2505 * (Disabling #RIGHTMOUSE case here results in bugs like #32647)
2506 * - Others (typically LMB): standard drawing (all) / straight line drawing (all).
2507 * Also making sure we have a valid event value, to not exit too early. */
2508
2509 if (ISMOUSE_BUTTON(event->type) && ELEM(event->val, KM_PRESS, KM_RELEASE)) {
2510 /* if painting, end stroke */
2511 if (p->status == GP_STATUS_PAINTING) {
2512 int sketch = 0;
2513
2514 /* basically, this should be mouse-button up = end stroke
2515 * BUT, polyline drawing is an exception -- all knots should be added during one session
2516 */
2517 sketch |= (p->paintmode == GP_PAINTMODE_DRAW_POLY);
2518
2519 if (sketch) {
2520 /* end stroke only, and then wait to resume painting soon */
2522
2523 /* If eraser mode is on, turn it off after the stroke finishes
2524 * NOTE: This just makes it nicer to work with drawing sessions
2525 */
2526 if (p->paintmode == GP_PAINTMODE_ERASER) {
2527 p->paintmode = eGPencil_PaintModes(RNA_enum_get(op->ptr, "mode"));
2528
2529 /* if the original mode was *still* eraser,
2530 * we'll let it say for now, since this gives
2531 * users an opportunity to have visual feedback
2532 * when adjusting eraser size
2533 */
2534 if (p->paintmode != GP_PAINTMODE_ERASER) {
2535 /* turn off cursor...
2536 * NOTE: this should be enough for now
2537 * Just hiding this makes it seem like
2538 * you can paint again...
2539 */
2541 }
2542 }
2543
2544 /* we've just entered idling state, so this event was processed (but no others yet) */
2545 estate = OPERATOR_RUNNING_MODAL;
2546
2547 /* stroke could be smoothed, send notifier to refresh screen */
2549 }
2550 else {
2552 estate = OPERATOR_FINISHED;
2553 }
2554 }
2555 else if (event->val == KM_PRESS) {
2556 bool in_bounds = false;
2557
2558 /* Check if we're outside the bounds of the active region
2559 * NOTE: An exception here is that if launched from the toolbar,
2560 * whatever region we're now in should become the new region
2561 */
2562 if ((p->region) && (p->region->regiontype == RGN_TYPE_TOOLS)) {
2563 /* Change to whatever region is now under the mouse */
2564 ARegion *current_region = BKE_area_find_region_xy(p->area, RGN_TYPE_ANY, event->xy);
2565
2566 if (current_region) {
2567 /* Assume that since we found the cursor in here, it is in bounds
2568 * and that this should be the region that we begin drawing in
2569 */
2570 p->region = current_region;
2571 in_bounds = true;
2572 }
2573 else {
2574 /* Out of bounds, or invalid in some other way */
2576 estate = OPERATOR_CANCELLED;
2577 }
2578 }
2579 else if (p->region) {
2580 /* Perform bounds check. */
2581 const rcti *region_rect = ED_region_visible_rect(p->region);
2582 in_bounds = BLI_rcti_isect_pt_v(region_rect, event->mval);
2583 }
2584 else {
2585 /* No region */
2587 estate = OPERATOR_CANCELLED;
2588 }
2589
2590 if (in_bounds) {
2591 /* Switch paintmode (temporarily if need be) based on which button was used
2592 * NOTE: This is to make it more convenient to erase strokes when using drawing sessions
2593 */
2594 if ((event->type == RIGHTMOUSE) || annotation_is_tablet_eraser_active(event)) {
2595 /* turn on eraser */
2597 }
2598 else { /* Any mouse button besides right. */
2599 /* restore drawmode to default */
2600 p->paintmode = eGPencil_PaintModes(RNA_enum_get(op->ptr, "mode"));
2601 }
2602
2604
2605 /* not painting, so start stroke (this should be mouse-button down) */
2606 p = annotation_stroke_begin(C, op);
2607
2608 if (p->status == GP_STATUS_ERROR) {
2609 estate = OPERATOR_CANCELLED;
2610 }
2611 }
2612 else if (p->status != GP_STATUS_ERROR) {
2613 /* User clicked outside bounds of window while idling, so exit paintmode
2614 * NOTE: Don't enter this case if an error occurred while finding the
2615 * region (as above)
2616 */
2618 estate = OPERATOR_FINISHED;
2619 }
2620 }
2621 else if (event->val == KM_RELEASE) {
2624 }
2625 }
2626
2627 /* handle mode-specific events */
2628 if (p->status == GP_STATUS_PAINTING) {
2629 /* handle painting mouse-movements? */
2630 if (ISMOUSE_MOTION(event->type) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) {
2631 /* handle drawing event */
2632 if ((p->flags & GP_PAINTFLAG_FIRSTRUN) == 0) {
2633 annotation_add_missing_events(C, op, event, p);
2634 }
2635
2636 /* TODO(sergey): Possibly evaluating dependency graph from modal operator? */
2638
2639 /* finish painting operation if anything went wrong just now */
2640 if (p->status == GP_STATUS_ERROR) {
2641 printf("\t\t\t\tGP - add error done!\n");
2642 estate = OPERATOR_CANCELLED;
2643 }
2644 else {
2645 /* event handled, so just tag as running modal */
2646 estate = OPERATOR_RUNNING_MODAL;
2647 }
2648 }
2649 /* eraser size */
2650 else if ((p->paintmode == GP_PAINTMODE_ERASER) &&
2652 {
2653 /* just resize the brush (local version)
2654 * TODO: fix the hardcoded size jumps (set to make a visible difference) and hardcoded keys
2655 */
2656 switch (event->type) {
2657 case WHEELDOWNMOUSE: /* larger */
2658 case EVT_PADPLUSKEY:
2659 p->radius += 5;
2660 break;
2661
2662 case WHEELUPMOUSE: /* smaller */
2663 case EVT_PADMINUS:
2664 p->radius -= 5;
2665
2666 if (p->radius <= 0) {
2667 p->radius = 1;
2668 }
2669 break;
2670 default: {
2671 break;
2672 }
2673 }
2674
2675 /* force refresh */
2676 /* just active area for now, since doing whole screen is too slow */
2678
2679 /* event handled, so just tag as running modal */
2680 estate = OPERATOR_RUNNING_MODAL;
2681 }
2682 /* there shouldn't be any other events, but just in case there are, let's swallow them
2683 * (i.e. to prevent problems with undo)
2684 */
2685 else {
2686 /* swallow event to save ourselves trouble */
2687 estate = OPERATOR_RUNNING_MODAL;
2688 }
2689 }
2690
2691 /* gpencil modal operator stores area, which can be removed while using it (like full-screen). */
2692 if (0 == annotation_area_exists(C, p->area)) {
2693 estate = OPERATOR_CANCELLED;
2694 }
2695 else {
2696 /* update status indicators - cursor, header, etc. */
2698 /* cursor may have changed outside our control - #44084 */
2700 }
2701
2702 /* process last operations before exiting */
2703 switch (estate) {
2704 case OPERATOR_FINISHED:
2705 /* one last flush before we're done */
2708 break;
2709
2710 case OPERATOR_CANCELLED:
2712 break;
2713
2714 /* Event doesn't need to be handled. */
2715 /* `OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH` */
2716 default:
2717 /* Quiet warnings. */
2718 break;
2719 }
2720
2721 /* return status code */
2722 return estate;
2723}
2724
2725/* ------------------------------- */
2726
2728 {GP_PAINTMODE_DRAW, "DRAW", 0, "Draw Freehand", "Draw freehand stroke(s)"},
2730 "DRAW_STRAIGHT",
2731 0,
2732 "Draw Straight Lines",
2733 "Draw straight line segment(s)"},
2735 "DRAW_POLY",
2736 0,
2737 "Draw Poly Line",
2738 "Click to place endpoints of straight line segments (connected)"},
2739 {GP_PAINTMODE_ERASER, "ERASER", 0, "Eraser", "Erase Annotation strokes"},
2740 {0, nullptr, 0, nullptr, nullptr},
2741};
2742
2744 {GP_STROKE_ARROWSTYLE_NONE, "NONE", 0, "None", "Don't use any arrow/style in corner"},
2745 {GP_STROKE_ARROWSTYLE_CLOSED, "ARROW", 0, "Arrow", "Use closed arrow style"},
2746 {GP_STROKE_ARROWSTYLE_OPEN, "ARROW_OPEN", 0, "Open Arrow", "Use open arrow style"},
2748 "ARROW_OPEN_INVERTED",
2749 0,
2750 "Segment",
2751 "Use perpendicular segment style"},
2752 {GP_STROKE_ARROWSTYLE_SQUARE, "DIAMOND", 0, "Square", "Use square style"},
2753 {0, nullptr, 0, nullptr, nullptr},
2754};
2755
2757{
2758 PropertyRNA *prop;
2759
2760 /* identifiers */
2761 ot->name = "Annotation Draw";
2762 ot->idname = "GPENCIL_OT_annotate";
2763 ot->description = "Make annotations on the active data";
2764
2765 /* API callbacks. */
2766 ot->exec = annotation_draw_exec;
2767 ot->invoke = annotation_draw_invoke;
2768 ot->modal = annotation_draw_modal;
2769 ot->cancel = annotation_draw_cancel;
2770 ot->poll = annotation_draw_poll;
2771
2772 /* flags */
2773 ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING;
2774
2775 /* settings for drawing */
2776 ot->prop = RNA_def_enum(
2777 ot->srna, "mode", prop_gpencil_drawmodes, 0, "Mode", "Way to interpret mouse movements");
2778
2779 /* properties */
2780 prop = RNA_def_enum(
2781 ot->srna, "arrowstyle_start", arrow_types, 0, "Start Arrow Style", "Stroke start style");
2782 prop = RNA_def_enum(
2783 ot->srna, "arrowstyle_end", arrow_types, 0, "End Arrow Style", "Stroke end style");
2784 prop = RNA_def_boolean(ot->srna,
2785 "use_stabilizer",
2786 false,
2787 "Stabilize Stroke",
2788 "Helper to draw smooth and clean lines. Press Shift for an invert effect "
2789 "(even if this option is not active)");
2790 prop = RNA_def_float(ot->srna,
2791 "stabilizer_factor",
2792 0.75f,
2793 0.0f,
2794 1.0f,
2795 "Stabilizer Stroke Factor",
2796 "Higher values gives a smoother stroke",
2797 0.0f,
2798 1.0f);
2799 prop = RNA_def_int(ot->srna,
2800 "stabilizer_radius",
2801 35,
2802 0,
2803 200,
2804 "Stabilizer Stroke Radius",
2805 "Minimum distance from last point before stroke continues",
2806 1,
2807 100);
2809
2810 prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
2812
2813 /* NOTE: wait for input is enabled by default,
2814 * so that all UI code can work properly without needing users to know about this */
2815 prop = RNA_def_boolean(ot->srna,
2816 "wait_for_input",
2817 true,
2818 "Wait for Input",
2819 "Wait for first click instead of painting immediately");
2821}
@ 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
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:126
ARegion * BKE_area_find_region_xy(const ScrArea *area, int regiontype, const int xy[2]) ATTR_NONNULL(3)
Definition screen.cc:869
struct MovieTrackingMarker * BKE_tracking_marker_get(struct MovieTrackingTrack *track, int framenr)
Definition tracking.cc:1357
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:65
#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
@ OP_IS_MODAL_CURSOR_REGION
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
@ OPERATOR_RUNNING_MODAL
@ OPERATOR_PASS_THROUGH
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)
Definition screen_ops.cc:98
const rcti * ED_region_visible_rect(ARegion *region)
Definition area.cc:4118
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:639
bool ED_view3d_depth_read_cached(const ViewDepths *vd, const int mval[2], int margin, float *r_depth)
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 ED_view3d_win_to_delta(const ARegion *region, const float xy_delta[2], float zfac, float r_out[3])
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 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 immBindBuiltinProgram(eGPUBuiltinShader shader_id)
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
@ GPU_BLEND_NONE
Definition GPU_state.hh:85
@ GPU_BLEND_ALPHA
Definition GPU_state.hh:87
void GPU_blend(eGPUBlend blend)
Definition gpu_state.cc:42
void GPU_line_width(float width)
Definition gpu_state.cc:166
void GPU_line_smooth(bool enable)
Definition gpu_state.cc:78
void GPU_viewport_size_get_f(float coords[4])
Definition gpu_state.cc:273
@ GPU_FETCH_FLOAT
uint GPU_vertformat_attr_add(GPUVertFormat *, blender::StringRef name, GPUVertCompType, uint comp_len, GPUVertFetchMode)
@ GPU_COMP_F32
Read Guarded memory(de)allocation.
#define RNA_BEGIN(sptr, itemptr, propname)
#define RNA_END
@ PROP_SKIP_SAVE
Definition RNA_types.hh:330
@ PROP_HIDDEN
Definition RNA_types.hh:324
@ PROP_PIXEL
Definition RNA_types.hh:236
#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:1667
#define V2D_IS_CLIPPED
Definition UI_view2d.hh:21
@ KM_CTRL
Definition WM_types.hh:276
@ KM_ALT
Definition WM_types.hh:277
@ KM_SHIFT
Definition WM_types.hh:275
@ KM_PRESS
Definition WM_types.hh:308
@ KM_RELEASE
Definition WM_types.hh:309
#define NA_EDITED
Definition WM_types.hh:581
#define NC_GPENCIL
Definition WM_types.hh:396
@ WM_OP_EXEC_DEFAULT
Definition WM_types.hh:245
@ OPTYPE_BLOCKING
Definition WM_types.hh:184
@ OPTYPE_UNDO
Definition WM_types.hh:182
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
void item(std::string text, int icon1, int icon2=0)
Definition area.cc:979
#define fabsf(x)
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
constexpr T sign(T) RET
#define abs
#define out
#define printf(...)
float length(VecOp< float, D >) RET
#define MEM_reallocN(vmemh, len)
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
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)
static const int steps
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:771
wmEventType type
Definition WM_types.hh:754
short val
Definition WM_types.hh:756
int xy[2]
Definition WM_types.hh:758
int mval[2]
Definition WM_types.hh:760
wmTabletData tablet
Definition WM_types.hh:783
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:174
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)
wmOperatorStatus WM_operator_name_call(bContext *C, const char *opstring, wmOperatorCallContext context, PointerRNA *properties, const wmEvent *event)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
#define ISMOUSE_BUTTON(event_type)
#define ISMOUSE_MOTION(event_type)
@ EVT_TABLET_NONE
@ EVT_TABLET_ERASER
@ 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:4226
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)