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

785 lines
25 KiB
C++

/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edsculpt
*/
#include "DNA_mesh_types.h"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "BLI_polyfill_2d.h"
#include "BKE_brush.hh"
#include "BKE_context.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_mesh.hh"
#include "DNA_modifier_types.h"
#include "DEG_depsgraph.hh"
#include "ED_sculpt.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "bmesh.hh"
#include "tools/bmesh_boolean.hh"
#include "tools/bmesh_intersect.hh"
#include "paint_intern.hh"
#include "sculpt_intern.hh"
namespace blender::ed::sculpt_paint::trim {
enum class OperationType {
Intersect = 0,
Difference = 1,
Union = 2,
Join = 3,
};
/* Intersect is not exposed in the UI because it does not work correctly with symmetry (it deletes
* the symmetrical part of the mesh in the first symmetry pass). */
static EnumPropertyItem operation_types[] = {
{int(OperationType::Difference),
"DIFFERENCE",
0,
"Difference",
"Use a difference boolean operation"},
{int(OperationType::Union), "UNION", 0, "Union", "Use a union boolean operation"},
{int(OperationType::Join),
"JOIN",
0,
"Join",
"Join the new mesh as separate geometry, without performing any boolean operation"},
{0, nullptr, 0, nullptr, nullptr},
};
enum class OrientationType {
View = 0,
Surface = 1,
};
static EnumPropertyItem orientation_types[] = {
{int(OrientationType::View),
"VIEW",
0,
"View",
"Use the view to orientate the trimming shape"},
{int(OrientationType::Surface),
"SURFACE",
0,
"Surface",
"Use the surface normal to orientate the trimming shape"},
{0, nullptr, 0, nullptr, nullptr},
};
enum class ExtrudeMode {
Project = 0,
Fixed = 1,
};
static EnumPropertyItem extrude_modes[] = {
{int(ExtrudeMode::Project), "PROJECT", 0, "Project", "Project back faces when extruding"},
{int(ExtrudeMode::Fixed), "FIXED", 0, "Fixed", "Extrude back faces by fixed amount"},
{0, nullptr, 0, nullptr, nullptr},
};
enum class SolverMode {
Exact = 0,
Fast = 1,
};
static EnumPropertyItem solver_modes[] = {
{int(SolverMode::Exact), "EXACT", 0, "Exact", "Use the exact boolean solver"},
{int(SolverMode::Fast), "FAST", 0, "Fast", "Use the fast float boolean solver"},
{0, nullptr, 0, nullptr, nullptr},
};
struct TrimOperation {
gesture::Operation op;
Mesh *mesh;
float (*true_mesh_co)[3];
float depth_front;
float depth_back;
bool use_cursor_depth;
OperationType mode;
SolverMode solver_mode;
OrientationType orientation;
ExtrudeMode extrude_mode;
};
static void update_normals(gesture::GestureData &gesture_data)
{
TrimOperation *trim_operation = (TrimOperation *)gesture_data.operation;
Mesh *trim_mesh = trim_operation->mesh;
const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(trim_mesh);
BMeshCreateParams bm_create_params{};
bm_create_params.use_toolflags = true;
BMesh *bm = BM_mesh_create(&allocsize, &bm_create_params);
BMeshFromMeshParams bm_from_me_params{};
bm_from_me_params.calc_face_normal = true;
bm_from_me_params.calc_vert_normal = true;
BM_mesh_bm_from_me(bm, trim_mesh, &bm_from_me_params);
BM_mesh_elem_hflag_enable_all(bm, BM_FACE, BM_ELEM_TAG, false);
BMO_op_callf(bm,
(BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
"recalc_face_normals faces=%hf",
BM_ELEM_TAG);
BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
BMeshToMeshParams convert_params{};
convert_params.calc_object_remap = false;
Mesh *result = BKE_mesh_from_bmesh_nomain(bm, &convert_params, trim_mesh);
BM_mesh_free(bm);
BKE_id_free(nullptr, trim_mesh);
trim_operation->mesh = result;
}
/* Get the origin and normal that are going to be used for calculating the depth and position the
* trimming geometry. */
static void get_origin_and_normal(gesture::GestureData &gesture_data,
float *r_origin,
float *r_normal)
{
TrimOperation *trim_operation = (TrimOperation *)gesture_data.operation;
/* Use the view origin and normal in world space. The trimming mesh coordinates are
* calculated in world space, aligned to the view, and then converted to object space to
* store them in the final trimming mesh which is going to be used in the boolean operation.
*/
switch (trim_operation->orientation) {
case OrientationType::View:
mul_v3_m4v3(r_origin,
gesture_data.vc.obact->object_to_world().ptr(),
gesture_data.ss->gesture_initial_location);
copy_v3_v3(r_normal, gesture_data.world_space_view_normal);
negate_v3(r_normal);
break;
case OrientationType::Surface:
mul_v3_m4v3(r_origin,
gesture_data.vc.obact->object_to_world().ptr(),
gesture_data.ss->gesture_initial_location);
/* Transforming the normal does not take non uniform scaling into account. Sculpt mode is not
* expected to work on object with non uniform scaling. */
copy_v3_v3(r_normal, gesture_data.ss->gesture_initial_normal);
mul_mat3_m4_v3(gesture_data.vc.obact->object_to_world().ptr(), r_normal);
break;
}
}
static void calculate_depth(gesture::GestureData &gesture_data)
{
TrimOperation *trim_operation = (TrimOperation *)gesture_data.operation;
SculptSession *ss = gesture_data.ss;
ViewContext *vc = &gesture_data.vc;
const int totvert = SCULPT_vertex_count_get(ss);
float shape_plane[4];
float shape_origin[3];
float shape_normal[3];
get_origin_and_normal(gesture_data, shape_origin, shape_normal);
plane_from_point_normal_v3(shape_plane, shape_origin, shape_normal);
trim_operation->depth_front = FLT_MAX;
trim_operation->depth_back = -FLT_MAX;
for (int i = 0; i < totvert; i++) {
PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i);
const float *vco = SCULPT_vertex_co_get(ss, vertex);
/* Convert the coordinates to world space to calculate the depth. When generating the trimming
* mesh, coordinates are first calculated in world space, then converted to object space to
* store them. */
float world_space_vco[3];
mul_v3_m4v3(world_space_vco, vc->obact->object_to_world().ptr(), vco);
const float dist = dist_signed_to_plane_v3(world_space_vco, shape_plane);
trim_operation->depth_front = min_ff(dist, trim_operation->depth_front);
trim_operation->depth_back = max_ff(dist, trim_operation->depth_back);
}
if (trim_operation->use_cursor_depth) {
float world_space_gesture_initial_location[3];
mul_v3_m4v3(world_space_gesture_initial_location,
vc->obact->object_to_world().ptr(),
ss->gesture_initial_location);
float mid_point_depth;
if (trim_operation->orientation == OrientationType::View) {
mid_point_depth = ss->gesture_initial_hit ?
dist_signed_to_plane_v3(world_space_gesture_initial_location,
shape_plane) :
(trim_operation->depth_back + trim_operation->depth_front) * 0.5f;
}
else {
/* When using normal orientation, if the stroke started over the mesh, position the mid point
* at 0 distance from the shape plane. This positions the trimming shape half inside of the
* surface. */
mid_point_depth = ss->gesture_initial_hit ?
0.0f :
(trim_operation->depth_back + trim_operation->depth_front) * 0.5f;
}
float depth_radius;
if (ss->gesture_initial_hit) {
depth_radius = ss->cursor_radius;
}
else {
/* ss->cursor_radius is only valid if the stroke started
* over the sculpt mesh. If it's not we must
* compute the radius ourselves. See #81452.
*/
Sculpt *sd = CTX_data_tool_settings(vc->C)->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
Scene *scene = CTX_data_scene(vc->C);
if (!BKE_brush_use_locked_size(scene, brush)) {
depth_radius = paint_calc_object_space_radius(
vc, ss->gesture_initial_location, BKE_brush_size_get(scene, brush));
}
else {
depth_radius = BKE_brush_unprojected_radius_get(scene, brush);
}
}
trim_operation->depth_front = mid_point_depth - depth_radius;
trim_operation->depth_back = mid_point_depth + depth_radius;
}
}
static void generate_geometry(gesture::GestureData &gesture_data)
{
TrimOperation *trim_operation = (TrimOperation *)gesture_data.operation;
ViewContext *vc = &gesture_data.vc;
ARegion *region = vc->region;
const Span<float2> screen_points = gesture_data.gesture_points;
BLI_assert(screen_points.size() > 1);
const int trim_totverts = screen_points.size() * 2;
const int trim_faces_nums = (2 * (screen_points.size() - 2)) + (2 * screen_points.size());
trim_operation->mesh = BKE_mesh_new_nomain(
trim_totverts, 0, trim_faces_nums, trim_faces_nums * 3);
trim_operation->true_mesh_co = static_cast<float(*)[3]>(
MEM_malloc_arrayN(trim_totverts, sizeof(float[3]), "mesh orco"));
float depth_front = trim_operation->depth_front;
float depth_back = trim_operation->depth_back;
float pad_factor = 0.0f;
if (!trim_operation->use_cursor_depth) {
pad_factor = (depth_back - depth_front) * 0.01f + 0.001f;
/* When using cursor depth, don't modify the depth set by the cursor radius. If full depth is
* used, adding a little padding to the trimming shape can help avoiding booleans with coplanar
* faces. */
depth_front -= pad_factor;
depth_back += pad_factor;
}
float shape_origin[3];
float shape_normal[3];
float shape_plane[4];
get_origin_and_normal(gesture_data, shape_origin, shape_normal);
plane_from_point_normal_v3(shape_plane, shape_origin, shape_normal);
const float(*ob_imat)[4] = vc->obact->world_to_object().ptr();
/* Write vertices coordinates OperationType::Difference for the front face. */
MutableSpan<float3> positions = trim_operation->mesh->vert_positions_for_write();
float depth_point[3];
/* Get origin point for OrientationType::View.
* Note: for projection extrusion we add depth_front here
* instead of in the loop.
*/
if (trim_operation->extrude_mode == ExtrudeMode::Fixed) {
copy_v3_v3(depth_point, shape_origin);
}
else {
madd_v3_v3v3fl(depth_point, shape_origin, shape_normal, depth_front);
}
for (const int i : screen_points.index_range()) {
float new_point[3];
if (trim_operation->orientation == OrientationType::View) {
ED_view3d_win_to_3d(vc->v3d, region, depth_point, screen_points[i], new_point);
/* For fixed mode we add the shape normal here to avoid projection errors. */
if (trim_operation->extrude_mode == ExtrudeMode::Fixed) {
madd_v3_v3fl(new_point, shape_normal, depth_front);
}
}
else {
ED_view3d_win_to_3d_on_plane(region, shape_plane, screen_points[i], false, new_point);
madd_v3_v3fl(new_point, shape_normal, depth_front);
}
copy_v3_v3(positions[i], new_point);
}
/* Write vertices coordinates for the back face. */
madd_v3_v3v3fl(depth_point, shape_origin, shape_normal, depth_back);
for (const int i : screen_points.index_range()) {
float new_point[3];
if (trim_operation->extrude_mode == ExtrudeMode::Project) {
if (trim_operation->orientation == OrientationType::View) {
ED_view3d_win_to_3d(vc->v3d, region, depth_point, screen_points[i], new_point);
}
else {
ED_view3d_win_to_3d_on_plane(region, shape_plane, screen_points[i], false, new_point);
madd_v3_v3fl(new_point, shape_normal, depth_back);
}
}
else {
copy_v3_v3(new_point, positions[i]);
float dist = dist_signed_to_plane_v3(new_point, shape_plane);
madd_v3_v3fl(new_point, shape_normal, depth_back - dist);
}
copy_v3_v3(positions[i + screen_points.size()], new_point);
}
/* Project to object space. */
for (int i = 0; i < screen_points.size() * 2; i++) {
float new_point[3];
copy_v3_v3(new_point, positions[i]);
mul_v3_m4v3(positions[i], ob_imat, new_point);
mul_v3_m4v3(trim_operation->true_mesh_co[i], ob_imat, new_point);
}
/* Get the triangulation for the front/back poly. */
const int face_tris_num = bke::mesh::face_triangles_num(screen_points.size());
Array<uint3> tris(face_tris_num);
BLI_polyfill_calc(reinterpret_cast<const float(*)[2]>(screen_points.data()),
screen_points.size(),
0,
reinterpret_cast<uint(*)[3]>(tris.data()));
/* Write the front face triangle indices. */
MutableSpan<int> face_offsets = trim_operation->mesh->face_offsets_for_write();
MutableSpan<int> corner_verts = trim_operation->mesh->corner_verts_for_write();
int face_index = 0;
int loop_index = 0;
for (const int i : tris.index_range()) {
face_offsets[face_index] = loop_index;
corner_verts[loop_index + 0] = tris[i][0];
corner_verts[loop_index + 1] = tris[i][1];
corner_verts[loop_index + 2] = tris[i][2];
face_index++;
loop_index += 3;
}
/* Write the back face triangle indices. */
for (const int i : tris.index_range()) {
face_offsets[face_index] = loop_index;
corner_verts[loop_index + 0] = tris[i][0] + screen_points.size();
corner_verts[loop_index + 1] = tris[i][1] + screen_points.size();
corner_verts[loop_index + 2] = tris[i][2] + screen_points.size();
face_index++;
loop_index += 3;
}
/* Write the indices for the lateral triangles. */
for (const int i : screen_points.index_range()) {
face_offsets[face_index] = loop_index;
int current_index = i;
int next_index = current_index + 1;
if (next_index >= screen_points.size()) {
next_index = 0;
}
corner_verts[loop_index + 0] = next_index + screen_points.size();
corner_verts[loop_index + 1] = next_index;
corner_verts[loop_index + 2] = current_index;
face_index++;
loop_index += 3;
}
for (const int i : screen_points.index_range()) {
face_offsets[face_index] = loop_index;
int current_index = i;
int next_index = current_index + 1;
if (next_index >= screen_points.size()) {
next_index = 0;
}
corner_verts[loop_index + 0] = current_index;
corner_verts[loop_index + 1] = current_index + screen_points.size();
corner_verts[loop_index + 2] = next_index + screen_points.size();
face_index++;
loop_index += 3;
}
bke::mesh_smooth_set(*trim_operation->mesh, false);
bke::mesh_calc_edges(*trim_operation->mesh, false, false);
update_normals(gesture_data);
}
static void free_geometry(gesture::GestureData &gesture_data)
{
TrimOperation *trim_operation = (TrimOperation *)gesture_data.operation;
BKE_id_free(nullptr, trim_operation->mesh);
MEM_freeN(trim_operation->true_mesh_co);
}
static int bm_face_isect_pair(BMFace *f, void * /*user_data*/)
{
return BM_elem_flag_test(f, BM_ELEM_DRAW) ? 1 : 0;
}
static void apply_trim(gesture::GestureData &gesture_data)
{
TrimOperation *trim_operation = (TrimOperation *)gesture_data.operation;
Mesh *sculpt_mesh = BKE_mesh_from_object(gesture_data.vc.obact);
Mesh *trim_mesh = trim_operation->mesh;
const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(sculpt_mesh, trim_mesh);
BMeshCreateParams bm_create_params{};
bm_create_params.use_toolflags = false;
BMesh *bm = BM_mesh_create(&allocsize, &bm_create_params);
BMeshFromMeshParams bm_from_me_params{};
bm_from_me_params.calc_face_normal = true;
bm_from_me_params.calc_vert_normal = true;
BM_mesh_bm_from_me(bm, trim_mesh, &bm_from_me_params);
BM_mesh_bm_from_me(bm, sculpt_mesh, &bm_from_me_params);
const int corner_tris_tot = poly_to_tri_count(bm->totface, bm->totloop);
Array<std::array<BMLoop *, 3>> corner_tris(corner_tris_tot);
BM_mesh_calc_tessellation_beauty(bm, corner_tris);
BMIter iter;
int i;
const int i_faces_end = trim_mesh->faces_num;
/* We need face normals because of 'BM_face_split_edgenet'
* we could calculate on the fly too (before calling split). */
const short ob_src_totcol = trim_mesh->totcol;
Array<short> material_remap(ob_src_totcol ? ob_src_totcol : 1);
BMFace *efa;
i = 0;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
normalize_v3(efa->no);
/* Temp tag to test which side split faces are from. */
BM_elem_flag_enable(efa, BM_ELEM_DRAW);
/* Remap material. */
if (efa->mat_nr < ob_src_totcol) {
efa->mat_nr = material_remap[efa->mat_nr];
}
if (++i == i_faces_end) {
break;
}
}
/* Join does not do a boolean operation, it just adds the geometry. */
if (trim_operation->mode != OperationType::Join) {
int boolean_mode = 0;
switch (trim_operation->mode) {
case OperationType::Intersect:
boolean_mode = eBooleanModifierOp_Intersect;
break;
case OperationType::Difference:
boolean_mode = eBooleanModifierOp_Difference;
break;
case OperationType::Union:
boolean_mode = eBooleanModifierOp_Union;
break;
case OperationType::Join:
BLI_assert(false);
break;
}
if (trim_operation->solver_mode == SolverMode::Exact) {
BM_mesh_boolean(
bm, corner_tris, bm_face_isect_pair, nullptr, 2, true, true, false, boolean_mode);
}
else {
BM_mesh_intersect(bm,
corner_tris,
bm_face_isect_pair,
nullptr,
false,
false,
true,
true,
false,
false,
boolean_mode,
1e-6f);
}
}
BMeshToMeshParams convert_params{};
convert_params.calc_object_remap = false;
Mesh *result = BKE_mesh_from_bmesh_nomain(bm, &convert_params, sculpt_mesh);
BM_mesh_free(bm);
BKE_mesh_nomain_to_mesh(
result, static_cast<Mesh *>(gesture_data.vc.obact->data), gesture_data.vc.obact);
}
static void gesture_begin(bContext &C, gesture::GestureData &gesture_data)
{
Object *object = gesture_data.vc.obact;
SculptSession *ss = object->sculpt;
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(&C);
calculate_depth(gesture_data);
generate_geometry(gesture_data);
SCULPT_topology_islands_invalidate(ss);
BKE_sculpt_update_object_for_edit(depsgraph, gesture_data.vc.obact, false);
undo::push_node(gesture_data.vc.obact, nullptr, undo::Type::Geometry);
}
static void gesture_apply_for_symmetry_pass(bContext & /*C*/, gesture::GestureData &gesture_data)
{
TrimOperation *trim_operation = (TrimOperation *)gesture_data.operation;
Mesh *trim_mesh = trim_operation->mesh;
MutableSpan<float3> positions = trim_mesh->vert_positions_for_write();
for (int i = 0; i < trim_mesh->verts_num; i++) {
flip_v3_v3(positions[i], trim_operation->true_mesh_co[i], gesture_data.symmpass);
}
update_normals(gesture_data);
apply_trim(gesture_data);
}
static void gesture_end(bContext & /*C*/, gesture::GestureData &gesture_data)
{
Object *object = gesture_data.vc.obact;
Mesh *mesh = (Mesh *)object->data;
const bke::AttributeAccessor attributes = mesh->attributes_for_write();
if (attributes.contains(".sculpt_face_set")) {
/* Assign a new Face Set ID to the new faces created by the trim operation. */
const int next_face_set_id = face_set::find_next_available_id(*object);
face_set::initialize_none_to_id(mesh, next_face_set_id);
}
free_geometry(gesture_data);
undo::push_node(gesture_data.vc.obact, nullptr, undo::Type::Geometry);
BKE_mesh_batch_cache_dirty_tag(mesh, BKE_MESH_BATCH_DIRTY_ALL);
DEG_id_tag_update(&gesture_data.vc.obact->id, ID_RECALC_GEOMETRY);
}
static void init_operation(gesture::GestureData &gesture_data, wmOperator &op)
{
gesture_data.operation = reinterpret_cast<gesture::Operation *>(
MEM_cnew<TrimOperation>(__func__));
TrimOperation *trim_operation = (TrimOperation *)gesture_data.operation;
trim_operation->op.begin = gesture_begin;
trim_operation->op.apply_for_symmetry_pass = gesture_apply_for_symmetry_pass;
trim_operation->op.end = gesture_end;
trim_operation->mode = OperationType(RNA_enum_get(op.ptr, "trim_mode"));
trim_operation->use_cursor_depth = RNA_boolean_get(op.ptr, "use_cursor_depth");
trim_operation->orientation = OrientationType(RNA_enum_get(op.ptr, "trim_orientation"));
trim_operation->extrude_mode = ExtrudeMode(RNA_enum_get(op.ptr, "trim_extrude_mode"));
trim_operation->solver_mode = SolverMode(RNA_enum_get(op.ptr, "trim_solver"));
/* If the cursor was not over the mesh, force the orientation to view. */
if (!gesture_data.ss->gesture_initial_hit) {
trim_operation->orientation = OrientationType::View;
}
}
static void operator_properties(wmOperatorType *ot)
{
RNA_def_enum(ot->srna,
"trim_mode",
operation_types,
int(OperationType::Difference),
"Trim Mode",
nullptr);
RNA_def_boolean(
ot->srna,
"use_cursor_depth",
false,
"Use Cursor for Depth",
"Use cursor location and radius for the dimensions and position of the trimming shape");
RNA_def_enum(ot->srna,
"trim_orientation",
orientation_types,
int(OrientationType::View),
"Shape Orientation",
nullptr);
RNA_def_enum(ot->srna,
"trim_extrude_mode",
extrude_modes,
int(ExtrudeMode::Fixed),
"Extrude Mode",
nullptr);
RNA_def_enum(ot->srna, "trim_solver", solver_modes, int(SolverMode::Fast), "Solver", nullptr);
}
static bool can_invoke(const bContext &C)
{
const View3D &v3d = *CTX_wm_view3d(&C);
const Base &base = *CTX_data_active_base(&C);
if (!BKE_base_is_visible(&v3d, &base)) {
return false;
}
return true;
}
static bool can_exec(const bContext &C)
{
const Object &object = *CTX_data_active_object(&C);
const SculptSession &ss = *object.sculpt;
if (BKE_pbvh_type(ss.pbvh) != PBVH_FACES) {
/* Not supported in Multires and Dyntopo. */
return false;
}
if (ss.totvert == 0) {
/* No geometry to trim or to detect a valid position for the trimming shape. */
return false;
}
return true;
}
static void initialize_cursor_info(bContext &C, const wmEvent *event)
{
const Object &ob = *CTX_data_active_object(&C);
SculptSession &ss = *ob.sculpt;
SCULPT_vertex_random_access_ensure(&ss);
SculptCursorGeometryInfo sgi;
const float mval_fl[2] = {float(event->mval[0]), float(event->mval[1])};
/* TODO: Remove gesture_* properties from SculptSession */
ss.gesture_initial_hit = SCULPT_cursor_geometry_info_update(&C, &sgi, mval_fl, false);
if (ss.gesture_initial_hit) {
copy_v3_v3(ss.gesture_initial_location, sgi.location);
copy_v3_v3(ss.gesture_initial_normal, sgi.normal);
}
}
static int gesture_box_exec(bContext *C, wmOperator *op)
{
if (!can_exec(*C)) {
return OPERATOR_CANCELLED;
}
std::unique_ptr<gesture::GestureData> gesture_data = gesture::init_from_box(C, op);
if (!gesture_data) {
return OPERATOR_CANCELLED;
}
init_operation(*gesture_data, *op);
gesture::apply(*C, *gesture_data, *op);
return OPERATOR_FINISHED;
}
static int gesture_box_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
if (!can_invoke(*C)) {
return OPERATOR_CANCELLED;
}
initialize_cursor_info(*C, event);
return WM_gesture_box_invoke(C, op, event);
}
static int gesture_lasso_exec(bContext *C, wmOperator *op)
{
if (!can_exec(*C)) {
return OPERATOR_CANCELLED;
}
std::unique_ptr<gesture::GestureData> gesture_data = gesture::init_from_lasso(C, op);
if (!gesture_data) {
return OPERATOR_CANCELLED;
}
init_operation(*gesture_data, *op);
gesture::apply(*C, *gesture_data, *op);
return OPERATOR_FINISHED;
}
static int gesture_lasso_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
if (!can_invoke(*C)) {
return OPERATOR_CANCELLED;
}
initialize_cursor_info(*C, event);
return WM_gesture_lasso_invoke(C, op, event);
}
void SCULPT_OT_trim_lasso_gesture(wmOperatorType *ot)
{
ot->name = "Trim Lasso Gesture";
ot->idname = "SCULPT_OT_trim_lasso_gesture";
ot->description = "Trims the mesh within the lasso as you move the brush";
ot->invoke = gesture_lasso_invoke;
ot->modal = WM_gesture_lasso_modal;
ot->exec = gesture_lasso_exec;
ot->poll = SCULPT_mode_poll_view3d;
ot->flag = OPTYPE_REGISTER | OPTYPE_DEPENDS_ON_CURSOR;
/* Properties. */
WM_operator_properties_gesture_lasso(ot);
gesture::operator_properties(ot, gesture::ShapeType::Lasso);
operator_properties(ot);
}
void SCULPT_OT_trim_box_gesture(wmOperatorType *ot)
{
ot->name = "Trim Box Gesture";
ot->idname = "SCULPT_OT_trim_box_gesture";
ot->description = "Trims the mesh within the box as you move the brush";
ot->invoke = gesture_box_invoke;
ot->modal = WM_gesture_box_modal;
ot->exec = gesture_box_exec;
ot->poll = SCULPT_mode_poll_view3d;
ot->flag = OPTYPE_REGISTER;
/* Properties. */
WM_operator_properties_border(ot);
gesture::operator_properties(ot, gesture::ShapeType::Box);
operator_properties(ot);
}
} // namespace blender::ed::sculpt_paint::trim