GPv3: Snap operators for edit mode
Implements snapping for grid, selected-to-cursor, and cursor-to-selected, as well as the regular menu and pie menu for these operators. Pull Request: https://projects.blender.org/blender/blender/pulls/121582
This commit is contained in:
@@ -4620,6 +4620,8 @@ def km_grease_pencil_edit_mode(params):
|
|||||||
("grease_pencil.paste", {"type": 'V', "value": 'PRESS', "ctrl": True}, None),
|
("grease_pencil.paste", {"type": 'V', "value": 'PRESS', "ctrl": True}, None),
|
||||||
("grease_pencil.paste", {"type": 'V', "value": 'PRESS', "shift": True, "ctrl": True},
|
("grease_pencil.paste", {"type": 'V', "value": 'PRESS', "shift": True, "ctrl": True},
|
||||||
{"properties": [("paste_back", True)]}),
|
{"properties": [("paste_back", True)]}),
|
||||||
|
# Snap
|
||||||
|
op_menu_pie("GREASE_PENCIL_MT_snap_pie", {"type": 'S', "value": 'PRESS', "shift": True}),
|
||||||
# Separate
|
# Separate
|
||||||
("grease_pencil.separate", {"type": 'P', "value": 'PRESS'}, None),
|
("grease_pencil.separate", {"type": 'P', "value": 'PRESS'}, None),
|
||||||
# Delete all active frames
|
# Delete all active frames
|
||||||
|
|||||||
@@ -964,6 +964,48 @@ class GreasePencilFlipTintColors(Operator):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class GREASE_PENCIL_MT_snap(Menu):
|
||||||
|
bl_label = "Snap"
|
||||||
|
|
||||||
|
def draw(self, _context):
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
layout.operator("grease_pencil.snap_to_grid", text="Selection to Grid")
|
||||||
|
layout.operator("grease_pencil.snap_to_cursor", text="Selection to Cursor").use_offset = False
|
||||||
|
layout.operator("grease_pencil.snap_to_cursor", text="Selection to Cursor (Keep Offset)").use_offset = True
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
layout.operator("grease_pencil.snap_cursor_to_selected", text="Cursor to Selected")
|
||||||
|
layout.operator("view3d.snap_cursor_to_center", text="Cursor to World Origin")
|
||||||
|
layout.operator("view3d.snap_cursor_to_grid", text="Cursor to Grid")
|
||||||
|
|
||||||
|
|
||||||
|
class GREASE_PENCIL_MT_snap_pie(Menu):
|
||||||
|
bl_label = "Snap"
|
||||||
|
|
||||||
|
def draw(self, _context):
|
||||||
|
layout = self.layout
|
||||||
|
pie = layout.menu_pie()
|
||||||
|
|
||||||
|
pie.operator("view3d.snap_cursor_to_grid", text="Cursor to Grid", icon='CURSOR')
|
||||||
|
pie.operator("grease_pencil.snap_to_grid", text="Selection to Grid", icon='RESTRICT_SELECT_OFF')
|
||||||
|
pie.operator("grease_pencil.snap_cursor_to_selected", text="Cursor to Selected", icon='CURSOR')
|
||||||
|
pie.operator(
|
||||||
|
"grease_pencil.snap_to_cursor",
|
||||||
|
text="Selection to Cursor",
|
||||||
|
icon='RESTRICT_SELECT_OFF',
|
||||||
|
).use_offset = False
|
||||||
|
pie.operator(
|
||||||
|
"grease_pencil.snap_to_cursor",
|
||||||
|
text="Selection to Cursor (Keep Offset)",
|
||||||
|
icon='RESTRICT_SELECT_OFF',
|
||||||
|
).use_offset = True
|
||||||
|
pie.separator()
|
||||||
|
pie.operator("view3d.snap_cursor_to_center", text="Cursor to World Origin", icon='CURSOR')
|
||||||
|
pie.separator()
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
GPENCIL_MT_snap,
|
GPENCIL_MT_snap,
|
||||||
GPENCIL_MT_snap_pie,
|
GPENCIL_MT_snap_pie,
|
||||||
@@ -982,6 +1024,9 @@ classes = (
|
|||||||
GREASE_PENCIL_MT_move_to_layer,
|
GREASE_PENCIL_MT_move_to_layer,
|
||||||
GREASE_PENCIL_MT_layer_active,
|
GREASE_PENCIL_MT_layer_active,
|
||||||
|
|
||||||
|
GREASE_PENCIL_MT_snap,
|
||||||
|
GREASE_PENCIL_MT_snap_pie,
|
||||||
|
|
||||||
GreasePencilFlipTintColors,
|
GreasePencilFlipTintColors,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6023,6 +6023,7 @@ class VIEW3D_MT_edit_greasepencil(Menu):
|
|||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.menu("VIEW3D_MT_transform")
|
layout.menu("VIEW3D_MT_transform")
|
||||||
layout.menu("VIEW3D_MT_mirror")
|
layout.menu("VIEW3D_MT_mirror")
|
||||||
|
layout.menu("GREASE_PENCIL_MT_snap")
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
|
|
||||||
|
|||||||
@@ -7,15 +7,23 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "BLI_array_utils.hh"
|
#include "BLI_array_utils.hh"
|
||||||
|
#include "BLI_assert.h"
|
||||||
#include "BLI_index_mask.hh"
|
#include "BLI_index_mask.hh"
|
||||||
#include "BLI_index_range.hh"
|
#include "BLI_index_range.hh"
|
||||||
|
#include "BLI_math_base.hh"
|
||||||
#include "BLI_math_geom.h"
|
#include "BLI_math_geom.h"
|
||||||
|
#include "BLI_math_matrix.hh"
|
||||||
|
#include "BLI_math_vector.hh"
|
||||||
#include "BLI_math_vector_types.hh"
|
#include "BLI_math_vector_types.hh"
|
||||||
|
#include "BLI_offset_indices.hh"
|
||||||
#include "BLI_span.hh"
|
#include "BLI_span.hh"
|
||||||
|
#include "BLI_utildefines.h"
|
||||||
#include "BLT_translation.hh"
|
#include "BLT_translation.hh"
|
||||||
|
|
||||||
#include "DNA_material_types.h"
|
#include "DNA_material_types.h"
|
||||||
|
#include "DNA_object_types.h"
|
||||||
#include "DNA_scene_types.h"
|
#include "DNA_scene_types.h"
|
||||||
|
#include "DNA_space_types.h"
|
||||||
|
|
||||||
#include "BKE_attribute.hh"
|
#include "BKE_attribute.hh"
|
||||||
#include "BKE_context.hh"
|
#include "BKE_context.hh"
|
||||||
@@ -27,6 +35,8 @@
|
|||||||
#include "BKE_material.h"
|
#include "BKE_material.h"
|
||||||
#include "BKE_report.hh"
|
#include "BKE_report.hh"
|
||||||
|
|
||||||
|
#include "DNA_view3d_types.h"
|
||||||
|
#include "DNA_windowmanager_types.h"
|
||||||
#include "RNA_access.hh"
|
#include "RNA_access.hh"
|
||||||
#include "RNA_define.hh"
|
#include "RNA_define.hh"
|
||||||
#include "RNA_enum_types.hh"
|
#include "RNA_enum_types.hh"
|
||||||
@@ -36,6 +46,7 @@
|
|||||||
#include "ED_curves.hh"
|
#include "ED_curves.hh"
|
||||||
#include "ED_grease_pencil.hh"
|
#include "ED_grease_pencil.hh"
|
||||||
#include "ED_object.hh"
|
#include "ED_object.hh"
|
||||||
|
#include "ED_view3d.hh"
|
||||||
|
|
||||||
#include "GEO_join_geometries.hh"
|
#include "GEO_join_geometries.hh"
|
||||||
#include "GEO_reorder.hh"
|
#include "GEO_reorder.hh"
|
||||||
@@ -43,6 +54,7 @@
|
|||||||
#include "GEO_subdivide_curves.hh"
|
#include "GEO_subdivide_curves.hh"
|
||||||
|
|
||||||
#include "UI_resources.hh"
|
#include "UI_resources.hh"
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace blender::ed::greasepencil {
|
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
|
} // namespace blender::ed::greasepencil
|
||||||
|
|
||||||
void ED_operatortypes_grease_pencil_edit()
|
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_paste);
|
||||||
WM_operatortype_append(GREASE_PENCIL_OT_stroke_cutter);
|
WM_operatortype_append(GREASE_PENCIL_OT_stroke_cutter);
|
||||||
WM_operatortype_append(GREASE_PENCIL_OT_extrude);
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user