From a9a54c88b95945b96a1996e2dbb56e77c6f2761b Mon Sep 17 00:00:00 2001 From: Eitan Traurig Date: Sun, 14 Sep 2025 05:23:47 +0000 Subject: [PATCH] UV: add pack to custom region option to "Pack Islands" operator This commit implements the design task #78398 - Box region - Set Custom UV Region (Ctrl-B). - Disabled with Custom Region checkbox in the menu or (Ctrl-Alt-B). - Box Select (Pinned) changed to (Alt-B). - When the Custom Region enum is chosen the islands are packed into the bounding box of the drawn region. Ref !140020 --- .../keyconfig/keymap_data/blender_default.py | 6 ++- scripts/startup/bl_ui/space_image.py | 4 ++ .../blender/editors/space_image/image_draw.cc | 35 ++++++++++++++++ .../editors/space_image/image_intern.hh | 2 + .../blender/editors/uvedit/uvedit_intern.hh | 2 + source/blender/editors/uvedit/uvedit_ops.cc | 1 + .../blender/editors/uvedit/uvedit_select.cc | 34 +++++++++++++++ .../editors/uvedit/uvedit_unwrap_ops.cc | 42 +++++++++++++++++-- source/blender/makesdna/DNA_scene_types.h | 3 ++ source/blender/makesrna/intern/rna_scene.cc | 6 +++ .../windowmanager/intern/wm_operators.cc | 1 + 11 files changed, 131 insertions(+), 5 deletions(-) diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index 3a4bdc87375..cb9e08f9dd5 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -1395,7 +1395,7 @@ def km_uv_editor(params): ("uv.select_box", {"type": 'B', "value": 'PRESS'}, {"properties": [("pinned", False)]}), (op_tool, "builtin.select_box"), params), - ("uv.select_box", {"type": 'B', "value": 'PRESS', "ctrl": True}, + ("uv.select_box", {"type": 'B', "value": 'PRESS', "alt": True}, {"properties": [("pinned", True)]}), op_tool_optional( ("uv.select_circle", {"type": 'C', "value": 'PRESS'}, None), @@ -1426,6 +1426,10 @@ def km_uv_editor(params): {"properties": [("clear", True)]}), ("uv.copy", {"type": 'C', "value": 'PRESS', "ctrl": True}, None), ("uv.paste", {"type": 'V', "value": 'PRESS', "ctrl": True}, None), + ("uv.custom_region_set", {"type": 'B', "value": 'PRESS', "ctrl": True}, None), + ("wm.context_toggle", {"type": 'B', "value": 'PRESS', "ctrl": True, "alt": True}, + {"properties": [("data_path", "tool_settings.use_uv_custom_region")]}), + op_menu("IMAGE_MT_uvs_unwrap", {"type": 'U', "value": 'PRESS'}), ( op_menu_pie("IMAGE_MT_uvs_snap_pie", {"type": 'S', "value": 'PRESS', "shift": True}) diff --git a/scripts/startup/bl_ui/space_image.py b/scripts/startup/bl_ui/space_image.py index 6c412af09dc..102d408bc1a 100644 --- a/scripts/startup/bl_ui/space_image.py +++ b/scripts/startup/bl_ui/space_image.py @@ -471,6 +471,10 @@ class IMAGE_MT_uvs(Menu): layout.operator_context = 'EXEC_REGION_WIN' layout.operator("uv.average_islands_scale") layout.operator("uv.arrange_islands") + layout.operator_context = 'INVOKE_REGION_WIN' + layout.operator("uv.custom_region_set") + layout.operator_context = 'EXEC_REGION_WIN' + layout.prop(context.tool_settings, "use_uv_custom_region", text="Custom Region", toggle=True) layout.separator() diff --git a/source/blender/editors/space_image/image_draw.cc b/source/blender/editors/space_image/image_draw.cc index 118f5209f72..310007e245a 100644 --- a/source/blender/editors/space_image/image_draw.cc +++ b/source/blender/editors/space_image/image_draw.cc @@ -449,6 +449,14 @@ void draw_image_main_helpers(const bContext *C, ARegion *region) ED_space_image_get_zoom(sima, region, &zoomx, &zoomy); draw_render_info(C, sima->iuser.scene, ima, region, zoomx, zoomy); } + + if (sima->mode == SI_MODE_UV) { + const Scene *scene = CTX_data_scene(C); + const ToolSettings *ts = scene->toolsettings; + if (ts->uv_flag & UV_FLAG_CUSTOM_REGION) { + draw_image_uv_custom_region(region, ts->uv_custom_region); + } + } } bool ED_space_image_show_cache(const SpaceImage *sima) @@ -606,3 +614,30 @@ float ED_space_image_increment_snap_value(const int grid_dimensions, /* Fallback */ return grid_steps[0]; } + +void draw_image_uv_custom_region(const ARegion *region, const rctf &custom_region) +{ + const uint shdr_pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", blender::gpu::VertAttrType::SFLOAT_32_32); + + GPU_line_width(1.0f); + + immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); + + float viewport_size[4]; + GPU_viewport_size_get_f(viewport_size); + immUniform2f("viewport_size", viewport_size[2] / UI_SCALE_FAC, viewport_size[3] / UI_SCALE_FAC); + + immUniform1i("colors_len", 0); /* "simple" mode */ + immUniform4f("color", 1.0f, 0.25f, 0.25f, 1.0f); + immUniform1f("dash_width", 6.0f); + immUniform1f("udash_factor", 0.5f); + rcti region_rect; + + UI_view2d_view_to_region_rcti(®ion->v2d, &custom_region, ®ion_rect); + + imm_draw_box_wire_2d( + shdr_pos, region_rect.xmin, region_rect.ymin, region_rect.xmax, region_rect.ymax); + + immUnbindProgram(); +} diff --git a/source/blender/editors/space_image/image_intern.hh b/source/blender/editors/space_image/image_intern.hh index d70b2fa65c0..0f141a82aca 100644 --- a/source/blender/editors/space_image/image_intern.hh +++ b/source/blender/editors/space_image/image_intern.hh @@ -16,6 +16,7 @@ struct SpaceImage; struct bContext; struct bNodeTree; struct wmOperatorType; +struct rctf; /* `space_image.cc` */ @@ -28,6 +29,7 @@ extern const char *image_context_dir[]; /* doc access */ void draw_image_main_helpers(const bContext *C, ARegion *region); void draw_image_cache(const bContext *C, ARegion *region); void draw_image_sample_line(SpaceImage *sima); +void draw_image_uv_custom_region(const ARegion *region, const rctf &custom_region); /* `image_ops.cc` */ diff --git a/source/blender/editors/uvedit/uvedit_intern.hh b/source/blender/editors/uvedit/uvedit_intern.hh index 611e670d6c4..87f707d32fe 100644 --- a/source/blender/editors/uvedit/uvedit_intern.hh +++ b/source/blender/editors/uvedit/uvedit_intern.hh @@ -157,5 +157,7 @@ void UV_OT_select_more(wmOperatorType *ot); void UV_OT_select_less(wmOperatorType *ot); void UV_OT_select_overlap(wmOperatorType *ot); void UV_OT_select_similar(wmOperatorType *ot); +void UV_OT_custom_region_set(wmOperatorType *ot); + /* Used only when UV sync select is disabled. */ void UV_OT_select_mode(wmOperatorType *ot); diff --git a/source/blender/editors/uvedit/uvedit_ops.cc b/source/blender/editors/uvedit/uvedit_ops.cc index 68d97a38dc9..aa2f811626c 100644 --- a/source/blender/editors/uvedit/uvedit_ops.cc +++ b/source/blender/editors/uvedit/uvedit_ops.cc @@ -2547,6 +2547,7 @@ void ED_operatortypes_uvedit() WM_operatortype_append(UV_OT_select_less); WM_operatortype_append(UV_OT_select_overlap); WM_operatortype_append(UV_OT_select_mode); + WM_operatortype_append(UV_OT_custom_region_set); WM_operatortype_append(UV_OT_snap_cursor); WM_operatortype_append(UV_OT_snap_selected); diff --git a/source/blender/editors/uvedit/uvedit_select.cc b/source/blender/editors/uvedit/uvedit_select.cc index 0d3c763880f..04e9096c40f 100644 --- a/source/blender/editors/uvedit/uvedit_select.cc +++ b/source/blender/editors/uvedit/uvedit_select.cc @@ -5873,4 +5873,38 @@ void UV_OT_select_mode(wmOperatorType *ot) RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } +static wmOperatorStatus uv_custom_region_set_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + const ARegion *region = CTX_wm_region(C); + ToolSettings *ts = scene->toolsettings; + + WM_operator_properties_border_to_rctf(op, &ts->uv_custom_region); + UI_view2d_region_to_view_rctf(®ion->v2d, &ts->uv_custom_region, &ts->uv_custom_region); + ts->uv_flag |= UV_FLAG_CUSTOM_REGION; + + return OPERATOR_FINISHED; +} + +void UV_OT_custom_region_set(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Set User Region"; + ot->description = "Set the boundaries of the user region"; + ot->idname = "UV_OT_custom_region_set"; + + /* API callbacks. */ + ot->invoke = WM_gesture_box_invoke; + ot->exec = uv_custom_region_set_exec; + ot->modal = WM_gesture_box_modal; + ot->poll = ED_operator_uvedit_space_image; + ot->cancel = WM_gesture_box_cancel; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + WM_operator_properties_gesture_box(ot); +} + /** \} */ diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.cc b/source/blender/editors/uvedit/uvedit_unwrap_ops.cc index e42a2218f88..0157fc7194d 100644 --- a/source/blender/editors/uvedit/uvedit_unwrap_ops.cc +++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.cc @@ -1438,6 +1438,7 @@ static void uvedit_pack_islands_multi(const Scene *scene, const SpaceImage *udim_source_closest, const bool original_selection, const bool notify_wm, + const rctf *custom_region, blender::geometry::UVPackIsland_Params *params) { blender::Vector island_vector; @@ -1526,6 +1527,17 @@ static void uvedit_pack_islands_multi(const Scene *scene, (selection_max_co[1] - selection_min_co[1]); } } + else if (custom_region) { + if (!BLI_rctf_is_empty(custom_region)) { + const blender::float2 custom_region_size = { + BLI_rctf_size_x(custom_region), + BLI_rctf_size_y(custom_region), + }; + ARRAY_SET_ITEMS(params->udim_base_offset, custom_region->xmin, custom_region->ymin); + params->target_extent = custom_region_size.y; + params->target_aspect_y = custom_region_size.x / custom_region_size.y; + } + } MemArena *arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); Heap *heap = BLI_heap_new(); @@ -1656,6 +1668,7 @@ enum { PACK_UDIM_SRC_CLOSEST = 0, PACK_UDIM_SRC_ACTIVE, PACK_ORIGINAL_AABB, + PACK_CUSTOM_REGION, }; struct UVPackIslandsData { @@ -1672,6 +1685,7 @@ struct UVPackIslandsData { bool use_job; blender::geometry::UVPackIsland_Params pack_island_params; + rctf custom_region; }; static void pack_islands_startjob(void *pidv, wmJobWorkerStatus *worker_status) @@ -1690,6 +1704,8 @@ static void pack_islands_startjob(void *pidv, wmJobWorkerStatus *worker_status) (pid->udim_source == PACK_UDIM_SRC_CLOSEST) ? pid->sima : nullptr, (pid->udim_source == PACK_ORIGINAL_AABB), !pid->use_job, + (pid->udim_source == PACK_CUSTOM_REGION) ? &pid->custom_region : + nullptr, &pid->pack_island_params); worker_status->progress = 0.99f; @@ -1723,6 +1739,7 @@ static wmOperatorStatus pack_islands_exec(bContext *C, wmOperator *op) ViewLayer *view_layer = CTX_data_view_layer(C); const Scene *scene = CTX_data_scene(C); const SpaceImage *sima = CTX_wm_space_image(C); + const ToolSettings *ts = scene->toolsettings; UnwrapOptions options = unwrap_options_get(op, nullptr, scene->toolsettings); options.topology_from_uvs = true; @@ -1756,6 +1773,19 @@ static wmOperatorStatus pack_islands_exec(bContext *C, wmOperator *op) pid->udim_source = udim_source; pid->wm = CTX_wm_manager(C); + if (udim_source == PACK_CUSTOM_REGION) { + if (ts->uv_flag & UV_FLAG_CUSTOM_REGION) { + pid->custom_region = ts->uv_custom_region; + } + else { + pid->custom_region.xmin = pid->custom_region.ymin = 0.0f; + pid->custom_region.xmax = pid->custom_region.ymax = 1.0f; + } + } + else { + pid->custom_region = {0.0f}; + } + blender::geometry::UVPackIsland_Params &pack_island_params = pid->pack_island_params; { /* Call default constructor and copy the defaults. */ @@ -1937,6 +1967,7 @@ void UV_OT_pack_islands(wmOperatorType *ot) 0, "Original bounding box", "Pack to starting bounding box of islands"}, + {PACK_CUSTOM_REGION, "CUSTOM_REGION", 0, "Custom Region", "Pack islands to custom region"}, {0, nullptr, 0, nullptr, nullptr}, }; /* identifiers */ @@ -2763,7 +2794,8 @@ void ED_uvedit_live_unwrap(const Scene *scene, const Span objects) pack_island_params.margin_method = ED_UVPACK_MARGIN_SCALED; pack_island_params.margin = scene->toolsettings->uvcalc_margin; - uvedit_pack_islands_multi(scene, objects, nullptr, nullptr, false, true, &pack_island_params); + uvedit_pack_islands_multi( + scene, objects, nullptr, nullptr, false, true, nullptr, &pack_island_params); } } @@ -2864,7 +2896,8 @@ static wmOperatorStatus unwrap_exec(bContext *C, wmOperator *op) RNA_enum_get(op->ptr, "margin_method")); pack_island_params.margin = RNA_float_get(op->ptr, "margin"); - uvedit_pack_islands_multi(scene, objects, nullptr, nullptr, false, true, &pack_island_params); + uvedit_pack_islands_multi( + scene, objects, nullptr, nullptr, false, true, nullptr, &pack_island_params); if (count_failed == 0 && count_changed == 0) { BKE_report(op->reports, @@ -3323,7 +3356,8 @@ static wmOperatorStatus smart_project_exec(bContext *C, wmOperator *op) params.margin_method = eUVPackIsland_MarginMethod(RNA_enum_get(op->ptr, "margin_method")); params.margin = RNA_float_get(op->ptr, "island_margin"); - uvedit_pack_islands_multi(scene, objects_changed, nullptr, nullptr, false, true, ¶ms); + uvedit_pack_islands_multi( + scene, objects_changed, nullptr, nullptr, false, true, nullptr, ¶ms); /* #uvedit_pack_islands_multi only supports `per_face_aspect = false`. */ const bool per_face_aspect = false; @@ -4302,7 +4336,7 @@ void ED_uvedit_add_simple_uvs(Main *bmain, const Scene *scene, Object *ob) params.margin_method = ED_UVPACK_MARGIN_SCALED; params.margin = 0.001f; - uvedit_pack_islands_multi(scene, {ob}, &bm, nullptr, false, true, ¶ms); + uvedit_pack_islands_multi(scene, {ob}, &bm, nullptr, false, true, nullptr, ¶ms); /* Write back from BMesh to Mesh. */ BMeshToMeshParams bm_to_me_params{}; diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 09b05694865..1e023598416 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1684,6 +1684,8 @@ typedef struct ToolSettings { char uv_selectmode; char uv_sticky; + rctf uv_custom_region; + float uvcalc_margin; int uvcalc_iterations; @@ -2800,6 +2802,7 @@ enum { * selection should be used - since not all combinations of options support it. */ UV_FLAG_ISLAND_SELECT = 1 << 2, + UV_FLAG_CUSTOM_REGION = 1 << 3, }; /** #ToolSettings::uv_selectmode */ diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index 279a96e0674..391e3c54e89 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -4224,6 +4224,12 @@ static void rna_def_tool_settings(BlenderRNA *brna) prop, "UV Local View", "Display only faces with the currently displayed image assigned"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, nullptr); + prop = RNA_def_property(srna, "use_uv_custom_region", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "uv_flag", UV_FLAG_CUSTOM_REGION); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + RNA_def_property_ui_text(prop, "UV Custom Region", "Custom defined region"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, nullptr); + /* Mesh */ prop = RNA_def_property(srna, "mesh_select_mode", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_bitset_array_sdna(prop, nullptr, "selectmode", 1 << 0, 3); diff --git a/source/blender/windowmanager/intern/wm_operators.cc b/source/blender/windowmanager/intern/wm_operators.cc index 966acb913ab..8e92f3595b7 100644 --- a/source/blender/windowmanager/intern/wm_operators.cc +++ b/source/blender/windowmanager/intern/wm_operators.cc @@ -4364,6 +4364,7 @@ static void gesture_box_modal_keymap(wmKeyConfig *keyconf) WM_modalkeymap_assign(keymap, "SEQUENCER_OT_select_box"); WM_modalkeymap_assign(keymap, "SEQUENCER_OT_view_ghost_border"); WM_modalkeymap_assign(keymap, "UV_OT_select_box"); + WM_modalkeymap_assign(keymap, "UV_OT_custom_region_set"); WM_modalkeymap_assign(keymap, "CLIP_OT_select_box"); WM_modalkeymap_assign(keymap, "CLIP_OT_graph_select_box"); WM_modalkeymap_assign(keymap, "MASK_OT_select_box");