GPv3: Add Reproject operator
This commit adds the `Reproject Strokes` operator in the `Grease Pencil` > `Cleanup` menu in edit mode. All similar operator settings have been ported over from GPv2. Pull Request: https://projects.blender.org/blender/blender/pulls/127735
This commit is contained in:
@@ -6183,6 +6183,8 @@ class VIEW3D_MT_edit_greasepencil_cleanup(Menu):
|
|||||||
if ob.mode != 'PAINT_GREASE_PENCIL':
|
if ob.mode != 'PAINT_GREASE_PENCIL':
|
||||||
layout.operator("grease_pencil.stroke_merge_by_distance", text="Merge by Distance")
|
layout.operator("grease_pencil.stroke_merge_by_distance", text="Merge by Distance")
|
||||||
|
|
||||||
|
layout.operator("grease_pencil.reproject")
|
||||||
|
|
||||||
|
|
||||||
class VIEW3D_MT_edit_greasepencil(Menu):
|
class VIEW3D_MT_edit_greasepencil(Menu):
|
||||||
bl_label = "Grease Pencil"
|
bl_label = "Grease Pencil"
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
#include "BKE_material.h"
|
#include "BKE_material.h"
|
||||||
#include "BKE_preview_image.hh"
|
#include "BKE_preview_image.hh"
|
||||||
#include "BKE_report.hh"
|
#include "BKE_report.hh"
|
||||||
|
#include "BKE_scene.hh"
|
||||||
|
|
||||||
#include "DNA_view3d_types.h"
|
#include "DNA_view3d_types.h"
|
||||||
#include "DNA_windowmanager_types.h"
|
#include "DNA_windowmanager_types.h"
|
||||||
@@ -43,10 +44,12 @@
|
|||||||
#include "RNA_enum_types.hh"
|
#include "RNA_enum_types.hh"
|
||||||
|
|
||||||
#include "DEG_depsgraph.hh"
|
#include "DEG_depsgraph.hh"
|
||||||
|
#include "DEG_depsgraph_query.hh"
|
||||||
|
|
||||||
#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_transform_snap_object_context.hh"
|
||||||
#include "ED_view3d.hh"
|
#include "ED_view3d.hh"
|
||||||
|
|
||||||
#include "GEO_join_geometries.hh"
|
#include "GEO_join_geometries.hh"
|
||||||
@@ -54,6 +57,7 @@
|
|||||||
#include "GEO_set_curve_type.hh"
|
#include "GEO_set_curve_type.hh"
|
||||||
#include "GEO_smooth_curves.hh"
|
#include "GEO_smooth_curves.hh"
|
||||||
#include "GEO_subdivide_curves.hh"
|
#include "GEO_subdivide_curves.hh"
|
||||||
|
#include "UI_interface_c.hh"
|
||||||
|
|
||||||
#include "UI_resources.hh"
|
#include "UI_resources.hh"
|
||||||
#include <limits>
|
#include <limits>
|
||||||
@@ -2690,6 +2694,190 @@ static void GREASE_PENCIL_OT_extrude(wmOperatorType *ot)
|
|||||||
|
|
||||||
/** \} */
|
/** \} */
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------- */
|
||||||
|
/** \name Reproject Strokes Operator
|
||||||
|
* \{ */
|
||||||
|
|
||||||
|
static int grease_pencil_reproject_exec(bContext *C, wmOperator *op)
|
||||||
|
{
|
||||||
|
Scene &scene = *CTX_data_scene(C);
|
||||||
|
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
||||||
|
|
||||||
|
View3D *v3d = CTX_wm_view3d(C);
|
||||||
|
ARegion *region = CTX_wm_region(C);
|
||||||
|
|
||||||
|
const ReprojectMode mode = ReprojectMode(RNA_enum_get(op->ptr, "type"));
|
||||||
|
const bool keep_original = RNA_boolean_get(op->ptr, "keep_original");
|
||||||
|
|
||||||
|
Object *object = CTX_data_active_object(C);
|
||||||
|
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
|
||||||
|
const float offset = RNA_float_get(op->ptr, "offset");
|
||||||
|
|
||||||
|
const bke::AttrDomain selection_domain = ED_grease_pencil_selection_domain_get(
|
||||||
|
scene.toolsettings);
|
||||||
|
|
||||||
|
const int oldframe = int(DEG_get_ctime(depsgraph));
|
||||||
|
/* TODO: This can probably be optimized further for the non-Surface projection usecase by
|
||||||
|
* considering all drawings for the parallel loop instead of having to partition by frame number
|
||||||
|
*/
|
||||||
|
if (keep_original) {
|
||||||
|
const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(scene, grease_pencil);
|
||||||
|
threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
|
||||||
|
IndexMaskMemory memory;
|
||||||
|
const IndexMask elements = retrieve_editable_and_selected_elements(
|
||||||
|
*object, info.drawing, info.layer_index, selection_domain, memory);
|
||||||
|
if (elements.is_empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bke::CurvesGeometry &curves = info.drawing.strokes_for_write();
|
||||||
|
if (selection_domain == bke::AttrDomain::Curve) {
|
||||||
|
curves::duplicate_curves(curves, elements);
|
||||||
|
}
|
||||||
|
else if (selection_domain == bke::AttrDomain::Point) {
|
||||||
|
curves::duplicate_points(curves, elements);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::atomic<bool> changed = false;
|
||||||
|
Array<Vector<MutableDrawingInfo>> drawings_per_frame =
|
||||||
|
retrieve_editable_drawings_grouped_per_frame(scene, grease_pencil);
|
||||||
|
for (const Span<MutableDrawingInfo> drawings : drawings_per_frame) {
|
||||||
|
if (drawings.is_empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const int current_frame_number = drawings.first().frame_number;
|
||||||
|
|
||||||
|
if (mode == ReprojectMode::Surface) {
|
||||||
|
scene.r.cfra = current_frame_number;
|
||||||
|
BKE_scene_graph_update_for_newframe(depsgraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
|
||||||
|
IndexMaskMemory memory;
|
||||||
|
const IndexMask points_to_reproject = retrieve_editable_and_selected_points(
|
||||||
|
*object, info.drawing, info.layer_index, memory);
|
||||||
|
if (points_to_reproject.is_empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bke::greasepencil::Layer &layer = *grease_pencil.layer(info.layer_index);
|
||||||
|
bke::CurvesGeometry &curves = info.drawing.strokes_for_write();
|
||||||
|
const DrawingPlacement drawing_placement(
|
||||||
|
scene, *region, *v3d, *object, &layer, mode, offset);
|
||||||
|
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||||
|
MutableSpan<float3> positions = curves.positions_for_write();
|
||||||
|
for (const int curve_index : curves.curves_range()) {
|
||||||
|
const IndexRange curve_points = points_by_curve[curve_index];
|
||||||
|
const IndexMask curve_points_to_reproject = points_to_reproject.slice_content(
|
||||||
|
curve_points);
|
||||||
|
curve_points_to_reproject.foreach_index(GrainSize(4096), [&](const int point_i) {
|
||||||
|
positions[point_i] = drawing_placement.reproject(positions[point_i]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
changed.store(true, std::memory_order_relaxed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == ReprojectMode::Surface) {
|
||||||
|
scene.r.cfra = oldframe;
|
||||||
|
BKE_scene_graph_update_for_newframe(depsgraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
|
||||||
|
WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
|
||||||
|
}
|
||||||
|
|
||||||
|
return OPERATOR_FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void grease_pencil_reproject_ui(bContext * /*C*/, wmOperator *op)
|
||||||
|
{
|
||||||
|
uiLayout *layout = op->layout;
|
||||||
|
uiLayout *row;
|
||||||
|
|
||||||
|
const ReprojectMode type = ReprojectMode(RNA_enum_get(op->ptr, "type"));
|
||||||
|
|
||||||
|
uiLayoutSetPropSep(layout, true);
|
||||||
|
uiLayoutSetPropDecorate(layout, false);
|
||||||
|
row = uiLayoutRow(layout, true);
|
||||||
|
uiItemR(row, op->ptr, "type", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||||
|
|
||||||
|
if (type == ReprojectMode::Surface) {
|
||||||
|
row = uiLayoutRow(layout, true);
|
||||||
|
uiItemR(row, op->ptr, "offset", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||||
|
}
|
||||||
|
row = uiLayoutRow(layout, true);
|
||||||
|
uiItemR(row, op->ptr, "keep_original", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GREASE_PENCIL_OT_reproject(wmOperatorType *ot)
|
||||||
|
{
|
||||||
|
static const EnumPropertyItem reproject_type[] = {
|
||||||
|
{int(ReprojectMode::Front),
|
||||||
|
"FRONT",
|
||||||
|
0,
|
||||||
|
"Front",
|
||||||
|
"Reproject the strokes using the X-Z plane"},
|
||||||
|
{int(ReprojectMode::Side), "SIDE", 0, "Side", "Reproject the strokes using the Y-Z plane"},
|
||||||
|
{int(ReprojectMode::Top), "TOP", 0, "Top", "Reproject the strokes using the X-Y plane"},
|
||||||
|
{int(ReprojectMode::View),
|
||||||
|
"VIEW",
|
||||||
|
0,
|
||||||
|
"View",
|
||||||
|
"Reproject the strokes to end up on the same plane, as if drawn from the current "
|
||||||
|
"viewpoint "
|
||||||
|
"using 'Cursor' Stroke Placement"},
|
||||||
|
{int(ReprojectMode::Surface),
|
||||||
|
"SURFACE",
|
||||||
|
0,
|
||||||
|
"Surface",
|
||||||
|
"Reproject the strokes on to the scene geometry, as if drawn using 'Surface' placement"},
|
||||||
|
{int(ReprojectMode::Cursor),
|
||||||
|
"CURSOR",
|
||||||
|
0,
|
||||||
|
"Cursor",
|
||||||
|
"Reproject the strokes using the orientation of 3D cursor"},
|
||||||
|
{0, nullptr, 0, nullptr, nullptr},
|
||||||
|
};
|
||||||
|
|
||||||
|
/* identifiers */
|
||||||
|
ot->name = "Reproject Strokes";
|
||||||
|
ot->idname = "GREASE_PENCIL_OT_reproject";
|
||||||
|
ot->description =
|
||||||
|
"Reproject the selected strokes from the current viewpoint as if they had been newly "
|
||||||
|
"drawn "
|
||||||
|
"(e.g. to fix problems from accidental 3D cursor movement or accidental viewport changes, "
|
||||||
|
"or for matching deforming geometry)";
|
||||||
|
|
||||||
|
/* callbacks */
|
||||||
|
ot->invoke = WM_menu_invoke;
|
||||||
|
ot->exec = grease_pencil_reproject_exec;
|
||||||
|
ot->poll = editable_grease_pencil_poll;
|
||||||
|
ot->ui = grease_pencil_reproject_ui;
|
||||||
|
|
||||||
|
/* flags */
|
||||||
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||||
|
|
||||||
|
/* properties */
|
||||||
|
ot->prop = RNA_def_enum(
|
||||||
|
ot->srna, "type", reproject_type, int(ReprojectMode::View), "Projection Type", "");
|
||||||
|
|
||||||
|
PropertyRNA *prop = RNA_def_boolean(
|
||||||
|
ot->srna,
|
||||||
|
"keep_original",
|
||||||
|
false,
|
||||||
|
"Keep Original",
|
||||||
|
"Keep original strokes and create a copy before reprojecting");
|
||||||
|
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_MOVIECLIP);
|
||||||
|
|
||||||
|
RNA_def_float(ot->srna, "offset", 0.0f, 0.0f, 10.0f, "Surface Offset", "", 0.0f, 10.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \} */
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
/** \name Snapping Selection to Grid Operator
|
/** \name Snapping Selection to Grid Operator
|
||||||
* \{ */
|
* \{ */
|
||||||
@@ -3188,6 +3376,7 @@ void ED_operatortypes_grease_pencil_edit()
|
|||||||
WM_operatortype_append(GREASE_PENCIL_OT_stroke_merge_by_distance);
|
WM_operatortype_append(GREASE_PENCIL_OT_stroke_merge_by_distance);
|
||||||
WM_operatortype_append(GREASE_PENCIL_OT_stroke_trim);
|
WM_operatortype_append(GREASE_PENCIL_OT_stroke_trim);
|
||||||
WM_operatortype_append(GREASE_PENCIL_OT_extrude);
|
WM_operatortype_append(GREASE_PENCIL_OT_extrude);
|
||||||
|
WM_operatortype_append(GREASE_PENCIL_OT_reproject);
|
||||||
WM_operatortype_append(GREASE_PENCIL_OT_snap_to_grid);
|
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_to_cursor);
|
||||||
WM_operatortype_append(GREASE_PENCIL_OT_snap_cursor_to_selected);
|
WM_operatortype_append(GREASE_PENCIL_OT_snap_cursor_to_selected);
|
||||||
|
|||||||
@@ -119,8 +119,9 @@ DrawingPlacement::DrawingPlacement(const Scene &scene,
|
|||||||
const View3D &view3d,
|
const View3D &view3d,
|
||||||
const Object &eval_object,
|
const Object &eval_object,
|
||||||
const bke::greasepencil::Layer *layer,
|
const bke::greasepencil::Layer *layer,
|
||||||
const ReprojectMode reproject_mode)
|
const ReprojectMode reproject_mode,
|
||||||
: region_(®ion), view3d_(&view3d)
|
const float surface_offset)
|
||||||
|
: region_(®ion), view3d_(&view3d), surface_offset_(surface_offset)
|
||||||
{
|
{
|
||||||
layer_space_to_world_space_ = (layer != nullptr) ? layer->to_world_space(eval_object) :
|
layer_space_to_world_space_ = (layer != nullptr) ? layer->to_world_space(eval_object) :
|
||||||
eval_object.object_to_world();
|
eval_object.object_to_world();
|
||||||
@@ -170,11 +171,14 @@ DrawingPlacement::DrawingPlacement(const Scene &scene,
|
|||||||
surface_offset_ = 0.0f;
|
surface_offset_ = 0.0f;
|
||||||
placement_loc_ = layer_space_to_world_space_.location();
|
placement_loc_ = layer_space_to_world_space_.location();
|
||||||
break;
|
break;
|
||||||
/* TODO: Implement ReprojectMode::Surface for reproject operator */
|
case ReprojectMode::Surface:
|
||||||
|
depth_ = DrawingPlacementDepth::Surface;
|
||||||
|
placement_loc_ = layer_space_to_world_space_.location();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
depth_ = DrawingPlacementDepth::ObjectOrigin;
|
depth_ = DrawingPlacementDepth::ObjectOrigin;
|
||||||
surface_offset_ = 0.0f;
|
surface_offset_ = 0.0f;
|
||||||
placement_loc_ = float3(0.0f);
|
placement_loc_ = layer_space_to_world_space_.location();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,8 @@ class DrawingPlacement {
|
|||||||
const View3D &view3d,
|
const View3D &view3d,
|
||||||
const Object &eval_object,
|
const Object &eval_object,
|
||||||
const bke::greasepencil::Layer *layer,
|
const bke::greasepencil::Layer *layer,
|
||||||
ReprojectMode reproject_mode);
|
ReprojectMode reproject_mode,
|
||||||
|
float surface_offset = 0.0f);
|
||||||
DrawingPlacement(const DrawingPlacement &other);
|
DrawingPlacement(const DrawingPlacement &other);
|
||||||
DrawingPlacement(DrawingPlacement &&other);
|
DrawingPlacement(DrawingPlacement &&other);
|
||||||
DrawingPlacement &operator=(const DrawingPlacement &other);
|
DrawingPlacement &operator=(const DrawingPlacement &other);
|
||||||
|
|||||||
Reference in New Issue
Block a user