|
|
|
|
@@ -7,15 +7,23 @@
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "BLI_array_utils.hh"
|
|
|
|
|
#include "BLI_assert.h"
|
|
|
|
|
#include "BLI_index_mask.hh"
|
|
|
|
|
#include "BLI_index_range.hh"
|
|
|
|
|
#include "BLI_math_base.hh"
|
|
|
|
|
#include "BLI_math_geom.h"
|
|
|
|
|
#include "BLI_math_matrix.hh"
|
|
|
|
|
#include "BLI_math_vector.hh"
|
|
|
|
|
#include "BLI_math_vector_types.hh"
|
|
|
|
|
#include "BLI_offset_indices.hh"
|
|
|
|
|
#include "BLI_span.hh"
|
|
|
|
|
#include "BLI_utildefines.h"
|
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
|
|
|
|
|
|
#include "DNA_material_types.h"
|
|
|
|
|
#include "DNA_object_types.h"
|
|
|
|
|
#include "DNA_scene_types.h"
|
|
|
|
|
#include "DNA_space_types.h"
|
|
|
|
|
|
|
|
|
|
#include "BKE_attribute.hh"
|
|
|
|
|
#include "BKE_context.hh"
|
|
|
|
|
@@ -27,6 +35,8 @@
|
|
|
|
|
#include "BKE_material.h"
|
|
|
|
|
#include "BKE_report.hh"
|
|
|
|
|
|
|
|
|
|
#include "DNA_view3d_types.h"
|
|
|
|
|
#include "DNA_windowmanager_types.h"
|
|
|
|
|
#include "RNA_access.hh"
|
|
|
|
|
#include "RNA_define.hh"
|
|
|
|
|
#include "RNA_enum_types.hh"
|
|
|
|
|
@@ -36,6 +46,7 @@
|
|
|
|
|
#include "ED_curves.hh"
|
|
|
|
|
#include "ED_grease_pencil.hh"
|
|
|
|
|
#include "ED_object.hh"
|
|
|
|
|
#include "ED_view3d.hh"
|
|
|
|
|
|
|
|
|
|
#include "GEO_join_geometries.hh"
|
|
|
|
|
#include "GEO_reorder.hh"
|
|
|
|
|
@@ -43,6 +54,7 @@
|
|
|
|
|
#include "GEO_subdivide_curves.hh"
|
|
|
|
|
|
|
|
|
|
#include "UI_resources.hh"
|
|
|
|
|
#include <limits>
|
|
|
|
|
|
|
|
|
|
namespace blender::ed::greasepencil {
|
|
|
|
|
|
|
|
|
|
@@ -2633,6 +2645,273 @@ static void GREASE_PENCIL_OT_extrude(wmOperatorType *ot)
|
|
|
|
|
|
|
|
|
|
/** \} */
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Snapping Selection to Grid Operator
|
|
|
|
|
* \{ */
|
|
|
|
|
|
|
|
|
|
/* Poll callback for snap operators */
|
|
|
|
|
/* NOTE: For now, we only allow these in the 3D view, as other editors do not
|
|
|
|
|
* define a cursor or grid-step which can be used.
|
|
|
|
|
*/
|
|
|
|
|
static bool grease_pencil_snap_poll(bContext *C)
|
|
|
|
|
{
|
|
|
|
|
if (!editable_grease_pencil_poll(C)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ScrArea *area = CTX_wm_area(C);
|
|
|
|
|
return (area != nullptr) && (area->spacetype == SPACE_VIEW3D);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int grease_pencil_snap_to_grid_exec(bContext *C, wmOperator * /*op*/)
|
|
|
|
|
{
|
|
|
|
|
using bke::greasepencil::Layer;
|
|
|
|
|
|
|
|
|
|
const Scene &scene = *CTX_data_scene(C);
|
|
|
|
|
Object &object = *CTX_data_active_object(C);
|
|
|
|
|
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
|
|
|
|
|
const View3D &v3d = *CTX_wm_view3d(C);
|
|
|
|
|
const ARegion ®ion = *CTX_wm_region(C);
|
|
|
|
|
const float grid_size = ED_view3d_grid_view_scale(&scene, &v3d, ®ion, nullptr);
|
|
|
|
|
|
|
|
|
|
const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
|
|
|
|
|
for (const MutableDrawingInfo &drawing_info : drawings) {
|
|
|
|
|
bke::CurvesGeometry &curves = drawing_info.drawing.strokes_for_write();
|
|
|
|
|
if (curves.curves_num() == 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!ed::curves::has_anything_selected(curves)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IndexMaskMemory memory;
|
|
|
|
|
const IndexMask selected_points = ed::curves::retrieve_selected_points(curves, memory);
|
|
|
|
|
|
|
|
|
|
const Layer &layer = *grease_pencil.layer(drawing_info.layer_index);
|
|
|
|
|
const float4x4 layer_to_world = layer.to_world_space(object);
|
|
|
|
|
const float4x4 world_to_layer = math::invert(layer_to_world);
|
|
|
|
|
|
|
|
|
|
MutableSpan<float3> positions = curves.positions_for_write();
|
|
|
|
|
selected_points.foreach_index(GrainSize(4096), [&](const int point_i) {
|
|
|
|
|
const float3 pos_world = math::transform_point(layer_to_world, positions[point_i]);
|
|
|
|
|
const float3 pos_snapped = grid_size * math::floor(pos_world / grid_size + 0.5f);
|
|
|
|
|
positions[point_i] = math::transform_point(world_to_layer, pos_snapped);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
|
|
|
|
|
DEG_id_tag_update(&object.id, ID_RECALC_SYNC_TO_EVAL);
|
|
|
|
|
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void GREASE_PENCIL_OT_snap_to_grid(wmOperatorType *ot)
|
|
|
|
|
{
|
|
|
|
|
/* identifiers */
|
|
|
|
|
ot->name = "Snap Selection to Grid";
|
|
|
|
|
ot->idname = "GREASE_PENCIL_OT_snap_to_grid";
|
|
|
|
|
ot->description = "Snap selected points to the nearest grid points";
|
|
|
|
|
|
|
|
|
|
/* callbacks */
|
|
|
|
|
ot->exec = grease_pencil_snap_to_grid_exec;
|
|
|
|
|
ot->poll = grease_pencil_snap_poll;
|
|
|
|
|
|
|
|
|
|
/* flags */
|
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** \} */
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Snapping Selection to Cursor Operator
|
|
|
|
|
* \{ */
|
|
|
|
|
|
|
|
|
|
static int grease_pencil_snap_to_cursor_exec(bContext *C, wmOperator *op)
|
|
|
|
|
{
|
|
|
|
|
using bke::greasepencil::Layer;
|
|
|
|
|
|
|
|
|
|
const Scene &scene = *CTX_data_scene(C);
|
|
|
|
|
Object &object = *CTX_data_active_object(C);
|
|
|
|
|
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
|
|
|
|
|
const bool use_offset = RNA_boolean_get(op->ptr, "use_offset");
|
|
|
|
|
const float3 cursor_world = scene.cursor.location;
|
|
|
|
|
|
|
|
|
|
const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
|
|
|
|
|
for (const MutableDrawingInfo &drawing_info : drawings) {
|
|
|
|
|
bke::CurvesGeometry &curves = drawing_info.drawing.strokes_for_write();
|
|
|
|
|
if (curves.curves_num() == 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!ed::curves::has_anything_selected(curves)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IndexMaskMemory selected_points_memory;
|
|
|
|
|
const IndexMask selected_points = ed::curves::retrieve_selected_points(curves,
|
|
|
|
|
selected_points_memory);
|
|
|
|
|
|
|
|
|
|
const Layer &layer = *grease_pencil.layer(drawing_info.layer_index);
|
|
|
|
|
const float4x4 layer_to_world = layer.to_world_space(object);
|
|
|
|
|
const float4x4 world_to_layer = math::invert(layer_to_world);
|
|
|
|
|
const float3 cursor_layer = math::transform_point(world_to_layer, cursor_world);
|
|
|
|
|
|
|
|
|
|
MutableSpan<float3> positions = curves.positions_for_write();
|
|
|
|
|
if (use_offset) {
|
|
|
|
|
const OffsetIndices points_by_curve = curves.points_by_curve();
|
|
|
|
|
IndexMaskMemory selected_curves_memory;
|
|
|
|
|
const IndexMask selected_curves = ed::curves::retrieve_selected_curves(
|
|
|
|
|
curves, selected_curves_memory);
|
|
|
|
|
|
|
|
|
|
selected_curves.foreach_index(GrainSize(512), [&](const int curve_i) {
|
|
|
|
|
const IndexRange points = points_by_curve[curve_i];
|
|
|
|
|
|
|
|
|
|
/* Offset from first point of the curve. */
|
|
|
|
|
const float3 offset = cursor_layer - positions[points.first()];
|
|
|
|
|
selected_points.slice_content(points).foreach_index(
|
|
|
|
|
GrainSize(4096), [&](const int point_i) { positions[point_i] += offset; });
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Set all selected positions to the cursor location. */
|
|
|
|
|
index_mask::masked_fill(positions, cursor_layer, selected_points);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
|
|
|
|
|
DEG_id_tag_update(&object.id, ID_RECALC_SYNC_TO_EVAL);
|
|
|
|
|
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void GREASE_PENCIL_OT_snap_to_cursor(wmOperatorType *ot)
|
|
|
|
|
{
|
|
|
|
|
/* identifiers */
|
|
|
|
|
ot->name = "Snap Selection to Cursor";
|
|
|
|
|
ot->idname = "GREASE_PENCIL_OT_snap_to_cursor";
|
|
|
|
|
ot->description = "Snap selected points/strokes to the cursor";
|
|
|
|
|
|
|
|
|
|
/* callbacks */
|
|
|
|
|
ot->exec = grease_pencil_snap_to_cursor_exec;
|
|
|
|
|
ot->poll = grease_pencil_snap_poll;
|
|
|
|
|
|
|
|
|
|
/* flags */
|
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
|
|
|
|
|
|
/* props */
|
|
|
|
|
ot->prop = RNA_def_boolean(ot->srna,
|
|
|
|
|
"use_offset",
|
|
|
|
|
true,
|
|
|
|
|
"With Offset",
|
|
|
|
|
"Offset the entire stroke instead of selected points only");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** \} */
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Snapping Cursor to Selection Operator
|
|
|
|
|
* \{ */
|
|
|
|
|
|
|
|
|
|
static bool grease_pencil_snap_compute_centroid(const Scene &scene,
|
|
|
|
|
const Object &object,
|
|
|
|
|
const GreasePencil &grease_pencil,
|
|
|
|
|
float3 &r_centroid,
|
|
|
|
|
float3 &r_min,
|
|
|
|
|
float3 &r_max)
|
|
|
|
|
{
|
|
|
|
|
using bke::greasepencil::Layer;
|
|
|
|
|
|
|
|
|
|
int num_selected = 0;
|
|
|
|
|
r_centroid = float3(0.0f);
|
|
|
|
|
r_min = float3(std::numeric_limits<float>::max());
|
|
|
|
|
r_max = float3(std::numeric_limits<float>::lowest());
|
|
|
|
|
|
|
|
|
|
const Vector<DrawingInfo> drawings = retrieve_visible_drawings(scene, grease_pencil, false);
|
|
|
|
|
for (const DrawingInfo &drawing_info : drawings) {
|
|
|
|
|
const bke::CurvesGeometry &curves = drawing_info.drawing.strokes();
|
|
|
|
|
if (curves.curves_num() == 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!ed::curves::has_anything_selected(curves)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IndexMaskMemory selected_points_memory;
|
|
|
|
|
const IndexMask selected_points = ed::curves::retrieve_selected_points(curves,
|
|
|
|
|
selected_points_memory);
|
|
|
|
|
|
|
|
|
|
const Layer &layer = *grease_pencil.layer(drawing_info.layer_index);
|
|
|
|
|
const float4x4 layer_to_world = layer.to_world_space(object);
|
|
|
|
|
|
|
|
|
|
Span<float3> positions = curves.positions();
|
|
|
|
|
selected_points.foreach_index(GrainSize(4096), [&](const int point_i) {
|
|
|
|
|
const float3 pos_world = math::transform_point(layer_to_world, positions[point_i]);
|
|
|
|
|
r_centroid += pos_world;
|
|
|
|
|
math::min_max(pos_world, r_min, r_max);
|
|
|
|
|
});
|
|
|
|
|
num_selected += selected_points.size();
|
|
|
|
|
}
|
|
|
|
|
if (num_selected == 0) {
|
|
|
|
|
r_min = r_max = float3(0.0f);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r_centroid /= num_selected;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int grease_pencil_snap_cursor_to_sel_exec(bContext *C, wmOperator *op)
|
|
|
|
|
{
|
|
|
|
|
Scene &scene = *CTX_data_scene(C);
|
|
|
|
|
const Object &object = *CTX_data_active_object(C);
|
|
|
|
|
const GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
|
|
|
|
|
float3 &cursor = reinterpret_cast<float3 &>(scene.cursor.location);
|
|
|
|
|
|
|
|
|
|
float3 centroid, points_min, points_max;
|
|
|
|
|
if (!grease_pencil_snap_compute_centroid(
|
|
|
|
|
scene, object, grease_pencil, centroid, points_min, points_max))
|
|
|
|
|
{
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (scene.toolsettings->transform_pivot_point) {
|
|
|
|
|
case V3D_AROUND_CENTER_BOUNDS:
|
|
|
|
|
cursor = math::midpoint(points_min, points_max);
|
|
|
|
|
break;
|
|
|
|
|
case V3D_AROUND_CENTER_MEDIAN:
|
|
|
|
|
case V3D_AROUND_CURSOR:
|
|
|
|
|
case V3D_AROUND_LOCAL_ORIGINS:
|
|
|
|
|
case V3D_AROUND_ACTIVE:
|
|
|
|
|
cursor = centroid;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DEG_id_tag_update(&scene.id, ID_RECALC_SYNC_TO_EVAL);
|
|
|
|
|
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, nullptr);
|
|
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void GREASE_PENCIL_OT_snap_cursor_to_selected(wmOperatorType *ot)
|
|
|
|
|
{
|
|
|
|
|
/* identifiers */
|
|
|
|
|
ot->name = "Snap Cursor to Selected Points";
|
|
|
|
|
ot->idname = "GREASE_PENCIL_OT_snap_cursor_to_selected";
|
|
|
|
|
ot->description = "Snap cursor to center of selected points";
|
|
|
|
|
|
|
|
|
|
/* callbacks */
|
|
|
|
|
ot->exec = grease_pencil_snap_cursor_to_sel_exec;
|
|
|
|
|
ot->poll = grease_pencil_snap_poll;
|
|
|
|
|
|
|
|
|
|
/* flags */
|
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** \} */
|
|
|
|
|
|
|
|
|
|
} // namespace blender::ed::greasepencil
|
|
|
|
|
|
|
|
|
|
void ED_operatortypes_grease_pencil_edit()
|
|
|
|
|
@@ -2661,4 +2940,7 @@ void ED_operatortypes_grease_pencil_edit()
|
|
|
|
|
WM_operatortype_append(GREASE_PENCIL_OT_paste);
|
|
|
|
|
WM_operatortype_append(GREASE_PENCIL_OT_stroke_cutter);
|
|
|
|
|
WM_operatortype_append(GREASE_PENCIL_OT_extrude);
|
|
|
|
|
WM_operatortype_append(GREASE_PENCIL_OT_snap_to_grid);
|
|
|
|
|
WM_operatortype_append(GREASE_PENCIL_OT_snap_to_cursor);
|
|
|
|
|
WM_operatortype_append(GREASE_PENCIL_OT_snap_cursor_to_selected);
|
|
|
|
|
}
|
|
|
|
|
|