/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ /** \file * \ingroup eduv */ #include #include #include #include "MEM_guardedalloc.h" #include "DNA_image_types.h" #include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_node_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_space_types.h" #include "BLI_array.h" #include "BLI_kdtree.h" #include "BLI_math.h" #include "BLI_utildefines.h" #include "BLT_translation.h" #include "BKE_context.h" #include "BKE_customdata.h" #include "BKE_editmesh.h" #include "BKE_layer.h" #include "BKE_main.h" #include "BKE_material.h" #include "BKE_mesh_mapping.h" #include "BKE_node.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" #include "ED_image.h" #include "ED_mesh.h" #include "ED_node.h" #include "ED_screen.h" #include "ED_uvedit.h" #include "RNA_access.h" #include "RNA_define.h" #include "WM_api.h" #include "WM_message.h" #include "WM_types.h" #include "UI_interface.h" #include "UI_resources.h" #include "UI_view2d.h" #include "uvedit_intern.h" /* -------------------------------------------------------------------- */ /** \name State Testing * \{ */ bool ED_uvedit_test(Object *obedit) { BMEditMesh *em; int ret; if (!obedit) { return 0; } if (obedit->type != OB_MESH) { return 0; } em = BKE_editmesh_from_object(obedit); ret = EDBM_uv_check(em); return ret; } static int UNUSED_FUNCTION(ED_operator_uvmap_mesh)(bContext *C) { Object *ob = CTX_data_active_object(C); if (ob && ob->type == OB_MESH) { Mesh *me = ob->data; if (CustomData_get_layer(&me->ldata, CD_MLOOPUV) != NULL) { return 1; } } return 0; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Object Active Image * \{ */ static bool is_image_texture_node(bNode *node) { return ELEM(node->type, SH_NODE_TEX_IMAGE, SH_NODE_TEX_ENVIRONMENT); } bool ED_object_get_active_image(Object *ob, int mat_nr, Image **r_ima, ImageUser **r_iuser, bNode **r_node, bNodeTree **r_ntree) { Material *ma = DEG_is_evaluated_object(ob) ? BKE_object_material_get_eval(ob, mat_nr) : BKE_object_material_get(ob, mat_nr); bNodeTree *ntree = (ma && ma->use_nodes) ? ma->nodetree : NULL; bNode *node = (ntree) ? nodeGetActiveTexture(ntree) : NULL; if (node && is_image_texture_node(node)) { if (r_ima) { *r_ima = (Image *)node->id; } if (r_iuser) { if (node->type == SH_NODE_TEX_IMAGE) { *r_iuser = &((NodeTexImage *)node->storage)->iuser; } else if (node->type == SH_NODE_TEX_ENVIRONMENT) { *r_iuser = &((NodeTexEnvironment *)node->storage)->iuser; } else { *r_iuser = NULL; } } if (r_node) { *r_node = node; } if (r_ntree) { *r_ntree = ntree; } return true; } if (r_ima) { *r_ima = NULL; } if (r_iuser) { *r_iuser = NULL; } if (r_node) { *r_node = node; } if (r_ntree) { *r_ntree = ntree; } return false; } void ED_object_assign_active_image(Main *bmain, Object *ob, int mat_nr, Image *ima) { Material *ma = BKE_object_material_get(ob, mat_nr); bNode *node = (ma && ma->use_nodes) ? nodeGetActiveTexture(ma->nodetree) : NULL; if (node && is_image_texture_node(node)) { node->id = &ima->id; ED_node_tree_propagate_change(NULL, bmain, ma->nodetree); } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Live Unwrap Utilities * \{ */ void uvedit_live_unwrap_update(SpaceImage *sima, Scene *scene, Object *obedit) { if (sima && (sima->flag & SI_LIVE_UNWRAP)) { ED_uvedit_live_unwrap_begin(scene, obedit); ED_uvedit_live_unwrap_re_solve(); ED_uvedit_live_unwrap_end(0); } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Geometric Utilities * \{ */ bool ED_uvedit_minmax_multi( const Scene *scene, Object **objects_edit, uint objects_len, float r_min[2], float r_max[2]) { bool changed = false; INIT_MINMAX2(r_min, r_max); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects_edit[ob_index]; 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); 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)) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); minmax_v2v2_v2(r_min, r_max, luv->uv); changed = true; } } } } return changed; } bool ED_uvedit_minmax(const Scene *scene, Object *obedit, float r_min[2], float r_max[2]) { return ED_uvedit_minmax_multi(scene, &obedit, 1, r_min, r_max); } void ED_uvedit_select_all(BMesh *bm) { BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); 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 | MLOOPUV_EDGESEL); } } } static bool ED_uvedit_median_multi(const Scene *scene, Object **objects_edit, uint objects_len, float co[2]) { uint sel = 0; zero_v2(co); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects_edit[ob_index]; 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); 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 (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { add_v2_v2(co, luv->uv); sel++; } } } } mul_v2_fl(co, 1.0f / (float)sel); return (sel != 0); } bool ED_uvedit_center_multi( const Scene *scene, Object **objects_edit, uint objects_len, float cent[2], char mode) { bool changed = false; if (mode == V3D_AROUND_CENTER_BOUNDS) { /* bounding box */ float min[2], max[2]; if (ED_uvedit_minmax_multi(scene, objects_edit, objects_len, min, max)) { mid_v2_v2v2(cent, min, max); changed = true; } } else { if (ED_uvedit_median_multi(scene, objects_edit, objects_len, cent)) { changed = true; } } return changed; } bool ED_uvedit_center_from_pivot_ex(SpaceImage *sima, Scene *scene, ViewLayer *view_layer, float r_center[2], char mode, bool *r_has_select) { bool changed = false; switch (mode) { case V3D_AROUND_CURSOR: { copy_v2_v2(r_center, sima->cursor); changed = true; if (r_has_select != NULL) { uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, ((View3D *)NULL), &objects_len); *r_has_select = uvedit_select_is_any_selected_multi(scene, objects, objects_len); MEM_freeN(objects); } break; } default: { uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, ((View3D *)NULL), &objects_len); changed = ED_uvedit_center_multi(scene, objects, objects_len, r_center, mode); MEM_freeN(objects); if (r_has_select != NULL) { *r_has_select = changed; } break; } } return changed; } bool ED_uvedit_center_from_pivot( SpaceImage *sima, Scene *scene, ViewLayer *view_layer, float r_center[2], char mode) { return ED_uvedit_center_from_pivot_ex(sima, scene, view_layer, r_center, mode, NULL); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Weld Align Operator * \{ */ typedef enum eUVWeldAlign { UV_STRAIGHTEN, UV_STRAIGHTEN_X, UV_STRAIGHTEN_Y, UV_ALIGN_AUTO, UV_ALIGN_X, UV_ALIGN_Y, UV_WELD, } eUVWeldAlign; static bool uvedit_uv_align_weld(Scene *scene, BMesh *bm, const eUVWeldAlign tool, const float cent[2]) { bool changed = false; const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); BMIter iter; BMFace *efa; BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BMIter liter; BMLoop *l; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (!uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { continue; } MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (ELEM(tool, UV_ALIGN_X, UV_WELD)) { if (luv->uv[0] != cent[0]) { luv->uv[0] = cent[0]; changed = true; } } if (ELEM(tool, UV_ALIGN_Y, UV_WELD)) { if (luv->uv[1] != cent[1]) { luv->uv[1] = cent[1]; changed = true; } } } } return changed; } /** Bitwise-or together, then choose #MLoopUV with highest value. */ typedef enum eUVEndPointPrecedence { UVEP_INVALID = 0, UVEP_SELECTED = (1 << 0), UVEP_PINNED = (1 << 1), /* i.e. Pinned verts are preferred to selected. */ } eUVEndPointPrecedence; static eUVEndPointPrecedence uvedit_line_update_get_precedence(const MLoopUV *luv) { eUVEndPointPrecedence precedence = UVEP_SELECTED; if (luv->flag & MLOOPUV_PINNED) { precedence |= UVEP_PINNED; } return precedence; } /** * Helper to find two endpoints (`a` and `b`) which have higher precedence, and are far apart. * Note that is only a heuristic and won't always find the best two endpoints. */ static bool uvedit_line_update_endpoint(const MLoopUV *luv, float uv_a[2], eUVEndPointPrecedence *prec_a, float uv_b[2], eUVEndPointPrecedence *prec_b) { eUVEndPointPrecedence flags = uvedit_line_update_get_precedence(luv); float len_sq_a = len_squared_v2v2(uv_a, luv->uv); float len_sq_b = len_squared_v2v2(uv_b, luv->uv); /* Caching the value of `len_sq_ab` is unlikely to be faster than recalculating. * Profile before optimizing. */ float len_sq_ab = len_squared_v2v2(uv_a, uv_b); if ((*prec_a < flags && 0.0f < len_sq_b) || (*prec_a == flags && len_sq_ab < len_sq_b)) { *prec_a = flags; copy_v2_v2(uv_a, luv->uv); return true; } if ((*prec_b < flags && 0.0f < len_sq_a) || (*prec_b == flags && len_sq_ab < len_sq_a)) { *prec_b = flags; copy_v2_v2(uv_b, luv->uv); return true; } return false; } /** * Find two end extreme points to specify a line, then straighten `len` elements * by moving UVs on the X-axis, Y-axis, or the closest point on the line segment. */ static bool uvedit_uv_straighten_elements(const UvElement *element, const int len, const int cd_loop_uv_offset, const eUVWeldAlign tool) { float uv_start[2]; float uv_end[2]; eUVEndPointPrecedence prec_start = UVEP_INVALID; eUVEndPointPrecedence prec_end = UVEP_INVALID; /* Find start and end of line. */ for (int i = 0; i < 10; i++) { /* Heuristic to prevent infinite loop. */ bool update = false; for (int j = 0; j < len; j++) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(element[j].l, cd_loop_uv_offset); update |= uvedit_line_update_endpoint(luv, uv_start, &prec_start, uv_end, &prec_end); } if (!update) { break; } } if (prec_start == UVEP_INVALID || prec_end == UVEP_INVALID) { return false; /* Unable to find two endpoints. */ } float a = 0.0f; /* Similar to "slope". */ eUVWeldAlign tool_local = tool; if (tool_local == UV_STRAIGHTEN_X) { if (uv_start[1] == uv_end[1]) { /* Caution, different behavior outside line segment. */ tool_local = UV_STRAIGHTEN; } else { a = (uv_end[0] - uv_start[0]) / (uv_end[1] - uv_start[1]); } } else if (tool_local == UV_STRAIGHTEN_Y) { if (uv_start[0] == uv_end[0]) { /* Caution, different behavior outside line segment. */ tool_local = UV_STRAIGHTEN; } else { a = (uv_end[1] - uv_start[1]) / (uv_end[0] - uv_start[0]); } } bool changed = false; for (int j = 0; j < len; j++) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(element[j].l, cd_loop_uv_offset); /* Projection of point (x, y) over line (x1, y1, x2, y2) along X axis: * new_y = (y2 - y1) / (x2 - x1) * (x - x1) + y1 * Maybe this should be a BLI func? Or is it already existing? * Could use interp_v2_v2v2, but not sure it's worth it here. */ if (tool_local == UV_STRAIGHTEN_X) { luv->uv[0] = a * (luv->uv[1] - uv_start[1]) + uv_start[0]; } else if (tool_local == UV_STRAIGHTEN_Y) { luv->uv[1] = a * (luv->uv[0] - uv_start[0]) + uv_start[1]; } else { closest_to_line_segment_v2(luv->uv, luv->uv, uv_start, uv_end); } changed = true; /* TODO: Did the UV actually move? */ } return changed; } /** * Group selected UVs into islands, then apply uvedit_uv_straighten_elements to each island. */ static bool uvedit_uv_straighten(Scene *scene, BMesh *bm, eUVWeldAlign tool) { const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); if (cd_loop_uv_offset == -1) { return false; } UvElementMap *element_map = BM_uv_element_map_create(bm, scene, true, false, true, true); if (element_map == NULL) { return false; } bool changed = false; for (int i = 0; i < element_map->total_islands; i++) { changed |= uvedit_uv_straighten_elements(element_map->storage + element_map->island_indices[i], element_map->island_total_uvs[i], cd_loop_uv_offset, tool); } BM_uv_element_map_free(element_map); return changed; } static void uv_weld_align(bContext *C, eUVWeldAlign tool) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); SpaceImage *sima = CTX_wm_space_image(C); const ToolSettings *ts = scene->toolsettings; const bool synced_selection = (ts->uv_flag & UV_SYNC_SELECTION) != 0; float cent[2], min[2], max[2]; INIT_MINMAX2(min, max); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, ((View3D *)NULL), &objects_len); if (tool == UV_ALIGN_AUTO) { for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); if (synced_selection && (em->bm->totvertsel == 0)) { continue; } const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BMIter iter, liter; BMFace *efa; BMLoop *l; 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)) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); minmax_v2v2_v2(min, max, luv->uv); } } } } tool = (max[0] - min[0] >= max[1] - min[1]) ? UV_ALIGN_Y : UV_ALIGN_X; } ED_uvedit_center_multi(scene, objects, objects_len, cent, 0); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); bool changed = false; if (synced_selection && (em->bm->totvertsel == 0)) { continue; } if (ELEM(tool, UV_ALIGN_AUTO, UV_ALIGN_X, UV_ALIGN_Y, UV_WELD)) { changed |= uvedit_uv_align_weld(scene, em->bm, tool, cent); } if (ELEM(tool, UV_STRAIGHTEN, UV_STRAIGHTEN_X, UV_STRAIGHTEN_Y)) { changed |= uvedit_uv_straighten(scene, em->bm, tool); } if (changed) { uvedit_live_unwrap_update(sima, scene, obedit); DEG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); } } MEM_freeN(objects); } static int uv_align_exec(bContext *C, wmOperator *op) { uv_weld_align(C, RNA_enum_get(op->ptr, "axis")); return OPERATOR_FINISHED; } static void UV_OT_align(wmOperatorType *ot) { static const EnumPropertyItem axis_items[] = { {UV_STRAIGHTEN, "ALIGN_S", 0, "Straighten", "Align UVs along the line defined by the endpoints"}, {UV_STRAIGHTEN_X, "ALIGN_T", 0, "Straighten X", "Align UVs along the line defined by the endpoints along the X axis"}, {UV_STRAIGHTEN_Y, "ALIGN_U", 0, "Straighten Y", "Align UVs along the line defined by the endpoints along the Y axis"}, {UV_ALIGN_AUTO, "ALIGN_AUTO", 0, "Align Auto", "Automatically choose the axis on which there is most alignment already"}, {UV_ALIGN_X, "ALIGN_X", 0, "Align X", "Align UVs on X axis"}, {UV_ALIGN_Y, "ALIGN_Y", 0, "Align Y", "Align UVs on Y axis"}, {0, NULL, 0, NULL, NULL}, }; /* identifiers */ ot->name = "Align"; ot->description = "Align selected UV vertices to an axis"; ot->idname = "UV_OT_align"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_align_exec; ot->poll = ED_operator_uvedit; /* properties */ RNA_def_enum( ot->srna, "axis", axis_items, UV_ALIGN_AUTO, "Axis", "Axis to align UV locations on"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Remove Doubles Operator * \{ */ static int uv_remove_doubles_to_selected(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); SpaceImage *sima = CTX_wm_space_image(C); const ToolSettings *ts = scene->toolsettings; const float threshold = RNA_float_get(op->ptr, "threshold"); const bool synced_selection = (ts->uv_flag & UV_SYNC_SELECTION) != 0; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, ((View3D *)NULL), &objects_len); bool *changed = MEM_callocN(sizeof(bool) * objects_len, "uv_remove_doubles_selected.changed"); /* Maximum index of an objects[i]'s MLoopUVs in MLoopUV_arr. * It helps find which MLoopUV in *MLoopUV_arr belongs to which object. */ uint *ob_mloopuv_max_idx = MEM_callocN(sizeof(uint) * objects_len, "uv_remove_doubles_selected.ob_mloopuv_max_idx"); /* Calculate max possible number of kdtree nodes. */ int uv_maxlen = 0; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); if (synced_selection && (em->bm->totvertsel == 0)) { continue; } uv_maxlen += em->bm->totloop; } KDTree_2d *tree = BLI_kdtree_2d_new(uv_maxlen); int *duplicates = NULL; BLI_array_declare(duplicates); MLoopUV **mloopuv_arr = NULL; BLI_array_declare(mloopuv_arr); int mloopuv_count = 0; /* Also used for *duplicates count. */ for (uint ob_index = 0; ob_index < objects_len; ob_index++) { BMIter iter, liter; BMFace *efa; BMLoop *l; Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); if (synced_selection && (em->bm->totvertsel == 0)) { continue; } const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); 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)) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); BLI_kdtree_2d_insert(tree, mloopuv_count, luv->uv); BLI_array_append(duplicates, -1); BLI_array_append(mloopuv_arr, luv); mloopuv_count++; } } } ob_mloopuv_max_idx[ob_index] = mloopuv_count - 1; } BLI_kdtree_2d_balance(tree); int found_duplicates = BLI_kdtree_2d_calc_duplicates_fast(tree, threshold, false, duplicates); if (found_duplicates > 0) { /* Calculate average uv for duplicates. */ int *uv_duplicate_count = MEM_callocN(sizeof(int) * mloopuv_count, "uv_remove_doubles_selected.uv_duplicate_count"); for (int i = 0; i < mloopuv_count; i++) { if (duplicates[i] == -1) { /* If doesn't reference another */ uv_duplicate_count[i]++; /* self */ continue; } if (duplicates[i] != i) { /* If not self then accumulate uv for averaging. * Self uv is already present in accumulator */ add_v2_v2(mloopuv_arr[duplicates[i]]->uv, mloopuv_arr[i]->uv); } uv_duplicate_count[duplicates[i]]++; } for (int i = 0; i < mloopuv_count; i++) { if (uv_duplicate_count[i] < 2) { continue; } mul_v2_fl(mloopuv_arr[i]->uv, 1.0f / (float)uv_duplicate_count[i]); } MEM_freeN(uv_duplicate_count); /* Update duplicated uvs. */ uint ob_index = 0; for (int i = 0; i < mloopuv_count; i++) { /* Make sure we know which object owns the MLoopUV at this index. * Remember that in some cases the object will have no loop uv, * thus we need the while loop, and not simply an if check. */ while (ob_mloopuv_max_idx[ob_index] < i) { ob_index++; } if (duplicates[i] == -1) { continue; } copy_v2_v2(mloopuv_arr[i]->uv, mloopuv_arr[duplicates[i]]->uv); changed[ob_index] = true; } for (ob_index = 0; ob_index < objects_len; ob_index++) { if (changed[ob_index]) { Object *obedit = objects[ob_index]; uvedit_live_unwrap_update(sima, scene, obedit); DEG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); } } } BLI_kdtree_2d_free(tree); BLI_array_free(mloopuv_arr); BLI_array_free(duplicates); MEM_freeN(changed); MEM_freeN(objects); MEM_freeN(ob_mloopuv_max_idx); return OPERATOR_FINISHED; } static int uv_remove_doubles_to_unselected(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); SpaceImage *sima = CTX_wm_space_image(C); const ToolSettings *ts = scene->toolsettings; const float threshold = RNA_float_get(op->ptr, "threshold"); const bool synced_selection = (ts->uv_flag & UV_SYNC_SELECTION) != 0; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, ((View3D *)NULL), &objects_len); /* Calculate max possible number of kdtree nodes. */ int uv_maxlen = 0; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); uv_maxlen += em->bm->totloop; } KDTree_2d *tree = BLI_kdtree_2d_new(uv_maxlen); MLoopUV **mloopuv_arr = NULL; BLI_array_declare(mloopuv_arr); int mloopuv_count = 0; /* Add visible non-selected uvs to tree */ for (uint ob_index = 0; ob_index < objects_len; ob_index++) { BMIter iter, liter; BMFace *efa; BMLoop *l; Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); if (synced_selection && (em->bm->totvertsel == em->bm->totvert)) { continue; } const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); 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)) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); BLI_kdtree_2d_insert(tree, mloopuv_count, luv->uv); BLI_array_append(mloopuv_arr, luv); mloopuv_count++; } } } } BLI_kdtree_2d_balance(tree); /* For each selected uv, find duplicate non selected uv. */ for (uint ob_index = 0; ob_index < objects_len; ob_index++) { BMIter iter, liter; BMFace *efa; BMLoop *l; bool changed = false; Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); if (synced_selection && (em->bm->totvertsel == 0)) { continue; } const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); 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)) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); KDTreeNearest_2d nearest; const int i = BLI_kdtree_2d_find_nearest(tree, luv->uv, &nearest); if (i != -1 && nearest.dist < threshold) { copy_v2_v2(luv->uv, mloopuv_arr[i]->uv); changed = true; } } } } if (changed) { uvedit_live_unwrap_update(sima, scene, obedit); DEG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); } } BLI_kdtree_2d_free(tree); BLI_array_free(mloopuv_arr); MEM_freeN(objects); return OPERATOR_FINISHED; } static int uv_remove_doubles_exec(bContext *C, wmOperator *op) { if (RNA_boolean_get(op->ptr, "use_unselected")) { return uv_remove_doubles_to_unselected(C, op); } return uv_remove_doubles_to_selected(C, op); } static void UV_OT_remove_doubles(wmOperatorType *ot) { /* identifiers */ ot->name = "Merge UVs by Distance"; ot->description = "Selected UV vertices that are within a radius of each other are welded together"; ot->idname = "UV_OT_remove_doubles"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_remove_doubles_exec; ot->poll = ED_operator_uvedit; RNA_def_float(ot->srna, "threshold", 0.02f, 0.0f, 10.0f, "Merge Distance", "Maximum distance between welded vertices", 0.0f, 1.0f); RNA_def_boolean( ot->srna, "use_unselected", 0, "Unselected", "Merge selected to other unselected vertices"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Weld Near Operator * \{ */ static int uv_weld_exec(bContext *C, wmOperator *UNUSED(op)) { uv_weld_align(C, UV_WELD); return OPERATOR_FINISHED; } static void UV_OT_weld(wmOperatorType *ot) { /* identifiers */ ot->name = "Weld"; ot->description = "Weld selected UV vertices together"; ot->idname = "UV_OT_weld"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_weld_exec; ot->poll = ED_operator_uvedit; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Snap Cursor Operator * \{ */ static void uv_snap_to_pixel(float uvco[2], float w, float h) { uvco[0] = roundf(uvco[0] * w) / w; uvco[1] = roundf(uvco[1] * h) / h; } static void uv_snap_cursor_to_pixels(SpaceImage *sima) { int width = 0, height = 0; ED_space_image_get_size(sima, &width, &height); uv_snap_to_pixel(sima->cursor, width, height); } static bool uv_snap_cursor_to_selection(Scene *scene, Object **objects_edit, uint objects_len, SpaceImage *sima) { return ED_uvedit_center_multi(scene, objects_edit, objects_len, sima->cursor, sima->around); } static void uv_snap_cursor_to_origin(float uvco[2]) { uvco[0] = 0; uvco[1] = 0; } static int uv_snap_cursor_exec(bContext *C, wmOperator *op) { SpaceImage *sima = CTX_wm_space_image(C); bool changed = false; switch (RNA_enum_get(op->ptr, "target")) { case 0: uv_snap_cursor_to_pixels(sima); changed = true; break; case 1: { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, ((View3D *)NULL), &objects_len); changed = uv_snap_cursor_to_selection(scene, objects, objects_len, sima); MEM_freeN(objects); break; } case 2: uv_snap_cursor_to_origin(sima->cursor); changed = true; break; } if (!changed) { return OPERATOR_CANCELLED; } WM_event_add_notifier(C, NC_SPACE | ND_SPACE_IMAGE, sima); return OPERATOR_FINISHED; } static void UV_OT_snap_cursor(wmOperatorType *ot) { static const EnumPropertyItem target_items[] = { {0, "PIXELS", 0, "Pixels", ""}, {1, "SELECTED", 0, "Selected", ""}, {2, "ORIGIN", 0, "Origin", ""}, {0, NULL, 0, NULL, NULL}, }; /* identifiers */ ot->name = "Snap Cursor"; ot->description = "Snap cursor to target type"; ot->idname = "UV_OT_snap_cursor"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_snap_cursor_exec; ot->poll = ED_operator_uvedit_space_image; /* requires space image */ /* properties */ RNA_def_enum( ot->srna, "target", target_items, 0, "Target", "Target to snap the selected UVs to"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Snap Selection Operator * \{ */ static bool uv_snap_uvs_to_cursor(Scene *scene, Object *obedit, const float cursor[2]) { BMEditMesh *em = BKE_editmesh_from_object(obedit); BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; bool changed = false; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); 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)) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); copy_v2_v2(luv->uv, cursor); changed = true; } } } return changed; } static bool uv_snap_uvs_offset(Scene *scene, Object *obedit, const float offset[2]) { BMEditMesh *em = BKE_editmesh_from_object(obedit); BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; bool changed = false; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); 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)) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); add_v2_v2(luv->uv, offset); changed = true; } } } return changed; } static bool uv_snap_uvs_to_adjacent_unselected(Scene *scene, Object *obedit) { BMEditMesh *em = BKE_editmesh_from_object(obedit); BMesh *bm = em->bm; BMFace *f; BMLoop *l, *lsub; BMIter iter, liter, lsubiter; MLoopUV *luv; bool changed = false; const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); /* index every vert that has a selected UV using it, but only once so as to * get unique indices and to count how much to malloc */ BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { if (uvedit_face_visible_test(scene, f)) { BM_elem_flag_enable(f, BM_ELEM_TAG); BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { BM_elem_flag_set(l, BM_ELEM_TAG, uvedit_uv_select_test(scene, l, cd_loop_uv_offset)); } } else { BM_elem_flag_disable(f, BM_ELEM_TAG); } } BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { if (BM_elem_flag_test(f, BM_ELEM_TAG)) { /* Face: visible. */ BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { if (BM_elem_flag_test(l, BM_ELEM_TAG)) { /* Loop: selected. */ float uv[2] = {0.0f, 0.0f}; int uv_tot = 0; BM_ITER_ELEM (lsub, &lsubiter, l->v, BM_LOOPS_OF_VERT) { if (BM_elem_flag_test(lsub->f, BM_ELEM_TAG) && /* Face: visible. */ !BM_elem_flag_test(lsub, BM_ELEM_TAG)) /* Loop: unselected. */ { luv = BM_ELEM_CD_GET_VOID_P(lsub, cd_loop_uv_offset); add_v2_v2(uv, luv->uv); uv_tot++; } } if (uv_tot) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); mul_v2_v2fl(luv->uv, uv, 1.0f / (float)uv_tot); changed = true; } } } } } return changed; } static bool uv_snap_uvs_to_pixels(SpaceImage *sima, Scene *scene, Object *obedit) { BMEditMesh *em = BKE_editmesh_from_object(obedit); BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; int width = 0, height = 0; float w, h; bool changed = false; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); ED_space_image_get_size(sima, &width, &height); w = (float)width; h = (float)height; 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)) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); uv_snap_to_pixel(luv->uv, w, h); } } changed = true; } return changed; } static int uv_snap_selection_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); SpaceImage *sima = CTX_wm_space_image(C); const ToolSettings *ts = scene->toolsettings; const bool synced_selection = (ts->uv_flag & UV_SYNC_SELECTION) != 0; const int target = RNA_enum_get(op->ptr, "target"); float offset[2] = {0}; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, ((View3D *)NULL), &objects_len); if (target == 2) { float center[2]; if (!ED_uvedit_center_multi(scene, objects, objects_len, center, sima->around)) { MEM_freeN(objects); return OPERATOR_CANCELLED; } sub_v2_v2v2(offset, sima->cursor, center); } bool changed_multi = false; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); if (synced_selection && (em->bm->totvertsel == 0)) { continue; } bool changed = false; switch (target) { case 0: changed = uv_snap_uvs_to_pixels(sima, scene, obedit); break; case 1: changed = uv_snap_uvs_to_cursor(scene, obedit, sima->cursor); break; case 2: changed = uv_snap_uvs_offset(scene, obedit, offset); break; case 3: changed = uv_snap_uvs_to_adjacent_unselected(scene, obedit); break; } if (changed) { changed_multi = true; uvedit_live_unwrap_update(sima, scene, obedit); DEG_id_tag_update(obedit->data, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); } } MEM_freeN(objects); return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } static void UV_OT_snap_selected(wmOperatorType *ot) { static const EnumPropertyItem target_items[] = { {0, "PIXELS", 0, "Pixels", ""}, {1, "CURSOR", 0, "Cursor", ""}, {2, "CURSOR_OFFSET", 0, "Cursor (Offset)", ""}, {3, "ADJACENT_UNSELECTED", 0, "Adjacent Unselected", ""}, {0, NULL, 0, NULL, NULL}, }; /* identifiers */ ot->name = "Snap Selection"; ot->description = "Snap selected UV vertices to target type"; ot->idname = "UV_OT_snap_selected"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_snap_selection_exec; ot->poll = ED_operator_uvedit_space_image; /* properties */ RNA_def_enum( ot->srna, "target", target_items, 0, "Target", "Target to snap the selected UVs to"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Pin UV's Operator * \{ */ static int uv_pin_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; const ToolSettings *ts = scene->toolsettings; const bool clear = RNA_boolean_get(op->ptr, "clear"); const bool synced_selection = (ts->uv_flag & UV_SYNC_SELECTION) != 0; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, ((View3D *)NULL), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); bool changed = false; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if (synced_selection && (em->bm->totvertsel == 0)) { continue; } 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 (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { changed = true; if (clear) { luv->flag &= ~MLOOPUV_PINNED; } else { luv->flag |= MLOOPUV_PINNED; } } } } if (changed) { WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data); DEG_id_tag_update(obedit->data, ID_RECALC_COPY_ON_WRITE); } } MEM_freeN(objects); return OPERATOR_FINISHED; } static void UV_OT_pin(wmOperatorType *ot) { /* identifiers */ ot->name = "Pin"; ot->description = "Set/clear selected UV vertices as anchored between multiple unwrap operations"; ot->idname = "UV_OT_pin"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_pin_exec; ot->poll = ED_operator_uvedit; /* properties */ RNA_def_boolean( ot->srna, "clear", 0, "Clear", "Clear pinning for the selection instead of setting it"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Hide Operator * \{ */ /* 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) #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; BMLoop *l_first; 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_EDGE_SEL_TEST(luv, select_test)) { return false; } } while ((l_iter = l_iter->next) != l_first); return true; } static int uv_hide_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); Scene *scene = CTX_data_scene(C); const ToolSettings *ts = scene->toolsettings; const bool swap = RNA_boolean_get(op->ptr, "unselected"); const bool use_face_center = (ts->uv_selectmode == UV_SELECT_FACE); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, ((View3D *)NULL), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(ob); BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if (ts->uv_flag & UV_SYNC_SELECTION) { if (EDBM_mesh_hide(em, swap)) { EDBM_update(ob->data, &(const struct EDBMUpdate_Params){ .calc_looptri = true, .calc_normals = false, .is_destructive = false, }); } continue; } BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { int hide = 0; 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_VERT_SEL_TEST(luv, !swap) || UV_EDGE_SEL_TEST(luv, !swap)) { hide = 1; break; } } if (hide) { if (use_face_center) { if (em->selectmode == SCE_SELECT_FACE) { /* 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->bm, efa, cd_loop_uv_offset); } else { 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); /* 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); } } } if (!swap) { uvedit_face_select_disable(scene, em->bm, efa, cd_loop_uv_offset); } } } else if (em->selectmode == SCE_SELECT_FACE) { /* 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; } if (UV_VERT_SEL_TEST(luv, !swap) && (ts->uv_selectmode == UV_SELECT_VERTEX)) { BM_face_select_set(em->bm, efa, false); break; } if (ts->uv_selectmode == UV_SELECT_ISLAND) { BM_face_select_set(em->bm, efa, false); break; } } uvedit_face_select_disable(scene, em->bm, 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_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->bm, efa, cd_loop_uv_offset); } } } } /* Flush editmesh selections to ensure valid selection states. */ if (em->selectmode != SCE_SELECT_FACE) { /* 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); DEG_id_tag_update(ob->data, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data); } MEM_freeN(objects); return OPERATOR_FINISHED; } #undef UV_VERT_SEL_TEST #undef UV_EDGE_SEL_TEST static void UV_OT_hide(wmOperatorType *ot) { /* identifiers */ ot->name = "Hide Selected"; ot->description = "Hide (un)selected UV vertices"; ot->idname = "UV_OT_hide"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_hide_exec; ot->poll = ED_operator_uvedit; /* props */ RNA_def_boolean(ot->srna, "unselected", 0, "Unselected", "Hide unselected rather than selected"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Reveal Operator * \{ */ static int uv_reveal_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(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 select = RNA_boolean_get(op->ptr, "select"); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, ((View3D *)NULL), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(ob); BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); /* 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 * visibility 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) { if (EDBM_mesh_reveal(em, select)) { EDBM_update(ob->data, &(const struct EDBMUpdate_Params){ .calc_looptri = true, .calc_normals = false, .is_destructive = false, }); } continue; } /* NOTE(@sidd017): Supporting selections in all cases is quite difficult considering there are * at least 12 cases to look into (3 mesh select-modes + 4 uv select-modes + 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) { 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) { 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); } } } 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)) { 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); } else if (em->selectmode == SCE_SELECT_EDGE) { totsel += BM_elem_flag_test(l->e, 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 | MLOOPUV_EDGESEL)); } } /* BM_face_select_set(em->bm, efa, true); */ BM_elem_flag_enable(efa, BM_ELEM_TAG); } } } } else if (em->selectmode == SCE_SELECT_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)) { 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 | MLOOPUV_EDGESEL)); } /* BM_face_select_set(em->bm, efa, true); */ BM_elem_flag_enable(efa, BM_ELEM_TAG); } } } 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)) { 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 | MLOOPUV_EDGESEL)); } /* BM_face_select_set(em->bm, efa, true); */ BM_elem_flag_enable(efa, BM_ELEM_TAG); } } } /* re-select tagged faces */ BM_mesh_elem_hflag_enable_test(em->bm, BM_FACE, BM_ELEM_SELECT, true, false, BM_ELEM_TAG); DEG_id_tag_update(ob->data, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data); } MEM_freeN(objects); return OPERATOR_FINISHED; } static void UV_OT_reveal(wmOperatorType *ot) { /* identifiers */ ot->name = "Reveal Hidden"; ot->description = "Reveal all hidden UV vertices"; ot->idname = "UV_OT_reveal"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_reveal_exec; ot->poll = ED_operator_uvedit; RNA_def_boolean(ot->srna, "select", true, "Select", ""); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Set 2D Cursor Operator * \{ */ static int uv_set_2d_cursor_exec(bContext *C, wmOperator *op) { SpaceImage *sima = CTX_wm_space_image(C); if (!sima) { return OPERATOR_CANCELLED; } RNA_float_get_array(op->ptr, "location", sima->cursor); { struct wmMsgBus *mbus = CTX_wm_message_bus(C); bScreen *screen = CTX_wm_screen(C); WM_msg_publish_rna_prop(mbus, &screen->id, sima, SpaceImageEditor, cursor_location); } WM_event_add_notifier(C, NC_SPACE | ND_SPACE_IMAGE, NULL); /* Use pass-through to allow click-drag to transform the cursor. */ return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH; } static int uv_set_2d_cursor_invoke(bContext *C, wmOperator *op, const wmEvent *event) { ARegion *region = CTX_wm_region(C); float location[2]; if (region->regiontype == RGN_TYPE_WINDOW) { SpaceImage *sima = CTX_wm_space_image(C); if (sima && ED_space_image_show_cache_and_mval_over(sima, region, event->mval)) { return OPERATOR_PASS_THROUGH; } } UI_view2d_region_to_view( ®ion->v2d, event->mval[0], event->mval[1], &location[0], &location[1]); RNA_float_set_array(op->ptr, "location", location); return uv_set_2d_cursor_exec(C, op); } static void UV_OT_cursor_set(wmOperatorType *ot) { /* identifiers */ ot->name = "Set 2D Cursor"; ot->description = "Set 2D cursor location"; ot->idname = "UV_OT_cursor_set"; /* api callbacks */ ot->exec = uv_set_2d_cursor_exec; ot->invoke = uv_set_2d_cursor_invoke; ot->poll = ED_space_image_cursor_poll; /* properties */ RNA_def_float_vector(ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX, "Location", "Cursor location in normalized (0.0 to 1.0) coordinates", -10.0f, 10.0f); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Seam from UV Islands Operator * \{ */ static int uv_seams_from_islands_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const bool mark_seams = RNA_boolean_get(op->ptr, "mark_seams"); const bool mark_sharp = RNA_boolean_get(op->ptr, "mark_sharp"); bool changed_multi = false; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, ((View3D *)NULL), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; Mesh *me = (Mesh *)ob->data; BMEditMesh *em = me->edit_mesh; BMesh *bm = em->bm; BMIter iter; if (!EDBM_uv_check(em)) { continue; } const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); bool changed = false; BMFace *f; BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, f)) { continue; } BMLoop *l_iter; BMLoop *l_first; l_iter = l_first = BM_FACE_FIRST_LOOP(f); do { if (l_iter == l_iter->radial_next) { continue; } if (!uvedit_edge_select_test(scene, l_iter, cd_loop_uv_offset)) { continue; } bool mark = false; BMLoop *l_other = l_iter->radial_next; do { if (!BM_loop_uv_share_edge_check(l_iter, l_other, cd_loop_uv_offset)) { mark = true; break; } } while ((l_other = l_other->radial_next) != l_iter); if (mark) { if (mark_seams) { BM_elem_flag_enable(l_iter->e, BM_ELEM_SEAM); } if (mark_sharp) { BM_elem_flag_disable(l_iter->e, BM_ELEM_SMOOTH); } changed = true; } } while ((l_iter = l_iter->next) != l_first); } if (changed) { changed_multi = true; DEG_id_tag_update(&me->id, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, me); } } MEM_freeN(objects); return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } static void UV_OT_seams_from_islands(wmOperatorType *ot) { /* identifiers */ ot->name = "Seams from Islands"; ot->description = "Set mesh seams according to island setup in the UV editor"; ot->idname = "UV_OT_seams_from_islands"; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_seams_from_islands_exec; ot->poll = ED_operator_uvedit; RNA_def_boolean(ot->srna, "mark_seams", 1, "Mark Seams", "Mark boundary edges as seams"); RNA_def_boolean(ot->srna, "mark_sharp", 0, "Mark Sharp", "Mark boundary edges as sharp"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Mark Seam Operator * \{ */ static int uv_mark_seam_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const ToolSettings *ts = scene->toolsettings; BMFace *efa; BMLoop *loop; BMIter iter, liter; const bool flag_set = !RNA_boolean_get(op->ptr, "clear"); const bool synced_selection = (ts->uv_flag & UV_SYNC_SELECTION) != 0; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, ((View3D *)NULL), &objects_len); bool changed = false; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; Mesh *me = (Mesh *)ob->data; BMEditMesh *em = me->edit_mesh; BMesh *bm = em->bm; if (synced_selection && (bm->totedgesel == 0)) { continue; } const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { if (uvedit_face_visible_test(scene, efa)) { BM_ITER_ELEM (loop, &liter, efa, BM_LOOPS_OF_FACE) { if (uvedit_edge_select_test(scene, loop, cd_loop_uv_offset)) { BM_elem_flag_set(loop->e, BM_ELEM_SEAM, flag_set); changed = true; } } } } if (changed) { DEG_id_tag_update(&me->id, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, me); } } if (changed) { ED_uvedit_live_unwrap(scene, objects, objects_len); } MEM_freeN(objects); return OPERATOR_FINISHED; } static int uv_mark_seam_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { uiPopupMenu *pup; uiLayout *layout; if (RNA_struct_property_is_set(op->ptr, "clear")) { return uv_mark_seam_exec(C, op); } pup = UI_popup_menu_begin(C, IFACE_("Edges"), ICON_NONE); layout = UI_popup_menu_layout(pup); uiLayoutSetOperatorContext(layout, WM_OP_EXEC_DEFAULT); uiItemBooleanO(layout, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Mark Seam"), ICON_NONE, op->type->idname, "clear", false); uiItemBooleanO(layout, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Seam"), ICON_NONE, op->type->idname, "clear", true); UI_popup_menu_end(C, pup); return OPERATOR_INTERFACE; } static void UV_OT_mark_seam(wmOperatorType *ot) { /* identifiers */ ot->name = "Mark Seam"; ot->description = "Mark selected UV edges as seams"; ot->idname = "UV_OT_mark_seam"; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_mark_seam_exec; ot->invoke = uv_mark_seam_invoke; ot->poll = ED_operator_uvedit; RNA_def_boolean(ot->srna, "clear", false, "Clear Seams", "Clear instead of marking seams"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Operator Registration & Keymap * \{ */ void ED_operatortypes_uvedit(void) { /* uvedit_select.c */ WM_operatortype_append(UV_OT_select_all); WM_operatortype_append(UV_OT_select); WM_operatortype_append(UV_OT_select_loop); WM_operatortype_append(UV_OT_select_edge_ring); WM_operatortype_append(UV_OT_select_linked); WM_operatortype_append(UV_OT_select_linked_pick); WM_operatortype_append(UV_OT_select_split); WM_operatortype_append(UV_OT_select_pinned); WM_operatortype_append(UV_OT_select_box); WM_operatortype_append(UV_OT_select_lasso); WM_operatortype_append(UV_OT_select_similar); WM_operatortype_append(UV_OT_select_circle); 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); WM_operatortype_append(UV_OT_align); WM_operatortype_append(UV_OT_rip); WM_operatortype_append(UV_OT_stitch); WM_operatortype_append(UV_OT_shortest_path_pick); WM_operatortype_append(UV_OT_shortest_path_select); WM_operatortype_append(UV_OT_seams_from_islands); WM_operatortype_append(UV_OT_mark_seam); WM_operatortype_append(UV_OT_weld); WM_operatortype_append(UV_OT_remove_doubles); WM_operatortype_append(UV_OT_pin); WM_operatortype_append(UV_OT_average_islands_scale); WM_operatortype_append(UV_OT_cube_project); WM_operatortype_append(UV_OT_cylinder_project); WM_operatortype_append(UV_OT_project_from_view); WM_operatortype_append(UV_OT_minimize_stretch); WM_operatortype_append(UV_OT_pack_islands); WM_operatortype_append(UV_OT_reset); WM_operatortype_append(UV_OT_sphere_project); WM_operatortype_append(UV_OT_unwrap); WM_operatortype_append(UV_OT_smart_project); WM_operatortype_append(UV_OT_reveal); WM_operatortype_append(UV_OT_hide); WM_operatortype_append(UV_OT_cursor_set); } void ED_operatormacros_uvedit(void) { wmOperatorType *ot; wmOperatorTypeMacro *otmacro; ot = WM_operatortype_append_macro("UV_OT_rip_move", "UV Rip Move", "Unstitch UV's and move the result", OPTYPE_UNDO | OPTYPE_REGISTER); WM_operatortype_macro_define(ot, "UV_OT_rip"); otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate"); RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false); RNA_boolean_set(otmacro->ptr, "mirror", false); } void ED_keymap_uvedit(wmKeyConfig *keyconf) { wmKeyMap *keymap; keymap = WM_keymap_ensure(keyconf, "UV Editor", 0, 0); keymap->poll = ED_operator_uvedit; } /** \} */