text: /*
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License
   * as published by the Free Software Foundation; either version 2
   * of the License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
   *
   * The Original Code is Copyright (C) 2004 Blender Foundation.
   * All rights reserved.
   */
  
  /** \file
   * \ingroup edmesh
   */
  
  #include "MEM_guardedalloc.h"
  
  #include "DNA_mesh_types.h"
  #include "DNA_object_types.h"
  #include "DNA_scene_types.h"
  #include "DNA_windowmanager_types.h"
  
  #ifdef WITH_FREESTYLE
  #  include "DNA_meshdata_types.h"
  #endif
  
  #include "BLI_linklist.h"
  #include "BLI_math.h"
  
  #include "BKE_context.h"
  #include "BKE_editmesh.h"
  #include "BKE_layer.h"
  #include "BKE_report.h"
  
  #include "ED_mesh.h"
  #include "ED_object.h"
  #include "ED_screen.h"
  #include "ED_uvedit.h"
  #include "ED_view3d.h"
  
  #include "RNA_access.h"
  #include "RNA_define.h"
  
  #include "WM_api.h"
  #include "WM_types.h"
  
  #include "bmesh.h"
  #include "bmesh_tools.h"
  
  #include "DEG_depsgraph.h"
  
  #include "mesh_intern.h" /* own include */
  
  /* -------------------------------------------------------------------- */
  /** \name Path Select Struct & Properties
   * \{ */
  
  enum {
    EDGE_MODE_SELECT = 0,
    EDGE_MODE_TAG_SEAM = 1,
    EDGE_MODE_TAG_SHARP = 2,
    EDGE_MODE_TAG_CREASE = 3,
    EDGE_MODE_TAG_BEVEL = 4,
    EDGE_MODE_TAG_FREESTYLE = 5,
  };
  
  struct PathSelectParams {
    /** ensure the active element is the last selected item (handy for picking) */
    bool track_active;
    bool use_topology_distance;
    bool use_face_step;
    bool use_fill;
    char edge_mode;
    struct CheckerIntervalParams interval_params;
  };
  
  static void path_select_properties(wmOperatorType *ot)
  {
    static const EnumPropertyItem edge_tag_items[] = {
        {EDGE_MODE_SELECT, "SELECT", 0, "Select", ""},
        {EDGE_MODE_TAG_SEAM, "SEAM", 0, "Tag Seam", ""},
        {EDGE_MODE_TAG_SHARP, "SHARP", 0, "Tag Sharp", ""},
        {EDGE_MODE_TAG_CREASE, "CREASE", 0, "Tag Crease", ""},
        {EDGE_MODE_TAG_BEVEL, "BEVEL", 0, "Tag Bevel", ""},
        {EDGE_MODE_TAG_FREESTYLE, "FREESTYLE", 0, "Tag Freestyle Edge Mark", ""},
        {0, NULL, 0, NULL, NULL},
    };
  
    RNA_def_enum(ot->srna,
                 "edge_mode",
                 edge_tag_items,
                 EDGE_MODE_SELECT,
                 "Edge Tag",
                 "The edge flag to tag when selecting the shortest path");
  
    RNA_def_boolean(ot->srna,
                    "use_face_step",
                    false,
                    "Face Stepping",
                    "Traverse connected faces (includes diagonals and edge-rings)");
    RNA_def_boolean(ot->srna,
                    "use_topology_distance",
                    false,
                    "Topology Distance",
                    "Find the minimum number of steps, ignoring spatial distance");
    RNA_def_boolean(ot->srna,
                    "use_fill",
                    false,
                    "Fill Region",
                    "Select all paths between the source/destination elements");
    WM_operator_properties_checker_interval(ot, true);
  }
  
  static void path_select_params_from_op(wmOperator *op,
                                         ToolSettings *ts,
                                         struct PathSelectParams *op_params)
  {
    {
      PropertyRNA *prop = RNA_struct_find_property(op->ptr, "edge_mode");
      if (RNA_property_is_set(op->ptr, prop)) {
        op_params->edge_mode = RNA_property_enum_get(op->ptr, prop);
        if (op->flag & OP_IS_INVOKE) {
          ts->edge_mode = op_params->edge_mode;
        }
      }
      else {
        op_params->edge_mode = ts->edge_mode;
        RNA_property_enum_set(op->ptr, prop, op_params->edge_mode);
      }
    }
  
    op_params->track_active = false;
    op_params->use_face_step = RNA_boolean_get(op->ptr, "use_face_step");
    op_params->use_fill = RNA_boolean_get(op->ptr, "use_fill");
    op_params->use_topology_distance = RNA_boolean_get(op->ptr, "use_topology_distance");
    WM_operator_properties_checker_interval_from_op(op, &op_params->interval_params);
  }
  
  static bool path_select_poll_property(const bContext *C,
                                        wmOperator *UNUSED(op),
                                        const PropertyRNA *prop)
  {
    const char *prop_id = RNA_property_identifier(prop);
    if (STREQ(prop_id, "edge_mode")) {
      const Scene *scene = CTX_data_scene(C);
      ToolSettings *ts = scene->toolsettings;
      if ((ts->selectmode & SCE_SELECT_EDGE) == 0) {
        return false;
      }
    }
    return true;
  }
  
  struct UserData {
    BMesh *bm;
    Mesh *me;
    const struct PathSelectParams *op_params;
  };
  
  /** \} */
  
  /* -------------------------------------------------------------------- */
  /** \name Vert Path
   * \{ */
  
  /* callbacks */
  static bool verttag_filter_cb(BMVert *v, void *UNUSED(user_data_v))
  {
    return !BM_elem_flag_test(v, BM_ELEM_HIDDEN);
  }
  static bool verttag_test_cb(BMVert *v, void *UNUSED(user_data_v))
  {
    return BM_elem_flag_test_bool(v, BM_ELEM_SELECT);
  }
  static void verttag_set_cb(BMVert *v, bool val, void *user_data_v)
  {
    struct UserData *user_data = user_data_v;
    BM_vert_select_set(user_data->bm, v, val);
  }
  
  static void mouse_mesh_shortest_path_vert(Scene *UNUSED(scene),
                                            Object *obedit,
                                            const struct PathSelectParams *op_params,
                                            BMVert *v_act,
                                            BMVert *v_dst)
  {
    BMEditMesh *em = BKE_editmesh_from_object(obedit);
    BMesh *bm = em->bm;
  
    struct UserData user_data = {bm, obedit->data, op_params};
    LinkNode *path = NULL;
    bool is_path_ordered = false;
  
    if (v_act && (v_act != v_dst)) {
      if (op_params->use_fill) {
        path = BM_mesh_calc_path_region_vert(
            bm, (BMElem *)v_act, (BMElem *)v_dst, verttag_filter_cb, &user_data);
      }
      else {
        is_path_ordered = true;
        path = BM_mesh_calc_path_vert(bm,
                                      v_act,
                                      v_dst,
                                      &(const struct BMCalcPathParams){
                                          .use_topology_distance = op_params->use_topology_distance,
                                          .use_step_face = op_params->use_face_step,
                                      },
                                      verttag_filter_cb,
                                      &user_data);
      }
  
      if (path) {
        if (op_params->track_active) {
          BM_select_history_remove(bm, v_act);
        }
      }
    }
  
    BMVert *v_dst_last = v_dst;
  
    if (path) {
      /* toggle the flag */
      bool all_set = true;
      LinkNode *node;
  
      node = path;
      do {
        if (!verttag_test_cb((BMVert *)node->link, &user_data)) {
          all_set = false;
          break;
        }
      } while ((node = node->next));
  
      int depth = -1;
      node = path;
      do {
        if ((is_path_ordered == false) ||
            WM_operator_properties_checker_interval_test(&op_params->interval_params, depth)) {
          verttag_set_cb((BMVert *)node->link, !all_set, &user_data);
          if (is_path_ordered) {
            v_dst_last = node->link;
          }
        }
      } while ((void)depth++, (node = node->next));
  
      BLI_linklist_free(path, NULL);
    }
    else {
      const bool is_act = !verttag_test_cb(v_dst, &user_data);
      verttag_set_cb(v_dst, is_act, &user_data); /* switch the face option */
    }
  
    EDBM_selectmode_flush(em);
  
    if (op_params->track_active) {
      /* even if this is selected it may not be in the selection list */
      if (BM_elem_flag_test(v_dst_last, BM_ELEM_SELECT) == 0) {
        BM_select_history_remove(bm, v_dst_last);
      }
      else {
        BM_select_history_store(bm, v_dst_last);
      }
    }
  
    EDBM_update_generic(obedit->data, false, false);
  }
  
  /** \} */
  
  /* -------------------------------------------------------------------- */
  /** \name Edge Path
   * \{ */
  
  /* callbacks */
  static bool edgetag_filter_cb(BMEdge *e, void *UNUSED(user_data_v))
  {
    return !BM_elem_flag_test(e, BM_ELEM_HIDDEN);
  }
  static bool edgetag_test_cb(BMEdge *e, void *user_data_v)
  {
    struct UserData *user_data = user_data_v;
    const char edge_mode = user_data->op_params->edge_mode;
    BMesh *bm = user_data->bm;
  
    switch (edge_mode) {
      case EDGE_MODE_SELECT:
        return BM_elem_flag_test(e, BM_ELEM_SELECT) ? true : false;
      case EDGE_MODE_TAG_SEAM:
        return BM_elem_flag_test(e, BM_ELEM_SEAM) ? true : false;
      case EDGE_MODE_TAG_SHARP:
        return BM_elem_flag_test(e, BM_ELEM_SMOOTH) ? false : true;
      case EDGE_MODE_TAG_CREASE:
        return BM_elem_float_data_get(&bm->edata, e, CD_CREASE) ? true : false;
      case EDGE_MODE_TAG_BEVEL:
        return BM_elem_float_data_get(&bm->edata, e, CD_BWEIGHT) ? true : false;
  #ifdef WITH_FREESTYLE
      case EDGE_MODE_TAG_FREESTYLE: {
        FreestyleEdge *fed = CustomData_bmesh_get(&bm->edata, e->head.data, CD_FREESTYLE_EDGE);
        return (!fed) ? false : (fed->flag & FREESTYLE_EDGE_MARK) ? true : false;
      }
  #endif
    }
    return 0;
  }
  static void edgetag_set_cb(BMEdge *e, bool val, void *user_data_v)
  {
    struct UserData *user_data = user_data_v;
    const char edge_mode = user_data->op_params->edge_mode;
    BMesh *bm = user_data->bm;
  
    switch (edge_mode) {
      case EDGE_MODE_SELECT:
        BM_edge_select_set(bm, e, val);
        break;
      case EDGE_MODE_TAG_SEAM:
        BM_elem_flag_set(e, BM_ELEM_SEAM, val);
        break;
      case EDGE_MODE_TAG_SHARP:
        BM_elem_flag_set(e, BM_ELEM_SMOOTH, !val);
        break;
      case EDGE_MODE_TAG_CREASE:
        BM_elem_float_data_set(&bm->edata, e, CD_CREASE, (val) ? 1.0f : 0.0f);
        break;
      case EDGE_MODE_TAG_BEVEL:
        BM_elem_float_data_set(&bm->edata, e, CD_BWEIGHT, (val) ? 1.0f : 0.0f);
        break;
  #ifdef WITH_FREESTYLE
      case EDGE_MODE_TAG_FREESTYLE: {
        FreestyleEdge *fed;
        fed = CustomData_bmesh_get(&bm->edata, e->head.data, CD_FREESTYLE_EDGE);
        if (!val) {
          fed->flag &= ~FREESTYLE_EDGE_MARK;
        }
        else {
          fed->flag |= FREESTYLE_EDGE_MARK;
        }
        break;
      }
  #endif
    }
  }
  
  static void edgetag_ensure_cd_flag(Mesh *me, const char edge_mode)
  {
    BMesh *bm = me->edit_mesh->bm;
  
    switch (edge_mode) {
      case EDGE_MODE_TAG_CREASE:
        BM_mesh_cd_flag_ensure(bm, me, ME_CDFLAG_EDGE_CREASE);
        break;
      case EDGE_MODE_TAG_BEVEL:
        BM_mesh_cd_flag_ensure(bm, me, ME_CDFLAG_EDGE_BWEIGHT);
        break;
  #ifdef WITH_FREESTYLE
      case EDGE_MODE_TAG_FREESTYLE:
        if (!CustomData_has_layer(&bm->edata, CD_FREESTYLE_EDGE)) {
          BM_data_layer_add(bm, &bm->edata, CD_FREESTYLE_EDGE);
        }
        break;
  #endif
      default:
        break;
    }
  }
  
  /* mesh shortest path select, uses prev-selected edge */
  
  /* since you want to create paths with multiple selects, it doesn't have extend option */
  static void mouse_mesh_shortest_path_edge(Scene *scene,
                                            Object *obedit,
                                            const struct PathSelectParams *op_params,
                                            BMEdge *e_act,
                                            BMEdge *e_dst)
  {
    BMEditMesh *em = BKE_editmesh_from_object(obedit);
    BMesh *bm = em->bm;
  
    struct UserData user_data = {bm, obedit->data, op_params};
    LinkNode *path = NULL;
    bool is_path_ordered = false;
  
    edgetag_ensure_cd_flag(obedit->data, op_params->edge_mode);
  
    if (e_act && (e_act != e_dst)) {
      if (op_params->use_fill) {
        path = BM_mesh_calc_path_region_edge(
            bm, (BMElem *)e_act, (BMElem *)e_dst, edgetag_filter_cb, &user_data);
      }
      else {
        is_path_ordered = true;
        path = BM_mesh_calc_path_edge(bm,
                                      e_act,
                                      e_dst,
                                      &(const struct BMCalcPathParams){
                                          .use_topology_distance = op_params->use_topology_distance,
                                          .use_step_face = op_params->use_face_step,
                                      },
                                      edgetag_filter_cb,
                                      &user_data);
      }
  
      if (path) {
        if (op_params->track_active) {
          BM_select_history_remove(bm, e_act);
        }
      }
    }
  
    BMEdge *e_dst_last = e_dst;
  
    if (path) {
      /* toggle the flag */
      bool all_set = true;
      LinkNode *node;
  
      node = path;
      do {
        if (!edgetag_test_cb((BMEdge *)node->link, &user_data)) {
          all_set = false;
          break;
        }
      } while ((node = node->next));
  
      int depth = -1;
      node = path;
      do {
        if ((is_path_ordered == false) ||
            WM_operator_properties_checker_interval_test(&op_params->interval_params, depth)) {
          edgetag_set_cb((BMEdge *)node->link, !all_set, &user_data);
          if (is_path_ordered) {
            e_dst_last = node->link;
          }
        }
      } while ((void)depth++, (node = node->next));
  
      BLI_linklist_free(path, NULL);
    }
    else {
      const bool is_act = !edgetag_test_cb(e_dst, &user_data);
      edgetag_ensure_cd_flag(obedit->data, op_params->edge_mode);
      edgetag_set_cb(e_dst, is_act, &user_data); /* switch the edge option */
    }
  
    if (op_params->edge_mode != EDGE_MODE_SELECT) {
      if (op_params->track_active) {
        /* simple rules - last edge is _always_ active and selected */
        if (e_act) {
          BM_edge_select_set(bm, e_act, false);
        }
        BM_edge_select_set(bm, e_dst_last, true);
        BM_select_history_store(bm, e_dst_last);
      }
    }
  
    EDBM_selectmode_flush(em);
  
    if (op_params->track_active) {
      /* even if this is selected it may not be in the selection list */
      if (op_params->edge_mode == EDGE_MODE_SELECT) {
        if (edgetag_test_cb(e_dst_last, &user_data) == 0) {
          BM_select_history_remove(bm, e_dst_last);
        }
        else {
          BM_select_history_store(bm, e_dst_last);
        }
      }
    }
  
    EDBM_update_generic(obedit->data, false, false);
  
    if (op_params->edge_mode == EDGE_MODE_TAG_SEAM) {
      ED_uvedit_live_unwrap(scene, &obedit, 1);
    }
  }
  
  /** \} */
  
  /* -------------------------------------------------------------------- */
  /** \name Face Path
   * \{ */
  
  /* callbacks */
  static bool facetag_filter_cb(BMFace *f, void *UNUSED(user_data_v))
  {
    return !BM_elem_flag_test(f, BM_ELEM_HIDDEN);
  }
  // static bool facetag_test_cb(Scene *UNUSED(scene), BMesh *UNUSED(bm), BMFace *f)
  static bool facetag_test_cb(BMFace *f, void *UNUSED(user_data_v))
  {
    return BM_elem_flag_test_bool(f, BM_ELEM_SELECT);
  }
  // static void facetag_set_cb(BMesh *bm, Scene *UNUSED(scene), BMFace *f, const bool val)
  static void facetag_set_cb(BMFace *f, bool val, void *user_data_v)
  {
    struct UserData *user_data = user_data_v;
    BM_face_select_set(user_data->bm, f, val);
  }
  
  static void mouse_mesh_shortest_path_face(Scene *UNUSED(scene),
                                            Object *obedit,
                                            const struct PathSelectParams *op_params,
                                            BMFace *f_act,
                                            BMFace *f_dst)
  {
    BMEditMesh *em = BKE_editmesh_from_object(obedit);
    BMesh *bm = em->bm;
  
    struct UserData user_data = {bm, obedit->data, op_params};
    LinkNode *path = NULL;
    bool is_path_ordered = false;
  
    if (f_act) {
      if (op_params->use_fill) {
        path = BM_mesh_calc_path_region_face(
            bm, (BMElem *)f_act, (BMElem *)f_dst, facetag_filter_cb, &user_data);
      }
      else {
        is_path_ordered = true;
        path = BM_mesh_calc_path_face(bm,
                                      f_act,
                                      f_dst,
                                      &(const struct BMCalcPathParams){
                                          .use_topology_distance = op_params->use_topology_distance,
                                          .use_step_face = op_params->use_face_step,
                                      },
                                      facetag_filter_cb,
                                      &user_data);
      }
  
      if (f_act != f_dst) {
        if (path) {
          if (op_params->track_active) {
            BM_select_history_remove(bm, f_act);
          }
        }
      }
    }
  
    BMFace *f_dst_last = f_dst;
  
    if (path) {
      /* toggle the flag */
      bool all_set = true;
      LinkNode *node;
  
      node = path;
      do {
        if (!facetag_test_cb((BMFace *)node->link, &user_data)) {
          all_set = false;
          break;
        }
      } while ((node = node->next));
  
      int depth = -1;
      node = path;
      do {
        if ((is_path_ordered == false) ||
            WM_operator_properties_checker_interval_test(&op_params->interval_params, depth)) {
          facetag_set_cb((BMFace *)node->link, !all_set, &user_data);
          if (is_path_ordered) {
            f_dst_last = node->link;
          }
        }
      } while ((void)depth++, (node = node->next));
  
      BLI_linklist_free(path, NULL);
    }
    else {
      const bool is_act = !facetag_test_cb(f_dst, &user_data);
      facetag_set_cb(f_dst, is_act, &user_data); /* switch the face option */
    }
  
    EDBM_selectmode_flush(em);
  
    if (op_params->track_active) {
      /* even if this is selected it may not be in the selection list */
      if (facetag_test_cb(f_dst_last, &user_data) == 0) {
        BM_select_history_remove(bm, f_dst_last);
      }
      else {
        BM_select_history_store(bm, f_dst_last);
      }
      BM_mesh_active_face_set(bm, f_dst_last);
    }
  
    EDBM_update_generic(obedit->data, false, false);
  }
  
  /** \} */
  
  /* -------------------------------------------------------------------- */
  /** \name Main Operator for vert/edge/face tag
   * \{ */
  
  static bool edbm_shortest_path_pick_ex(Scene *scene,
                                         Object *obedit,
                                         const struct PathSelectParams *op_params,
                                         BMElem *ele_src,
                                         BMElem *ele_dst)
  {
    bool ok = false;
  
    if (ELEM(NULL, ele_src, ele_dst) || (ele_src->head.htype != ele_dst->head.htype)) {
      /* pass */
    }
    else if (ele_src->head.htype == BM_VERT) {
      mouse_mesh_shortest_path_vert(scene, obedit, op_params, (BMVert *)ele_src, (BMVert *)ele_dst);
      ok = true;
    }
    else if (ele_src->head.htype == BM_EDGE) {
      mouse_mesh_shortest_path_edge(scene, obedit, op_params, (BMEdge *)ele_src, (BMEdge *)ele_dst);
      ok = true;
    }
    else if (ele_src->head.htype == BM_FACE) {
      mouse_mesh_shortest_path_face(scene, obedit, op_params, (BMFace *)ele_src, (BMFace *)ele_dst);
      ok = true;
    }
  
    if (ok) {
      DEG_id_tag_update(obedit->data, ID_RECALC_SELECT);
      WM_main_add_notifier(NC_GEOM | ND_SELECT, obedit->data);
    }
  
    return ok;
  }
  
  static int edbm_shortest_path_pick_exec(bContext *C, wmOperator *op);
  
  static BMElem *edbm_elem_find_nearest(ViewContext *vc, const char htype)
  {
    BMEditMesh *em = vc->em;
    float dist = ED_view3d_select_dist_px();
  
    if ((em->selectmode & SCE_SELECT_VERTEX) && (htype == BM_VERT)) {
      return (BMElem *)EDBM_vert_find_nearest(vc, &dist);
    }
    if ((em->selectmode & SCE_SELECT_EDGE) && (htype == BM_EDGE)) {
      return (BMElem *)EDBM_edge_find_nearest(vc, &dist);
    }
    if ((em->selectmode & SCE_SELECT_FACE) && (htype == BM_FACE)) {
      return (BMElem *)EDBM_face_find_nearest(vc, &dist);
    }
  
    return NULL;
  }
  
  static BMElem *edbm_elem_active_elem_or_face_get(BMesh *bm)
  {
    BMElem *ele = BM_mesh_active_elem_get(bm);
  
    if ((ele == NULL) && bm->act_face && BM_elem_flag_test(bm->act_face, BM_ELEM_SELECT)) {
      ele = (BMElem *)bm->act_face;
    }
  
    return ele;
  }
  
  static int edbm_shortest_path_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
  {
    if (RNA_struct_property_is_set(op->ptr, "index")) {
      return edbm_shortest_path_pick_exec(C, op);
    }
  
    BMVert *eve = NULL;
    BMEdge *eed = NULL;
    BMFace *efa = NULL;
  
    ViewContext vc;
    bool track_active = true;
  
    em_setup_viewcontext(C, &vc);
    copy_v2_v2_int(vc.mval, event->mval);
    Base *basact = BASACT(vc.view_layer);
    BMEditMesh *em = vc.em;
  
    view3d_operator_needs_opengl(C);
  
    {
      int base_index = -1;
      uint bases_len = 0;
      Base **bases = BKE_view_layer_array_from_bases_in_edit_mode(vc.view_layer, vc.v3d, &bases_len);
      if (EDBM_unified_findnearest(&vc, bases, bases_len, &base_index, &eve, &eed, &efa)) {
        basact = bases[base_index];
        ED_view3d_viewcontext_init_object(&vc, basact->object);
        em = vc.em;
      }
      MEM_freeN(bases);
    }
  
    /* If nothing is selected, let's select the picked vertex/edge/face. */
    if ((vc.em->bm->totvertsel == 0) && (eve || eed || efa)) {
      /* TODO(dfelinto): right now we try to find the closest element twice.
       * The ideal is to refactor EDBM_select_pick so it doesn't
       * have to pick the nearest vert/edge/face again. */
      EDBM_select_pick(C, event->mval, true, false, false);
      return OPERATOR_FINISHED;
    }
  
    struct PathSelectParams op_params;
    path_select_params_from_op(op, vc.scene->toolsettings, &op_params);
  
    BMElem *ele_src, *ele_dst;
    if (!(ele_src = edbm_elem_active_elem_or_face_get(em->bm)) ||
        !(ele_dst = edbm_elem_find_nearest(&vc, ele_src->head.htype))) {
      /* special case, toggle edge tags even when we don't have a path */
      if (((em->selectmode & SCE_SELECT_EDGE) && (op_params.edge_mode != EDGE_MODE_SELECT)) &&
          /* check if we only have a destination edge */
          ((ele_src == NULL) && (ele_dst = edbm_elem_find_nearest(&vc, BM_EDGE)))) {
        ele_src = ele_dst;
        track_active = false;
      }
      else {
        return OPERATOR_PASS_THROUGH;
      }
    }
  
    op_params.track_active = track_active;
  
    if (!edbm_shortest_path_pick_ex(vc.scene, vc.obedit, &op_params, ele_src, ele_dst)) {
      return OPERATOR_PASS_THROUGH;
    }
  
    if (vc.view_layer->basact != basact) {
      ED_object_base_activate(C, basact);
    }
  
    /* to support redo */
    BM_mesh_elem_index_ensure(em->bm, ele_dst->head.htype);
    int index = EDBM_elem_to_index_any(em, ele_dst);
  
    RNA_int_set(op->ptr, "index", index);
  
    return OPERATOR_FINISHED;
  }
  
  static int edbm_shortest_path_pick_exec(bContext *C, wmOperator *op)
  {
    Scene *scene = CTX_data_scene(C);
    Object *obedit = CTX_data_edit_object(C);
    BMEditMesh *em = BKE_editmesh_from_object(obedit);
    BMesh *bm = em->bm;
  
    const int index = RNA_int_get(op->ptr, "index");
    if (index < 0 || index >= (bm->totvert + bm->totedge + bm->totface)) {
      return OPERATOR_CANCELLED;
    }
  
    BMElem *ele_src, *ele_dst;
    if (!(ele_src = edbm_elem_active_elem_or_face_get(em->bm)) ||
        !(ele_dst = EDBM_elem_from_index_any(em, index))) {
      return OPERATOR_CANCELLED;
    }
  
    struct PathSelectParams op_params;
    path_select_params_from_op(op, scene->toolsettings, &op_params);
    op_params.track_active = true;
  
    if (!edbm_shortest_path_pick_ex(scene, obedit, &op_params, ele_src, ele_dst)) {
      return OPERATOR_CANCELLED;
    }
  
    return OPERATOR_FINISHED;
  }
  
  void MESH_OT_shortest_path_pick(wmOperatorType *ot)
  {
    PropertyRNA *prop;
  
    /* identifiers */
    ot->name = "Pick Shortest Path";
    ot->idname = "MESH_OT_shortest_path_pick";
    ot->description = "Select shortest path between two selections";
  
    /* api callbacks */
    ot->invoke = edbm_shortest_path_pick_invoke;
    ot->exec = edbm_shortest_path_pick_exec;
    ot->poll = ED_operator_editmesh_region_view3d;
    ot->poll_property = path_select_poll_property;
  
    /* flags */
    ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
  
    /* properties */
    path_select_properties(ot);
  
    /* use for redo */
    prop = RNA_def_int(ot->srna, "index", -1, -1, INT_MAX, "", "", 0, INT_MAX);
    RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
  }
  
  /** \} */
  
  /* -------------------------------------------------------------------- */
  /** \name Select Path Between Existing Selection
   * \{ */
  
  static int edbm_shortest_path_select_exec(bContext *C, wmOperator *op)
  {
    Scene *scene = CTX_data_scene(C);
    bool found_valid_elements = false;
  
    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(
        view_layer, CTX_wm_view3d(C), &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);
      BMesh *bm = em->bm;
      BMIter iter;
      BMEditSelection *ese_src, *ese_dst;
      BMElem *ele_src = NULL, *ele_dst = NULL, *ele;
  
      if ((em->bm->totvertsel == 0) && (em->bm->totedgesel == 0) && (em->bm->totfacesel == 0)) {
        continue;
      }
  
      /* first try to find vertices in edit selection */
      ese_src = bm->selected.last;
      if (ese_src && (ese_dst = ese_src->prev) && (ese_src->htype == ese_dst->htype)) {
        ele_src = ese_src->ele;
        ele_dst = ese_dst->ele;
      }
      else {
        /* if selection history isn't available, find two selected elements */
        ele_src = ele_dst = NULL;
        if ((em->selectmode & SCE_SELECT_VERTEX) && (bm->totvertsel >= 2)) {
          BM_ITER_MESH (ele, &iter, bm, BM_VERTS_OF_MESH) {
            if (BM_elem_flag_test(ele, BM_ELEM_SELECT)) {
              if (ele_src == NULL) {
                ele_src = ele;
              }
              else if (ele_dst == NULL) {
                ele_dst = ele;
              }
              else {
                break;
              }
            }
          }
        }
  
        if ((ele_dst == NULL) && (em->selectmode & SCE_SELECT_EDGE) && (bm->totedgesel >= 2)) {
          ele_src = NULL;
          BM_ITER_MESH (ele, &iter, bm, BM_EDGES_OF_MESH) {
            if (BM_elem_flag_test(ele, BM_ELEM_SELECT)) {
              if (ele_src == NULL) {
                ele_src = ele;
              }
              else if (ele_dst == NULL) {
                ele_dst = ele;
              }
              else {
                break;
              }
            }
          }
        }
  
        if ((ele_dst == NULL) && (em->selectmode & SCE_SELECT_FACE) && (bm->totfacesel >= 2)) {
          ele_src = NULL;
          BM_ITER_MESH (ele, &iter, bm, BM_FACES_OF_MESH) {
            if (BM_elem_flag_test(ele, BM_ELEM_SELECT)) {
              if (ele_src == NULL) {
                ele_src = ele;
              }
              else if (ele_dst == NULL) {
                ele_dst = ele;
              }
              else {
                break;
              }
            }
          }
        }
      }
  
      if (ele_src && ele_dst) {
        struct PathSelectParams op_params;
        path_select_params_from_op(op, scene->toolsettings, &op_params);
  
        edbm_shortest_path_pick_ex(scene, obedit, &op_params, ele_src, ele_dst);
  
        found_valid_elements = true;
      }
    }
    MEM_freeN(objects);
  
    if (!found_valid_elements) {
      BKE_report(
          op->reports, RPT_WARNING, "Path selection requires two matching elements to be selected");
      return OPERATOR_CANCELLED;
    }
  
    return OPERATOR_FINISHED;
  }
  
  void MESH_OT_shortest_path_select(wmOperatorType *ot)
  {
    /* identifiers */
    ot->name = "Select Shortest Path";
    ot->idname = "MESH_OT_shortest_path_select";
    ot->description = "Selected shortest path between two vertices/edges/faces";
  
    /* api callbacks */
    ot->exec = edbm_shortest_path_select_exec;
    ot->poll = ED_operator_editmesh;
    ot->poll_property = path_select_poll_property;
  
    /* flags */
    ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
  
    /* properties */
    path_select_properties(ot);
  }
  
  /** \} */
  