Sculpt: Add Line Trim tool

This PR adds the *Line Trim* tool to Sculpt mode. It is exposed via
the toolbar along with the other *Trim* tools and as an entry in the
*Sculpt* menu.

## Technical Approach
Line gestures are represented as two points in screen space: the `start`
and `end` of the gesture. Trim tools work by taking a set of screen
points as the top face of a volume, projecting a copy of the face into
the scene, then using that as the operand shape for boolean operations.
To behave as users would expect, the *Line Trim* tool does the following
steps to make the initial face:
1. Take the sculpted object's bounding box.
2. Project the bounding box into screen space
3. Take the magnitude of the diagonal line made from the bounding box
4. Use the magnitude multiplied by an arbitrary factor to ensure the
   initial line is long enough to fully divide the object.
5. Create two points by moving in a perpendicular direction from start
   and end points.
6. Use the resulting four points as vertices of the quad in screen space.

## Differences with Other Trim Tools
* Line Trim **only** supports the **Difference** mode. As such, the
corresponding tool options have been disabled in the header.

## Alternatives
* Instead of using a boolean operation, this could be achieved by using
a bisect operation when using the *Fixed* projection mode. While this
may result in a better performing tool, it is not guaranteed and
requires extra work to integrate this approach.

Pull Request: https://projects.blender.org/blender/blender/pulls/120845
This commit is contained in:
Sean Kim
2024-05-01 14:10:26 +02:00
committed by Hans Goudey
parent 5bc8dd7d3b
commit d4a61647bf
8 changed files with 151 additions and 1 deletions

View File

@@ -8131,6 +8131,16 @@ def km_3d_view_tool_sculpt_lasso_trim(params):
)
def km_3d_view_tool_sculpt_line_trim(params):
return (
"3D View Tool: Sculpt, Line Trim",
{"space_type": 'VIEW_3D', "region_type": 'WINDOW'},
{"items": [
("sculpt.trim_line_gesture", params.tool_maybe_tweak_event, None),
]},
)
def km_3d_view_tool_sculpt_line_mask(params):
return (
"3D View Tool: Sculpt, Line Mask",
@@ -9059,6 +9069,7 @@ def generate_keymaps(params=None):
km_3d_view_tool_sculpt_lasso_face_set(params),
km_3d_view_tool_sculpt_box_trim(params),
km_3d_view_tool_sculpt_lasso_trim(params),
km_3d_view_tool_sculpt_line_trim(params),
km_3d_view_tool_sculpt_line_mask(params),
km_3d_view_tool_sculpt_line_project(params),
km_3d_view_tool_sculpt_mesh_filter(params),

View File

@@ -1579,6 +1579,23 @@ class _defs_sculpt:
draw_settings=draw_settings,
)
@ToolDef.from_fn
def trim_line():
def draw_settings(_context, layout, tool):
props = tool.operator_properties("sculpt.trim_line_gesture")
layout.prop(props, "trim_solver", expand=False)
layout.prop(props, "trim_orientation", expand=False)
layout.prop(props, "use_cursor_depth", expand=False)
layout.prop(props, "use_limit_to_segment", expand=False)
return dict(
idname="builtin.line_trim",
label="Line Trim",
icon="ops.sculpt.line_trim",
widget=None,
keymap=(),
draw_settings=draw_settings,
)
@ToolDef.from_fn
def project_line():
def draw_settings(_context, layout, tool):
@@ -3410,6 +3427,7 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
(
_defs_sculpt.trim_box,
_defs_sculpt.trim_lasso,
_defs_sculpt.trim_line,
),
_defs_sculpt.project_line,
None,

View File

@@ -3775,6 +3775,9 @@ class VIEW3D_MT_sculpt(Menu):
props = layout.operator("sculpt.trim_lasso_gesture", text="Lasso Trim")
props.trim_mode = 'DIFFERENCE'
props = layout.operator("sculpt.trim_line_gesture", text="Line Trim")
props.trim_mode = 'DIFFERENCE'
props = layout.operator("sculpt.trim_box_gesture", text="Box Add")
props.trim_mode = 'JOIN'

View File

@@ -235,6 +235,12 @@ std::unique_ptr<GestureData> init_from_line(bContext *C, wmOperator *op)
line_points[1][0] = RNA_int_get(op->ptr, "xend");
line_points[1][1] = RNA_int_get(op->ptr, "yend");
gesture_data->gesture_points.reinitialize(2);
gesture_data->gesture_points[0][0] = line_points[0][0];
gesture_data->gesture_points[0][1] = line_points[0][1];
gesture_data->gesture_points[1][0] = line_points[1][0];
gesture_data->gesture_points[1][1] = line_points[1][1];
gesture_data->line.flip = RNA_boolean_get(op->ptr, "flip");
float plane_points[4][3];

View File

@@ -1787,6 +1787,7 @@ void SCULPT_OT_project_line_gesture(wmOperatorType *ot);
namespace blender::ed::sculpt_paint::trim {
void SCULPT_OT_trim_lasso_gesture(wmOperatorType *ot);
void SCULPT_OT_trim_box_gesture(wmOperatorType *ot);
void SCULPT_OT_trim_line_gesture(wmOperatorType *ot);
}
/** \} */

View File

@@ -1321,6 +1321,7 @@ void ED_operatortypes_sculpt()
WM_operatortype_append(face_set::SCULPT_OT_face_set_box_gesture);
WM_operatortype_append(trim::SCULPT_OT_trim_box_gesture);
WM_operatortype_append(trim::SCULPT_OT_trim_lasso_gesture);
WM_operatortype_append(trim::SCULPT_OT_trim_line_gesture);
WM_operatortype_append(project::SCULPT_OT_project_line_gesture);
WM_operatortype_append(SCULPT_OT_sample_color);

