diff options
23 files changed, 1349 insertions, 306 deletions
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 6f4f862a3b8..83946fbf68f 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -385,9 +385,9 @@ def _template_items_uv_select_mode(params): *_template_items_editmode_mesh_select_mode(params), # Hack to prevent fall-through, when sync select isn't enabled (and the island button isn't visible). ("mesh.select_mode", {"type": 'FOUR', "value": 'PRESS'}, None), - *(("wm.context_set_enum", {"type": NUMBERS_1[i], "value": 'PRESS'}, - {"properties": [("data_path", 'tool_settings.uv_select_mode'), ("value", ty)]}) - for i, ty in enumerate(('VERTEX', 'EDGE', 'FACE', 'ISLAND'))) + *(("uv.select_mode", {"type": k, "value": 'PRESS'}, + {"properties": [("type", e)]}) + for k, e in (('ONE', 'VERTEX'), ('TWO', 'EDGE'), ('THREE', 'FACE'), ('FOUR', 'ISLAND'))) ] diff --git a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index e65ac32d088..8c303bc3f69 100644 --- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -554,14 +554,14 @@ def km_uv_editor(params): ("wm.search_menu", {"type": 'TAB', "value": 'PRESS'}, None), # Selection modes. *_template_items_editmode_mesh_select_mode(params), - ("wm.context_set_enum", {"type": 'ONE', "value": 'PRESS'}, - {"properties": [("data_path", 'tool_settings.uv_select_mode'), ("value", 'VERTEX')]}), - ("wm.context_set_enum", {"type": 'TWO', "value": 'PRESS'}, - {"properties": [("data_path", 'tool_settings.uv_select_mode'), ("value", 'EDGE')]}), - ("wm.context_set_enum", {"type": 'THREE', "value": 'PRESS'}, - {"properties": [("data_path", 'tool_settings.uv_select_mode'), ("value", 'FACE')]}), - ("wm.context_set_enum", {"type": 'FOUR', "value": 'PRESS'}, - {"properties": [("data_path", 'tool_settings.uv_select_mode'), ("value", 'ISLAND')]}), + ("uv.select_mode", {"type": 'ONE', "value": 'PRESS'}, + {"properties": [("type", 'VERTEX')]}), + ("uv.select_mode", {"type": 'TWO', "value": 'PRESS'}, + {"properties": [("type", 'EDGE')]}), + ("uv.select_mode", {"type": 'THREE', "value": 'PRESS'}, + {"properties": [("type", 'FACE')]}), + ("uv.select_mode", {"type": 'FOUR', "value": 'PRESS'}, + {"properties": [("type", 'ISLAND')]}), ("uv.select", {"type": 'LEFTMOUSE', "value": 'CLICK'}, {"properties": [("extend", False), ("deselect_all", True)]}), diff --git a/release/scripts/startup/bl_ui/space_image.py b/release/scripts/startup/bl_ui/space_image.py index 5b840fae341..0aac224bc68 100644 --- a/release/scripts/startup/bl_ui/space_image.py +++ b/release/scripts/startup/bl_ui/space_image.py @@ -779,7 +779,17 @@ class IMAGE_HT_header(Header): if tool_settings.use_uv_select_sync: layout.template_edit_mode_selection() else: - layout.prop(tool_settings, "uv_select_mode", text="", expand=True) + row = layout.row(align=True) + uv_select_mode = tool_settings.uv_select_mode[:] + row.operator("uv.select_mode", text="", icon='UV_VERTEXSEL', + depress=(uv_select_mode == 'VERTEX')).type = 'VERTEX' + row.operator("uv.select_mode", text="", icon='UV_EDGESEL', + depress=(uv_select_mode == 'EDGE')).type = 'EDGE' + row.operator("uv.select_mode", text="", icon='UV_FACESEL', + depress=(uv_select_mode == 'FACE')).type = 'FACE' + row.operator("uv.select_mode", text="", icon='UV_ISLANDSEL', + depress=(uv_select_mode == 'ISLAND')).type = 'ISLAND' + layout.prop(tool_settings, "uv_sticky_select_mode", icon_only=True) IMAGE_MT_editor_menus.draw_collapsible(context, layout) diff --git a/source/blender/bmesh/tools/bmesh_path_uv.c b/source/blender/bmesh/tools/bmesh_path_uv.c index a2b35839454..76697f51ac7 100644 --- a/source/blender/bmesh/tools/bmesh_path_uv.c +++ b/source/blender/bmesh/tools/bmesh_path_uv.c @@ -185,7 +185,8 @@ struct LinkNode *BM_mesh_calc_path_uv_vert(BMesh *bm, /** \name BM_mesh_calc_path_uv_edge * \{ */ -/* TODO(campbell): not very urgent, since the operator fakes this using vertex path. */ +/* TODO(@sidd017): Setting this as todo, since we now support proper UV edge selection (D12028). + * Till then, continue using vertex path to fake shortest path calculation for edges. */ /** \} */ diff --git a/source/blender/draw/engines/overlay/overlay_edit_uv.c b/source/blender/draw/engines/overlay/overlay_edit_uv.c index 8f566970337..93c75bde1e7 100644 --- a/source/blender/draw/engines/overlay/overlay_edit_uv.c +++ b/source/blender/draw/engines/overlay/overlay_edit_uv.c @@ -113,6 +113,11 @@ void OVERLAY_edit_uv_init(OVERLAY_Data *vedata) const bool do_uv_overlay = is_image_type && is_uv_editor && has_edit_object; const bool show_modified_uvs = sima->flag & SI_DRAWSHADOW; const bool is_tiled_image = image && (image->source == IMA_SRC_TILED); + const bool do_edges_only = (ts->uv_flag & UV_SYNC_SELECTION) ? + /* NOTE: Ignore #SCE_SELECT_EDGE because a single selected edge + * on the mesh may cause singe UV vertices to be selected. */ + false : + (ts->uv_selectmode == UV_SELECT_EDGE); const bool do_faces = ((sima->flag & SI_NO_DRAWFACES) == 0); const bool do_face_dots = (ts->uv_flag & UV_SYNC_SELECTION) ? (ts->selectmode & SCE_SELECT_FACE) != 0 : @@ -124,6 +129,7 @@ void OVERLAY_edit_uv_init(OVERLAY_Data *vedata) (brush->imagepaint_tool == PAINT_TOOL_CLONE) && brush->clone.image; + pd->edit_uv.do_verts = show_overlays && (!do_edges_only); pd->edit_uv.do_faces = show_overlays && do_faces && !do_uvstretching_overlay; pd->edit_uv.do_face_dots = show_overlays && do_faces && do_face_dots; pd->edit_uv.do_uv_overlay = show_overlays && do_uv_overlay; @@ -183,7 +189,11 @@ void OVERLAY_edit_uv_cache_init(OVERLAY_Data *vedata) DRW_PASS_CREATE(psl->edit_uv_edges_ps, DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS_EQUAL | DRW_STATE_BLEND_ALPHA); - GPUShader *sh = OVERLAY_shader_edit_uv_edges_get(); + const bool do_edges_only = (ts->uv_flag & UV_SYNC_SELECTION) ? + false : + (ts->uv_selectmode & UV_SELECT_EDGE); + GPUShader *sh = do_edges_only ? OVERLAY_shader_edit_uv_edges_for_edge_select_get() : + OVERLAY_shader_edit_uv_edges_get(); if (pd->edit_uv.do_uv_shadow_overlay) { pd->edit_uv_shadow_edges_grp = DRW_shgroup_create(sh, psl->edit_uv_edges_ps); DRW_shgroup_uniform_block(pd->edit_uv_shadow_edges_grp, "globalsBlock", G_draw.block_ubo); @@ -211,11 +221,14 @@ void OVERLAY_edit_uv_cache_init(OVERLAY_Data *vedata) } if (pd->edit_uv.do_uv_overlay) { - /* uv verts */ - { + if (pd->edit_uv.do_verts || pd->edit_uv.do_face_dots) { DRW_PASS_CREATE(psl->edit_uv_verts_ps, DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS_EQUAL | DRW_STATE_BLEND_ALPHA); + } + + /* uv verts */ + if (pd->edit_uv.do_verts) { GPUShader *sh = OVERLAY_shader_edit_uv_verts_get(); pd->edit_uv_verts_grp = DRW_shgroup_create(sh, psl->edit_uv_verts_ps); @@ -430,9 +443,11 @@ static void overlay_edit_uv_cache_populate(OVERLAY_Data *vedata, Object *ob) if (geom) { DRW_shgroup_call_obmat(pd->edit_uv_edges_grp, geom, NULL); } - geom = DRW_mesh_batch_cache_get_edituv_verts(ob, ob->data); - if (geom) { - DRW_shgroup_call_obmat(pd->edit_uv_verts_grp, geom, NULL); + if (pd->edit_uv.do_verts) { + geom = DRW_mesh_batch_cache_get_edituv_verts(ob, ob->data); + if (geom) { + DRW_shgroup_call_obmat(pd->edit_uv_verts_grp, geom, NULL); + } } if (pd->edit_uv.do_faces) { geom = DRW_mesh_batch_cache_get_edituv_faces(ob, ob->data); diff --git a/source/blender/draw/engines/overlay/overlay_private.h b/source/blender/draw/engines/overlay/overlay_private.h index 3cc70527659..94eccbcb1f3 100644 --- a/source/blender/draw/engines/overlay/overlay_private.h +++ b/source/blender/draw/engines/overlay/overlay_private.h @@ -359,6 +359,7 @@ typedef struct OVERLAY_PrivateData { bool do_stencil_overlay; bool do_mask_overlay; + bool do_verts; bool do_faces; bool do_face_dots; @@ -699,6 +700,7 @@ GPUShader *OVERLAY_shader_edit_mesh_vert(void); GPUShader *OVERLAY_shader_edit_particle_strand(void); GPUShader *OVERLAY_shader_edit_particle_point(void); GPUShader *OVERLAY_shader_edit_uv_edges_get(void); +GPUShader *OVERLAY_shader_edit_uv_edges_for_edge_select_get(void); GPUShader *OVERLAY_shader_edit_uv_face_get(void); GPUShader *OVERLAY_shader_edit_uv_face_dots_get(void); GPUShader *OVERLAY_shader_edit_uv_verts_get(void); diff --git a/source/blender/draw/engines/overlay/overlay_shader.c b/source/blender/draw/engines/overlay/overlay_shader.c index da2c2cff2f6..639b20feae4 100644 --- a/source/blender/draw/engines/overlay/overlay_shader.c +++ b/source/blender/draw/engines/overlay/overlay_shader.c @@ -167,6 +167,7 @@ typedef struct OVERLAY_Shaders { GPUShader *edit_uv_verts; GPUShader *edit_uv_faces; GPUShader *edit_uv_edges; + GPUShader *edit_uv_edges_for_edge_select; GPUShader *edit_uv_face_dots; GPUShader *edit_uv_stretching_angle; GPUShader *edit_uv_stretching_area; @@ -1605,6 +1606,20 @@ GPUShader *OVERLAY_shader_edit_uv_edges_get(void) return sh_data->edit_uv_edges; } +GPUShader *OVERLAY_shader_edit_uv_edges_for_edge_select_get(void) +{ + OVERLAY_Shaders *sh_data = &e_data.sh_data[0]; + if (!sh_data->edit_uv_edges_for_edge_select) { + sh_data->edit_uv_edges_for_edge_select = DRW_shader_create_with_shaderlib( + datatoc_edit_uv_edges_vert_glsl, + datatoc_edit_uv_edges_geom_glsl, + datatoc_edit_uv_edges_frag_glsl, + e_data.lib, + "#define USE_EDGE_SELECT\n"); + } + return sh_data->edit_uv_edges_for_edge_select; +} + GPUShader *OVERLAY_shader_edit_uv_face_get(void) { OVERLAY_Shaders *sh_data = &e_data.sh_data[0]; diff --git a/source/blender/draw/engines/overlay/shaders/edit_uv_edges_frag.glsl b/source/blender/draw/engines/overlay/shaders/edit_uv_edges_frag.glsl index 8f90c1acbbb..b6793e5ab28 100644 --- a/source/blender/draw/engines/overlay/shaders/edit_uv_edges_frag.glsl +++ b/source/blender/draw/engines/overlay/shaders/edit_uv_edges_frag.glsl @@ -35,7 +35,13 @@ void main() float line_distance = distance(stipplePos_f, stippleStart_f) / max(dd.x, dd.y); if (lineStyle == OVERLAY_UV_LINE_STYLE_OUTLINE) { +#ifdef USE_EDGE_SELECT + /* TODO(@campbellbarton): The current wire-edit color contrast enough against the selection. + * Look into changing the default theme color instead of reducing contrast with edge-select. */ + inner_color = (selectionFac_f != 0.0) ? colorEdgeSelect : (colorWireEdit * 0.5); +#else inner_color = mix(colorWireEdit, colorEdgeSelect, selectionFac_f); +#endif outer_color = vec4(vec3(0.0), 1.0); } else if (lineStyle == OVERLAY_UV_LINE_STYLE_DASH) { diff --git a/source/blender/draw/engines/overlay/shaders/edit_uv_edges_geom.glsl b/source/blender/draw/engines/overlay/shaders/edit_uv_edges_geom.glsl index 4f8d553a220..089cfdaa710 100644 --- a/source/blender/draw/engines/overlay/shaders/edit_uv_edges_geom.glsl +++ b/source/blender/draw/engines/overlay/shaders/edit_uv_edges_geom.glsl @@ -53,11 +53,19 @@ void main() vec2 line_dir = normalize(line); vec2 line_perp = vec2(-line_dir.y, line_dir.x); vec2 edge_ofs = line_perp * sizeViewportInv * ceil(half_size); +#ifdef USE_EDGE_SELECT + /* No blending with edge selection. */ + float selectFac0 = selectionFac[0]; + float selectFac1 = selectionFac[0]; +#else + float selectFac0 = selectionFac[0]; + float selectFac1 = selectionFac[1]; +#endif - do_vertex(pos0, selectionFac[0], stippleStart[0], stipplePos[0], half_size, edge_ofs.xy); - do_vertex(pos0, selectionFac[0], stippleStart[0], stipplePos[0], -half_size, -edge_ofs.xy); - do_vertex(pos1, selectionFac[1], stippleStart[1], stipplePos[1], half_size, edge_ofs.xy); - do_vertex(pos1, selectionFac[1], stippleStart[1], stipplePos[1], -half_size, -edge_ofs.xy); + do_vertex(pos0, selectFac0, stippleStart[0], stipplePos[0], half_size, edge_ofs.xy); + do_vertex(pos0, selectFac0, stippleStart[0], stipplePos[0], -half_size, -edge_ofs.xy); + do_vertex(pos1, selectFac1, stippleStart[1], stipplePos[1], half_size, edge_ofs.xy); + do_vertex(pos1, selectFac1, stippleStart[1], stipplePos[1], -half_size, -edge_ofs.xy); EndPrimitive(); } diff --git a/source/blender/draw/engines/overlay/shaders/edit_uv_edges_vert.glsl b/source/blender/draw/engines/overlay/shaders/edit_uv_edges_vert.glsl index 7627a287a05..a291b93603c 100644 --- a/source/blender/draw/engines/overlay/shaders/edit_uv_edges_vert.glsl +++ b/source/blender/draw/engines/overlay/shaders/edit_uv_edges_vert.glsl @@ -19,7 +19,11 @@ void main() gl_Position.xy = floor(gl_Position.xy * half_viewport_res) / half_viewport_res + half_pixel_offset; +#ifdef USE_EDGE_SELECT + bool is_select = (flag & EDGE_UV_SELECT) != 0; +#else bool is_select = (flag & VERT_UV_SELECT) != 0; +#endif selectionFac = is_select ? 1.0 : 0.0; /* Move selected edges to the top * Vertices are between 0.0 and 0.2, Edges between 0.2 and 0.4 diff --git a/source/blender/editors/include/ED_uvedit.h b/source/blender/editors/include/ED_uvedit.h index 26af378b1b7..cf4d023ccdf 100644 --- a/source/blender/editors/include/ED_uvedit.h +++ b/source/blender/editors/include/ED_uvedit.h @@ -25,6 +25,7 @@ struct Scene; struct SpaceImage; struct ToolSettings; struct ViewLayer; +struct bContext; struct bNode; struct bNodeTree; struct wmKeyConfig; @@ -80,7 +81,8 @@ void ED_object_assign_active_image(struct Main *bmain, bool ED_uvedit_test(struct Object *obedit); -/* visibility and selection */ +/* Visibility and selection tests. */ + bool uvedit_face_visible_test_ex(const struct ToolSettings *ts, struct BMFace *efa); bool uvedit_face_select_test_ex(const struct ToolSettings *ts, struct BMFace *efa, @@ -91,24 +93,50 @@ bool uvedit_edge_select_test_ex(const struct ToolSettings *ts, bool uvedit_uv_select_test_ex(const struct ToolSettings *ts, struct BMLoop *l, int cd_loop_uv_offset); - bool uvedit_face_visible_test(const struct Scene *scene, struct BMFace *efa); bool uvedit_face_select_test(const struct Scene *scene, struct BMFace *efa, int cd_loop_uv_offset); bool uvedit_edge_select_test(const struct Scene *scene, struct BMLoop *l, int cd_loop_uv_offset); bool uvedit_uv_select_test(const struct Scene *scene, struct BMLoop *l, int cd_loop_uv_offset); -/* uv face */ -void uvedit_face_select_set_with_sticky(const struct Scene *scene, - struct BMEditMesh *em, - struct BMFace *efa, - bool select, - bool do_history, - int cd_loop_uv_offset); + +/* Individual UV element selection functions. */ + +/** + * \brief Select UV Face + * + * Changes selection state of a single UV Face. + */ void uvedit_face_select_set(const struct Scene *scene, struct BMEditMesh *em, struct BMFace *efa, bool select, bool do_history, int cd_loop_uv_offset); +/** + * \brief Select UV Edge + * + * Changes selection state of a single UV Edge. + */ +void uvedit_edge_select_set(const struct Scene *scene, + struct BMEditMesh *em, + struct BMLoop *l, + bool select, + bool do_history, + int cd_loop_uv_offset); +/** + * \brief Select UV Vertex + * + * Changes selection state of a single UV vertex. + */ +void uvedit_uv_select_set(const struct Scene *scene, + struct BMEditMesh *em, + struct BMLoop *l, + bool select, + bool do_history, + int cd_loop_uv_offset); + +/* Low level functions for (de)selecting individual UV elements. Ensure UV face visibility before + * use. */ + void uvedit_face_select_enable(const struct Scene *scene, struct BMEditMesh *em, struct BMFace *efa, @@ -118,19 +146,6 @@ void uvedit_face_select_disable(const struct Scene *scene, struct BMEditMesh *em, struct BMFace *efa, int cd_loop_uv_offset); -/* uv edge */ -void uvedit_edge_select_set_with_sticky(const struct Scene *scene, - struct BMEditMesh *em, - struct BMLoop *l, - bool select, - bool do_history, - uint cd_loop_uv_offset); -void uvedit_edge_select_set(const struct Scene *scene, - struct BMEditMesh *em, - struct BMLoop *l, - bool select, - bool do_history, - int cd_loop_uv_offset); void uvedit_edge_select_enable(const struct Scene *scene, struct BMEditMesh *em, struct BMLoop *l, @@ -140,19 +155,6 @@ void uvedit_edge_select_disable(const struct Scene *scene, struct BMEditMesh *em, struct BMLoop *l, int cd_loop_uv_offset); -/* uv vert */ -void uvedit_uv_select_set_with_sticky(const struct Scene *scene, - struct BMEditMesh *em, - struct BMLoop *l, - bool select, - bool do_history, - uint cd_loop_uv_offset); -void uvedit_uv_select_set(const struct Scene *scene, - struct BMEditMesh *em, - struct BMLoop *l, - bool select, - bool do_history, - int cd_loop_uv_offset); void uvedit_uv_select_enable(const struct Scene *scene, struct BMEditMesh *em, struct BMLoop *l, @@ -163,6 +165,83 @@ void uvedit_uv_select_disable(const struct Scene *scene, struct BMLoop *l, int cd_loop_uv_offset); +/* Sticky mode UV element selection functions. */ + +void uvedit_face_select_set_with_sticky(const struct Scene *scene, + struct BMEditMesh *em, + struct BMFace *efa, + bool select, + bool do_history, + int cd_loop_uv_offset); +void uvedit_edge_select_set_with_sticky(const struct Scene *scene, + struct BMEditMesh *em, + struct BMLoop *l, + bool select, + bool do_history, + uint cd_loop_uv_offset); +void uvedit_uv_select_set_with_sticky(const struct Scene *scene, + struct BMEditMesh *em, + struct BMLoop *l, + bool select, + bool do_history, + uint cd_loop_uv_offset); + +/* Low level functions for sticky element selection (sticky mode independent). Type of sticky + * selection is specified explicitly (using sticky_flag, except for face selection). */ + +void uvedit_face_select_shared_vert(const struct Scene *scene, + struct BMEditMesh *em, + struct BMFace *efa, + const bool select, + const bool do_history, + const int cd_loop_uv_offset); +void uvedit_edge_select_shared_vert(const struct Scene *scene, + struct BMEditMesh *em, + struct BMLoop *l, + const bool select, + const int sticky_flag, + const bool do_history, + const int cd_loop_uv_offset); +void uvedit_uv_select_shared_vert(const struct Scene *scene, + struct BMEditMesh *em, + struct BMLoop *l, + const bool select, + const int sticky_flag, + const bool do_history, + const int cd_loop_uv_offset); + +/* Sets required UV edge flags as specified by the sticky_flag. */ +void uvedit_edge_select_set_noflush(const struct Scene *scene, + struct BMLoop *l, + const bool select, + const int sticky_flag, + const int cd_loop_uv_offset); + +/** + * \brief UV Select Mode set + * + * Updates selection state for UVs based on the select mode and sticky mode. Similar to + * #EDBM_selectmode_set. + */ +void ED_uvedit_selectmode_clean(struct Scene *scene, struct Object *obedit); +void ED_uvedit_selectmode_clean_multi(struct bContext *C); + +/** + * \brief UV Select Mode Flush + * + * Flushes selections upwards as dictated by the UV select mode. + */ +void ED_uvedit_selectmode_flush(struct Scene *scene, struct BMEditMesh *em); + +/** + * Mode independent UV de-selection flush. + */ +void uvedit_deselect_flush(struct Scene *scene, struct BMEditMesh *em); +/** + * Mode independent UV selection flush. + */ +void uvedit_select_flush(struct Scene *scene, struct BMEditMesh *em); + bool ED_uvedit_nearest_uv(const struct Scene *scene, struct Object *obedit, const float co[2], diff --git a/source/blender/editors/mesh/editmesh_undo.c b/source/blender/editors/mesh/editmesh_undo.c index 417fdca4988..97f67060c82 100644 --- a/source/blender/editors/mesh/editmesh_undo.c +++ b/source/blender/editors/mesh/editmesh_undo.c @@ -88,6 +88,7 @@ typedef struct UndoMesh { Mesh me; int selectmode; + char uv_selectmode; /** \note * This isn't a perfect solution, if you edit keys and change shapes this works well @@ -769,6 +770,7 @@ static bool mesh_undosys_step_encode(struct bContext *C, struct Main *bmain, Und /* Important not to use the 3D view when getting objects because all objects * outside of this list will be moved out of edit-mode when reading back undo steps. */ ViewLayer *view_layer = CTX_data_view_layer(C); + ToolSettings *ts = CTX_data_tool_settings(C); uint objects_len = 0; Object **objects = ED_undo_editmode_objects_from_view_layer(view_layer, &objects_len); @@ -792,6 +794,7 @@ static bool mesh_undosys_step_encode(struct bContext *C, struct Main *bmain, Und &elem->data, me->edit_mesh, me->key, um_references ? um_references[i] : NULL); em->needs_flush_to_id = 1; us->step.data_size += elem->data.undo_size; + elem->data.uv_selectmode = ts->uv_selectmode; #ifdef USE_ARRAY_STORE /** As this is only data storage it is safe to set the session ID here. */ @@ -849,6 +852,7 @@ static void mesh_undosys_step_decode(struct bContext *C, Scene *scene = CTX_data_scene(C); scene->toolsettings->selectmode = us->elems[0].data.selectmode; + scene->toolsettings->uv_selectmode = us->elems[0].data.uv_selectmode; bmain->is_memfile_undo_flush_needed = true; diff --git a/source/blender/editors/uvedit/uvedit_intern.h b/source/blender/editors/uvedit/uvedit_intern.h index 42f0a32ea6f..b2a411147a5 100644 --- a/source/blender/editors/uvedit/uvedit_intern.h +++ b/source/blender/editors/uvedit/uvedit_intern.h @@ -70,11 +70,13 @@ bool uv_find_nearest_vert_multi(struct Scene *scene, bool uv_find_nearest_edge(struct Scene *scene, struct Object *obedit, const float co[2], + float penalty, struct UvNearestHit *hit); bool uv_find_nearest_edge_multi(struct Scene *scene, struct Object **objects, uint objects_len, const float co[2], + float penalty, struct UvNearestHit *hit); /** @@ -116,6 +118,16 @@ BMLoop *uv_find_nearest_loop_from_edge(struct Scene *scene, struct BMEdge *e, const float co[2]); +bool uvedit_vert_is_edge_select_any_other(const struct Scene *scene, + struct BMLoop *l, + const int cd_loop_uv_offset); +bool uvedit_vert_is_face_select_any_other(const struct Scene *scene, + struct BMLoop *l, + const int cd_loop_uv_offset); +bool uvedit_vert_is_all_other_faces_selected(const struct Scene *scene, + struct BMLoop *l, + const int cd_loop_uv_offset); + /* utility tool functions */ void uvedit_live_unwrap_update(struct SpaceImage *sima, @@ -169,3 +181,5 @@ void UV_OT_select_circle(struct wmOperatorType *ot); void UV_OT_select_more(struct wmOperatorType *ot); void UV_OT_select_less(struct wmOperatorType *ot); void UV_OT_select_overlap(struct wmOperatorType *ot); +/* Used only when UV sync select is disabled. */ +void UV_OT_select_mode(struct wmOperatorType *ot); diff --git a/source/blender/editors/uvedit/uvedit_ops.c b/source/blender/editors/uvedit/uvedit_ops.c index 0742b612b97..cbf0522c328 100644 --- a/source/blender/editors/uvedit/uvedit_ops.c +++ b/source/blender/editors/uvedit/uvedit_ops.c @@ -249,7 +249,7 @@ void ED_uvedit_select_all(BMesh *bm) BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - luv->flag |= MLOOPUV_VERTSEL; + luv->flag |= (MLOOPUV_VERTSEL | MLOOPUV_EDGESEL); } } } @@ -1406,12 +1406,15 @@ static void UV_OT_pin(wmOperatorType *ot) /** \name Hide Operator * \{ */ -/* check if we are selected or unselected based on 'bool_test' arg, - * needed for select swap support */ -#define UV_SEL_TEST(luv, bool_test) \ +/* Check if vertex/edge is selected or unselected based on #bool_test arg. Needed for select swap + * support */ +#define UV_VERT_SEL_TEST(luv, bool_test) \ ((((luv)->flag & MLOOPUV_VERTSEL) == MLOOPUV_VERTSEL) == bool_test) -/* is every UV vert selected or unselected depending on bool_test */ +#define UV_EDGE_SEL_TEST(luv, bool_test) \ + ((((luv)->flag & MLOOPUV_EDGESEL) == MLOOPUV_EDGESEL) == bool_test) + +/* Is the specified UV face, selected or unselected depending on bool_test. */ static bool bm_face_is_all_uv_sel(BMFace *f, bool select_test, const int cd_loop_uv_offset) { BMLoop *l_iter; @@ -1420,7 +1423,7 @@ static bool bm_face_is_all_uv_sel(BMFace *f, bool select_test, const int cd_loop l_iter = l_first = BM_FACE_FIRST_LOOP(f); do { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); - if (!UV_SEL_TEST(luv, select_test)) { + if (!UV_EDGE_SEL_TEST(luv, select_test)) { return false; } } while ((l_iter = l_iter->next) != l_first); @@ -1472,19 +1475,17 @@ static int uv_hide_exec(bContext *C, wmOperator *op) BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - if (UV_SEL_TEST(luv, !swap)) { + if (UV_VERT_SEL_TEST(luv, !swap) || UV_EDGE_SEL_TEST(luv, !swap)) { hide = 1; break; } } if (hide) { - /* NOTE: a special case for edges could be used, - * for now edges act like verts and get flushed */ if (use_face_center) { if (em->selectmode == SCE_SELECT_FACE) { - /* check that every UV is selected */ - if (bm_face_is_all_uv_sel(efa, true, cd_loop_uv_offset) == !swap) { + /* Deselect BMesh face if UV face is (de)selected depending on #swap. */ + if (bm_face_is_all_uv_sel(efa, !swap, cd_loop_uv_offset)) { BM_face_select_set(em->bm, efa, false); } uvedit_face_select_disable(scene, em, efa, cd_loop_uv_offset); @@ -1493,7 +1494,12 @@ static int uv_hide_exec(bContext *C, wmOperator *op) if (bm_face_is_all_uv_sel(efa, true, cd_loop_uv_offset) == !swap) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - if (UV_SEL_TEST(luv, !swap)) { + /* For both cases rely on edge sel tests, since all vert sel tests are invalid in + * case of sticky selections. */ + if (UV_EDGE_SEL_TEST(luv, !swap) && (em->selectmode == SCE_SELECT_EDGE)) { + BM_edge_select_set(em->bm, l->e, false); + } + else if (UV_EDGE_SEL_TEST(luv, !swap) && (em->selectmode == SCE_SELECT_VERTEX)) { BM_vert_select_set(em->bm, l->v, false); } } @@ -1504,29 +1510,57 @@ static int uv_hide_exec(bContext *C, wmOperator *op) } } else if (em->selectmode == SCE_SELECT_FACE) { - /* check if a UV is de-selected */ - if (bm_face_is_all_uv_sel(efa, false, cd_loop_uv_offset) != !swap) { - BM_face_select_set(em->bm, efa, false); - uvedit_face_select_disable(scene, em, efa, cd_loop_uv_offset); + /* Deselect BMesh face depending on the type of UV selectmode and the type of UV element + * being considered. */ + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + if (UV_EDGE_SEL_TEST(luv, !swap) && (ts->uv_selectmode == UV_SELECT_EDGE)) { + BM_face_select_set(em->bm, efa, false); + break; + } + else if (UV_VERT_SEL_TEST(luv, !swap) && (ts->uv_selectmode == UV_SELECT_VERTEX)) { + BM_face_select_set(em->bm, efa, false); + break; + } + else if (ts->uv_selectmode == UV_SELECT_ISLAND) { + BM_face_select_set(em->bm, efa, false); + break; + } } + uvedit_face_select_disable(scene, em, efa, cd_loop_uv_offset); } else { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - if (UV_SEL_TEST(luv, !swap)) { - BM_vert_select_set(em->bm, l->v, false); - if (!swap) { - luv->flag &= ~MLOOPUV_VERTSEL; + if (UV_EDGE_SEL_TEST(luv, !swap) && (ts->uv_selectmode == UV_SELECT_EDGE)) { + if (em->selectmode == SCE_SELECT_EDGE) + BM_edge_select_set(em->bm, l->e, false); + else { + BM_vert_select_set(em->bm, l->v, false); + BM_vert_select_set(em->bm, l->next->v, false); } } + else if (UV_VERT_SEL_TEST(luv, !swap) && (ts->uv_selectmode != UV_SELECT_EDGE)) { + if (em->selectmode == SCE_SELECT_EDGE) + BM_edge_select_set(em->bm, l->e, false); + else + BM_vert_select_set(em->bm, l->v, false); + } + } + if (!swap) { + uvedit_face_select_disable(scene, em, efa, cd_loop_uv_offset); } } } } - /* flush vertex selection changes */ + /* Flush editmesh selections to ensure valid selection states. */ if (em->selectmode != SCE_SELECT_FACE) { - EDBM_selectmode_flush_ex(em, SCE_SELECT_VERTEX | SCE_SELECT_EDGE); + /* NOTE: Make sure correct flags are used. Previously this was done by passing + * (SCE_SELECT_VERTEX | SCE_SELECT_EDGE), which doesn't work now that we support proper UV + * edge selection. */ + + BM_mesh_select_mode_flush(em->bm); } BM_select_history_validate(em->bm); @@ -1540,7 +1574,8 @@ static int uv_hide_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } -#undef UV_SEL_TEST +#undef UV_VERT_SEL_TEST +#undef UV_EDGE_SEL_TEST static void UV_OT_hide(wmOperatorType *ot) { @@ -1567,12 +1602,10 @@ static void UV_OT_hide(wmOperatorType *ot) static int uv_reveal_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); - SpaceImage *sima = CTX_wm_space_image(C); Scene *scene = CTX_data_scene(C); const ToolSettings *ts = scene->toolsettings; const bool use_face_center = (ts->uv_selectmode == UV_SELECT_FACE); - const bool stickymode = sima ? (ts->uv_sticky != SI_STICKY_DISABLE) : 1; const bool select = RNA_boolean_get(op->ptr, "select"); uint objects_len = 0; @@ -1589,8 +1622,10 @@ static int uv_reveal_exec(bContext *C, wmOperator *op) const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); - /* note on tagging, selecting faces needs to be delayed so it doesn't select the verts and - * confuse our checks on selected verts. */ + /* NOTE: Selecting faces is delayed so that it doesn't select verts/edges and confuse certain + * UV selection checks. + * This creates a temporary state which breaks certain UV selection functions that do face + * visibilty checks internally. Current implementation handles each case separately. */ /* call the mesh function if we are in mesh sync sel */ if (ts->uv_flag & UV_SYNC_SELECTION) { @@ -1604,6 +1639,11 @@ static int uv_reveal_exec(bContext *C, wmOperator *op) } continue; } + + /* NOTE(@sidd017): Supporting selections in all cases is quite difficult considering there are + * at least 12 cases to look into (3 mesh selectmodes + 4 uv selectmodes + sticky modes). + * For now we select all UV faces as sticky disabled to ensure proper UV selection states (vert + * + edge flags) */ if (use_face_center) { if (em->selectmode == SCE_SELECT_FACE) { BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { @@ -1611,7 +1651,7 @@ static int uv_reveal_exec(bContext *C, wmOperator *op) if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN) && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - SET_FLAG_FROM_TEST(luv->flag, select, MLOOPUV_VERTSEL); + SET_FLAG_FROM_TEST(luv->flag, select, (MLOOPUV_VERTSEL | MLOOPUV_EDGESEL)); } /* BM_face_select_set(em->bm, efa, true); */ BM_elem_flag_enable(efa, BM_ELEM_TAG); @@ -1619,42 +1659,27 @@ static int uv_reveal_exec(bContext *C, wmOperator *op) } } else { - /* enable adjacent faces to have disconnected UV selections if sticky is disabled */ - if (!stickymode) { - BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { - BM_elem_flag_disable(efa, BM_ELEM_TAG); - if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN) && - !BM_elem_flag_test(efa, BM_ELEM_SELECT)) { - int totsel = 0; - BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + BM_elem_flag_disable(efa, BM_ELEM_TAG); + if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN) && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + int totsel = 0; + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + if (em->selectmode == SCE_SELECT_VERTEX) { totsel += BM_elem_flag_test(l->v, BM_ELEM_SELECT); } - - if (!totsel) { - BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - SET_FLAG_FROM_TEST(luv->flag, select, MLOOPUV_VERTSEL); - } - /* BM_face_select_set(em->bm, efa, true); */ - BM_elem_flag_enable(efa, BM_ELEM_TAG); + else if (em->selectmode == SCE_SELECT_EDGE) { + totsel += BM_elem_flag_test(l->e, BM_ELEM_SELECT); } } - } - } - else { - BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { - BM_elem_flag_disable(efa, BM_ELEM_TAG); - if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN) && - !BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + + if (!totsel) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (BM_elem_flag_test(l->v, BM_ELEM_SELECT) == 0) { - luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - SET_FLAG_FROM_TEST(luv->flag, select, MLOOPUV_VERTSEL); - } + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + SET_FLAG_FROM_TEST(luv->flag, select, (MLOOPUV_VERTSEL | MLOOPUV_EDGESEL)); } - /* BM_face_select_set(em->bm, efa, true); */ - BM_elem_flag_enable(efa, BM_ELEM_TAG); } + /* BM_face_select_set(em->bm, efa, true); */ + BM_elem_flag_enable(efa, BM_ELEM_TAG); } } } @@ -1665,7 +1690,7 @@ static int uv_reveal_exec(bContext *C, wmOperator *op) if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN) && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - SET_FLAG_FROM_TEST(luv->flag, select, MLOOPUV_VERTSEL); + SET_FLAG_FROM_TEST(luv->flag, select, (MLOOPUV_VERTSEL | MLOOPUV_EDGESEL)); } /* BM_face_select_set(em->bm, efa, true); */ BM_elem_flag_enable(efa, BM_ELEM_TAG); @@ -1677,10 +1702,8 @@ static int uv_reveal_exec(bContext *C, wmOperator *op) BM_elem_flag_disable(efa, BM_ELEM_TAG); if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN) && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (BM_elem_flag_test(l->v, BM_ELEM_SELECT) == 0) { - luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - SET_FLAG_FROM_TEST(luv->flag, select, MLOOPUV_VERTSEL); - } + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + SET_FLAG_FROM_TEST(luv->flag, select, (MLOOPUV_VERTSEL | MLOOPUV_EDGESEL)); } /* BM_face_select_set(em->bm, efa, true); */ BM_elem_flag_enable(efa, BM_ELEM_TAG); @@ -2022,6 +2045,7 @@ void ED_operatortypes_uvedit(void) WM_operatortype_append(UV_OT_select_more); 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_snap_cursor); WM_operatortype_append(UV_OT_snap_selected); diff --git a/source/blender/editors/uvedit/uvedit_path.c b/source/blender/editors/uvedit/uvedit_path.c index 33621c1f0b6..7c6960a634a 100644 --- a/source/blender/editors/uvedit/uvedit_path.c +++ b/source/blender/editors/uvedit/uvedit_path.c @@ -234,6 +234,9 @@ static int mouse_mesh_uv_shortest_path_vert(Scene *scene, const int cd_loop_uv_offset) { const char uv_selectmode = ED_uvedit_select_mode_get(scene); + /* TODO(@sidd017): Implement logic to calculate shortest path for UV edges, since we now support + * proper edge selection for UVs (D12028). + * Till then continue using vertex path to fake shortest path calculation for edges. */ const bool use_fake_edge_select = (uv_selectmode & UV_SELECT_EDGE); BMEditMesh *em = BKE_editmesh_from_object(obedit); BMesh *bm = em->bm; @@ -377,7 +380,7 @@ static bool facetag_test_cb(BMFace *f, void *user_data_v) BMIter iter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &iter, f, BM_LOOPS_OF_FACE) { - if (!uvedit_uv_select_test(scene, l_iter, cd_loop_uv_offset)) { + if (!uvedit_edge_select_test(scene, l_iter, cd_loop_uv_offset)) { return false; } } @@ -531,8 +534,21 @@ static bool uv_shortest_path_pick_ex(Scene *scene, * flush the selection from the vertices. */ BM_mesh_select_mode_flush_ex(em->bm, SCE_SELECT_VERTEX, BM_SELECT_LEN_FLUSH_RECALC_ALL); } + ED_uvedit_select_sync_flush(scene->toolsettings, em, select); + } + else { + if (uv_selectmode & UV_SELECT_EDGE) { + /* TODO(@sidd017): Remove this case when adding proper uv edge support for this operator. + * In the meantime, this case helps ensures proper UV selection states for edge mode. */ + if (select) { + uvedit_select_flush(scene, em); + } + else { + uvedit_deselect_flush(scene, em); + } + } + ED_uvedit_selectmode_flush(scene, em); } - ED_uvedit_select_sync_flush(scene->toolsettings, em, select); } if (ts->uv_flag & UV_SYNC_SELECTION) { @@ -603,7 +619,7 @@ static int uv_shortest_path_pick_invoke(bContext *C, wmOperator *op, const wmEve else if (uv_selectmode & UV_SELECT_EDGE) { UvNearestHit hit = UV_NEAREST_HIT_INIT_MAX(®ion->v2d); - if (!uv_find_nearest_edge(scene, obedit, co, &hit)) { + if (!uv_find_nearest_edge(scene, obedit, co, 0.0f, &hit)) { return OPERATOR_CANCELLED; } diff --git a/source/blender/editors/uvedit/uvedit_rip.c b/source/blender/editors/uvedit/uvedit_rip.c index 272540d61c7..545cc57e3c4 100644 --- a/source/blender/editors/uvedit/uvedit_rip.c +++ b/source/blender/editors/uvedit/uvedit_rip.c @@ -52,7 +52,7 @@ /** Unordered loop data, stored in #BMLoop.head.index. */ typedef struct ULData { - /** When this UV is selected as well as the next UV. */ + /** When the specified UV edge is selected. */ uint is_select_edge : 1; /** * When only this UV is selected and none of the other UV's @@ -762,15 +762,17 @@ static bool uv_rip_object(Scene *scene, Object *obedit, const float co[2], const const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (luv->flag & MLOOPUV_VERTSEL) { const MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l->prev, cd_loop_uv_offset); - const MLoopUV *luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); - if (luv_next->flag & MLOOPUV_VERTSEL) { + if (luv->flag & MLOOPUV_EDGESEL) { UL(l)->is_select_edge = true; } + else if ((luv_prev->flag & MLOOPUV_EDGESEL) == 0) { + /* #bm_loop_uv_select_single_vert_validate validates below. */ + UL(l)->is_select_vert_single = true; + is_all = false; + } else { - if ((luv_prev->flag & MLOOPUV_VERTSEL) == 0) { - /* #bm_loop_uv_select_single_vert_validate validates below. */ - UL(l)->is_select_vert_single = true; - } + /* Cases where all vertices of a face are selected but not all edges are selected. */ + is_all = false; } } else { @@ -797,7 +799,7 @@ static bool uv_rip_object(Scene *scene, Object *obedit, const float co[2], const } } - /* Special case: if we have selected faces, isolated them. + /* Special case: if we have selected faces, isolate them. * This isn't a rip, however it's useful for users as a quick way * to detach the selection. * @@ -812,6 +814,10 @@ static bool uv_rip_object(Scene *scene, Object *obedit, const float co[2], const luv->flag &= ~MLOOPUV_VERTSEL; changed = true; } + if (luv->flag & MLOOPUV_EDGESEL) { + luv->flag &= ~MLOOPUV_EDGESEL; + changed = true; + } } } } @@ -871,6 +877,9 @@ static bool uv_rip_object(Scene *scene, Object *obedit, const float co[2], const } } } + if (changed) { + uvedit_deselect_flush(scene, em); + } return changed; } diff --git a/source/blender/editors/uvedit/uvedit_select.c b/source/blender/editors/uvedit/uvedit_select.c index 4454a8414b5..ca66981cd3a 100644 --- a/source/blender/editors/uvedit/uvedit_select.c +++ b/source/blender/editors/uvedit/uvedit_select.c @@ -46,6 +46,7 @@ #include "RNA_access.h" #include "RNA_define.h" +#include "RNA_enum_types.h" #include "WM_api.h" #include "WM_types.h" @@ -65,6 +66,8 @@ static void uv_select_all_perform_multi(Scene *scene, static void uv_select_flush_from_tag_face(Scene *scene, Object *obedit, const bool select); static void uv_select_flush_from_tag_loop(Scene *scene, Object *obedit, const bool select); +static void uv_select_flush_from_loop_edge_flag(Scene *scene, BMEditMesh *em); + static void uv_select_tag_update_for_object(Depsgraph *depsgraph, const ToolSettings *ts, Object *obedit); @@ -217,8 +220,15 @@ bool uvedit_face_select_test_ex(const ToolSettings *ts, BMFace *efa, const int c BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - if (!(luv->flag & MLOOPUV_VERTSEL)) { - return false; + if (ts->uv_selectmode & UV_SELECT_VERTEX) { + if ((luv->flag & MLOOPUV_VERTSEL) == 0) { + return false; + } + } + else { + if ((luv->flag & MLOOPUV_EDGESEL) == 0) { + return false; + } } } return true; @@ -236,21 +246,58 @@ void uvedit_face_select_set_with_sticky(const Scene *scene, const int cd_loop_uv_offset) { const ToolSettings *ts = scene->toolsettings; + const char sticky = ts->uv_sticky; if (ts->uv_flag & UV_SYNC_SELECTION) { uvedit_face_select_set(scene, em, efa, select, do_history, cd_loop_uv_offset); return; } + if (!uvedit_face_visible_test(scene, efa)) { + return; + } + /* NOTE: Previously face selections done in sticky vertex mode selected stray UV vertices + * (not part of any face selections). This now uses the sticky location mode logic instead. */ + switch (sticky) { + case SI_STICKY_DISABLE: { + uvedit_face_select_set(scene, em, efa, select, do_history, cd_loop_uv_offset); + break; + } + default: { + /* SI_STICKY_LOC and SI_STICKY_VERTEX modes. */ + uvedit_face_select_shared_vert(scene, em, efa, select, do_history, cd_loop_uv_offset); + } + } +} - BMLoop *l_iter, *l_first; - l_iter = l_first = BM_FACE_FIRST_LOOP(efa); - do { - uvedit_uv_select_set_with_sticky(scene, em, l_iter, select, do_history, cd_loop_uv_offset); - } while ((l_iter = l_iter->next) != l_first); +void uvedit_face_select_shared_vert(const Scene *scene, + BMEditMesh *em, + BMFace *efa, + const bool select, + const bool do_history, + const int cd_loop_uv_offset) +{ + BMLoop *l; + BMIter liter; + + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + if (select) { + luv->flag |= MLOOPUV_EDGESEL; + uvedit_uv_select_shared_vert( + scene, em, l, select, SI_STICKY_LOC, do_history, cd_loop_uv_offset); + } + else { + luv->flag &= ~MLOOPUV_EDGESEL; + if (!uvedit_vert_is_face_select_any_other(scene, l, cd_loop_uv_offset)) { + uvedit_uv_select_shared_vert( + scene, em, l, select, SI_STICKY_LOC, do_history, cd_loop_uv_offset); + } + } + } } -void uvedit_face_select_set(const struct Scene *scene, - struct BMEditMesh *em, - struct BMFace *efa, +void uvedit_face_select_set(const Scene *scene, + BMEditMesh *em, + BMFace *efa, const bool select, const bool do_history, const int cd_loop_uv_offset) @@ -284,7 +331,7 @@ void uvedit_face_select_enable(const Scene *scene, BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - luv->flag |= MLOOPUV_VERTSEL; + luv->flag |= (MLOOPUV_VERTSEL | MLOOPUV_EDGESEL); } } } @@ -306,7 +353,7 @@ void uvedit_face_select_disable(const Scene *scene, BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - luv->flag &= ~MLOOPUV_VERTSEL; + luv->flag &= ~(MLOOPUV_VERTSEL | MLOOPUV_EDGESEL); } } } @@ -324,13 +371,15 @@ bool uvedit_edge_select_test_ex(const ToolSettings *ts, BMLoop *l, const int cd_ BM_elem_flag_test(l->next->v, BM_ELEM_SELECT); } - MLoopUV *luv1, *luv2; - - luv1 = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - luv2 = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); + MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - return (luv1->flag & MLOOPUV_VERTSEL) && (luv2->flag & MLOOPUV_VERTSEL); + if (ts->uv_selectmode & UV_SELECT_VERTEX) { + MLoopUV *luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); + return (luv->flag & MLOOPUV_VERTSEL) && (luv_next->flag & MLOOPUV_VERTSEL); + } + return (luv->flag & MLOOPUV_EDGESEL); } + bool uvedit_edge_select_test(const Scene *scene, BMLoop *l, const int cd_loop_uv_offset) { return uvedit_edge_select_test_ex(scene->toolsettings, l, cd_loop_uv_offset); @@ -349,8 +398,89 @@ void uvedit_edge_select_set_with_sticky(const Scene *scene, return; } - uvedit_uv_select_set_with_sticky(scene, em, l, select, do_history, cd_loop_uv_offset); - uvedit_uv_select_set_with_sticky(scene, em, l->next, select, do_history, cd_loop_uv_offset); + const int sticky = ts->uv_sticky; + switch (sticky) { + case SI_STICKY_DISABLE: { + if (uvedit_face_visible_test(scene, l->f)) { + uvedit_edge_select_set(scene, em, l, select, do_history, cd_loop_uv_offset); + } + break; + } + case SI_STICKY_VERTEX: { + uvedit_edge_select_shared_vert( + scene, em, l, select, SI_STICKY_VERTEX, do_history, cd_loop_uv_offset); + break; + } + default: { + /* SI_STICKY_LOC (Fallback) */ + uvedit_edge_select_shared_vert( + scene, em, l, select, SI_STICKY_LOC, do_history, cd_loop_uv_offset); + break; + } + } +} + +/** + * Selects UV edges and shared vertices according to sticky_flag. + * + * \param sticky_flag: + * - SI_STICKY_LOC: selects all UV edges that share the same mesh vertices and UV coordinates. + * - SI_STICKY_VERTEX: selects all UV edges sharing the same mesh vertices. + */ +void uvedit_edge_select_shared_vert(const Scene *scene, + BMEditMesh *em, + BMLoop *l, + const bool select, + const int sticky_flag, + const bool do_history, + const int cd_loop_uv_offset) +{ + BLI_assert((sticky_flag == SI_STICKY_LOC) || (sticky_flag == SI_STICKY_VERTEX)); + /* Set edge flags. Rely on this for face visibility checks */ + uvedit_edge_select_set_noflush(scene, l, select, sticky_flag, cd_loop_uv_offset); + + /* Vert selections. */ + BMLoop *l_iter = l; + do { + MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); + if (select && (luv->flag & MLOOPUV_EDGESEL)) { + uvedit_uv_select_shared_vert( + scene, em, l_iter, true, SI_STICKY_LOC, do_history, cd_loop_uv_offset); + uvedit_uv_select_shared_vert( + scene, em, l_iter->next, true, SI_STICKY_LOC, do_history, cd_loop_uv_offset); + } + + else if (!select && !(luv->flag & MLOOPUV_EDGESEL)) { + if (!uvedit_vert_is_edge_select_any_other(scene, l, cd_loop_uv_offset)) { + uvedit_uv_select_shared_vert( + scene, em, l_iter, false, SI_STICKY_LOC, do_history, cd_loop_uv_offset); + } + if (!uvedit_vert_is_edge_select_any_other(scene, l->next, cd_loop_uv_offset)) { + uvedit_uv_select_shared_vert( + scene, em, l_iter->next, false, SI_STICKY_LOC, do_history, cd_loop_uv_offset); + } + } + } while (((l_iter = l_iter->radial_next) != l) && (sticky_flag != SI_STICKY_LOC)); +} + +/* Set edge flags for required UV edges. */ +void uvedit_edge_select_set_noflush(const Scene *scene, + BMLoop *l, + const bool select, + const int sticky_flag, + const int cd_loop_uv_offset) +{ + MLoopUV *luv; + BMLoop *l_iter = l; + do { + if (uvedit_face_visible_test(scene, l_iter->f)) { + if ((sticky_flag == SI_STICKY_VERTEX) || + BM_loop_uv_share_edge_check(l, l_iter, cd_loop_uv_offset)) { + luv = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); + SET_FLAG_FROM_TEST(luv->flag, select, MLOOPUV_EDGESEL); + } + } + } while (((l_iter = l_iter->radial_next) != l) && (sticky_flag != SI_STICKY_DISABLE)); } void uvedit_edge_select_set(const Scene *scene, @@ -395,13 +525,12 @@ void uvedit_edge_select_enable(const Scene *scene, } } else { - MLoopUV *luv1, *luv2; - - luv1 = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - luv2 = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); + MLoopUV *luv, *luv_next; + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); - luv1->flag |= MLOOPUV_VERTSEL; - luv2->flag |= MLOOPUV_VERTSEL; + luv->flag |= (MLOOPUV_VERTSEL | MLOOPUV_EDGESEL); + luv_next->flag |= MLOOPUV_VERTSEL; } } @@ -426,13 +555,25 @@ void uvedit_edge_select_disable(const Scene *scene, } } else { - MLoopUV *luv1, *luv2; - - luv1 = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - luv2 = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); - - luv1->flag &= ~MLOOPUV_VERTSEL; - luv2->flag &= ~MLOOPUV_VERTSEL; + MLoopUV *luv, *luv_next; + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); + + luv->flag &= ~MLOOPUV_EDGESEL; + if ((ts->uv_selectmode & UV_SELECT_VERTEX) == 0) { + /* Deselect UV vertex if not part of another edge selection */ + MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l->prev, cd_loop_uv_offset); + if (!(luv_next->flag & MLOOPUV_EDGESEL)) { + luv_next->flag &= ~MLOOPUV_VERTSEL; + } + if (!(luv_prev->flag & MLOOPUV_EDGESEL)) { + luv->flag &= ~MLOOPUV_VERTSEL; + } + } + else { + luv_next->flag &= ~MLOOPUV_VERTSEL; + luv->flag &= ~MLOOPUV_VERTSEL; + } } } @@ -469,45 +610,66 @@ void uvedit_uv_select_set_with_sticky(const Scene *scene, const int sticky = ts->uv_sticky; switch (sticky) { case SI_STICKY_DISABLE: { - uvedit_uv_select_set(scene, em, l, select, do_history, cd_loop_uv_offset); + if (uvedit_face_visible_test(scene, l->f)) { + uvedit_uv_select_set(scene, em, l, select, do_history, cd_loop_uv_offset); + } + break; + } + case SI_STICKY_VERTEX: { + uvedit_uv_select_shared_vert( + scene, em, l, select, SI_STICKY_VERTEX, do_history, cd_loop_uv_offset); break; } default: { - /* #SI_STICKY_VERTEX or #SI_STICKY_LOC. */ - const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - BMEdge *e_first, *e_iter; - e_first = e_iter = l->e; - do { - if (e_iter->l) { - BMLoop *l_radial_iter = e_iter->l; - do { - if (l_radial_iter->v == l->v) { - if (uvedit_face_visible_test(scene, l_radial_iter->f)) { - bool do_select = false; - if (sticky == SI_STICKY_VERTEX) { - do_select = true; - } - else { - const MLoopUV *luv_other = BM_ELEM_CD_GET_VOID_P(l_radial_iter, - cd_loop_uv_offset); - if (equals_v2v2(luv_other->uv, luv->uv)) { - do_select = true; - } - } - - if (do_select) { - uvedit_uv_select_set( - scene, em, l_radial_iter, select, do_history, cd_loop_uv_offset); - } - } - } - } while ((l_radial_iter = l_radial_iter->radial_next) != e_iter->l); - } - } while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, l->v)) != e_first); + /* SI_STICKY_LOC. */ + uvedit_uv_select_shared_vert( + scene, em, l, select, SI_STICKY_LOC, do_history, cd_loop_uv_offset); + break; } } } +/** + * Selects shared UVs based on #sticky_flag. + * + * \param sticky_flag: Type of sticky selection : + * - SI_STICKY_LOC: selects all UVs sharing same mesh vertex and UV coordinates. + * - SI_STICKY_VERTEX: selects all UVs sharing same mesh vertex. + */ +void uvedit_uv_select_shared_vert(const Scene *scene, + BMEditMesh *em, + BMLoop *l, + const bool select, + const int sticky_flag, + const bool do_history, + const int cd_loop_uv_offset) +{ + BLI_assert((sticky_flag == SI_STICKY_LOC) || (sticky_flag == SI_STICKY_VERTEX)); + + BMEdge *e_first, *e_iter; + e_first = e_iter = l->e; + do { + BMLoop *l_radial_iter = e_iter->l; + do { + if (l_radial_iter->v == l->v) { + if (uvedit_face_visible_test(scene, l_radial_iter->f)) { + bool do_select = false; + if (sticky_flag == SI_STICKY_VERTEX) { + do_select = true; + } + else if (BM_loop_uv_share_vert_check(l, l_radial_iter, cd_loop_uv_offset)) { + do_select = true; + } + + if (do_select) { + uvedit_uv_select_set(scene, em, l_radial_iter, select, do_history, cd_loop_uv_offset); + } + } + } + } while ((l_radial_iter = l_radial_iter->radial_next) != e_iter->l); + } while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, l->v)) != e_first); +} + void uvedit_uv_select_set(const Scene *scene, BMEditMesh *em, BMLoop *l, @@ -630,7 +792,8 @@ static BMLoop *uvedit_loop_find_other_boundary_loop_with_visible_face(const Scen /** \name Find Nearest Elements * \{ */ -bool uv_find_nearest_edge(Scene *scene, Object *obedit, const float co[2], UvNearestHit *hit) +bool uv_find_nearest_edge( + Scene *scene, Object *obedit, const float co[2], const float penalty, UvNearestHit *hit) { BLI_assert((hit->scale[0] > 0.0f) && (hit->scale[1] > 0.0f)); BMEditMesh *em = BKE_editmesh_from_object(obedit); @@ -659,8 +822,13 @@ bool uv_find_nearest_edge(Scene *scene, Object *obedit, const float co[2], UvNea sub_v2_v2(delta, co); mul_v2_v2(delta, hit->scale); - const float dist_test_sq = len_squared_v2(delta); + float dist_test_sq = len_squared_v2(delta); + /* Ensures that successive selection attempts will select other edges sharing the same + * UV coordinates as the previous selection. */ + if ((penalty != 0.0f) && uvedit_edge_select_test(scene, l, cd_loop_uv_offset)) { + dist_test_sq = square_f(sqrtf(dist_test_sq) + penalty); + } if (dist_test_sq < hit->dist_sq) { hit->ob = obedit; hit->efa = efa; @@ -675,13 +843,17 @@ bool uv_find_nearest_edge(Scene *scene, Object *obedit, const float co[2], UvNea return found; } -bool uv_find_nearest_edge_multi( - Scene *scene, Object **objects, const uint objects_len, const float co[2], UvNearestHit *hit) +bool uv_find_nearest_edge_multi(Scene *scene, + Object **objects, + const uint objects_len, + const float co[2], + const float penalty, + UvNearestHit *hit) { bool found = false; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; - if (uv_find_nearest_edge(scene, obedit, co, hit)) { + if (uv_find_nearest_edge(scene, obedit, co, penalty, hit)) { found = true; } } @@ -801,6 +973,8 @@ bool uv_find_nearest_vert( float dist_test_sq = len_squared_v2(delta); + /* Ensures that successive selection attempts will select other vertices sharing the same + * UV coordinates */ if ((penalty_dist != 0.0f) && uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { dist_test_sq = square_f(sqrtf(dist_test_sq) + penalty_dist); } @@ -961,6 +1135,191 @@ BMLoop *uv_find_nearest_loop_from_edge(struct Scene *scene, /** \} */ /* -------------------------------------------------------------------- */ +/** \name Helper functions for UV selection. + * \{ */ + +bool uvedit_vert_is_edge_select_any_other(const Scene *scene, + BMLoop *l, + const int cd_loop_uv_offset) +{ + BMEdge *e_iter = l->e; + do { + BMLoop *l_radial_iter = e_iter->l, *l_other; + do { + if (uvedit_face_visible_test(scene, l_radial_iter->f)) { + /* Use #l_other to check if the uvs are connected (share the same uv coordinates) + * and #l_radial_iter for the actual edge selection test. */ + l_other = (l_radial_iter->v != l->v) ? l_radial_iter->next : l_radial_iter; + if (BM_loop_uv_share_vert_check(l, l_other, cd_loop_uv_offset) && + uvedit_edge_select_test(scene, l_radial_iter, cd_loop_uv_offset)) { + return true; + } + } + } while ((l_radial_iter = l_radial_iter->radial_next) != e_iter->l); + } while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, l->v)) != l->e); + + return false; +} + +bool uvedit_vert_is_face_select_any_other(const Scene *scene, + BMLoop *l, + const int cd_loop_uv_offset) +{ + BMIter liter; + BMLoop *l_iter; + BM_ITER_ELEM (l_iter, &liter, l->v, BM_LOOPS_OF_VERT) { + if (!uvedit_face_visible_test(scene, l_iter->f) || (l_iter->f == l->f)) { + continue; + } + if (BM_loop_uv_share_vert_check(l, l_iter, cd_loop_uv_offset) && + uvedit_face_select_test(scene, l_iter->f, cd_loop_uv_offset)) { + return true; + } + } + return false; +} + +bool uvedit_vert_is_all_other_faces_selected(const Scene *scene, + BMLoop *l, + const int cd_loop_uv_offset) +{ + BMIter liter; + BMLoop *l_iter; + BM_ITER_ELEM (l_iter, &liter, l->v, BM_LOOPS_OF_VERT) { + if (!uvedit_face_visible_test(scene, l_iter->f) || (l_iter->f == l->f)) { + continue; + } + if (BM_loop_uv_share_vert_check(l, l_iter, cd_loop_uv_offset) && + !uvedit_face_select_test(scene, l_iter->f, cd_loop_uv_offset)) { + return false; + } + } + return true; +} + +/** + * Clear specified UV flag (vert/edge/pinned). + */ +static void bm_uv_flag_clear(Scene *scene, BMesh *bm, const int flag, const int cd_loop_uv_offset) +{ + BMFace *efa; + BMLoop *l; + BMIter iter, liter; + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + if (!uvedit_face_visible_test(scene, efa)) { + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + luv->flag &= ~flag; + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Select-Mode Flushing + * + * \{ */ + +void ED_uvedit_selectmode_flush(Scene *scene, BMEditMesh *em) +{ + ToolSettings *ts = scene->toolsettings; + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + + BLI_assert((ts->uv_flag & UV_SYNC_SELECTION) == 0); + + /* Vertex Mode only. */ + if (ts->uv_selectmode & UV_SELECT_VERTEX) { + BMFace *efa; + BMLoop *l; + BMIter iter, liter; + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (!uvedit_face_visible_test(scene, efa)) { + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + MLoopUV *luv, *luv_next; + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); + + if ((luv->flag & MLOOPUV_VERTSEL) && (luv_next->flag & MLOOPUV_VERTSEL)) { + luv->flag |= MLOOPUV_EDGESEL; + } + else { + luv->flag &= ~MLOOPUV_EDGESEL; + } + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Flush selection (up/down) + * \{ */ + +/* Careful when using this in face select mode. + * For face selections with sticky mode enabled, this can create invalid selection states. */ +void uvedit_select_flush(Scene *scene, BMEditMesh *em) +{ + ToolSettings *ts = scene->toolsettings; + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + + BLI_assert((ts->uv_flag & UV_SYNC_SELECTION) == 0); + + BMFace *efa; + BMLoop *l; + BMIter iter, liter; + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (!uvedit_face_visible_test(scene, efa)) { + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + MLoopUV *luv, *luv_next; + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); + + if ((luv->flag & MLOOPUV_VERTSEL) && (luv_next->flag & MLOOPUV_VERTSEL)) { + luv->flag |= MLOOPUV_EDGESEL; + } + } + } +} + +void uvedit_deselect_flush(Scene *scene, BMEditMesh *em) +{ + ToolSettings *ts = scene->toolsettings; + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + + BLI_assert((ts->uv_flag & UV_SYNC_SELECTION) == 0); + + BMFace *efa; + BMLoop *l; + BMIter iter, liter; + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (!uvedit_face_visible_test(scene, efa)) { + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + MLoopUV *luv, *luv_next; + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); + + if (luv->flag & MLOOPUV_EDGESEL) { + if (!(luv->flag & MLOOPUV_VERTSEL) || !(luv_next->flag & MLOOPUV_VERTSEL)) { + luv->flag &= ~MLOOPUV_EDGESEL; + } + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Edge Loop Select * \{ */ @@ -1124,13 +1483,14 @@ static void uv_select_edgeloop_single_side_tag(const Scene *scene, static int uv_select_edgeloop(Scene *scene, Object *obedit, UvNearestHit *hit, const bool extend) { + const ToolSettings *ts = scene->toolsettings; BMEditMesh *em = BKE_editmesh_from_object(obedit); bool select; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if (extend) { - select = !(uvedit_uv_select_test(scene, hit->l, cd_loop_uv_offset)); + select = !(uvedit_edge_select_test(scene, hit->l, cd_loop_uv_offset)); } else { select = true; @@ -1185,7 +1545,15 @@ static int uv_select_edgeloop(Scene *scene, Object *obedit, UvNearestHit *hit, c BMLoop *l_iter; BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) { if (BM_elem_flag_test(l_iter, BM_ELEM_TAG)) { - uvedit_edge_select_set_with_sticky(scene, em, l_iter, select, false, cd_loop_uv_offset); + if (ts->uv_selectmode == UV_SELECT_VERTEX) { + uvedit_uv_select_set_with_sticky(scene, em, l_iter, select, false, cd_loop_uv_offset); + uvedit_uv_select_set_with_sticky( + scene, em, l_iter->next, select, false, cd_loop_uv_offset); + } + else { + uvedit_edge_select_set_with_sticky( + scene, em, l_iter, select, false, cd_loop_uv_offset); + } } } } @@ -1197,6 +1565,68 @@ static int uv_select_edgeloop(Scene *scene, Object *obedit, UvNearestHit *hit, c /** \} */ /* -------------------------------------------------------------------- */ +/** \name Face Loop Select + * \{ */ + +static int uv_select_faceloop(Scene *scene, Object *obedit, UvNearestHit *hit, const bool extend) +{ + BMEditMesh *em = BKE_editmesh_from_object(obedit); + bool select; + + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + + if (!extend) { + uv_select_all_perform(scene, obedit, SEL_DESELECT); + } + + BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false); + + if (extend) { + select = !(uvedit_face_select_test(scene, hit->l->f, cd_loop_uv_offset)); + } + else { + select = true; + } + + BMLoop *l_pair[2] = { + hit->l, + uvedit_loop_find_other_radial_loop_with_visible_face(scene, hit->l, cd_loop_uv_offset), + }; + + for (int side = 0; side < 2; side++) { + BMLoop *l_step = l_pair[side]; + while (l_step) { + if (!uvedit_face_visible_test(scene, l_step->f)) { + break; + } + + uvedit_face_select_set_with_sticky(scene, em, l_step->f, select, false, cd_loop_uv_offset); + + BM_elem_flag_enable(l_step->f, BM_ELEM_TAG); + if (l_step->f->len == 4) { + BMLoop *l_step_opposite = l_step->next->next; + l_step = uvedit_loop_find_other_radial_loop_with_visible_face( + scene, l_step_opposite, cd_loop_uv_offset); + } + else { + l_step = NULL; + } + + /* Break iteration when `l_step`: + * - is the first loop where we started from. + * - tagged using #BM_ELEM_TAG (meaning this loop has been visited in this iteration). */ + if (l_step && BM_elem_flag_test(l_step->f, BM_ELEM_TAG)) { + break; + } + } + } + + return (select) ? 1 : -1; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Edge Ring Select * \{ */ @@ -1207,6 +1637,9 @@ static int uv_select_edgering(Scene *scene, Object *obedit, UvNearestHit *hit, c const bool use_face_select = (ts->uv_flag & UV_SYNC_SELECTION) ? (ts->selectmode & SCE_SELECT_FACE) : (ts->uv_selectmode & UV_SELECT_FACE); + const bool use_vertex_select = (ts->uv_flag & UV_SYNC_SELECTION) ? + (ts->selectmode & SCE_SELECT_VERTEX) : + (ts->uv_selectmode & UV_SELECT_VERTEX); bool select; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); @@ -1218,7 +1651,7 @@ static int uv_select_edgering(Scene *scene, Object *obedit, UvNearestHit *hit, c BM_mesh_elem_hflag_disable_all(em->bm, BM_EDGE, BM_ELEM_TAG, false); if (extend) { - select = !(uvedit_uv_select_test(scene, hit->l, cd_loop_uv_offset)); + select = !(uvedit_edge_select_test(scene, hit->l, cd_loop_uv_offset)); } else { select = true; @@ -1239,9 +1672,18 @@ static int uv_select_edgering(Scene *scene, Object *obedit, UvNearestHit *hit, c } if (use_face_select) { + /* While selecting face loops is now done in a separate function #uv_select_faceloop(), + * this check is still kept for edge ring selection, to keep it consistent with how edge + * ring selection works in face mode in the 3D viewport. */ uvedit_face_select_set_with_sticky(scene, em, l_step->f, select, false, cd_loop_uv_offset); } + else if (use_vertex_select) { + uvedit_uv_select_set_with_sticky(scene, em, l_step, select, false, cd_loop_uv_offset); + uvedit_uv_select_set_with_sticky( + scene, em, l_step->next, select, false, cd_loop_uv_offset); + } else { + /* Edge select mode */ uvedit_edge_select_set_with_sticky(scene, em, l_step, select, false, cd_loop_uv_offset); } @@ -1259,8 +1701,19 @@ static int uv_select_edgering(Scene *scene, Object *obedit, UvNearestHit *hit, c l_step = NULL; } + /* Break iteration when `l_step`: + * - Is the first loop where we started from. + * - Tagged using #BM_ELEM_TAG (meaning this loop has been visited in this iteration). + * - Has its corresponding UV edge selected/unselected based on #select. */ if (l_step && BM_elem_flag_test(l_step->e, BM_ELEM_TAG)) { - break; + /* Previously this check was not done and this resulted in the final edge in the edge ring + * cycle to be skipped during selection (caused by old sticky selection behavior). */ + if (select && uvedit_edge_select_test(scene, l_step, cd_loop_uv_offset)) { + break; + } + else if (!select && !uvedit_edge_select_test(scene, l_step, cd_loop_uv_offset)) { + break; + } } } } @@ -1570,30 +2023,50 @@ static int uv_select_more_less(bContext *C, const bool select) BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (uvedit_face_visible_test(scene, efa)) { -#define IS_SEL 1 -#define IS_UNSEL 2 + if (select) { +#define NEIGHBORING_FACE_IS_SEL 1 +#define CURR_FACE_IS_UNSEL 2 - int sel_state = 0; + int sel_state = 0; - BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - if (luv->flag & MLOOPUV_VERTSEL) { - sel_state |= IS_SEL; - } - else { - sel_state |= IS_UNSEL; + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + if (luv->flag & MLOOPUV_VERTSEL) { + sel_state |= NEIGHBORING_FACE_IS_SEL; + } + else { + sel_state |= CURR_FACE_IS_UNSEL; + } + + if (!(luv->flag & MLOOPUV_EDGESEL)) { + sel_state |= CURR_FACE_IS_UNSEL; + } + + /* If the current face is not selected and at least one neighboring face is + * selected, then tag the current face to grow selection.*/ + if (sel_state == (NEIGHBORING_FACE_IS_SEL | CURR_FACE_IS_UNSEL)) { + BM_elem_flag_enable(efa, BM_ELEM_TAG); + changed = true; + break; + } } - /* if we have a mixed selection, tag to grow it */ - if (sel_state == (IS_SEL | IS_UNSEL)) { - BM_elem_flag_enable(efa, BM_ELEM_TAG); - changed = true; - break; +#undef NEIGHBORING_FACE_IS_SEL +#undef CURR_FACE_IS_UNSEL + } + else { + if (!uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) { + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + /* Deselect face when at least one of the surrounding faces is not selected */ + if (!uvedit_vert_is_all_other_faces_selected(scene, l, cd_loop_uv_offset)) { + BM_elem_flag_enable(efa, BM_ELEM_TAG); + changed = true; + break; + } } } - -#undef IS_SEL -#undef IS_UNSEL } } } @@ -1631,6 +2104,13 @@ static int uv_select_more_less(bContext *C, const bool select) else { /* Select tagged loops. */ uv_select_flush_from_tag_loop(scene, obedit, select); + /* Set/unset edge flags based on selected verts. */ + if (select) { + uvedit_select_flush(scene, em); + } + else { + uvedit_deselect_flush(scene, em); + } } DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); @@ -1724,16 +2204,68 @@ bool uvedit_select_is_any_selected_multi(Scene *scene, Object **objects, const u return found; } -static void uv_select_all_perform(Scene *scene, Object *obedit, int action) +static void uv_select_all(Scene *scene, BMEditMesh *em, bool select_all) { - const ToolSettings *ts = scene->toolsettings; - BMEditMesh *em = BKE_editmesh_from_object(obedit); BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + const int uv_select_flags = (MLOOPUV_VERTSEL | MLOOPUV_EDGESEL); + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (!uvedit_face_visible_test(scene, efa)) { + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + SET_FLAG_FROM_TEST(luv->flag, select_all, uv_select_flags); + } + } +} + +static void uv_select_invert(Scene *scene, BMEditMesh *em) +{ + const ToolSettings *ts = scene->toolsettings; + BLI_assert((ts->uv_flag & UV_SYNC_SELECTION) == 0); const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + BMFace *efa; + BMLoop *l; + BMIter iter, liter; + MLoopUV *luv; + char uv_selectmode = ts->uv_selectmode; + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (!uvedit_face_visible_test(scene, efa)) { + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + if ((uv_selectmode == UV_SELECT_EDGE) || (uv_selectmode == UV_SELECT_FACE)) { + /* Use #MLOOPUV_EDGESEL to flag edges that must be selected. */ + luv->flag ^= MLOOPUV_EDGESEL; + luv->flag &= ~MLOOPUV_VERTSEL; + } + /* Use #MLOOPUV_VERTSEL to flag verts that must be selected. */ + else if ((uv_selectmode == UV_SELECT_VERTEX) || (uv_selectmode == UV_SELECT_ISLAND)) { + luv->flag ^= MLOOPUV_VERTSEL; + luv->flag &= ~MLOOPUV_EDGESEL; + } + } + } + + /* Flush based on uv vert/edge flags and current UV select mode */ + if ((uv_selectmode == UV_SELECT_EDGE) || (uv_selectmode == UV_SELECT_FACE)) { + uv_select_flush_from_loop_edge_flag(scene, em); + } + else if ((uv_selectmode == UV_SELECT_VERTEX) || (uv_selectmode == UV_SELECT_ISLAND)) { + uvedit_select_flush(scene, em); + } +} + +static void uv_select_all_perform(Scene *scene, Object *obedit, int action) +{ + const ToolSettings *ts = scene->toolsettings; + BMEditMesh *em = BKE_editmesh_from_object(obedit); if (action == SEL_TOGGLE) { action = uvedit_select_is_any_selected(scene, obedit) ? SEL_DESELECT : SEL_SELECT; @@ -1757,26 +2289,16 @@ static void uv_select_all_perform(Scene *scene, Object *obedit, int action) } } else { - BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { - if (!uvedit_face_visible_test(scene, efa)) { - continue; - } - - BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - - switch (action) { - case SEL_SELECT: - luv->flag |= MLOOPUV_VERTSEL; - break; - case SEL_DESELECT: - luv->flag &= ~MLOOPUV_VERTSEL; - break; - case SEL_INVERT: - luv->flag ^= MLOOPUV_VERTSEL; - break; - } - } + switch (action) { + case SEL_SELECT: + uv_select_all(scene, em, true); + break; + case SEL_DESELECT: + uv_select_all(scene, em, false); + break; + case SEL_INVERT: + uv_select_invert(scene, em); + break; } } } @@ -1905,7 +2427,7 @@ static int uv_mouse_select_multi(bContext *C, } else if (selectmode == UV_SELECT_EDGE) { /* find edge */ - found_item = uv_find_nearest_edge_multi(scene, objects, objects_len, co, &hit); + found_item = uv_find_nearest_edge_multi(scene, objects, objects_len, co, penalty_dist, &hit); if (found_item) { if ((ts->uv_flag & UV_SYNC_SELECTION) == 0) { BMesh *bm = BKE_editmesh_from_object(hit.ob)->bm; @@ -1931,7 +2453,7 @@ static int uv_mouse_select_multi(bContext *C, } } else if (selectmode == UV_SELECT_ISLAND) { - found_item = uv_find_nearest_edge_multi(scene, objects, objects_len, co, &hit); + found_item = uv_find_nearest_edge_multi(scene, objects, objects_len, co, 0.0f, &hit); if (!found_item) { /* Without this, we can be within the face of an island but too far from an edge, @@ -2028,6 +2550,10 @@ static int uv_mouse_select_multi(bContext *C, EDBM_selectmode_flush(em); } } + /* #extend=false implies single vertex selection, which doesn't need to be flushed. */ + else if (extend) { + ED_uvedit_selectmode_flush(scene, em); + } for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obiter = objects[ob_index]; @@ -2144,7 +2670,7 @@ static int uv_mouse_select_loop_generic_multi(bContext *C, int flush = 0; /* Find edge. */ - found_item = uv_find_nearest_edge_multi(scene, objects, objects_len, co, &hit); + found_item = uv_find_nearest_edge_multi(scene, objects, objects_len, co, 0.0f, &hit); if (!found_item) { return OPERATOR_CANCELLED; } @@ -2158,7 +2684,12 @@ static int uv_mouse_select_loop_generic_multi(bContext *C, } if (loop_type == UV_LOOP_SELECT) { - flush = uv_select_edgeloop(scene, obedit, &hit, extend); + if (ED_uvedit_select_mode_get(scene) == UV_SELECT_FACE) { + flush = uv_select_faceloop(scene, obedit, &hit, extend); + } + else { + flush = uv_select_edgeloop(scene, obedit, &hit, extend); + } } else if (loop_type == UV_RING_SELECT) { flush = uv_select_edgering(scene, obedit, &hit, extend); @@ -2175,6 +2706,9 @@ static int uv_mouse_select_loop_generic_multi(bContext *C, EDBM_deselect_flush(em); } } + else { + ED_uvedit_selectmode_flush(scene, em); + } for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obiter = objects[ob_index]; @@ -2210,15 +2744,7 @@ static int uv_select_loop_exec(bContext *C, wmOperator *op) RNA_float_get_array(op->ptr, "location", co); const bool extend = RNA_boolean_get(op->ptr, "extend"); - Scene *scene = CTX_data_scene(C); - enum eUVLoopGenericType type = UV_LOOP_SELECT; - if (ED_uvedit_select_mode_get(scene) == UV_SELECT_FACE) { - /* For now ring-select and face-loop is the same thing, - * if we support real edge selection this will no longer be the case. */ - type = UV_RING_SELECT; - } - - return uv_mouse_select_loop_generic(C, co, extend, type); + return uv_mouse_select_loop_generic(C, co, extend, UV_LOOP_SELECT); } static int uv_select_loop_invoke(bContext *C, wmOperator *op, const wmEvent *event) @@ -2368,7 +2894,7 @@ static int uv_select_linked_internal(bContext *C, wmOperator *op, const wmEvent RNA_float_get_array(op->ptr, "location", co); } - if (!uv_find_nearest_edge_multi(scene, objects, objects_len, co, &hit)) { + if (!uv_find_nearest_edge_multi(scene, objects, objects_len, co, 0.0f, &hit)) { MEM_freeN(objects); return OPERATOR_CANCELLED; } @@ -2533,10 +3059,10 @@ static int uv_select_split_exec(bContext *C, wmOperator *op) BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - if (luv->flag & MLOOPUV_VERTSEL) { + if ((luv->flag & MLOOPUV_VERTSEL) || (luv->flag & MLOOPUV_EDGESEL)) { is_sel = true; } - else { + if (!(luv->flag & MLOOPUV_VERTSEL) || !(luv->flag & MLOOPUV_EDGESEL)) { is_unsel = true; } @@ -2549,7 +3075,7 @@ static int uv_select_split_exec(bContext *C, wmOperator *op) if (is_sel && is_unsel) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - luv->flag &= ~MLOOPUV_VERTSEL; + luv->flag &= ~(MLOOPUV_VERTSEL | MLOOPUV_EDGESEL); } changed = true; @@ -2658,7 +3184,7 @@ static void uv_select_flush_from_tag_sticky_loc_internal(Scene *scene, /** * Flush the selection from face tags based on sticky and selection modes. * - * needed because settings the selection a face is done in a number of places but it also + * needed because setting the selection of a face is done in a number of places but it also * needs to respect the sticky modes for the UV verts, so dealing with the sticky modes * is best done in a separate function. * @@ -2680,31 +3206,9 @@ static void uv_select_flush_from_tag_face(Scene *scene, Object *obedit, const bo BMIter iter, liter; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); - if ((ts->uv_flag & UV_SYNC_SELECTION) == 0 && ts->uv_sticky == SI_STICKY_VERTEX) { - /* Tag all verts as untouched, then touch the ones that have a face center - * in the loop and select all MLoopUV's that use a touched vert. */ - BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT, BM_ELEM_TAG, false); - - BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(efa, BM_ELEM_TAG)) { - BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - BM_elem_flag_enable(l->v, BM_ELEM_TAG); - } - } - } - - /* now select tagged verts */ - BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { - /* tf = BM_ELEM_CD_GET_VOID_P(efa, cd_poly_tex_offset); */ /* UNUSED */ + if ((ts->uv_flag & UV_SYNC_SELECTION) == 0 && + ((ts->uv_sticky == SI_STICKY_VERTEX) || (ts->uv_sticky == SI_STICKY_LOC))) { - BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (BM_elem_flag_test(l->v, BM_ELEM_TAG)) { - uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); - } - } - } - } - else if ((ts->uv_flag & UV_SYNC_SELECTION) == 0 && ts->uv_sticky == SI_STICKY_LOC) { struct UvVertMap *vmap; uint efa_index; @@ -2719,8 +3223,19 @@ static void uv_select_flush_from_tag_face(Scene *scene, Object *obedit, const bo /* tf = BM_ELEM_CD_GET_VOID_P(efa, cd_poly_tex_offset); */ /* UNUSED */ BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - uv_select_flush_from_tag_sticky_loc_internal( - scene, em, vmap, efa_index, l, select, cd_loop_uv_offset); + MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + if (select) { + luv->flag |= MLOOPUV_EDGESEL; + uv_select_flush_from_tag_sticky_loc_internal( + scene, em, vmap, efa_index, l, select, cd_loop_uv_offset); + } + else { + luv->flag &= ~MLOOPUV_EDGESEL; + if (!uvedit_vert_is_face_select_any_other(scene, l, cd_loop_uv_offset)) { + uv_select_flush_from_tag_sticky_loc_internal( + scene, em, vmap, efa_index, l, select, cd_loop_uv_offset); + } + } } } } @@ -2738,11 +3253,11 @@ static void uv_select_flush_from_tag_face(Scene *scene, Object *obedit, const bo /** * Flush the selection from loop tags based on sticky and selection modes. * - * needed because settings the selection a face is done in a number of places but it also needs + * needed because setting the selection of a face is done in a number of places but it also needs * to respect the sticky modes for the UV verts, so dealing with the sticky modes is best done * in a separate function. * - * \note This function is very similar to #uv_select_flush_from_tag_loop, + * \note This function is very similar to #uv_select_flush_from_tag_face, * be sure to update both upon changing. */ static void uv_select_flush_from_tag_loop(Scene *scene, Object *obedit, const bool select) @@ -2818,6 +3333,77 @@ static void uv_select_flush_from_tag_loop(Scene *scene, Object *obedit, const bo } } +/** + * Flush the selection from UV edge flags based on sticky modes. + * + * Useful when performing edge selections in different sticky modes, since setting the required + * edge flags (#MLOOPUV_EDGESEL) is done manually or using #uvedit_edge_select_set_noflush, + * but dealing with sticky modes for vertex selections is best done in a separate function. + * + * \note Current behavior is selecting only; deselecting can be added but the behavior isn't + * required anywhere.*/ +static void uv_select_flush_from_loop_edge_flag(Scene *scene, BMEditMesh *em) +{ + const ToolSettings *ts = scene->toolsettings; + BMFace *efa; + BMLoop *l; + BMIter iter, liter; + + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + + if ((ts->uv_flag & UV_SYNC_SELECTION) == 0 && + ((ts->uv_sticky == SI_STICKY_LOC) || (ts->uv_sticky == SI_STICKY_VERTEX))) { + /* Use the #MLOOPUV_EDGESEL flag to identify which verts must to be selected */ + struct UvVertMap *vmap; + uint efa_index; + /* Clear UV vert flags */ + bm_uv_flag_clear(scene, em->bm, MLOOPUV_VERTSEL, cd_loop_uv_offset); + + BM_mesh_elem_table_ensure(em->bm, BM_FACE); + vmap = BM_uv_vert_map_create(em->bm, false, false); + if (vmap == NULL) { + return; + } + BM_ITER_MESH_INDEX (efa, &iter, em->bm, BM_FACES_OF_MESH, efa_index) { + if (!uvedit_face_visible_test(scene, efa)) { + /* This visibility check could be removed? Simply relying on edge flags to ensure + * visibility might be sufficient. */ + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + MLoopUV *luv; + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + /* Select verts based on UV edge flag. */ + if (luv->flag & MLOOPUV_EDGESEL) { + uv_select_flush_from_tag_sticky_loc_internal( + scene, em, vmap, efa_index, l, true, cd_loop_uv_offset); + uv_select_flush_from_tag_sticky_loc_internal( + scene, em, vmap, efa_index, l->next, true, cd_loop_uv_offset); + } + } + } + BM_uv_vert_map_free(vmap); + } + else { + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + MLoopUV *luv, *luv_next, *luv_prev; + luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); + luv_prev = BM_ELEM_CD_GET_VOID_P(l->prev, cd_loop_uv_offset); + + if (luv->flag & MLOOPUV_EDGESEL) { + luv->flag |= MLOOPUV_VERTSEL; + luv_next->flag |= MLOOPUV_VERTSEL; + } + else if (!(luv_prev->flag & MLOOPUV_EDGESEL)) { + luv->flag &= ~MLOOPUV_VERTSEL; + } + } + } + } +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -2964,8 +3550,12 @@ static int uv_box_select_exec(bContext *C, wmOperator *op) if (changed || use_pre_deselect) { changed_multi = true; - - ED_uvedit_select_sync_flush(ts, em, select); + if (ts->uv_flag & UV_SYNC_SELECTION) { + ED_uvedit_select_sync_flush(ts, em, select); + } + else { + ED_uvedit_selectmode_flush(scene, em); + } uv_select_tag_update_for_object(depsgraph, ts, obedit); } } @@ -3173,8 +3763,12 @@ static int uv_circle_select_exec(bContext *C, wmOperator *op) if (changed || use_pre_deselect) { changed_multi = true; - - ED_uvedit_select_sync_flush(ts, em, select); + if (ts->uv_flag & UV_SYNC_SELECTION) { + ED_uvedit_select_sync_flush(ts, em, select); + } + else { + ED_uvedit_selectmode_flush(scene, em); + } uv_select_tag_update_for_object(depsgraph, ts, obedit); } } @@ -3355,8 +3949,12 @@ static bool do_lasso_select_mesh_uv(bContext *C, if (changed || use_pre_deselect) { changed_multi = true; - - ED_uvedit_select_sync_flush(ts, em, select); + if (ts->uv_flag & UV_SYNC_SELECTION) { + ED_uvedit_select_sync_flush(ts, em, select); + } + else { + ED_uvedit_selectmode_flush(scene, em); + } uv_select_tag_update_for_object(depsgraph, ts, obedit); } } @@ -3407,11 +4005,21 @@ void UV_OT_select_lasso(wmOperatorType *ot) /** \name Select Pinned UV's Operator * \{ */ -static int uv_select_pinned_exec(bContext *C, wmOperator *UNUSED(op)) +static int uv_select_pinned_exec(bContext *C, wmOperator *op) { - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = CTX_data_scene(C); const ToolSettings *ts = scene->toolsettings; + + /* Use this operator only in vertex mode, since it is not guaranteed that pinned vertices may + * form higher selection states (like edges/faces/islands) in other modes. */ + if ((ts->uv_flag & UV_SYNC_SELECTION) == 0) { + if (ts->uv_selectmode != UV_SELECT_VERTEX) { + BKE_report(op->reports, RPT_ERROR, "Pinned vertices can be selected in Vertex Mode only"); + return OPERATOR_CANCELLED; + } + } + + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ViewLayer *view_layer = CTX_data_view_layer(C); BMFace *efa; BMLoop *l; @@ -3443,6 +4051,9 @@ static int uv_select_pinned_exec(bContext *C, wmOperator *UNUSED(op)) } } } + if ((ts->uv_flag & UV_SYNC_SELECTION) == 0) { + ED_uvedit_selectmode_flush(scene, em); + } if (changed) { uv_select_tag_update_for_object(depsgraph, ts, obedit); @@ -3797,9 +4408,7 @@ BMLoop **ED_uvedit_selected_edges(Scene *scene, BMesh *bm, int len_max, int *r_e BMLoop *l_iter; BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) { if (!BM_elem_flag_test(l_iter, BM_ELEM_TAG)) { - const MLoopUV *luv_curr = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); - const MLoopUV *luv_next = BM_ELEM_CD_GET_VOID_P(l_iter->next, cd_loop_uv_offset); - if ((luv_curr->flag & MLOOPUV_VERTSEL) && (luv_next->flag & MLOOPUV_VERTSEL)) { + if (uvedit_edge_select_test(scene, l_iter, cd_loop_uv_offset)) { BM_elem_flag_enable(l_iter, BM_ELEM_TAG); edges[edges_len++] = l_iter; @@ -3887,3 +4496,211 @@ finally: } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Mode UV Vert/Edge/Face/Island Operator + * \{ */ + +/** Deselects UVs that are not part of a complete island selection. + * + * Use only when sync select disabled. + */ +static void uv_isolate_selected_islands(Scene *scene, BMEditMesh *em, const int cd_loop_uv_offset) +{ + BLI_assert((scene->toolsettings->uv_flag & UV_SYNC_SELECTION) == 0); + BMFace *efa; + BMIter iter, liter; + UvElementMap *elementmap = BM_uv_element_map_create(em->bm, scene, true, false, false, true); + if (elementmap == NULL) { + return; + } + + int num_islands = elementmap->totalIslands; + /* Boolean array that tells if island with index i is completely selected or not. */ + bool *is_island_not_selected = MEM_callocN(sizeof(bool) * (num_islands), __func__); + + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + BMLoop *l; + if (!uvedit_face_visible_test(scene, efa)) { + BM_elem_flag_disable(efa, BM_ELEM_TAG); + continue; + } + BM_elem_flag_enable(efa, BM_ELEM_TAG); + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + if (!uvedit_edge_select_test(scene, l, cd_loop_uv_offset)) { + UvElement *element = BM_uv_element_get(elementmap, efa, l); + is_island_not_selected[element->island] = true; + } + } + } + + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + BMLoop *l; + if (!BM_elem_flag_test(efa, BM_ELEM_TAG)) { + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + UvElement *element = BM_uv_element_get(elementmap, efa, l); + /* Deselect all elements of islands which are not completely selected. */ + if (is_island_not_selected[element->island] == true) { + MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + luv->flag &= ~(MLOOPUV_VERTSEL | MLOOPUV_EDGESEL); + } + } + } + + BM_uv_element_map_free(elementmap); + MEM_freeN(is_island_not_selected); +} + +void ED_uvedit_selectmode_clean(Scene *scene, Object *obedit) +{ + ToolSettings *ts = scene->toolsettings; + BLI_assert((ts->uv_flag & UV_SYNC_SELECTION) == 0); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + char sticky = ts->uv_sticky; + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); + BMFace *efa; + BMLoop *l; + BMIter iter, liter; + + if (ts->uv_selectmode == UV_SELECT_VERTEX) { + /* Vertex mode. */ + if (sticky != SI_STICKY_DISABLE) { + bm_loop_tags_clear(em->bm); + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (!uvedit_face_visible_test(scene, efa)) { + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { + BM_elem_flag_enable(l, BM_ELEM_TAG); + } + } + } + uv_select_flush_from_tag_loop(scene, obedit, true); + } + } + + else if (ts->uv_selectmode == UV_SELECT_EDGE) { + /* Edge mode. */ + if (sticky != SI_STICKY_DISABLE) { + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (!uvedit_face_visible_test(scene, efa)) { + continue; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + if (uvedit_edge_select_test(scene, l, cd_loop_uv_offset)) { + uvedit_edge_select_set_noflush(scene, l, true, sticky, cd_loop_uv_offset); + } + } + } + } + uv_select_flush_from_loop_edge_flag(scene, em); + } + + else if (ts->uv_selectmode == UV_SELECT_FACE) { + /* Face mode. */ + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + BM_elem_flag_disable(efa, BM_ELEM_TAG); + if (uvedit_face_visible_test(scene, efa)) { + if (uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) { + BM_elem_flag_enable(efa, BM_ELEM_TAG); + } + uvedit_face_select_set(scene, em, efa, false, false, cd_loop_uv_offset); + } + } + uv_select_flush_from_tag_face(scene, obedit, true); + } + + else if (ts->uv_selectmode == UV_SELECT_ISLAND) { + /* Island mode. */ + uv_isolate_selected_islands(scene, em, cd_loop_uv_offset); + } + + ED_uvedit_selectmode_flush(scene, em); +} + +void ED_uvedit_selectmode_clean_multi(bContext *C) +{ + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( + view_layer, ((View3D *)NULL), &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + ED_uvedit_selectmode_clean(scene, obedit); + + uv_select_tag_update_for_object(depsgraph, scene->toolsettings, obedit); + } + MEM_freeN(objects); +} + +static int uv_select_mode_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + const char new_uv_selectmode = RNA_enum_get(op->ptr, "type"); + + /* Early exit if no change in current selection mode */ + if (new_uv_selectmode == ts->uv_selectmode) { + return OPERATOR_CANCELLED; + } + + /* Set new UV select mode. */ + ts->uv_selectmode = new_uv_selectmode; + + /* Handle UV selection states according to new select mode and sticky mode. */ + ED_uvedit_selectmode_clean_multi(C); + + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT); + WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, NULL); + + return OPERATOR_FINISHED; +} + +static int uv_select_mode_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + const ToolSettings *ts = CTX_data_tool_settings(C); + const SpaceImage *sima = CTX_wm_space_image(C); + + /* Could be removed? - Already done in poll callback. */ + if ((!sima) || (sima->mode != SI_MODE_UV)) { + return OPERATOR_CANCELLED; + } + /* Pass through when UV sync selection is enabled. + * Allow for mesh select-mode key-map. */ + if (ts->uv_flag & UV_SYNC_SELECTION) { + return OPERATOR_PASS_THROUGH; + } + + return uv_select_mode_exec(C, op); +} + +void UV_OT_select_mode(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "UV Select Mode"; + ot->description = "Change UV selection mode"; + ot->idname = "UV_OT_select_mode"; + + /* api callbacks */ + ot->invoke = uv_select_mode_invoke; + ot->exec = uv_select_mode_exec; + ot->poll = ED_operator_uvedit_space_image; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* RNA props */ + PropertyRNA *prop; + ot->prop = prop = RNA_def_enum( + ot->srna, "type", rna_enum_mesh_select_mode_uv_items, 0, "Type", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); +} + +/** \} */ diff --git a/source/blender/editors/uvedit/uvedit_smart_stitch.c b/source/blender/editors/uvedit/uvedit_smart_stitch.c index c0d0fe95c8c..b104ebe8bf2 100644 --- a/source/blender/editors/uvedit/uvedit_smart_stitch.c +++ b/source/blender/editors/uvedit/uvedit_smart_stitch.c @@ -2545,7 +2545,7 @@ static StitchState *stitch_select(bContext *C, return state; } } - else if (uv_find_nearest_edge_multi(scene, ssc->objects, ssc->objects_len, co, &hit)) { + else if (uv_find_nearest_edge_multi(scene, ssc->objects, ssc->objects_len, co, 0.0f, &hit)) { /* find StitchState from hit->ob */ StitchState *state = NULL; for (uint ob_index = 0; ob_index < ssc->objects_len; ob_index++) { diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.c b/source/blender/editors/uvedit/uvedit_unwrap_ops.c index d6400e9340f..3ed99052753 100644 --- a/source/blender/editors/uvedit/uvedit_unwrap_ops.c +++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.c @@ -119,7 +119,7 @@ static bool ED_uvedit_ensure_uvs(Object *obedit) BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); - luv->flag |= MLOOPUV_VERTSEL; + luv->flag |= (MLOOPUV_VERTSEL | MLOOPUV_EDGESEL); } } diff --git a/source/blender/makesdna/DNA_meshdata_types.h b/source/blender/makesdna/DNA_meshdata_types.h index 3a7de6860ee..3c734419bfe 100644 --- a/source/blender/makesdna/DNA_meshdata_types.h +++ b/source/blender/makesdna/DNA_meshdata_types.h @@ -327,7 +327,7 @@ typedef struct MLoopUV { /** #MLoopUV.flag */ enum { - /* MLOOPUV_DEPRECATED = (1 << 0), MLOOPUV_EDGESEL removed */ + MLOOPUV_EDGESEL = (1 << 0), MLOOPUV_VERTSEL = (1 << 1), MLOOPUV_PINNED = (1 << 2), }; diff --git a/source/blender/makesrna/intern/rna_mesh.c b/source/blender/makesrna/intern/rna_mesh.c index 87459587a9e..22275df61f9 100644 --- a/source/blender/makesrna/intern/rna_mesh.c +++ b/source/blender/makesrna/intern/rna_mesh.c @@ -2148,6 +2148,10 @@ static void rna_def_mloopuv(BlenderRNA *brna) prop = RNA_def_property(srna, "select", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flag", MLOOPUV_VERTSEL); RNA_def_property_ui_text(prop, "UV Select", ""); + + prop = RNA_def_property(srna, "select_edge", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", MLOOPUV_EDGESEL); + RNA_def_property_ui_text(prop, "UV Edge Select", ""); } static void rna_def_mloopcol(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index 29b06060256..145746cdce4 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -34,6 +34,7 @@ #include "ED_gpencil.h" #include "ED_object.h" +#include "ED_uvedit.h" #include "RNA_define.h" #include "RNA_enum_types.h" @@ -1835,6 +1836,13 @@ static void rna_Scene_editmesh_select_mode_update(bContext *C, PointerRNA *UNUSE } } +static void rna_Scene_uv_select_mode_update(bContext *C, PointerRNA *UNUSED(ptr)) +{ + /* Makes sure that the UV selection states are consistent with the current UV select mode and + * sticky mode.*/ + ED_uvedit_selectmode_clean_multi(C); +} + static void object_simplify_update(Object *ob) { ModifierData *md; @@ -3463,7 +3471,8 @@ static void rna_def_tool_settings(BlenderRNA *brna) RNA_def_property_enum_sdna(prop, NULL, "uv_selectmode"); RNA_def_property_enum_items(prop, rna_enum_mesh_select_mode_uv_items); RNA_def_property_ui_text(prop, "UV Selection Mode", "UV selection and display mode"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, NULL); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, "rna_Scene_uv_select_mode_update"); prop = RNA_def_property(srna, "uv_sticky_select_mode", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "uv_sticky"); diff --git a/source/blender/python/bmesh/bmesh_py_types_meshdata.c b/source/blender/python/bmesh/bmesh_py_types_meshdata.c index 6e6207f7229..d4455fd3668 100644 --- a/source/blender/python/bmesh/bmesh_py_types_meshdata.c +++ b/source/blender/python/bmesh/bmesh_py_types_meshdata.c @@ -56,6 +56,7 @@ static int bpy_bmloopuv_uv_set(BPy_BMLoopUV *self, PyObject *value, void *UNUSED PyDoc_STRVAR(bpy_bmloopuv_flag__pin_uv_doc, "UV pin state.\n\n:type: boolean"); PyDoc_STRVAR(bpy_bmloopuv_flag__select_doc, "UV select state.\n\n:type: boolean"); +PyDoc_STRVAR(bpy_bmloopuv_flag__select_edge_doc, "UV edge select state.\n\n:type: boolean"); static PyObject *bpy_bmloopuv_flag_get(BPy_BMLoopUV *self, void *flag_p) { @@ -93,6 +94,11 @@ static PyGetSetDef bpy_bmloopuv_getseters[] = { (setter)bpy_bmloopuv_flag_set, bpy_bmloopuv_flag__select_doc, (void *)MLOOPUV_VERTSEL}, + {"select_edge", + (getter)bpy_bmloopuv_flag_get, + (setter)bpy_bmloopuv_flag_set, + bpy_bmloopuv_flag__select_edge_doc, + (void *)MLOOPUV_EDGESEL}, {NULL, NULL, NULL, NULL, NULL} /* Sentinel */ }; |