tornavis/source/blender/editors/sculpt_paint/paint_mask.cc

760 lines
24 KiB
C++

/* SPDX-FileCopyrightText: 2012 by Nicholas Bishop. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edsculpt
*/
#include <cstdlib>
#include "MEM_guardedalloc.h"
#include "DNA_object_types.h"
#include "BLI_array_utils.hh"
#include "BLI_bit_span_ops.hh"
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_math_base.hh"
#include "BLI_span.hh"
#include "BLI_utildefines.h"
#include "BLI_vector.hh"
#include "BKE_attribute.hh"
#include "BKE_brush.hh"
#include "BKE_ccg.h"
#include "BKE_context.hh"
#include "BKE_layer.hh"
#include "BKE_mesh.hh"
#include "BKE_multires.hh"
#include "BKE_paint.hh"
#include "BKE_pbvh_api.hh"
#include "BKE_subdiv_ccg.hh"
#include "BKE_subsurf.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_sculpt.hh"
#include "bmesh.hh"
#include "paint_intern.hh"
#include "sculpt_intern.hh"
namespace blender::ed::sculpt_paint::mask {
Array<float> duplicate_mask(const Object &object)
{
const SculptSession &ss = *object.sculpt;
switch (BKE_pbvh_type(ss.pbvh)) {
case PBVH_FACES: {
const Mesh &mesh = *static_cast<const Mesh *>(object.data);
const bke::AttributeAccessor attributes = mesh.attributes();
const VArray mask = *attributes.lookup_or_default<float>(
".sculpt_mask", bke::AttrDomain::Point, 0.0f);
Array<float> result(mask.size());
mask.materialize(result);
return result;
}
case PBVH_GRIDS: {
const SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
const Span<CCGElem *> grids = subdiv_ccg.grids;
Array<float> result(grids.size() * key.grid_area);
int index = 0;
for (const int grid : grids.index_range()) {
CCGElem *elem = grids[grid];
for (const int i : IndexRange(key.grid_area)) {
result[index] = *CCG_elem_offset_mask(&key, elem, i);
index++;
}
}
return result;
}
case PBVH_BMESH: {
BMesh &bm = *ss.bm;
const int offset = CustomData_get_offset_named(&bm.vdata, CD_PROP_FLOAT, ".sculpt_mask");
Array<float> result(bm.totvert);
if (offset == -1) {
result.fill(0.0f);
}
else {
BM_mesh_elem_table_ensure(&bm, BM_VERT);
for (const int i : result.index_range()) {
result[i] = BM_ELEM_CD_GET_FLOAT(BM_vert_at_index(&bm, i), offset);
}
}
return result;
}
}
BLI_assert_unreachable();
return {};
}
/* The gesture API doesn't write to this enum type,
* it writes to eSelectOp from ED_select_utils.hh.
* We must thus map the modes here to the desired
* eSelectOp modes.
*
* Fixes #102349.
*/
enum PaintMaskFloodMode {
PAINT_MASK_FLOOD_VALUE = SEL_OP_SUB,
PAINT_MASK_FLOOD_VALUE_INVERSE = SEL_OP_ADD,
PAINT_MASK_INVERT = SEL_OP_XOR,
};
static const EnumPropertyItem mode_items[] = {
{PAINT_MASK_FLOOD_VALUE,
"VALUE",
0,
"Value",
"Set mask to the level specified by the 'value' property"},
{PAINT_MASK_FLOOD_VALUE_INVERSE,
"VALUE_INVERSE",
0,
"Value Inverted",
"Set mask to the level specified by the inverted 'value' property"},
{PAINT_MASK_INVERT, "INVERT", 0, "Invert", "Invert the mask"},
{0}};
static float mask_flood_fill_get_new_value_for_elem(const float elem,
PaintMaskFloodMode mode,
float value)
{
switch (mode) {
case PAINT_MASK_FLOOD_VALUE:
return value;
case PAINT_MASK_FLOOD_VALUE_INVERSE:
return 1.0f - value;
case PAINT_MASK_INVERT:
return 1.0f - elem;
}
BLI_assert_unreachable();
return 0.0f;
}
static Span<int> get_visible_verts(const PBVHNode &node,
const Span<bool> hide_vert,
Vector<int> &indices)
{
if (BKE_pbvh_node_fully_hidden_get(&node)) {
return {};
}
const Span<int> verts = BKE_pbvh_node_get_unique_vert_indices(&node);
if (hide_vert.is_empty()) {
return verts;
}
indices.resize(verts.size());
const int *end = std::copy_if(verts.begin(), verts.end(), indices.begin(), [&](const int vert) {
return !hide_vert[vert];
});
indices.resize(end - indices.begin());
return indices;
}
static Span<int> get_hidden_verts(const PBVHNode &node,
const Span<bool> hide_vert,
Vector<int> &indices)
{
if (hide_vert.is_empty()) {
return {};
}
const Span<int> verts = BKE_pbvh_node_get_unique_vert_indices(&node);
if (BKE_pbvh_node_fully_hidden_get(&node)) {
return verts;
}
indices.resize(verts.size());
const int *end = std::copy_if(verts.begin(), verts.end(), indices.begin(), [&](const int vert) {
return hide_vert[vert];
});
indices.resize(end - indices.begin());
return indices;
}
static bool try_remove_mask_mesh(Object &object, const Span<PBVHNode *> nodes)
{
Mesh &mesh = *static_cast<Mesh *>(object.data);
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
const VArraySpan mask = *attributes.lookup<float>(".sculpt_mask", bke::AttrDomain::Point);
if (mask.is_empty()) {
return true;
}
/* If there are any hidden vertices that shouldn't be affected with a mask value set, the
* attribute cannot be removed. This could also be done by building an IndexMask in the full
* vertex domain. */
const VArraySpan hide_vert = *attributes.lookup<bool>(".hide_vert", bke::AttrDomain::Point);
threading::EnumerableThreadSpecific<Vector<int>> all_index_data;
const bool hidden_masked_verts = threading::parallel_reduce(
nodes.index_range(),
1,
false,
[&](const IndexRange range, bool init) {
if (init) {
return init;
}
Vector<int> &index_data = all_index_data.local();
for (const PBVHNode *node : nodes.slice(range)) {
const Span<int> verts = get_hidden_verts(*node, hide_vert, index_data);
if (std::any_of(verts.begin(), verts.end(), [&](int i) { return mask[i] > 0.0f; })) {
return true;
}
}
return false;
},
std::logical_or());
if (hidden_masked_verts) {
return false;
}
/* Store undo data for nodes with changed mask. */
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (PBVHNode *node : nodes.slice(range)) {
const Span<int> verts = BKE_pbvh_node_get_unique_vert_indices(node);
if (std::all_of(verts.begin(), verts.end(), [&](const int i) { return mask[i] == 0.0f; })) {
continue;
}
undo::push_node(&object, node, undo::Type::Mask);
BKE_pbvh_node_mark_redraw(node);
}
});
attributes.remove(".sculpt_mask");
return true;
}
static void fill_mask_mesh(Object &object, const float value, const Span<PBVHNode *> nodes)
{
Mesh &mesh = *static_cast<Mesh *>(object.data);
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
const VArraySpan hide_vert = *attributes.lookup<bool>(".hide_vert", bke::AttrDomain::Point);
if (value == 0.0f) {
if (try_remove_mask_mesh(object, nodes)) {
return;
}
}
bke::SpanAttributeWriter<float> mask = attributes.lookup_or_add_for_write_span<float>(
".sculpt_mask", bke::AttrDomain::Point);
threading::EnumerableThreadSpecific<Vector<int>> all_index_data;
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
Vector<int> &index_data = all_index_data.local();
for (PBVHNode *node : nodes.slice(range)) {
const Span<int> verts = get_visible_verts(*node, hide_vert, index_data);
if (std::all_of(verts.begin(), verts.end(), [&](int i) { return mask.span[i] == value; })) {
continue;
}
undo::push_node(&object, node, undo::Type::Mask);
mask.span.fill_indices(verts, value);
BKE_pbvh_node_mark_redraw(node);
}
});
mask.finish();
}
static void fill_mask_grids(Main &bmain,
const Scene &scene,
Depsgraph &depsgraph,
Object &object,
const float value,
const Span<PBVHNode *> nodes)
{
SubdivCCG &subdiv_ccg = *object.sculpt->subdiv_ccg;
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
if (value == 0.0f && !key.has_mask) {
/* Unlike meshes, don't dynamically remove masks since it is interleaved with other data. */
return;
}
MultiresModifierData &mmd = *BKE_sculpt_multires_active(&scene, &object);
BKE_sculpt_mask_layers_ensure(&depsgraph, &bmain, &object, &mmd);
const BitGroupVector<> &grid_hidden = subdiv_ccg.grid_hidden;
const Span<CCGElem *> grids = subdiv_ccg.grids;
bool any_changed = false;
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (PBVHNode *node : nodes.slice(range)) {
const Span<int> grid_indices = BKE_pbvh_node_get_grid_indices(*node);
if (std::all_of(grid_indices.begin(), grid_indices.end(), [&](const int grid) {
CCGElem *elem = grids[grid];
for (const int i : IndexRange(key.grid_area)) {
if (*CCG_elem_offset_mask(&key, elem, i) != value) {
return false;
}
}
return true;
}))
{
continue;
}
undo::push_node(&object, node, undo::Type::Mask);
if (grid_hidden.is_empty()) {
for (const int grid : grid_indices) {
CCGElem *elem = grids[grid];
for (const int i : IndexRange(key.grid_area)) {
*CCG_elem_offset_mask(&key, elem, i) = value;
}
}
}
else {
for (const int grid : grid_indices) {
CCGElem *elem = grids[grid];
bits::foreach_0_index(grid_hidden[grid], [&](const int i) {
*CCG_elem_offset_mask(&key, elem, i) = value;
});
}
}
BKE_pbvh_node_mark_redraw(node);
any_changed = true;
}
});
if (any_changed) {
multires_mark_as_modified(&depsgraph, &object, MULTIRES_COORDS_MODIFIED);
}
}
static void fill_mask_bmesh(Object &object, const float value, const Span<PBVHNode *> nodes)
{
BMesh &bm = *object.sculpt->bm;
const int offset = CustomData_get_offset_named(&bm.vdata, CD_PROP_FLOAT, ".sculpt_mask");
if (value == 0.0f && offset == -1) {
return;
}
if (offset == -1) {
/* Mask is not dynamically added or removed for dynamic topology sculpting. */
BLI_assert_unreachable();
return;
}
undo::push_node(&object, nodes.first(), undo::Type::Mask);
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (PBVHNode *node : nodes.slice(range)) {
bool redraw = false;
for (BMVert *vert : BKE_pbvh_bmesh_node_unique_verts(node)) {
if (!BM_elem_flag_test(vert, BM_ELEM_HIDDEN)) {
if (BM_ELEM_CD_GET_FLOAT(vert, offset) != value) {
BM_ELEM_CD_SET_FLOAT(vert, offset, value);
redraw = true;
}
}
}
if (redraw) {
BKE_pbvh_node_mark_redraw(node);
}
}
});
}
static void fill_mask(
Main &bmain, const Scene &scene, Depsgraph &depsgraph, Object &object, const float value)
{
PBVH &pbvh = *object.sculpt->pbvh;
Vector<PBVHNode *> nodes = bke::pbvh::search_gather(&pbvh, {});
switch (BKE_pbvh_type(&pbvh)) {
case PBVH_FACES:
fill_mask_mesh(object, value, nodes);
break;
case PBVH_GRIDS:
fill_mask_grids(bmain, scene, depsgraph, object, value, nodes);
break;
case PBVH_BMESH:
fill_mask_bmesh(object, value, nodes);
break;
}
/* Avoid calling #BKE_pbvh_node_mark_update_mask by doing that update here. */
for (PBVHNode *node : nodes) {
BKE_pbvh_node_fully_masked_set(node, value == 1.0f);
BKE_pbvh_node_fully_unmasked_set(node, value == 0.0f);
}
}
static void invert_mask_mesh(Object &object, const Span<PBVHNode *> nodes)
{
Mesh &mesh = *static_cast<Mesh *>(object.data);
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
const VArraySpan hide_vert = *attributes.lookup<bool>(".hide_vert", bke::AttrDomain::Point);
bke::SpanAttributeWriter<float> mask = attributes.lookup_or_add_for_write_span<float>(
".sculpt_mask", bke::AttrDomain::Point);
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (PBVHNode *node : nodes.slice(range)) {
undo::push_node(&object, node, undo::Type::Mask);
for (const int vert : BKE_pbvh_node_get_unique_vert_indices(node)) {
if (!hide_vert.is_empty() && hide_vert[vert]) {
continue;
}
mask.span[vert] = 1.0f - mask.span[vert];
}
BKE_pbvh_node_mark_redraw(node);
bke::pbvh::node_update_mask_mesh(mask.span, *node);
}
});
mask.finish();
}
static void invert_mask_grids(Main &bmain,
const Scene &scene,
Depsgraph &depsgraph,
Object &object,
const Span<PBVHNode *> nodes)
{
SubdivCCG &subdiv_ccg = *object.sculpt->subdiv_ccg;
MultiresModifierData &mmd = *BKE_sculpt_multires_active(&scene, &object);
BKE_sculpt_mask_layers_ensure(&depsgraph, &bmain, &object, &mmd);
const BitGroupVector<> &grid_hidden = subdiv_ccg.grid_hidden;
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
const Span<CCGElem *> grids = subdiv_ccg.grids;
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (PBVHNode *node : nodes.slice(range)) {
undo::push_node(&object, node, undo::Type::Mask);
const Span<int> grid_indices = BKE_pbvh_node_get_grid_indices(*node);
if (grid_hidden.is_empty()) {
for (const int grid : grid_indices) {
CCGElem *elem = grids[grid];
for (const int i : IndexRange(key.grid_area)) {
*CCG_elem_offset_mask(&key, elem, i) = 1.0f - *CCG_elem_offset_mask(&key, elem, i);
}
}
}
else {
for (const int grid : grid_indices) {
CCGElem *elem = grids[grid];
bits::foreach_0_index(grid_hidden[grid], [&](const int i) {
*CCG_elem_offset_mask(&key, elem, i) = 1.0f - *CCG_elem_offset_mask(&key, elem, i);
});
}
}
BKE_pbvh_node_mark_update_mask(node);
bke::pbvh::node_update_mask_grids(key, grids, *node);
}
});
multires_mark_as_modified(&depsgraph, &object, MULTIRES_COORDS_MODIFIED);
}
static void invert_mask_bmesh(Object &object, const Span<PBVHNode *> nodes)
{
BMesh &bm = *object.sculpt->bm;
const int offset = CustomData_get_offset_named(&bm.vdata, CD_PROP_FLOAT, ".sculpt_mask");
if (offset == -1) {
BLI_assert_unreachable();
return;
}
undo::push_node(&object, nodes.first(), undo::Type::Mask);
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (PBVHNode *node : nodes.slice(range)) {
for (BMVert *vert : BKE_pbvh_bmesh_node_unique_verts(node)) {
if (!BM_elem_flag_test(vert, BM_ELEM_HIDDEN)) {
BM_ELEM_CD_SET_FLOAT(vert, offset, 1.0f - BM_ELEM_CD_GET_FLOAT(vert, offset));
}
}
BKE_pbvh_node_mark_update_mask(node);
bke::pbvh::node_update_mask_bmesh(offset, *node);
}
});
}
static void invert_mask(Main &bmain, const Scene &scene, Depsgraph &depsgraph, Object &object)
{
Vector<PBVHNode *> nodes = bke::pbvh::search_gather(object.sculpt->pbvh, {});
switch (BKE_pbvh_type(object.sculpt->pbvh)) {
case PBVH_FACES:
invert_mask_mesh(object, nodes);
break;
case PBVH_GRIDS:
invert_mask_grids(bmain, scene, depsgraph, object, nodes);
break;
case PBVH_BMESH:
invert_mask_bmesh(object, nodes);
break;
}
}
static int mask_flood_fill_exec(bContext *C, wmOperator *op)
{
Main &bmain = *CTX_data_main(C);
const Scene &scene = *CTX_data_scene(C);
Object &object = *CTX_data_active_object(C);
Depsgraph &depsgraph = *CTX_data_ensure_evaluated_depsgraph(C);
const PaintMaskFloodMode mode = PaintMaskFloodMode(RNA_enum_get(op->ptr, "mode"));
const float value = RNA_float_get(op->ptr, "value");
BKE_sculpt_update_object_for_edit(&depsgraph, &object, false);
undo::push_begin(&object, op);
switch (mode) {
case PAINT_MASK_FLOOD_VALUE:
fill_mask(bmain, scene, depsgraph, object, value);
break;
case PAINT_MASK_FLOOD_VALUE_INVERSE:
fill_mask(bmain, scene, depsgraph, object, 1.0f - value);
break;
case PAINT_MASK_INVERT:
invert_mask(bmain, scene, depsgraph, object);
break;
}
undo::push_end(&object);
SCULPT_tag_update_overlays(C);
return OPERATOR_FINISHED;
}
void PAINT_OT_mask_flood_fill(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Mask Flood Fill";
ot->idname = "PAINT_OT_mask_flood_fill";
ot->description = "Fill the whole mask with a given value, or invert its values";
/* API callbacks. */
ot->exec = mask_flood_fill_exec;
ot->poll = SCULPT_mode_poll;
ot->flag = OPTYPE_REGISTER;
/* RNA. */
RNA_def_enum(ot->srna, "mode", mode_items, PAINT_MASK_FLOOD_VALUE, "Mode", nullptr);
RNA_def_float(
ot->srna,
"value",
0.0f,
0.0f,
1.0f,
"Value",
"Mask level to use when mode is 'Value'; zero means no masking and one is fully masked",
0.0f,
1.0f);
}
/* Mask Gesture Operation. */
struct SculptGestureMaskOperation {
gesture::Operation op;
PaintMaskFloodMode mode;
float value;
};
static void sculpt_gesture_mask_begin(bContext &C, gesture::GestureData &gesture_data)
{
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C);
BKE_sculpt_update_object_for_edit(depsgraph, gesture_data.vc.obact, false);
}
static void mask_gesture_apply_task(gesture::GestureData &gesture_data,
const SculptMaskWriteInfo mask_write,
PBVHNode *node)
{
SculptGestureMaskOperation *mask_operation = (SculptGestureMaskOperation *)
gesture_data.operation;
Object *ob = gesture_data.vc.obact;
const bool is_multires = BKE_pbvh_type(gesture_data.ss->pbvh) == PBVH_GRIDS;
PBVHVertexIter vd;
bool any_masked = false;
bool redraw = false;
BKE_pbvh_vertex_iter_begin (gesture_data.ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
float vertex_normal[3];
const float *co = SCULPT_vertex_co_get(gesture_data.ss, vd.vertex);
SCULPT_vertex_normal_get(gesture_data.ss, vd.vertex, vertex_normal);
if (gesture::is_affected(gesture_data, co, vertex_normal)) {
float prevmask = vd.mask;
if (!any_masked) {
any_masked = true;
undo::push_node(ob, node, undo::Type::Mask);
if (is_multires) {
BKE_pbvh_node_mark_positions_update(node);
}
}
const float new_mask = mask_flood_fill_get_new_value_for_elem(
prevmask, mask_operation->mode, mask_operation->value);
if (prevmask != new_mask) {
SCULPT_mask_vert_set(BKE_pbvh_type(ob->sculpt->pbvh), mask_write, new_mask, vd);
redraw = true;
}
}
}
BKE_pbvh_vertex_iter_end;
if (redraw) {
BKE_pbvh_node_mark_update_mask(node);
}
}
static void sculpt_gesture_mask_apply_for_symmetry_pass(bContext & /*C*/,
gesture::GestureData &gesture_data)
{
const SculptMaskWriteInfo mask_write = SCULPT_mask_get_for_write(gesture_data.ss);
threading::parallel_for(gesture_data.nodes.index_range(), 1, [&](const IndexRange range) {
for (const int i : range) {
mask_gesture_apply_task(gesture_data, mask_write, gesture_data.nodes[i]);
}
});
}
static void sculpt_gesture_mask_end(bContext &C, gesture::GestureData &gesture_data)
{
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C);
if (BKE_pbvh_type(gesture_data.ss->pbvh) == PBVH_GRIDS) {
multires_mark_as_modified(depsgraph, gesture_data.vc.obact, MULTIRES_COORDS_MODIFIED);
}
blender::bke::pbvh::update_mask(*gesture_data.ss->pbvh);
}
static void sculpt_gesture_init_mask_properties(bContext &C,
gesture::GestureData &gesture_data,
wmOperator &op)
{
gesture_data.operation = reinterpret_cast<gesture::Operation *>(
MEM_cnew<SculptGestureMaskOperation>(__func__));
SculptGestureMaskOperation *mask_operation = (SculptGestureMaskOperation *)
gesture_data.operation;
Object *object = gesture_data.vc.obact;
MultiresModifierData *mmd = BKE_sculpt_multires_active(gesture_data.vc.scene, object);
BKE_sculpt_mask_layers_ensure(
CTX_data_depsgraph_pointer(&C), CTX_data_main(&C), gesture_data.vc.obact, mmd);
mask_operation->op.begin = sculpt_gesture_mask_begin;
mask_operation->op.apply_for_symmetry_pass = sculpt_gesture_mask_apply_for_symmetry_pass;
mask_operation->op.end = sculpt_gesture_mask_end;
mask_operation->mode = PaintMaskFloodMode(RNA_enum_get(op.ptr, "mode"));
mask_operation->value = RNA_float_get(op.ptr, "value");
}
static void paint_mask_gesture_operator_properties(wmOperatorType *ot)
{
RNA_def_enum(ot->srna, "mode", mode_items, PAINT_MASK_FLOOD_VALUE, "Mode", nullptr);
RNA_def_float(
ot->srna,
"value",
1.0f,
0.0f,
1.0f,
"Value",
"Mask level to use when mode is 'Value'; zero means no masking and one is fully masked",
0.0f,
1.0f);
}
static int paint_mask_gesture_box_exec(bContext *C, wmOperator *op)
{
std::unique_ptr<gesture::GestureData> gesture_data = gesture::init_from_box(C, op);
if (!gesture_data) {
return OPERATOR_CANCELLED;
}
sculpt_gesture_init_mask_properties(*C, *gesture_data, *op);
gesture::apply(*C, *gesture_data, *op);
return OPERATOR_FINISHED;
}
static int paint_mask_gesture_lasso_exec(bContext *C, wmOperator *op)
{
std::unique_ptr<gesture::GestureData> gesture_data = gesture::init_from_lasso(C, op);
if (!gesture_data) {
return OPERATOR_CANCELLED;
}
sculpt_gesture_init_mask_properties(*C, *gesture_data, *op);
gesture::apply(*C, *gesture_data, *op);
return OPERATOR_FINISHED;
}
static int paint_mask_gesture_line_exec(bContext *C, wmOperator *op)
{
std::unique_ptr<gesture::GestureData> gesture_data = gesture::init_from_line(C, op);
if (!gesture_data) {
return OPERATOR_CANCELLED;
}
sculpt_gesture_init_mask_properties(*C, *gesture_data, *op);
gesture::apply(*C, *gesture_data, *op);
return OPERATOR_FINISHED;
}
void PAINT_OT_mask_lasso_gesture(wmOperatorType *ot)
{
ot->name = "Mask Lasso Gesture";
ot->idname = "PAINT_OT_mask_lasso_gesture";
ot->description = "Add mask within the lasso as you move the brush";
ot->invoke = WM_gesture_lasso_invoke;
ot->modal = WM_gesture_lasso_modal;
ot->exec = paint_mask_gesture_lasso_exec;
ot->poll = SCULPT_mode_poll_view3d;
ot->flag = OPTYPE_REGISTER | OPTYPE_DEPENDS_ON_CURSOR;
WM_operator_properties_gesture_lasso(ot);
gesture::operator_properties(ot, gesture::ShapeType::Lasso);
paint_mask_gesture_operator_properties(ot);
}
void PAINT_OT_mask_box_gesture(wmOperatorType *ot)
{
ot->name = "Mask Box Gesture";
ot->idname = "PAINT_OT_mask_box_gesture";
ot->description = "Add mask within the box as you move the brush";
ot->invoke = WM_gesture_box_invoke;
ot->modal = WM_gesture_box_modal;
ot->exec = paint_mask_gesture_box_exec;
ot->poll = SCULPT_mode_poll_view3d;
ot->flag = OPTYPE_REGISTER;
WM_operator_properties_border(ot);
gesture::operator_properties(ot, gesture::ShapeType::Box);
paint_mask_gesture_operator_properties(ot);
}
void PAINT_OT_mask_line_gesture(wmOperatorType *ot)
{
ot->name = "Mask Line Gesture";
ot->idname = "PAINT_OT_mask_line_gesture";
ot->description = "Add mask to the right of a line as you move the brush";
ot->invoke = WM_gesture_straightline_active_side_invoke;
ot->modal = WM_gesture_straightline_oneshot_modal;
ot->exec = paint_mask_gesture_line_exec;
ot->poll = SCULPT_mode_poll_view3d;
ot->flag = OPTYPE_REGISTER;
WM_operator_properties_gesture_straightline(ot, WM_CURSOR_EDIT);
gesture::operator_properties(ot, gesture::ShapeType::Line);
paint_mask_gesture_operator_properties(ot);
}
} // namespace blender::ed::sculpt_paint::mask