/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ /** \file * \ingroup edsculpt * \brief Functions to paint images in 2D and 3D. */ #include #include #include #include #include "MEM_guardedalloc.h" #include "BLI_math.h" #include "BLI_utildefines.h" #include "BLT_translation.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" #include "DNA_brush_types.h" #include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_node_types.h" #include "DNA_object_types.h" #include "BKE_brush.h" #include "BKE_colorband.h" #include "BKE_context.h" #include "BKE_image.h" #include "BKE_main.h" #include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_paint.h" #include "NOD_texture.h" #include "DEG_depsgraph.h" #include "UI_interface.h" #include "UI_view2d.h" #include "ED_image.h" #include "ED_object.h" #include "ED_paint.h" #include "ED_screen.h" #include "WM_api.h" #include "WM_message.h" #include "WM_toolsystem.h" #include "WM_types.h" #include "RNA_access.h" #include "RNA_define.h" #include "IMB_colormanagement.h" #include "paint_intern.h" extern "C" { /** * This is a static resource for non-global access. * Maybe it should be exposed as part of the paint operation, * but for now just give a public interface. */ static ImagePaintPartialRedraw imapaintpartial = {{0}}; ImagePaintPartialRedraw *get_imapaintpartial(void) { return &imapaintpartial; } void set_imapaintpartial(struct ImagePaintPartialRedraw *ippr) { imapaintpartial = *ippr; } /* Image paint Partial Redraw & Dirty Region. */ void ED_imapaint_clear_partial_redraw(void) { BLI_rcti_init_minmax(&imapaintpartial.dirty_region); } void imapaint_region_tiles( ImBuf *ibuf, int x, int y, int w, int h, int *tx, int *ty, int *tw, int *th) { int srcx = 0, srcy = 0; IMB_rectclip(ibuf, nullptr, &x, &y, &srcx, &srcy, &w, &h); *tw = ((x + w - 1) >> ED_IMAGE_UNDO_TILE_BITS); *th = ((y + h - 1) >> ED_IMAGE_UNDO_TILE_BITS); *tx = (x >> ED_IMAGE_UNDO_TILE_BITS); *ty = (y >> ED_IMAGE_UNDO_TILE_BITS); } void ED_imapaint_dirty_region( Image *ima, ImBuf *ibuf, ImageUser *iuser, int x, int y, int w, int h, bool find_old) { ImBuf *tmpibuf = nullptr; int tilex, tiley, tilew, tileh, tx, ty; int srcx = 0, srcy = 0; IMB_rectclip(ibuf, nullptr, &x, &y, &srcx, &srcy, &w, &h); if (w == 0 || h == 0) { return; } rcti rect_to_merge; BLI_rcti_init(&rect_to_merge, x, x + w, y, y + h); BLI_rcti_do_minmax_rcti(&imapaintpartial.dirty_region, &rect_to_merge); imapaint_region_tiles(ibuf, x, y, w, h, &tilex, &tiley, &tilew, &tileh); PaintTileMap *undo_tiles = ED_image_paint_tile_map_get(); for (ty = tiley; ty <= tileh; ty++) { for (tx = tilex; tx <= tilew; tx++) { ED_image_paint_tile_push( undo_tiles, ima, ibuf, &tmpibuf, iuser, tx, ty, nullptr, nullptr, false, find_old); } } BKE_image_mark_dirty(ima, ibuf); if (tmpibuf) { IMB_freeImBuf(tmpibuf); } } void imapaint_image_update( SpaceImage *sima, Image *image, ImBuf *ibuf, ImageUser *iuser, short texpaint) { if (BLI_rcti_is_empty(&imapaintpartial.dirty_region)) { return; } if (ibuf->mipmap[0]) { ibuf->userflags |= IB_MIPMAP_INVALID; } IMB_partial_display_buffer_update_delayed(ibuf, imapaintpartial.dirty_region.xmin, imapaintpartial.dirty_region.ymin, imapaintpartial.dirty_region.xmax, imapaintpartial.dirty_region.ymax); /* When buffer is partial updated the planes should be set to a larger value than 8. This will * make sure that partial updating is working but uses more GPU memory as the gpu texture will * have 4 channels. When so the whole texture needs to be reuploaded to the GPU using the new * texture format. */ if (ibuf != nullptr && ibuf->planes == 8) { ibuf->planes = 32; BKE_image_partial_update_mark_full_update(image); return; } /* TODO: should set_tpage create ->rect? */ if (texpaint || (sima && sima->lock)) { const int w = BLI_rcti_size_x(&imapaintpartial.dirty_region); const int h = BLI_rcti_size_y(&imapaintpartial.dirty_region); /* Testing with partial update in uv editor too. */ BKE_image_update_gputexture( image, iuser, imapaintpartial.dirty_region.xmin, imapaintpartial.dirty_region.ymin, w, h); } } BlurKernel *paint_new_blur_kernel(Brush *br, bool proj) { int i, j; BlurKernel *kernel = MEM_new("BlurKernel"); float radius; int side; eBlurKernelType type = static_cast(br->blur_mode); if (proj) { radius = 0.5f; side = kernel->side = 2; kernel->side_squared = kernel->side * kernel->side; kernel->wdata = static_cast( MEM_mallocN(sizeof(float) * kernel->side_squared, "blur kernel data")); kernel->pixel_len = radius; } else { if (br->blur_kernel_radius <= 0) { br->blur_kernel_radius = 1; } radius = br->blur_kernel_radius; side = kernel->side = radius * 2 + 1; kernel->side_squared = kernel->side * kernel->side; kernel->wdata = static_cast( MEM_mallocN(sizeof(float) * kernel->side_squared, "blur kernel data")); kernel->pixel_len = br->blur_kernel_radius; } switch (type) { case KERNEL_BOX: for (i = 0; i < kernel->side_squared; i++) { kernel->wdata[i] = 1.0; } break; case KERNEL_GAUSSIAN: { /* at 3.0 standard deviations distance, kernel is about zero */ float standard_dev = radius / 3.0f; /* make the necessary adjustment to the value for use in the normal distribution formula */ standard_dev = -standard_dev * standard_dev * 2; for (i = 0; i < side; i++) { for (j = 0; j < side; j++) { float idist = radius - i; float jdist = radius - j; float value = exp((idist * idist + jdist * jdist) / standard_dev); kernel->wdata[i + j * side] = value; } } break; } default: printf("unidentified kernel type, aborting\n"); paint_delete_blur_kernel(kernel); MEM_delete(kernel); return nullptr; } return kernel; } void paint_delete_blur_kernel(BlurKernel *kernel) { if (kernel->wdata) { MEM_freeN(kernel->wdata); } } /************************ image paint poll ************************/ static Brush *image_paint_brush(bContext *C) { Scene *scene = CTX_data_scene(C); ToolSettings *settings = scene->toolsettings; return BKE_paint_brush(&settings->imapaint.paint); } static bool image_paint_poll_ex(bContext *C, bool check_tool) { Object *obact; if (!image_paint_brush(C)) { return false; } obact = CTX_data_active_object(C); if ((obact && obact->mode & OB_MODE_TEXTURE_PAINT) && CTX_wm_region_view3d(C)) { if (!check_tool || WM_toolsystem_active_tool_is_brush(C)) { return true; } } else { SpaceImage *sima = CTX_wm_space_image(C); if (sima) { if (sima->image != nullptr && (ID_IS_LINKED(sima->image) || ID_IS_OVERRIDE_LIBRARY(sima->image))) { return false; } if (sima->mode == SI_MODE_PAINT) { const ARegion *region = CTX_wm_region(C); if (region->regiontype == RGN_TYPE_WINDOW) { return true; } } } } return false; } bool ED_image_tools_paint_poll(bContext *C) { return image_paint_poll_ex(C, true); } static bool image_paint_poll_ignore_tool(bContext *C) { return image_paint_poll_ex(C, false); } static bool image_paint_2d_clone_poll(bContext *C) { Brush *brush = image_paint_brush(C); if (!CTX_wm_region_view3d(C) && ED_image_tools_paint_poll(C)) { if (brush && (brush->imagepaint_tool == PAINT_TOOL_CLONE)) { if (brush->clone.image) { return true; } } } return false; } /************************ paint operator ************************/ bool paint_use_opacity_masking(Brush *brush) { return ((brush->flag & BRUSH_AIRBRUSH) || (brush->flag & BRUSH_DRAG_DOT) || (brush->flag & BRUSH_ANCHORED) || ELEM(brush->imagepaint_tool, PAINT_TOOL_SMEAR, PAINT_TOOL_SOFTEN) || (brush->imagepaint_tool == PAINT_TOOL_FILL) || (brush->flag & BRUSH_USE_GRADIENT) || (brush->mtex.tex && !ELEM(brush->mtex.brush_map_mode, MTEX_MAP_MODE_TILED, MTEX_MAP_MODE_STENCIL, MTEX_MAP_MODE_3D)) ? false : true); } void paint_brush_color_get(struct Scene *scene, struct Brush *br, bool color_correction, bool invert, float distance, float pressure, float color[3], struct ColorManagedDisplay *display) { if (invert) { copy_v3_v3(color, BKE_brush_secondary_color_get(scene, br)); } else { if (br->flag & BRUSH_USE_GRADIENT) { float color_gr[4]; switch (br->gradient_stroke_mode) { case BRUSH_GRADIENT_PRESSURE: BKE_colorband_evaluate(br->gradient, pressure, color_gr); break; case BRUSH_GRADIENT_SPACING_REPEAT: { float coord = fmod(distance / br->gradient_spacing, 1.0); BKE_colorband_evaluate(br->gradient, coord, color_gr); break; } case BRUSH_GRADIENT_SPACING_CLAMP: { BKE_colorband_evaluate(br->gradient, distance / br->gradient_spacing, color_gr); break; } } /* Gradient / Color-band colors are not considered #PROP_COLOR_GAMMA. * Brush colors are expected to be in sRGB though. */ IMB_colormanagement_scene_linear_to_srgb_v3(color, color_gr); } else { copy_v3_v3(color, BKE_brush_color_get(scene, br)); } } if (color_correction) { IMB_colormanagement_display_to_scene_linear_v3(color, display); } } void paint_brush_init_tex(Brush *brush) { /* init mtex nodes */ if (brush) { MTex *mtex = &brush->mtex; if (mtex->tex && mtex->tex->nodetree) { /* has internal flag to detect it only does it once */ ntreeTexBeginExecTree(mtex->tex->nodetree); } mtex = &brush->mask_mtex; if (mtex->tex && mtex->tex->nodetree) { ntreeTexBeginExecTree(mtex->tex->nodetree); } } } void paint_brush_exit_tex(Brush *brush) { if (brush) { MTex *mtex = &brush->mtex; if (mtex->tex && mtex->tex->nodetree) { ntreeTexEndExecTree(mtex->tex->nodetree->execdata); } mtex = &brush->mask_mtex; if (mtex->tex && mtex->tex->nodetree) { ntreeTexEndExecTree(mtex->tex->nodetree->execdata); } } } bool get_imapaint_zoom(bContext *C, float *zoomx, float *zoomy) { ScrArea *area = CTX_wm_area(C); if (area && area->spacetype == SPACE_IMAGE) { SpaceImage *sima = static_cast(area->spacedata.first); if (sima->mode == SI_MODE_PAINT) { ARegion *region = CTX_wm_region(C); ED_space_image_get_zoom(sima, region, zoomx, zoomy); return true; } } *zoomx = *zoomy = 1; return false; } /************************ cursor drawing *******************************/ static void toggle_paint_cursor(Scene *scene, bool enable) { ToolSettings *settings = scene->toolsettings; Paint *p = &settings->imapaint.paint; if (p->paint_cursor && !enable) { WM_paint_cursor_end(static_cast(p->paint_cursor)); p->paint_cursor = nullptr; paint_cursor_delete_textures(); } else if (enable) { ED_paint_cursor_start(p, ED_image_tools_paint_poll); } } void ED_space_image_paint_update(Main *bmain, wmWindowManager *wm, Scene *scene) { ToolSettings *settings = scene->toolsettings; ImagePaintSettings *imapaint = &settings->imapaint; bool enabled = false; LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { bScreen *screen = WM_window_get_active_screen(win); LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { if (area->spacetype == SPACE_IMAGE) { if (((SpaceImage *)area->spacedata.first)->mode == SI_MODE_PAINT) { enabled = true; } } } } if (enabled) { BKE_paint_init(bmain, scene, PAINT_MODE_TEXTURE_2D, PAINT_CURSOR_TEXTURE_PAINT); ED_paint_cursor_start(&imapaint->paint, ED_image_tools_paint_poll); } else { paint_cursor_delete_textures(); } } /************************ grab clone operator ************************/ struct GrabClone { float startoffset[2]; int startx, starty; }; static void grab_clone_apply(bContext *C, wmOperator *op) { Brush *brush = image_paint_brush(C); float delta[2]; RNA_float_get_array(op->ptr, "delta", delta); add_v2_v2(brush->clone.offset, delta); ED_region_tag_redraw(CTX_wm_region(C)); } static int grab_clone_exec(bContext *C, wmOperator *op) { grab_clone_apply(C, op); return OPERATOR_FINISHED; } static int grab_clone_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Brush *brush = image_paint_brush(C); GrabClone *cmv; cmv = MEM_new("GrabClone"); copy_v2_v2(cmv->startoffset, brush->clone.offset); cmv->startx = event->xy[0]; cmv->starty = event->xy[1]; op->customdata = cmv; WM_event_add_modal_handler(C, op); return OPERATOR_RUNNING_MODAL; } static int grab_clone_modal(bContext *C, wmOperator *op, const wmEvent *event) { Brush *brush = image_paint_brush(C); ARegion *region = CTX_wm_region(C); GrabClone *cmv = static_cast(op->customdata); float startfx, startfy, fx, fy, delta[2]; int xmin = region->winrct.xmin, ymin = region->winrct.ymin; switch (event->type) { case LEFTMOUSE: case MIDDLEMOUSE: case RIGHTMOUSE: /* XXX hardcoded */ MEM_freeN(op->customdata); return OPERATOR_FINISHED; case MOUSEMOVE: /* mouse moved, so move the clone image */ UI_view2d_region_to_view( ®ion->v2d, cmv->startx - xmin, cmv->starty - ymin, &startfx, &startfy); UI_view2d_region_to_view(®ion->v2d, event->xy[0] - xmin, event->xy[1] - ymin, &fx, &fy); delta[0] = fx - startfx; delta[1] = fy - startfy; RNA_float_set_array(op->ptr, "delta", delta); copy_v2_v2(brush->clone.offset, cmv->startoffset); grab_clone_apply(C, op); break; } return OPERATOR_RUNNING_MODAL; } static void grab_clone_cancel(bContext * /*C*/, wmOperator *op) { GrabClone *cmv = static_cast(op->customdata); MEM_delete(cmv); } void PAINT_OT_grab_clone(wmOperatorType *ot) { /* identifiers */ ot->name = "Grab Clone"; ot->idname = "PAINT_OT_grab_clone"; ot->description = "Move the clone source image"; /* api callbacks */ ot->exec = grab_clone_exec; ot->invoke = grab_clone_invoke; ot->modal = grab_clone_modal; ot->cancel = grab_clone_cancel; ot->poll = image_paint_2d_clone_poll; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; /* properties */ RNA_def_float_vector(ot->srna, "delta", 2, nullptr, -FLT_MAX, FLT_MAX, "Delta", "Delta offset of clone image in 0.0 to 1.0 coordinates", -1.0f, 1.0f); } /******************** sample color operator ********************/ struct SampleColorData { bool show_cursor; short launch_event; float initcolor[3]; bool sample_palette; }; static void sample_color_update_header(SampleColorData *data, bContext *C) { char msg[UI_MAX_DRAW_STR]; ScrArea *area = CTX_wm_area(C); if (area) { BLI_snprintf(msg, sizeof(msg), TIP_("Sample color for %s"), !data->sample_palette ? TIP_("Brush. Use Left Click to sample for palette instead") : TIP_("Palette. Use Left Click to sample more colors")); ED_workspace_status_text(C, msg); } } static int sample_color_exec(bContext *C, wmOperator *op) { Paint *paint = BKE_paint_get_active_from_context(C); Brush *brush = BKE_paint_brush(paint); ePaintMode mode = BKE_paintmode_get_active_from_context(C); ARegion *region = CTX_wm_region(C); wmWindow *win = CTX_wm_window(C); const bool show_cursor = ((paint->flags & PAINT_SHOW_BRUSH) != 0); int location[2]; paint->flags &= ~PAINT_SHOW_BRUSH; /* force redraw without cursor */ WM_paint_cursor_tag_redraw(win, region); WM_redraw_windows(C); RNA_int_get_array(op->ptr, "location", location); const bool use_palette = RNA_boolean_get(op->ptr, "palette"); const bool use_sample_texture = (mode == PAINT_MODE_TEXTURE_3D) && !RNA_boolean_get(op->ptr, "merged"); paint_sample_color(C, region, location[0], location[1], use_sample_texture, use_palette); if (show_cursor) { paint->flags |= PAINT_SHOW_BRUSH; } WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); return OPERATOR_FINISHED; } static int sample_color_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Scene *scene = CTX_data_scene(C); Paint *paint = BKE_paint_get_active_from_context(C); Brush *brush = BKE_paint_brush(paint); SampleColorData *data = MEM_new("sample color custom data"); ARegion *region = CTX_wm_region(C); wmWindow *win = CTX_wm_window(C); data->launch_event = WM_userdef_event_type_from_keymap_type(event->type); data->show_cursor = ((paint->flags & PAINT_SHOW_BRUSH) != 0); copy_v3_v3(data->initcolor, BKE_brush_color_get(scene, brush)); data->sample_palette = false; op->customdata = data; paint->flags &= ~PAINT_SHOW_BRUSH; sample_color_update_header(data, C); WM_event_add_modal_handler(C, op); /* force redraw without cursor */ WM_paint_cursor_tag_redraw(win, region); WM_redraw_windows(C); RNA_int_set_array(op->ptr, "location", event->mval); ePaintMode mode = BKE_paintmode_get_active_from_context(C); const bool use_sample_texture = (mode == PAINT_MODE_TEXTURE_3D) && !RNA_boolean_get(op->ptr, "merged"); paint_sample_color(C, region, event->mval[0], event->mval[1], use_sample_texture, false); WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER); WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); return OPERATOR_RUNNING_MODAL; } static int sample_color_modal(bContext *C, wmOperator *op, const wmEvent *event) { Scene *scene = CTX_data_scene(C); SampleColorData *data = static_cast(op->customdata); Paint *paint = BKE_paint_get_active_from_context(C); Brush *brush = BKE_paint_brush(paint); if ((event->type == data->launch_event) && (event->val == KM_RELEASE)) { if (data->show_cursor) { paint->flags |= PAINT_SHOW_BRUSH; } if (data->sample_palette) { BKE_brush_color_set(scene, brush, data->initcolor); RNA_boolean_set(op->ptr, "palette", true); } WM_cursor_modal_restore(CTX_wm_window(C)); MEM_delete(data); ED_workspace_status_text(C, nullptr); return OPERATOR_FINISHED; } ePaintMode mode = BKE_paintmode_get_active_from_context(C); const bool use_sample_texture = (mode == PAINT_MODE_TEXTURE_3D) && !RNA_boolean_get(op->ptr, "merged"); switch (event->type) { case MOUSEMOVE: { ARegion *region = CTX_wm_region(C); RNA_int_set_array(op->ptr, "location", event->mval); paint_sample_color(C, region, event->mval[0], event->mval[1], use_sample_texture, false); WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); break; } case LEFTMOUSE: if (event->val == KM_PRESS) { ARegion *region = CTX_wm_region(C); RNA_int_set_array(op->ptr, "location", event->mval); paint_sample_color(C, region, event->mval[0], event->mval[1], use_sample_texture, true); if (!data->sample_palette) { data->sample_palette = true; sample_color_update_header(data, C); } WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); } break; } return OPERATOR_RUNNING_MODAL; } static bool sample_color_poll(bContext *C) { return (image_paint_poll_ignore_tool(C) || vertex_paint_poll_ignore_tool(C)); } void PAINT_OT_sample_color(wmOperatorType *ot) { /* identifiers */ ot->name = "Sample Color"; ot->idname = "PAINT_OT_sample_color"; ot->description = "Use the mouse to sample a color in the image"; /* api callbacks */ ot->exec = sample_color_exec; ot->invoke = sample_color_invoke; ot->modal = sample_color_modal; ot->poll = sample_color_poll; /* flags */ ot->flag = OPTYPE_REGISTER; /* properties */ PropertyRNA *prop; prop = RNA_def_int_vector( ot->srna, "location", 2, nullptr, 0, INT_MAX, "Location", "", 0, 16384); RNA_def_property_flag(prop, static_cast(PROP_SKIP_SAVE | PROP_HIDDEN)); RNA_def_boolean(ot->srna, "merged", false, "Sample Merged", "Sample the output display color"); RNA_def_boolean(ot->srna, "palette", false, "Add to Palette", ""); } /******************** texture paint toggle operator ********************/ void ED_object_texture_paint_mode_enter_ex(Main *bmain, Scene *scene, Object *ob) { Image *ima = nullptr; ImagePaintSettings *imapaint = &scene->toolsettings->imapaint; /* This has to stay here to regenerate the texture paint * cache in case we are loading a file */ BKE_texpaint_slots_refresh_object(scene, ob); ED_paint_proj_mesh_data_check(scene, ob, nullptr, nullptr, nullptr, nullptr); /* entering paint mode also sets image to editors */ if (imapaint->mode == IMAGEPAINT_MODE_MATERIAL) { /* set the current material active paint slot on image editor */ Material *ma = BKE_object_material_get(ob, ob->actcol); if (ma && ma->texpaintslot) { ima = ma->texpaintslot[ma->paint_active_slot].ima; } } else if (imapaint->mode == IMAGEPAINT_MODE_IMAGE) { ima = imapaint->canvas; } if (ima) { wmWindowManager *wm = static_cast(bmain->wm.first); LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { const bScreen *screen = WM_window_get_active_screen(win); LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { SpaceLink *sl = static_cast(area->spacedata.first); if (sl->spacetype == SPACE_IMAGE) { SpaceImage *sima = (SpaceImage *)sl; if (!sima->pin) { ED_space_image_set(bmain, sima, ima, true); } } } } } ob->mode |= OB_MODE_TEXTURE_PAINT; BKE_paint_init(bmain, scene, PAINT_MODE_TEXTURE_3D, PAINT_CURSOR_TEXTURE_PAINT); BKE_paint_toolslots_brush_validate(bmain, &imapaint->paint); if (U.glreslimit != 0) { BKE_image_free_all_gputextures(bmain); } BKE_image_paint_set_mipmap(bmain, false); toggle_paint_cursor(scene, true); Mesh *me = BKE_mesh_from_object(ob); BLI_assert(me != nullptr); DEG_id_tag_update(&me->id, ID_RECALC_COPY_ON_WRITE); WM_main_add_notifier(NC_SCENE | ND_MODE, scene); } void ED_object_texture_paint_mode_enter(bContext *C) { Main *bmain = CTX_data_main(C); Object *ob = CTX_data_active_object(C); Scene *scene = CTX_data_scene(C); ED_object_texture_paint_mode_enter_ex(bmain, scene, ob); } void ED_object_texture_paint_mode_exit_ex(Main *bmain, Scene *scene, Object *ob) { ob->mode &= ~OB_MODE_TEXTURE_PAINT; if (U.glreslimit != 0) { BKE_image_free_all_gputextures(bmain); } BKE_image_paint_set_mipmap(bmain, true); toggle_paint_cursor(scene, false); Mesh *me = BKE_mesh_from_object(ob); BLI_assert(me != nullptr); DEG_id_tag_update(&me->id, ID_RECALC_COPY_ON_WRITE); WM_main_add_notifier(NC_SCENE | ND_MODE, scene); } void ED_object_texture_paint_mode_exit(bContext *C) { Main *bmain = CTX_data_main(C); Object *ob = CTX_data_active_object(C); Scene *scene = CTX_data_scene(C); ED_object_texture_paint_mode_exit_ex(bmain, scene, ob); } static bool texture_paint_toggle_poll(bContext *C) { Object *ob = CTX_data_active_object(C); if (ob == nullptr || ob->type != OB_MESH) { return false; } if (ob->data == nullptr || ID_IS_LINKED(ob->data) || ID_IS_OVERRIDE_LIBRARY(ob->data)) { return false; } return true; } static int texture_paint_toggle_exec(bContext *C, wmOperator *op) { struct wmMsgBus *mbus = CTX_wm_message_bus(C); Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); const int mode_flag = OB_MODE_TEXTURE_PAINT; const bool is_mode_set = (ob->mode & mode_flag) != 0; if (!is_mode_set) { if (!ED_object_mode_compat_set(C, ob, static_cast(mode_flag), op->reports)) { return OPERATOR_CANCELLED; } } if (ob->mode & mode_flag) { ED_object_texture_paint_mode_exit_ex(bmain, scene, ob); } else { ED_object_texture_paint_mode_enter_ex(bmain, scene, ob); } WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); WM_toolsystem_update_from_context_view3d(C); return OPERATOR_FINISHED; } void PAINT_OT_texture_paint_toggle(wmOperatorType *ot) { /* identifiers */ ot->name = "Texture Paint Toggle"; ot->idname = "PAINT_OT_texture_paint_toggle"; ot->description = "Toggle texture paint mode in 3D view"; /* api callbacks */ ot->exec = texture_paint_toggle_exec; ot->poll = texture_paint_toggle_poll; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } static int brush_colors_flip_exec(bContext *C, wmOperator * /*op*/) { Scene *scene = CTX_data_scene(C); UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; Paint *paint = BKE_paint_get_active_from_context(C); Brush *br = BKE_paint_brush(paint); if (ups->flag & UNIFIED_PAINT_COLOR) { swap_v3_v3(ups->rgb, ups->secondary_rgb); } else if (br) { swap_v3_v3(br->rgb, br->secondary_rgb); } else { return OPERATOR_CANCELLED; } WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, br); return OPERATOR_FINISHED; } static bool brush_colors_flip_poll(bContext *C) { if (ED_image_tools_paint_poll(C)) { Brush *br = image_paint_brush(C); if (ELEM(br->imagepaint_tool, PAINT_TOOL_DRAW, PAINT_TOOL_FILL)) { return true; } } else { Object *ob = CTX_data_active_object(C); if (ob != nullptr) { if (ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_TEXTURE_PAINT | OB_MODE_SCULPT)) { return true; } } } return false; } void PAINT_OT_brush_colors_flip(wmOperatorType *ot) { /* identifiers */ ot->name = "Swap Colors"; ot->idname = "PAINT_OT_brush_colors_flip"; ot->description = "Swap primary and secondary brush colors"; /* api callbacks */ ot->exec = brush_colors_flip_exec; ot->poll = brush_colors_flip_poll; /* flags */ ot->flag = OPTYPE_REGISTER; } void ED_imapaint_bucket_fill(struct bContext *C, float color[3], wmOperator *op, const int mouse[2]) { SpaceImage *sima = CTX_wm_space_image(C); if (sima && sima->image) { Image *ima = sima->image; ED_image_undo_push_begin(op->type->name, PAINT_MODE_TEXTURE_2D); const float mouse_init[2] = {float(mouse[0]), float(mouse[1])}; paint_2d_bucket_fill(C, color, nullptr, mouse_init, nullptr, nullptr); ED_image_undo_push_end(); DEG_id_tag_update(&ima->id, 0); } } static bool texture_paint_poll(bContext *C) { if (texture_paint_toggle_poll(C)) { if (CTX_data_active_object(C)->mode & OB_MODE_TEXTURE_PAINT) { return true; } } return false; } bool image_texture_paint_poll(bContext *C) { return (texture_paint_poll(C) || ED_image_tools_paint_poll(C)); } bool facemask_paint_poll(bContext *C) { return BKE_paint_select_face_test(CTX_data_active_object(C)); } bool vert_paint_poll(bContext *C) { return BKE_paint_select_vert_test(CTX_data_active_object(C)); } bool mask_paint_poll(bContext *C) { return BKE_paint_select_elem_test(CTX_data_active_object(C)); } }