Merge branch 'blender2.8' into soc-2018-bevel
[blender.git] / source / blender / bmesh / intern / bmesh_mesh.c
index 8071637d95ec5211e2d155095dabd39d4be7a421..4d0f259b4e3ab9b6e9943d40272f68d9ef44ffda 100644 (file)
@@ -30,6 +30,7 @@
 
 #include "DNA_listBase.h"
 #include "DNA_object_types.h"
 
 #include "DNA_listBase.h"
 #include "DNA_object_types.h"
+#include "DNA_scene_types.h"
 
 #include "BLI_linklist_stack.h"
 #include "BLI_listbase.h"
 
 #include "BLI_linklist_stack.h"
 #include "BLI_listbase.h"
@@ -168,7 +169,7 @@ BMesh *BM_mesh_create(
 {
        /* allocate the structure */
        BMesh *bm = MEM_callocN(sizeof(BMesh), __func__);
 {
        /* allocate the structure */
        BMesh *bm = MEM_callocN(sizeof(BMesh), __func__);
-       
+
        /* allocate the memory pools for the mesh elements */
        bm_mempool_init(bm, allocsize, params->use_toolflags);
 
        /* allocate the memory pools for the mesh elements */
        bm_mempool_init(bm, allocsize, params->use_toolflags);
 
@@ -260,6 +261,11 @@ void BM_mesh_data_free(BMesh *bm)
 
        BLI_freelistN(&bm->selected);
 
 
        BLI_freelistN(&bm->selected);
 
+       if (bm->lnor_spacearr) {
+               BKE_lnor_spacearr_free(bm->lnor_spacearr);
+               MEM_freeN(bm->lnor_spacearr);
+       }
+
        BMO_error_clear(bm);
 }
 
        BMO_error_clear(bm);
 }
 
@@ -313,6 +319,10 @@ void BM_mesh_free(BMesh *bm)
  * Helpers for #BM_mesh_normals_update and #BM_verts_calc_normal_vcos
  */
 
  * Helpers for #BM_mesh_normals_update and #BM_verts_calc_normal_vcos
  */
 
+/* We use that existing internal API flag, assuming no other tool using it would run concurrently to clnors editing. */
+/* XXX Should we rather add a new internal flag? */
+#define BM_LNORSPACE_UPDATE _FLAG_MF
+
 typedef struct BMEdgesCalcVectorsData {
        /* Read-only data. */
        const float (*vcos)[3];
 typedef struct BMEdgesCalcVectorsData {
        /* Read-only data. */
        const float (*vcos)[3];
@@ -638,7 +648,8 @@ bool BM_loop_check_cyclic_smooth_fan(BMLoop *l_curr)
  * Will use first clnors_data array, and fallback to cd_loop_clnors_offset (use NULL and -1 to not use clnors). */
 static void bm_mesh_loops_calc_normals(
         BMesh *bm, const float (*vcos)[3], const float (*fnos)[3], float (*r_lnos)[3],
  * Will use first clnors_data array, and fallback to cd_loop_clnors_offset (use NULL and -1 to not use clnors). */
 static void bm_mesh_loops_calc_normals(
         BMesh *bm, const float (*vcos)[3], const float (*fnos)[3], float (*r_lnos)[3],
-        MLoopNorSpaceArray *r_lnors_spacearr, short (*clnors_data)[2], const int cd_loop_clnors_offset)
+        MLoopNorSpaceArray *r_lnors_spacearr, short (*clnors_data)[2],
+        const int cd_loop_clnors_offset, const bool do_rebuild)
 {
        BMIter fiter;
        BMFace *f_curr;
 {
        BMIter fiter;
        BMFace *f_curr;
@@ -694,6 +705,11 @@ static void bm_mesh_loops_calc_normals(
 
                l_curr = l_first = BM_FACE_FIRST_LOOP(f_curr);
                do {
 
                l_curr = l_first = BM_FACE_FIRST_LOOP(f_curr);
                do {
+                       if (do_rebuild && !BM_ELEM_API_FLAG_TEST(l_curr, BM_LNORSPACE_UPDATE) &&
+                           !(bm->spacearr_dirty & BM_SPACEARR_DIRTY_ALL))
+                       {
+                               continue;
+                       }
                        /* A smooth edge, we have to check for cyclic smooth fan case.
                         * If we find a new, never-processed cyclic smooth fan, we can do it now using that loop/edge as
                         * 'entry point', otherwise we can skip it. */
                        /* A smooth edge, we have to check for cyclic smooth fan case.
                         * If we find a new, never-processed cyclic smooth fan, we can do it now using that loop/edge as
                         * 'entry point', otherwise we can skip it. */
@@ -894,7 +910,10 @@ static void bm_mesh_loops_calc_normals(
                                                                clnors_avg[0] /= clnors_nbr;
                                                                clnors_avg[1] /= clnors_nbr;
                                                                /* Fix/update all clnors of this fan with computed average value. */
                                                                clnors_avg[0] /= clnors_nbr;
                                                                clnors_avg[1] /= clnors_nbr;
                                                                /* Fix/update all clnors of this fan with computed average value. */
-                                                               printf("Invalid clnors in this fan!\n");
+
+                                                               /* Prints continuously when merge custom normals, so commenting. */
+                                                               /* printf("Invalid clnors in this fan!\n"); */
+
                                                                while ((clnor = BLI_SMALLSTACK_POP(clnors))) {
                                                                        //print_v2("org clnor", clnor);
                                                                        clnor[0] = (short)clnors_avg[0];
                                                                while ((clnor = BLI_SMALLSTACK_POP(clnors))) {
                                                                        //print_v2("org clnor", clnor);
                                                                        clnor[0] = (short)clnors_avg[0];
@@ -1009,7 +1028,8 @@ void BM_mesh_loop_normals_update(
 void BM_loops_calc_normal_vcos(
         BMesh *bm, const float (*vcos)[3], const float (*vnos)[3], const float (*fnos)[3],
         const bool use_split_normals, const float split_angle, float (*r_lnos)[3],
 void BM_loops_calc_normal_vcos(
         BMesh *bm, const float (*vcos)[3], const float (*vnos)[3], const float (*fnos)[3],
         const bool use_split_normals, const float split_angle, float (*r_lnos)[3],
-        MLoopNorSpaceArray *r_lnors_spacearr, short (*clnors_data)[2], const int cd_loop_clnors_offset)
+        MLoopNorSpaceArray *r_lnors_spacearr, short (*clnors_data)[2],
+        const int cd_loop_clnors_offset, const bool do_rebuild)
 {
        const bool has_clnors = clnors_data || (cd_loop_clnors_offset != -1);
 
 {
        const bool has_clnors = clnors_data || (cd_loop_clnors_offset != -1);
 
@@ -1019,7 +1039,8 @@ void BM_loops_calc_normal_vcos(
                bm_mesh_edges_sharp_tag(bm, vnos, fnos, r_lnos, has_clnors ? (float)M_PI : split_angle, false);
 
                /* Finish computing lnos by accumulating face normals in each fan of faces defined by sharp edges. */
                bm_mesh_edges_sharp_tag(bm, vnos, fnos, r_lnos, has_clnors ? (float)M_PI : split_angle, false);
 
                /* Finish computing lnos by accumulating face normals in each fan of faces defined by sharp edges. */
-               bm_mesh_loops_calc_normals(bm, vcos, fnos, r_lnos, r_lnors_spacearr, clnors_data, cd_loop_clnors_offset);
+               bm_mesh_loops_calc_normals(
+                           bm, vcos, fnos, r_lnos, r_lnors_spacearr, clnors_data, cd_loop_clnors_offset, do_rebuild);
        }
        else {
                BLI_assert(!r_lnors_spacearr);
        }
        else {
                BLI_assert(!r_lnors_spacearr);
@@ -1041,6 +1062,415 @@ void BM_edges_sharp_from_angle_set(BMesh *bm, const float split_angle)
        bm_mesh_edges_sharp_tag(bm, NULL, NULL, NULL, split_angle, true);
 }
 
        bm_mesh_edges_sharp_tag(bm, NULL, NULL, NULL, split_angle, true);
 }
 
+void BM_lnorspacearr_store(BMesh *bm, float(*r_lnors)[3])
+{
+       BLI_assert(bm->lnor_spacearr != NULL);
+
+       if (!CustomData_has_layer(&bm->ldata, CD_CUSTOMLOOPNORMAL)) {
+               BM_data_layer_add(bm, &bm->ldata, CD_CUSTOMLOOPNORMAL);
+       }
+
+       int cd_loop_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL);
+
+       BM_loops_calc_normal_vcos(
+               bm, NULL, NULL, NULL, true, M_PI, r_lnors, bm->lnor_spacearr, NULL, cd_loop_clnors_offset, false);
+       bm->spacearr_dirty &= ~(BM_SPACEARR_DIRTY | BM_SPACEARR_DIRTY_ALL);
+}
+
+/* will change later */
+#define CLEAR_SPACEARRAY_THRESHOLD(x) ((x) / 2)
+
+void BM_lnorspace_invalidate(BMesh *bm, const bool do_invalidate_all)
+{
+       if (bm->spacearr_dirty & BM_SPACEARR_DIRTY_ALL) {
+               return;
+       }
+       if (do_invalidate_all || bm->totvertsel > CLEAR_SPACEARRAY_THRESHOLD(bm->totvert)) {
+               bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL;
+               return;
+       }
+       if (bm->lnor_spacearr == NULL) {
+               bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL;
+               return;
+       }
+
+       BMVert *v;
+       BMLoop *l;
+       BMIter viter, liter;
+       /* Note: we could use temp tag of BMItem for that, but probably better not use it in such a low-level func?
+       * --mont29 */
+       BLI_bitmap *done_verts = BLI_BITMAP_NEW(bm->totvert, __func__);
+
+       BM_mesh_elem_index_ensure(bm, BM_VERT);
+
+       /* When we affect a given vertex, we may affect following smooth fans:
+       *     - all smooth fans of said vertex;
+       *     - all smooth fans of all immediate loop-neighbors vertices;
+       * This can be simplified as 'all loops of selected vertices and their immediate neighbors'
+       * need to be tagged for update.
+*/
+BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) {
+       if (BM_elem_flag_test(v, BM_ELEM_SELECT)) {
+               BM_ITER_ELEM(l, &liter, v, BM_LOOPS_OF_VERT) {
+                       BM_ELEM_API_FLAG_ENABLE(l, BM_LNORSPACE_UPDATE);
+
+                       /* Note that we only handle unselected neighbor vertices here, main loop will take care of
+                       * selected ones. */
+                       if (!BM_elem_flag_test(l->prev->v, BM_ELEM_SELECT) &&
+                               !BLI_BITMAP_TEST(done_verts, BM_elem_index_get(l->prev->v)))
+                       {
+                               BMLoop *l_prev;
+                               BMIter liter_prev;
+                               BM_ITER_ELEM(l_prev, &liter_prev, l->prev->v, BM_LOOPS_OF_VERT) {
+                                       BM_ELEM_API_FLAG_ENABLE(l_prev, BM_LNORSPACE_UPDATE);
+                               }
+                               BLI_BITMAP_ENABLE(done_verts, BM_elem_index_get(l_prev->v));
+                       }
+
+                       if (!BM_elem_flag_test(l->next->v, BM_ELEM_SELECT) &&
+                               !BLI_BITMAP_TEST(done_verts, BM_elem_index_get(l->next->v)))
+                       {
+                               BMLoop *l_next;
+                               BMIter liter_next;
+                               BM_ITER_ELEM(l_next, &liter_next, l->next->v, BM_LOOPS_OF_VERT) {
+                                       BM_ELEM_API_FLAG_ENABLE(l_next, BM_LNORSPACE_UPDATE);
+                               }
+                               BLI_BITMAP_ENABLE(done_verts, BM_elem_index_get(l_next->v));
+                       }
+               }
+
+               BLI_BITMAP_ENABLE(done_verts, BM_elem_index_get(v));
+       }
+}
+
+MEM_freeN(done_verts);
+bm->spacearr_dirty |= BM_SPACEARR_DIRTY;
+}
+
+void BM_lnorspace_rebuild(BMesh *bm, bool preserve_clnor)
+{
+       BLI_assert(bm->lnor_spacearr != NULL);
+
+       if (!(bm->spacearr_dirty & (BM_SPACEARR_DIRTY | BM_SPACEARR_DIRTY_ALL))) {
+               return;
+       }
+       BMFace *f;
+       BMLoop *l;
+       BMIter fiter, liter;
+
+       float(*r_lnors)[3] = MEM_callocN(sizeof(*r_lnors) * bm->totloop, __func__);
+       float(*oldnors)[3] = preserve_clnor ? MEM_mallocN(sizeof(*oldnors) * bm->totloop, __func__) : NULL;
+
+       int cd_loop_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL);
+
+       BM_mesh_elem_index_ensure(bm, BM_LOOP);
+
+       if (preserve_clnor) {
+               BLI_assert(bm->lnor_spacearr->lspacearr != NULL);
+
+               BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) {
+                       BM_ITER_ELEM(l, &liter, f, BM_LOOPS_OF_FACE) {
+                               if (BM_ELEM_API_FLAG_TEST(l, BM_LNORSPACE_UPDATE) || bm->spacearr_dirty & BM_SPACEARR_DIRTY_ALL) {
+                                       short(*clnor)[2] = BM_ELEM_CD_GET_VOID_P(l, cd_loop_clnors_offset);
+                                       int l_index = BM_elem_index_get(l);
+
+                                       BKE_lnor_space_custom_data_to_normal(bm->lnor_spacearr->lspacearr[l_index], *clnor, oldnors[l_index]);
+                               }
+                       }
+               }
+       }
+
+       if (bm->spacearr_dirty & BM_SPACEARR_DIRTY_ALL) {
+               BKE_lnor_spacearr_clear(bm->lnor_spacearr);
+       }
+       BM_loops_calc_normal_vcos(
+               bm, NULL, NULL, NULL, true, M_PI, r_lnors, bm->lnor_spacearr, NULL, cd_loop_clnors_offset, true);
+       MEM_freeN(r_lnors);
+
+       BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) {
+               BM_ITER_ELEM(l, &liter, f, BM_LOOPS_OF_FACE) {
+                       if (BM_ELEM_API_FLAG_TEST(l, BM_LNORSPACE_UPDATE) || bm->spacearr_dirty & BM_SPACEARR_DIRTY_ALL) {
+                               if (preserve_clnor) {
+                                       short(*clnor)[2] = BM_ELEM_CD_GET_VOID_P(l, cd_loop_clnors_offset);
+                                       int l_index = BM_elem_index_get(l);
+                                       BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[l_index], oldnors[l_index], *clnor);
+                               }
+                               BM_ELEM_API_FLAG_DISABLE(l, BM_LNORSPACE_UPDATE);
+                       }
+               }
+       }
+
+       MEM_SAFE_FREE(oldnors);
+       bm->spacearr_dirty &= ~(BM_SPACEARR_DIRTY | BM_SPACEARR_DIRTY_ALL);
+
+#ifndef NDEBUG
+       BM_lnorspace_err(bm);
+#endif
+}
+
+void BM_lnorspace_update(BMesh *bm)
+{
+       if (bm->lnor_spacearr == NULL) {
+               bm->lnor_spacearr = MEM_callocN(sizeof(*bm->lnor_spacearr), __func__);
+       }
+       if (bm->lnor_spacearr->lspacearr == NULL) {
+               float(*lnors)[3] = MEM_callocN(sizeof(*lnors) * bm->totloop, __func__);
+
+               BM_lnorspacearr_store(bm, lnors);
+
+               MEM_freeN(lnors);
+       }
+       else if (bm->spacearr_dirty & (BM_SPACEARR_DIRTY | BM_SPACEARR_DIRTY_ALL)) {
+               BM_lnorspace_rebuild(bm, false);
+       }
+}
+
+void BM_normals_loops_edges_tag(BMesh *bm, const bool do_edges)
+{
+       BMFace *f;
+       BMEdge *e;
+       BMIter fiter, eiter;
+       BMLoop *l_curr, *l_first;
+
+       if (do_edges) {
+               int index_edge;
+               BM_ITER_MESH_INDEX(e, &eiter, bm, BM_EDGES_OF_MESH, index_edge) {
+                       BMLoop *l_a, *l_b;
+
+                       BM_elem_index_set(e, index_edge);  /* set_inline */
+                       BM_elem_flag_disable(e, BM_ELEM_TAG);
+                       if (BM_edge_loop_pair(e, &l_a, &l_b)) {
+                               if (BM_elem_flag_test(e, BM_ELEM_SMOOTH) && l_a->v != l_b->v) {
+                                       BM_elem_flag_enable(e, BM_ELEM_TAG);
+                               }
+                       }
+               }
+               bm->elem_index_dirty &= ~BM_EDGE;
+       }
+
+       int index_face, index_loop = 0;
+       BM_ITER_MESH_INDEX(f, &fiter, bm, BM_FACES_OF_MESH, index_face) {
+               BM_elem_index_set(f, index_face);  /* set_inline */
+               l_curr = l_first = BM_FACE_FIRST_LOOP(f);
+               do {
+                       BM_elem_index_set(l_curr, index_loop++);  /* set_inline */
+                       BM_elem_flag_disable(l_curr, BM_ELEM_TAG);
+               } while ((l_curr = l_curr->next) != l_first);
+       }
+       bm->elem_index_dirty &= ~(BM_FACE | BM_LOOP);
+}
+
+/**
+* Auxillary function only used by rebuild to detect if any spaces were not marked as invalid.
+* Reports error if any of the lnor spaces change after rebuilding, meaning that all the possible
+* lnor spaces to be rebuilt were not correctly marked.
+*/
+#ifndef NDEBUG
+void BM_lnorspace_err(BMesh *bm)
+{
+       bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL;
+       bool clear = true;
+
+       MLoopNorSpaceArray *temp = MEM_callocN(sizeof(*temp), __func__);
+       temp->lspacearr = NULL;
+
+       BKE_lnor_spacearr_init(temp, bm->totloop, MLNOR_SPACEARR_BMLOOP_PTR);
+
+       int cd_loop_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL);
+       float(*lnors)[3] = MEM_callocN(sizeof(*lnors) * bm->totloop, __func__);
+       BM_loops_calc_normal_vcos(bm, NULL, NULL, NULL, true, M_PI, lnors, temp, NULL, cd_loop_clnors_offset, true);
+
+       for (int i = 0; i < bm->totloop; i++) {
+               int j = 0;
+               j += compare_ff(temp->lspacearr[i]->ref_alpha, bm->lnor_spacearr->lspacearr[i]->ref_alpha, 1e-4f);
+               j += compare_ff(temp->lspacearr[i]->ref_beta, bm->lnor_spacearr->lspacearr[i]->ref_beta, 1e-4f);
+               j += compare_v3v3(temp->lspacearr[i]->vec_lnor, bm->lnor_spacearr->lspacearr[i]->vec_lnor, 1e-4f);
+               j += compare_v3v3(temp->lspacearr[i]->vec_ortho, bm->lnor_spacearr->lspacearr[i]->vec_ortho, 1e-4f);
+               j += compare_v3v3(temp->lspacearr[i]->vec_ref, bm->lnor_spacearr->lspacearr[i]->vec_ref, 1e-4f);
+
+               if (j != 5) {
+                       clear = false;
+                       break;
+               }
+       }
+       BKE_lnor_spacearr_free(temp);
+       MEM_freeN(temp);
+       MEM_freeN(lnors);
+       BLI_assert(clear);
+
+       bm->spacearr_dirty &= ~BM_SPACEARR_DIRTY_ALL;
+}
+#endif
+
+static void bm_loop_normal_mark_indiv_do_loop(
+       BMLoop *l, BLI_bitmap *loops, MLoopNorSpaceArray *lnor_spacearr, int *totloopsel)
+{
+       if (l != NULL) {
+               const int l_idx = BM_elem_index_get(l);
+
+               if (!BLI_BITMAP_TEST(loops, BM_elem_index_get(l))) {
+                       /* If vert and face selected share a loop, mark it for editing. */
+                       BLI_BITMAP_ENABLE(loops, l_idx);
+                       (*totloopsel)++;
+
+                       /* Mark all loops in same loop normal space (aka smooth fan). */
+                       if ((lnor_spacearr->lspacearr[l_idx]->flags & MLNOR_SPACE_IS_SINGLE) == 0) {
+                               for (LinkNode *node = lnor_spacearr->lspacearr[l_idx]->loops; node; node = node->next) {
+                                       const int lfan_idx = BM_elem_index_get((BMLoop *)node->link);
+                                       if (!BLI_BITMAP_TEST(loops, lfan_idx)) {
+                                               BLI_BITMAP_ENABLE(loops, lfan_idx);
+                                               (*totloopsel)++;
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
+/* Mark the individual clnors to be edited, if multiple selection methods are used. */
+static int bm_loop_normal_mark_indiv(BMesh *bm, BLI_bitmap *loops)
+{
+       BMEditSelection *ese, *ese_prev;
+       int totloopsel = 0;
+
+       BM_mesh_elem_index_ensure(bm, BM_LOOP);
+
+       BLI_assert(bm->lnor_spacearr != NULL);
+       BLI_assert(bm->lnor_spacearr->data_type == MLNOR_SPACEARR_BMLOOP_PTR);
+
+       /* Goes from last selected to the first selected element. */
+       for (ese = bm->selected.last; ese; ese = ese->prev) {
+               if (ese->htype == BM_FACE) {
+                       ese_prev = ese;
+                       /* If current face is selected, then any verts to be edited must have been selected before it. */
+                       while ((ese_prev = ese_prev->prev)) {
+                               if (ese_prev->htype == BM_VERT) {
+                                       bm_loop_normal_mark_indiv_do_loop(
+                                               BM_face_vert_share_loop((BMFace *)ese->ele, (BMVert *)ese_prev->ele),
+                                               loops, bm->lnor_spacearr, &totloopsel);
+                               }
+                               else if (ese_prev->htype == BM_EDGE) {
+                                       bm_loop_normal_mark_indiv_do_loop(
+                                               BM_face_vert_share_loop((BMFace *)ese->ele, ((BMEdge *)ese_prev->ele)->v1),
+                                               loops, bm->lnor_spacearr, &totloopsel);
+
+                                       bm_loop_normal_mark_indiv_do_loop(
+                                               BM_face_vert_share_loop((BMFace *)ese->ele, ((BMEdge *)ese_prev->ele)->v2),
+                                               loops, bm->lnor_spacearr, &totloopsel);
+                               }
+                       }
+               }
+       }
+
+       return totloopsel;
+}
+
+static void loop_normal_editdata_init(BMesh *bm, BMLoopNorEditData *lnor_ed, BMVert *v, BMLoop *l, const int offset)
+{
+       BLI_assert(bm->lnor_spacearr != NULL);
+       BLI_assert(bm->lnor_spacearr->lspacearr != NULL);
+
+       const int l_index = BM_elem_index_get(l);
+       short *clnors_data = BM_ELEM_CD_GET_VOID_P(l, offset);
+
+       lnor_ed->loop_index = l_index;
+       lnor_ed->loop = l;
+
+       float custom_normal[3];
+       BKE_lnor_space_custom_data_to_normal(bm->lnor_spacearr->lspacearr[l_index], clnors_data, custom_normal);
+
+       lnor_ed->clnors_data = clnors_data;
+       copy_v3_v3(lnor_ed->nloc, custom_normal);
+       copy_v3_v3(lnor_ed->niloc, custom_normal);
+
+       lnor_ed->loc = v->co;
+}
+
+BMLoopNorEditDataArray *BM_loop_normal_editdata_array_init(BMesh *bm)
+{
+       BMLoop *l;
+       BMVert *v;
+       BMIter liter, viter;
+
+       bool verts = (bm->selectmode & SCE_SELECT_VERTEX) != 0;
+       bool edges = (bm->selectmode & SCE_SELECT_EDGE) != 0;
+       bool faces = (bm->selectmode & SCE_SELECT_FACE) != 0;
+       int totloopsel = 0;
+
+       BLI_assert(bm->spacearr_dirty == 0);
+
+       BMLoopNorEditDataArray *lnors_ed_arr = MEM_mallocN(sizeof(*lnors_ed_arr), __func__);
+       lnors_ed_arr->lidx_to_lnor_editdata = MEM_callocN(sizeof(*lnors_ed_arr->lidx_to_lnor_editdata) * bm->totloop, __func__);
+
+       if (!CustomData_has_layer(&bm->ldata, CD_CUSTOMLOOPNORMAL)) {
+               BM_data_layer_add(bm, &bm->ldata, CD_CUSTOMLOOPNORMAL);
+       }
+       const int cd_custom_normal_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL);
+
+       BM_mesh_elem_index_ensure(bm, BM_LOOP);
+
+       BLI_bitmap *loops = BLI_BITMAP_NEW(bm->totloop, __func__);
+       if (faces && (verts || edges)) {
+               /* More than one selection mode, check for individual normals to edit. */
+               totloopsel = bm_loop_normal_mark_indiv(bm, loops);
+       }
+
+       if (totloopsel) {
+               BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata = MEM_mallocN(sizeof(*lnor_ed) * totloopsel, __func__);
+
+               BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) {
+                       BM_ITER_ELEM(l, &liter, v, BM_LOOPS_OF_VERT) {
+                               if (BLI_BITMAP_TEST(loops, BM_elem_index_get(l))) {
+                                       loop_normal_editdata_init(bm, lnor_ed, v, l, cd_custom_normal_offset);
+                                       lnors_ed_arr->lidx_to_lnor_editdata[BM_elem_index_get(l)] = lnor_ed;
+                                       lnor_ed++;
+                               }
+                       }
+               }
+               lnors_ed_arr->totloop = totloopsel;
+       }
+       else {  /* If multiple selection modes are inactive OR no such loop is found, fall back to editing all loops. */
+               totloopsel = BM_total_loop_select(bm);
+               BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata = MEM_mallocN(sizeof(*lnor_ed) * totloopsel, __func__);
+
+               BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) {
+                       if (BM_elem_flag_test(v, BM_ELEM_SELECT)) {
+                               BM_ITER_ELEM(l, &liter, v, BM_LOOPS_OF_VERT) {
+                                       loop_normal_editdata_init(bm, lnor_ed, v, l, cd_custom_normal_offset);
+                                       lnors_ed_arr->lidx_to_lnor_editdata[BM_elem_index_get(l)] = lnor_ed;
+                                       lnor_ed++;
+                               }
+                       }
+               }
+               lnors_ed_arr->totloop = totloopsel;
+       }
+
+       MEM_freeN(loops);
+       lnors_ed_arr->cd_custom_normal_offset = cd_custom_normal_offset;
+       return lnors_ed_arr;
+}
+
+void BM_loop_normal_editdata_array_free(BMLoopNorEditDataArray *lnors_ed_arr)
+{
+       MEM_SAFE_FREE(lnors_ed_arr->lnor_editdata);
+       MEM_SAFE_FREE(lnors_ed_arr->lidx_to_lnor_editdata);
+       MEM_freeN(lnors_ed_arr);
+}
+
+int BM_total_loop_select(BMesh *bm)
+{
+       int r_sel = 0;
+       BMVert *v;
+       BMIter viter;
+
+       BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) {
+               if (BM_elem_flag_test(v, BM_ELEM_SELECT)) {
+                       r_sel += BM_vert_face_count(v);
+               }
+       }
+       return r_sel;
+}
+
 static void UNUSED_FUNCTION(bm_mdisps_space_set)(
         Object *ob, BMesh *bm, int from, int to)
 {
 static void UNUSED_FUNCTION(bm_mdisps_space_set)(
         Object *ob, BMesh *bm, int from, int to)
 {
@@ -1052,41 +1482,41 @@ static void UNUSED_FUNCTION(bm_mdisps_space_set)(
                BMFace *f;
                BMIter iter;
                // int i = 0; // UNUSED
                BMFace *f;
                BMIter iter;
                // int i = 0; // UNUSED
-               
+
                multires_set_space(dm, ob, from, to);
                multires_set_space(dm, ob, from, to);
-               
+
                mdisps = CustomData_get_layer(&dm->loopData, CD_MDISPS);
                mdisps = CustomData_get_layer(&dm->loopData, CD_MDISPS);
-               
+
                BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
                        BMLoop *l;
                        BMIter liter;
                        BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
                                MDisps *lmd = CustomData_bmesh_get(&bm->ldata, l->head.data, CD_MDISPS);
                BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
                        BMLoop *l;
                        BMIter liter;
                        BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
                                MDisps *lmd = CustomData_bmesh_get(&bm->ldata, l->head.data, CD_MDISPS);
-                               
+
                                if (!lmd->disps) {
                                        printf("%s: warning - 'lmd->disps' == NULL\n", __func__);
                                }
                                if (!lmd->disps) {
                                        printf("%s: warning - 'lmd->disps' == NULL\n", __func__);
                                }
-                               
+
                                if (lmd->disps && lmd->totdisp == mdisps->totdisp) {
                                        memcpy(lmd->disps, mdisps->disps, sizeof(float) * 3 * lmd->totdisp);
                                }
                                else if (mdisps->disps) {
                                        if (lmd->disps)
                                                MEM_freeN(lmd->disps);
                                if (lmd->disps && lmd->totdisp == mdisps->totdisp) {
                                        memcpy(lmd->disps, mdisps->disps, sizeof(float) * 3 * lmd->totdisp);
                                }
                                else if (mdisps->disps) {
                                        if (lmd->disps)
                                                MEM_freeN(lmd->disps);
-                                       
+
                                        lmd->disps = MEM_dupallocN(mdisps->disps);
                                        lmd->totdisp = mdisps->totdisp;
                                        lmd->level = mdisps->level;
                                }
                                        lmd->disps = MEM_dupallocN(mdisps->disps);
                                        lmd->totdisp = mdisps->totdisp;
                                        lmd->level = mdisps->level;
                                }
-                               
+
                                mdisps++;
                                // i += 1;
                        }
                }
                                mdisps++;
                                // i += 1;
                        }
                }
-               
+
                dm->needsFree = 1;
                dm->release(dm);
                dm->needsFree = 1;
                dm->release(dm);
-               
+
                /* setting this to NULL prevents BKE_editmesh_free from freeing it */
                em->bm = NULL;
                BKE_editmesh_free(em);
                /* setting this to NULL prevents BKE_editmesh_free from freeing it */
                em->bm = NULL;
                BKE_editmesh_free(em);
@@ -1142,6 +1572,7 @@ void bmesh_edit_end(BMesh *bm, BMOpTypeFlag type_flag)
 
        /* compute normals, clear temp flags and flush selections */
        if (type_flag & BMO_OPTYPE_FLAG_NORMALS_CALC) {
 
        /* compute normals, clear temp flags and flush selections */
        if (type_flag & BMO_OPTYPE_FLAG_NORMALS_CALC) {
+               bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL;
                BM_mesh_normals_update(bm);
        }
 
                BM_mesh_normals_update(bm);
        }
 
@@ -1158,6 +1589,9 @@ void bmesh_edit_end(BMesh *bm, BMOpTypeFlag type_flag)
        if ((type_flag & BMO_OPTYPE_FLAG_SELECT_VALIDATE) == 0) {
                bm->selected = select_history;
        }
        if ((type_flag & BMO_OPTYPE_FLAG_SELECT_VALIDATE) == 0) {
                bm->selected = select_history;
        }
+       if (type_flag & BMO_OPTYPE_FLAG_INVALIDATE_CLNOR_ALL) {
+               bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL;
+       }
 }
 
 void BM_mesh_elem_index_ensure_ex(BMesh *bm, const char htype, int elem_offset[4])
 }
 
 void BM_mesh_elem_index_ensure_ex(BMesh *bm, const char htype, int elem_offset[4])