View File

@@ -10,6 +10,7 @@
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector.hh"
#include "BLI_polyfill_2d.h"
#include "BKE_brush.hh"
@@ -17,6 +18,7 @@
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_mesh.hh"
#include "BKE_object.hh"
#include "DNA_modifier_types.h"
@@ -274,13 +276,63 @@ static void calculate_depth(gesture::GestureData &gesture_data,
r_depth_back = depth_back;
}
/* Calculates a scalar factor to use to ensure a drawn line gesture
* encompasses the entire object to be acted on. */
static float calc_expand_factor(const gesture::GestureData &gesture_data)
{
Object *object = gesture_data.vc.obact;
rcti rect;
const Bounds<float3> bounds = *BKE_object_boundbox_get(object);
paint_convert_bb_to_rect(
&rect, bounds.min, bounds.max, gesture_data.vc.region, gesture_data.vc.rv3d, object);
const float2 min_corner(rect.xmin, rect.ymin);
const float2 max_corner(rect.xmax, rect.ymax);
/* Mutiply the screen space bounds by an arbitrary factor to ensure the created points are
* sufficiently far and enclose the mesh to be operated on. */
return math::distance(min_corner, max_corner) * 2.0f;
}
/* Converts a line gesture's points into usable screen points. */
static Array<float2> gesture_to_screen_points(gesture::GestureData &gesture_data)
{
if (gesture_data.shape_type != gesture::ShapeType::Line) {
return gesture_data.gesture_points;
}
const float expand_factor = calc_expand_factor(gesture_data);
float2 start(gesture_data.gesture_points[0]);
float2 end(gesture_data.gesture_points[1]);
const float2 dir = math::normalize(end - start);
if (!gesture_data.line.use_side_planes) {
end = end + dir * expand_factor;
start = start - dir * expand_factor;
}
float2 perp(dir.y, -dir.x);
if (gesture_data.line.flip) {
perp *= -1;
}
const float2 parallel_start = start + perp * expand_factor;
const float2 parallel_end = end + perp * expand_factor;
return {start, end, parallel_end, parallel_start};
}
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;
const Array<float2> screen_points = gesture_to_screen_points(gesture_data);
BLI_assert(screen_points.size() > 1);
const int trim_totverts = screen_points.size() * 2;
@@ -614,6 +666,11 @@ static void init_operation(gesture::GestureData &gesture_data, wmOperator &op)
if (!trim_operation->initial_hit) {
trim_operation->orientation = OrientationType::View;
}
if (gesture_data.shape_type == gesture::ShapeType::Line) {
/* Line gestures only support Difference, no extrusion. */
trim_operation->mode = OperationType::Difference;
}
}
static void operator_properties(wmOperatorType *ot)
@@ -773,6 +830,37 @@ static int gesture_lasso_invoke(bContext *C, wmOperator *op, const wmEvent *even
return WM_gesture_lasso_invoke(C, op, event);
}
static int gesture_line_exec(bContext *C, wmOperator *op)
{
if (!can_exec(*C)) {
return OPERATOR_CANCELLED;
}
std::unique_ptr<gesture::GestureData> gesture_data = gesture::init_from_line(C, op);
if (!gesture_data) {
return OPERATOR_CANCELLED;
}
gesture_data->operation = reinterpret_cast<gesture::Operation *>(
MEM_cnew<TrimOperation>(__func__));
initialize_cursor_info(*C, *op, *gesture_data);
init_operation(*gesture_data, *op);
gesture::apply(*C, *gesture_data, *op);
return OPERATOR_FINISHED;
}
static int gesture_line_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
if (!can_invoke(*C)) {
return OPERATOR_CANCELLED;
}
RNA_int_set_array(op->ptr, "location", event->mval);
return WM_gesture_straightline_active_side_invoke(C, op, event);
}
void SCULPT_OT_trim_lasso_gesture(wmOperatorType *ot)
{
ot->name = "Trim Lasso Gesture";
@@ -814,4 +902,25 @@ void SCULPT_OT_trim_box_gesture(wmOperatorType *ot)
operator_properties(ot);
}
void SCULPT_OT_trim_line_gesture(wmOperatorType *ot)
{
ot->name = "Trim Line Gesture";
ot->idname = "SCULPT_OT_trim_line_gesture";
ot->description = "Trims the mesh divided by the line as you move the brush";
ot->invoke = gesture_line_invoke;
ot->modal = WM_gesture_straightline_oneshot_modal;
ot->exec = gesture_line_exec;
ot->poll = SCULPT_mode_poll_view3d;
ot->flag = OPTYPE_REGISTER;
/* Properties. */
WM_operator_properties_gesture_straightline(ot, WM_CURSOR_EDIT);
gesture::operator_properties(ot, gesture::ShapeType::Line);
operator_properties(ot);
}
} // namespace blender::ed::sculpt_paint::trim

View File

@@ -4135,6 +4135,7 @@ static void gesture_straightline_modal_keymap(wmKeyConfig *keyconf)
WM_modalkeymap_assign(keymap, "PAINT_OT_weight_gradient");
WM_modalkeymap_assign(keymap, "MESH_OT_bisect");
WM_modalkeymap_assign(keymap, "PAINT_OT_mask_line_gesture");
WM_modalkeymap_assign(keymap, "SCULPT_OT_trim_line_gesture");
WM_modalkeymap_assign(keymap, "SCULPT_OT_project_line_gesture");
WM_modalkeymap_assign(keymap, "PAINT_OT_hide_show_line_gesture");
}