UV: Move on Axis selected UVs operator

The operator moves selected UVs, using the num-pad for directional keys:

Modifiers control the units:
- UDIM / UV unit (NUMPAD KEY)
- Dynamic grid unit (CTRL + NUMPAD KEY)
- Pixel unit (SHIFT + NUMPAD KEY)

Implements design task #78405.

Ref !139608
This commit is contained in:
Eitan Traurig
2025-10-03 15:50:14 +10:00
committed by Campbell Barton
parent eed9e557ec
commit cfad6f4f25
3 changed files with 115 additions and 0 deletions

View File

@@ -1418,6 +1418,24 @@ def km_uv_editor(params):
op_menu("IMAGE_MT_uvs_merge", {"type": 'M', "value": 'PRESS'}),
op_menu("IMAGE_MT_uvs_split", {"type": 'M', "value": 'PRESS', "alt": True}),
op_menu("IMAGE_MT_uvs_align", {"type": 'W', "value": 'PRESS', "shift": True}),
*[
(
"uv.move_on_axis",
{"type": key, "value": 'PRESS', **mod_dict},
{"properties": [("axis", axis), ("type", move_type), ("distance", distance)]}
)
for mod_dict, move_type in (
({"ctrl": True}, 'DYNAMIC'),
({"shift": True}, 'PIXEL'),
({}, 'UDIM'),
)
for key, axis, distance in (
('NUMPAD_8', 'Y', 1),
('NUMPAD_2', 'Y', -1),
('NUMPAD_6', 'X', 1),
('NUMPAD_4', 'X', -1),
)
],
("uv.stitch", {"type": 'V', "value": 'PRESS', "alt": True}, None),
("uv.rip_move", {"type": 'V', "value": 'PRESS'}, None),
("uv.pin", {"type": 'P', "value": 'PRESS'},

View File

@@ -484,6 +484,7 @@ class IMAGE_MT_uvs(Menu):
layout.operator_context = 'EXEC_REGION_WIN'
layout.menu("IMAGE_MT_uvs_align")
layout.operator("uv.align_rotation")
layout.operator_menu_enum("uv.move_on_axis", "type", text="Move on Axis")
layout.separator()

View File

@@ -344,6 +344,101 @@ bool ED_uvedit_center_from_pivot_ex(const SpaceImage *sima,
return changed;
}
enum class UVMoveType {
Dynamic = 0,
Pixel = 1,
Udim = 2,
};
enum class UVMoveDirection {
X = 0,
Y = 1,
};
static wmOperatorStatus uv_move_on_axis_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
SpaceImage *sima = CTX_wm_space_image(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
UVMoveType type = UVMoveType(RNA_enum_get(op->ptr, "type"));
UVMoveDirection axis = UVMoveDirection(RNA_enum_get(op->ptr, "axis"));
int distance = RNA_int_get(op->ptr, "distance");
int size[2];
ED_space_image_get_size(sima, &size[0], &size[1]);
float distance_final;
if (type == UVMoveType::Dynamic) {
distance_final = float(distance) / sima->tile_grid_shape[int(axis)];
}
else if (type == UVMoveType::Pixel) {
distance_final = float(distance) / size[int(axis)];
}
else {
distance_final = distance;
}
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
bool changed = false;
if (em->bm->totvertsel == 0) {
continue;
}
ED_uvedit_foreach_uv(
scene, em->bm, true, true, [&axis, &distance_final, &changed](float luv[2]) {
luv[int(axis)] += distance_final;
changed = true;
});
if (changed) {
uvedit_live_unwrap_update(sima, scene, obedit);
DEG_id_tag_update(static_cast<ID *>(obedit->data), 0);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
}
}
return OPERATOR_FINISHED;
}
static void UV_OT_move_on_axis(wmOperatorType *ot)
{
static const EnumPropertyItem shift_items[] = {
{int(UVMoveType::Dynamic), "DYNAMIC", 0, "Dynamic", "Move by dynamic grid"},
{int(UVMoveType::Pixel), "PIXEL", 0, "Pixel", "Move by pixel"},
{int(UVMoveType::Udim), "UDIM", 0, "UDIM", "Move by UDIM"},
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem axis_items[] = {
{int(UVMoveDirection::X), "X", 0, "X axis", "Move vertices on the X axis"},
{int(UVMoveDirection::Y), "Y", 0, "Y axis", "Move vertices on the Y axis"},
{0, nullptr, 0, nullptr, nullptr},
};
/* identifiers */
ot->name = "Move on Axis";
ot->description = "Move UVs on an axis";
ot->idname = "UV_OT_move_on_axis";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* API callbacks. */
ot->exec = uv_move_on_axis_exec;
ot->poll = ED_operator_uvedit;
/* properties */
RNA_def_enum(ot->srna, "type", shift_items, int(UVMoveType::Udim), "Type", "Move Type");
RNA_def_enum(
ot->srna, "axis", axis_items, int(UVMoveDirection::X), "Axis", "Axis to move UVs on");
RNA_def_int(ot->srna,
"distance",
1,
INT_MIN,
INT_MAX,
"Distance",
"Distance to move UVs",
INT_MIN,
INT_MAX);
}
/** \} */
/* -------------------------------------------------------------------- */
@@ -2584,6 +2679,7 @@ void ED_operatortypes_uvedit()
WM_operatortype_append(UV_OT_cursor_set);
WM_operatortype_append(UV_OT_copy_mirrored_faces);
WM_operatortype_append(UV_OT_move_on_axis);
}
void ED_operatormacros_uvedit()