Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
m---------release/datafiles/locale0
m---------release/scripts/addons0
m---------release/scripts/addons_contrib0
-rw-r--r--release/scripts/modules/bpy_extras/keyconfig_utils.py1
-rw-r--r--release/scripts/startup/bl_operators/mesh.py50
-rw-r--r--release/scripts/startup/bl_ui/properties_data_modifier.py16
-rw-r--r--release/scripts/startup/bl_ui/space_view3d.py24
-rw-r--r--release/scripts/startup/bl_ui/space_view3d_toolbar.py21
-rw-r--r--source/blender/blenkernel/BKE_editmesh.h1
-rw-r--r--source/blender/blenkernel/BKE_mesh.h3
-rw-r--r--source/blender/blenkernel/intern/editderivedmesh.c2
-rw-r--r--source/blender/blenkernel/intern/editmesh.c23
-rw-r--r--source/blender/blenkernel/intern/mesh_evaluate.c9
-rw-r--r--source/blender/bmesh/bmesh_class.h29
-rw-r--r--source/blender/bmesh/intern/bmesh_mesh.c407
-rw-r--r--source/blender/bmesh/intern/bmesh_mesh.h20
-rw-r--r--source/blender/bmesh/intern/bmesh_operator_api.h1
-rw-r--r--source/blender/draw/intern/draw_cache_impl_mesh.c4
-rw-r--r--source/blender/editors/include/ED_screen.h1
-rw-r--r--source/blender/editors/include/ED_transform.h2
-rw-r--r--source/blender/editors/mesh/editmesh_tools.c1375
-rw-r--r--source/blender/editors/mesh/editmesh_undo.c2
-rw-r--r--source/blender/editors/mesh/editmesh_utils.c5
-rw-r--r--source/blender/editors/mesh/mesh_intern.h10
-rw-r--r--source/blender/editors/mesh/mesh_ops.c12
-rw-r--r--source/blender/editors/screen/screen_ops.c10
-rw-r--r--source/blender/editors/space_outliner/outliner_draw.c1
-rw-r--r--source/blender/editors/transform/transform.c220
-rw-r--r--source/blender/editors/transform/transform.h4
-rw-r--r--source/blender/editors/transform/transform_ops.c24
-rw-r--r--source/blender/makesdna/DNA_modifier_types.h27
-rw-r--r--source/blender/makesdna/DNA_scene_types.h11
-rw-r--r--source/blender/makesrna/RNA_access.h1
-rw-r--r--source/blender/makesrna/intern/rna_modifier.c67
-rw-r--r--source/blender/makesrna/intern/rna_scene.c15
-rw-r--r--source/blender/modifiers/CMakeLists.txt1
-rw-r--r--source/blender/modifiers/MOD_modifiertypes.h1
-rw-r--r--source/blender/modifiers/intern/MOD_util.c1
-rw-r--r--source/blender/modifiers/intern/MOD_weighted_normal.c670
m---------source/tools0
40 files changed, 2993 insertions, 78 deletions
diff --git a/release/datafiles/locale b/release/datafiles/locale
-Subproject d3349b42856d00c278f72f2a5909a6c96b9cdb5
+Subproject 59495b4b59077aa1cc68fffbdae1463af980f08
diff --git a/release/scripts/addons b/release/scripts/addons
-Subproject 8f2fd7e23f0b5ce023440182f51c40e88d66332
+Subproject 27970761a18926abe1b0020aa350305e3109a53
diff --git a/release/scripts/addons_contrib b/release/scripts/addons_contrib
-Subproject 34a27a42d781d80f9f1833bad8cc5b2abcac293
+Subproject 6a4f93c9b8f36b19bd02087abf3d7f5983df035
diff --git a/release/scripts/modules/bpy_extras/keyconfig_utils.py b/release/scripts/modules/bpy_extras/keyconfig_utils.py
index b0e4d65a3ce..b8fd96e2ec8 100644
--- a/release/scripts/modules/bpy_extras/keyconfig_utils.py
+++ b/release/scripts/modules/bpy_extras/keyconfig_utils.py
@@ -56,6 +56,7 @@ KM_HIERARCHY = [
('Particle', 'EMPTY', 'WINDOW', []),
('Knife Tool Modal Map', 'EMPTY', 'WINDOW', []),
+ ('Custom Normals Modal Map', 'EMPTY', 'WINDOW', []),
('Paint Stroke Modal', 'EMPTY', 'WINDOW', []),
('Paint Curve', 'EMPTY', 'WINDOW', []),
diff --git a/release/scripts/startup/bl_operators/mesh.py b/release/scripts/startup/bl_operators/mesh.py
index c7a11c23c3f..8543847122d 100644
--- a/release/scripts/startup/bl_operators/mesh.py
+++ b/release/scripts/startup/bl_operators/mesh.py
@@ -203,57 +203,7 @@ class MeshSelectPrev(Operator):
return {'FINISHED'}
-# XXX This is hackish (going forth and back from Object mode...), to be redone once we have proper support of
-# custom normals in BMesh/edit mode.
-class MehsSetNormalsFromFaces(Operator):
- """Set the custom vertex normals from the selected faces ones"""
- bl_idname = "mesh.set_normals_from_faces"
- bl_label = "Set Normals From Faces"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(cls, context):
- return (context.mode == 'EDIT_MESH' and context.edit_object.data.polygons)
-
- def execute(self, context):
- import mathutils
-
- bpy.ops.object.mode_set(mode='OBJECT')
- obj = context.active_object
- me = obj.data
-
- v2nors = {}
- for p in me.polygons:
- if not p.select:
- continue
- for lidx, vidx in zip(p.loop_indices, p.vertices):
- assert(me.loops[lidx].vertex_index == vidx)
- v2nors.setdefault(vidx, []).append(p.normal)
-
- for nors in v2nors.values():
- nors[:] = [sum(nors, mathutils.Vector((0, 0, 0))).normalized()]
-
- if not me.has_custom_normals:
- me.create_normals_split()
- me.calc_normals_split()
-
- normals = []
- for l in me.loops:
- nor = v2nors.get(l.vertex_index, [None])[0]
- if nor is None:
- nor = l.normal
- normals.append(nor.to_tuple())
-
- me.normals_split_custom_set(normals)
-
- me.free_normals_split()
- bpy.ops.object.mode_set(mode='EDIT')
-
- return {'FINISHED'}
-
-
classes = (
- MehsSetNormalsFromFaces,
MeshMirrorUV,
MeshSelectNext,
MeshSelectPrev,
diff --git a/release/scripts/startup/bl_ui/properties_data_modifier.py b/release/scripts/startup/bl_ui/properties_data_modifier.py
index 466e7dfa325..abdf1ed2db1 100644
--- a/release/scripts/startup/bl_ui/properties_data_modifier.py
+++ b/release/scripts/startup/bl_ui/properties_data_modifier.py
@@ -1558,6 +1558,22 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel):
if md.rest_source == 'BIND':
layout.operator("object.correctivesmooth_bind", text="Unbind" if is_bind else "Bind")
+ def WEIGHTED_NORMAL(self, layout, ob, md):
+ layout.label("Weighting Mode:")
+ split = layout.split(align=True)
+ col = split.column(align=True)
+ col.prop(md, "mode", text="")
+ col.prop(md, "weight", text="Weight")
+ col.prop(md, "keep_sharp")
+
+ col = split.column(align=True)
+ row = col.row(align=True)
+ row.prop_search(md, "vertex_group", ob, "vertex_groups", text="")
+ row.active = bool(md.vertex_group)
+ row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT')
+ col.prop(md, "thresh", text="Threshold")
+ col.prop(md, "face_influence")
+
classes = (
DATA_PT_modifiers,
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py
index c9decadf150..213d231c37a 100644
--- a/release/scripts/startup/bl_ui/space_view3d.py
+++ b/release/scripts/startup/bl_ui/space_view3d.py
@@ -2843,6 +2843,30 @@ class VIEW3D_MT_edit_mesh_normals(Menu):
layout.operator("mesh.flip_normals")
layout.operator("mesh.set_normals_from_faces", text="Set From Faces")
+ layout.operator("transform.rotate_normal", text="Rotate Normal")
+ layout.operator("mesh.point_normals", text="Point normals to target")
+
+ layout.operator("mesh.merge_normals", text="Merge")
+ layout.operator("mesh.split_normals", text="Split")
+
+ layout.operator("mesh.average_normals", text="Average Normals")
+
+ layout.label(text="Normal Vector")
+
+ layout.operator("mesh.normals_tools", text="Copy").mode = 'COPY'
+ layout.operator("mesh.normals_tools", text="Paste").mode = 'PASTE'
+
+ layout.operator("mesh.normals_tools", text="Multiply").mode = 'MULTIPLY'
+ layout.operator("mesh.normals_tools", text="Add").mode = 'ADD'
+
+ layout.operator("mesh.normals_tools", text="Reset").mode = 'RESET'
+
+ layout.operator("mesh.smoothen_normals", text="Smoothen")
+
+ layout.label(text="Face Strength")
+ layout.operator("mesh.mod_weighted_strength", text="Face select", icon = "FACESEL").set = False
+ layout.operator("mesh.mod_weighted_strength", text="Set Strength", icon = "ZOOMIN").set = True
+
class VIEW3D_MT_edit_mesh_shading(Menu):
bl_label = "Shading"
diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
index c38ead16dc8..1b1e0f73dd1 100644
--- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py
+++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
@@ -1298,6 +1298,26 @@ class VIEW3D_PT_tools_particlemode(View3DPanel, Panel):
sub.prop(pe, "fade_frames", slider=True)
+class VIEW3D_PT_tools_normal(View3DPanel, Panel):
+ bl_category = ""
+ bl_context = ".mesh_edit"
+ bl_label = "Normal Tools"
+
+ def draw(self, context):
+ layout = self.layout
+ toolsettings = context.tool_settings
+
+ col = layout.column(align=True)
+ col.label(text="Normal Vector")
+ col.prop(toolsettings, "normal_vector", text="")
+
+ layout.separator()
+ layout.label(text="Face Strength")
+ layout.prop(toolsettings, "face_strength", text="")
+
+ col = layout.column(align=True)
+
+
# Grease Pencil drawing tools
class VIEW3D_PT_tools_grease_pencil_draw(GreasePencilDrawingToolsPanel, Panel):
bl_space_type = 'VIEW_3D'
@@ -1362,6 +1382,7 @@ classes = (
VIEW3D_PT_tools_grease_pencil_sculpt,
VIEW3D_PT_tools_grease_pencil_brush,
VIEW3D_PT_tools_grease_pencil_brushcurves,
+ VIEW3D_PT_tools_normal,
)
if __name__ == "__main__": # only for live edit.
diff --git a/source/blender/blenkernel/BKE_editmesh.h b/source/blender/blenkernel/BKE_editmesh.h
index 5e456fea64f..39986129c61 100644
--- a/source/blender/blenkernel/BKE_editmesh.h
+++ b/source/blender/blenkernel/BKE_editmesh.h
@@ -93,6 +93,7 @@ void BKE_editmesh_free(BMEditMesh *em);
void BKE_editmesh_color_free(BMEditMesh *em);
void BKE_editmesh_color_ensure(BMEditMesh *em, const char htype);
float (*BKE_editmesh_vertexCos_get_orco(BMEditMesh *em, int *r_numVerts))[3];
+void BKE_editmesh_lnorspace_update(BMEditMesh *em);
/* editderivedmesh.c */
/* should really be defined in editmesh.c, but they use 'EditDerivedBMesh' */
diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h
index f1326974f10..6e0514fd009 100644
--- a/source/blender/blenkernel/BKE_mesh.h
+++ b/source/blender/blenkernel/BKE_mesh.h
@@ -269,6 +269,8 @@ typedef struct MLoopNorSpace {
* - BMLoop pointers. */
struct LinkNode *loops;
char flags;
+
+ void *user_data; /* To be used for extended processing related to loop normal spaces (aka smooth fans). */
} MLoopNorSpace;
/**
* MLoopNorSpace.flags
@@ -284,6 +286,7 @@ typedef struct MLoopNorSpaceArray {
MLoopNorSpace **lspacearr; /* MLoop aligned array */
struct LinkNode *loops_pool; /* Allocated once, avoids to call BLI_linklist_prepend_arena() for each loop! */
char data_type; /* Whether we store loop indices, or pointers to BMLoop. */
+ int num_spaces; /* Number of clnors spaces defined in this array. */
struct MemArena *mem;
} MLoopNorSpaceArray;
/**
diff --git a/source/blender/blenkernel/intern/editderivedmesh.c b/source/blender/blenkernel/intern/editderivedmesh.c
index f5e5a37c7d7..657461e3bf5 100644
--- a/source/blender/blenkernel/intern/editderivedmesh.c
+++ b/source/blender/blenkernel/intern/editderivedmesh.c
@@ -202,7 +202,7 @@ static void emDM_calcLoopNormalsSpaceArray(
cd_loop_clnors_offset = clnors_data ? -1 : CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL);
BM_loops_calc_normal_vcos(bm, vertexCos, vertexNos, polyNos, use_split_normals, split_angle, loopNos,
- r_lnors_spacearr, clnors_data, cd_loop_clnors_offset);
+ r_lnors_spacearr, clnors_data, cd_loop_clnors_offset, false);
#ifdef DEBUG_CLNORS
if (r_lnors_spacearr) {
int i;
diff --git a/source/blender/blenkernel/intern/editmesh.c b/source/blender/blenkernel/intern/editmesh.c
index b63ab276b14..a7f93ca6c49 100644
--- a/source/blender/blenkernel/intern/editmesh.c
+++ b/source/blender/blenkernel/intern/editmesh.c
@@ -246,3 +246,26 @@ float (*BKE_editmesh_vertexCos_get_orco(BMEditMesh *em, int *r_numVerts))[3]
return orco;
}
+
+void BKE_editmesh_lnorspace_update(BMEditMesh *em)
+{
+ BMesh *bm = em->bm;
+
+ /* We need to create clnors data is none exist yet, otherwise there is no way to edit them. */
+ /* Similar code to MESH_OT_customdata_custom_splitnormals_add operator, we want to keep same shading
+ * in case we were using autosmooth so far... */
+ /* Note: there is a problem here, which is that is someone starts a normal editing operation on previously
+ * autosmooth-ed mesh, and cancel that operation, generated clnors data remain, with related sharp edges
+ * (and hence autosmooth is 'lost').
+ * Not sure how critical this is, and how to fix that issue? */
+ if (!CustomData_has_layer(&bm->ldata, CD_CUSTOMLOOPNORMAL)) {
+ Mesh *me = em->ob->data;
+ if (me->flag & ME_AUTOSMOOTH) {
+ BM_edges_sharp_from_angle_set(bm, me->smoothresh);
+
+ me->drawflag |= ME_DRAWSHARP;
+ }
+ }
+
+ BM_lnorspace_update(bm);
+}
diff --git a/source/blender/blenkernel/intern/mesh_evaluate.c b/source/blender/blenkernel/intern/mesh_evaluate.c
index 068be0ef304..997201fc4d0 100644
--- a/source/blender/blenkernel/intern/mesh_evaluate.c
+++ b/source/blender/blenkernel/intern/mesh_evaluate.c
@@ -465,6 +465,8 @@ void BKE_lnor_spacearr_init(MLoopNorSpaceArray *lnors_spacearr, const int numLoo
mem = lnors_spacearr->mem;
lnors_spacearr->lspacearr = BLI_memarena_calloc(mem, sizeof(MLoopNorSpace *) * (size_t)numLoops);
lnors_spacearr->loops_pool = BLI_memarena_alloc(mem, sizeof(LinkNode) * (size_t)numLoops);
+
+ lnors_spacearr->num_spaces = 0;
}
BLI_assert(ELEM(data_type, MLNOR_SPACEARR_BMLOOP_PTR, MLNOR_SPACEARR_LOOP_INDEX));
lnors_spacearr->data_type = data_type;
@@ -472,21 +474,24 @@ void BKE_lnor_spacearr_init(MLoopNorSpaceArray *lnors_spacearr, const int numLoo
void BKE_lnor_spacearr_clear(MLoopNorSpaceArray *lnors_spacearr)
{
- BLI_memarena_clear(lnors_spacearr->mem);
+ lnors_spacearr->num_spaces = 0;
lnors_spacearr->lspacearr = NULL;
lnors_spacearr->loops_pool = NULL;
+ BLI_memarena_clear(lnors_spacearr->mem);
}
void BKE_lnor_spacearr_free(MLoopNorSpaceArray *lnors_spacearr)
{
- BLI_memarena_free(lnors_spacearr->mem);
+ lnors_spacearr->num_spaces = 0;
lnors_spacearr->lspacearr = NULL;
lnors_spacearr->loops_pool = NULL;
+ BLI_memarena_free(lnors_spacearr->mem);
lnors_spacearr->mem = NULL;
}
MLoopNorSpace *BKE_lnor_space_create(MLoopNorSpaceArray *lnors_spacearr)
{
+ lnors_spacearr->num_spaces++;
return BLI_memarena_calloc(lnors_spacearr->mem, sizeof(MLoopNorSpace));
}
diff --git a/source/blender/bmesh/bmesh_class.h b/source/blender/bmesh/bmesh_class.h
index bec2c7a1f45..8159aae1ab8 100644
--- a/source/blender/bmesh/bmesh_class.h
+++ b/source/blender/bmesh/bmesh_class.h
@@ -38,6 +38,8 @@ struct BMEdge;
struct BMLoop;
struct BMFace;
+struct MLoopNorSpaceArray;
+
struct BLI_mempool;
/* note: it is very important for BMHeader to start with two
@@ -236,6 +238,9 @@ typedef struct BMesh {
struct BLI_mempool *looplistpool;
#endif
+ struct MLoopNorSpaceArray *lnor_spacearr; /* Stores MLoopNorSpaceArray for this BMesh */
+ char spacearr_dirty;
+
/* should be copy of scene select mode */
/* stored in BMEditMesh too, this is a bit confusing,
* make sure they're in sync!
@@ -263,9 +268,33 @@ enum {
BM_FACE = 8
};
+typedef struct BMLoopNorEditData {
+ int loop_index;
+ BMLoop *loop;
+ float niloc[3];
+ float nloc[3];
+ float *loc;
+ short *clnors_data;
+} BMLoopNorEditData;
+
+typedef struct BMLoopNorEditDataArray {
+ BMLoopNorEditData *lnor_editdata;
+ /* This one has full amount of loops, used to map loop index to actual BMLoopNorEditData struct. */
+ BMLoopNorEditData **lidx_to_lnor_editdata;
+
+ int cd_custom_normal_offset;
+ int totloop;
+} BMLoopNorEditDataArray;
+
#define BM_ALL (BM_VERT | BM_EDGE | BM_LOOP | BM_FACE)
#define BM_ALL_NOLOOP (BM_VERT | BM_EDGE | BM_FACE)
+enum {
+ BM_SPACEARR_DIRTY = 1 << 0,
+ BM_SPACEARR_DIRTY_ALL = 1 << 1,
+ BM_SPACEARR_BMO_SET = 1 << 2,
+};
+
/* args for _Generic */
#define _BM_GENERIC_TYPE_ELEM_NONCONST \
void *, BMVert *, BMEdge *, BMLoop *, BMFace *, \
diff --git a/source/blender/bmesh/intern/bmesh_mesh.c b/source/blender/bmesh/intern/bmesh_mesh.c
index 8071637d95e..79c835cca11 100644
--- a/source/blender/bmesh/intern/bmesh_mesh.c
+++ b/source/blender/bmesh/intern/bmesh_mesh.c
@@ -30,6 +30,7 @@
#include "DNA_listBase.h"
#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
#include "BLI_linklist_stack.h"
#include "BLI_listbase.h"
@@ -260,6 +261,11 @@ void BM_mesh_data_free(BMesh *bm)
BLI_freelistN(&bm->selected);
+ if (bm->lnor_spacearr) {
+ BKE_lnor_spacearr_free(bm->lnor_spacearr);
+ MEM_freeN(bm->lnor_spacearr);
+ }
+
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
*/
+/* 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];
@@ -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],
- 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;
@@ -694,6 +705,11 @@ static void bm_mesh_loops_calc_normals(
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. */
@@ -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. */
- 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];
@@ -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],
- 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);
@@ -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_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);
@@ -1041,6 +1062,380 @@ 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);
}
+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);
+ }
+}
+
+/**
+* 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)
{
@@ -1142,6 +1537,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) {
+ bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL;
BM_mesh_normals_update(bm);
}
@@ -1158,6 +1554,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_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])
diff --git a/source/blender/bmesh/intern/bmesh_mesh.h b/source/blender/bmesh/intern/bmesh_mesh.h
index b4443c748ce..44887e81157 100644
--- a/source/blender/bmesh/intern/bmesh_mesh.h
+++ b/source/blender/bmesh/intern/bmesh_mesh.h
@@ -29,6 +29,7 @@
struct BMAllocTemplate;
struct MLoopNorSpaceArray;
+struct BMLoopNorEditDataArray;
void BM_mesh_elem_toolflags_ensure(BMesh *bm);
void BM_mesh_elem_toolflags_clear(BMesh *bm);
@@ -48,11 +49,24 @@ void BM_mesh_clear(BMesh *bm);
void BM_mesh_normals_update(BMesh *bm);
void BM_verts_calc_normal_vcos(BMesh *bm, const float (*fnos)[3], const float (*vcos)[3], float (*vnos)[3]);
void BM_loops_calc_normal_vcos(
- BMesh *bm, const float (*vcos)[3], const float (*vnos)[3], const float (*pnos)[3],
- const bool use_split_normals, const float split_angle, float (*r_lnos)[3],
- struct MLoopNorSpaceArray *r_lnors_spacearr, short (*clnors_data)[2], const int cd_loop_clnors_offset);
+ BMesh *bm, const float(*vcos)[3], const float(*vnos)[3], const float(*pnos)[3],
+ const bool use_split_normals, const float split_angle, float(*r_lnos)[3],
+ struct MLoopNorSpaceArray *r_lnors_spacearr, short(*clnors_data)[2],
+ const int cd_loop_clnors_offset, const bool do_rebuild);
bool BM_loop_check_cyclic_smooth_fan(BMLoop *l_curr);
+void BM_lnorspacearr_store(BMesh *bm, float(*r_lnors)[3]);
+void BM_lnorspace_invalidate(BMesh *bm, const bool do_invalidate_all);
+void BM_lnorspace_rebuild(BMesh *bm, bool preserve_clnor);
+void BM_lnorspace_update(BMesh *bm);
+#ifndef NDEBUG
+void BM_lnorspace_err(BMesh *bm);
+#endif
+
+/* Loop Generics */
+struct BMLoopNorEditDataArray *BM_loop_normal_editdata_array_init(BMesh *bm);
+void BM_loop_normal_editdata_array_free(struct BMLoopNorEditDataArray *lnors_ed_arr);
+int BM_total_loop_select(BMesh *bm);
void BM_edges_sharp_from_angle_set(BMesh *bm, const float split_angle);
diff --git a/source/blender/bmesh/intern/bmesh_operator_api.h b/source/blender/bmesh/intern/bmesh_operator_api.h
index 30cd1df9c4e..de87da71e8d 100644
--- a/source/blender/bmesh/intern/bmesh_operator_api.h
+++ b/source/blender/bmesh/intern/bmesh_operator_api.h
@@ -244,6 +244,7 @@ typedef enum {
BMO_OPTYPE_FLAG_NORMALS_CALC = (1 << 1),
BMO_OPTYPE_FLAG_SELECT_FLUSH = (1 << 2),
BMO_OPTYPE_FLAG_SELECT_VALIDATE = (1 << 3),
+ BMO_OPTYPE_FLAG_INVALIDATE_CLNOR_ALL = (1 << 4),
} BMOpTypeFlag;
typedef struct BMOperator {
diff --git a/source/blender/draw/intern/draw_cache_impl_mesh.c b/source/blender/draw/intern/draw_cache_impl_mesh.c
index 5d60b144fd3..cf972434ba5 100644
--- a/source/blender/draw/intern/draw_cache_impl_mesh.c
+++ b/source/blender/draw/intern/draw_cache_impl_mesh.c
@@ -423,7 +423,7 @@ static MeshRenderData *mesh_render_data_create_ex(
int totloop = bm->totloop;
if (is_auto_smooth) {
rdata->loop_normals = MEM_mallocN(sizeof(*rdata->loop_normals) * totloop, __func__);
- BM_loops_calc_normal_vcos(bm, NULL, NULL, NULL, true, split_angle, rdata->loop_normals, NULL, NULL, -1);
+ BM_loops_calc_normal_vcos(bm, NULL, NULL, NULL, true, split_angle, rdata->loop_normals, NULL, NULL, -1, false);
}
rdata->loop_len = totloop;
bm_ensure_types |= BM_LOOP;
@@ -720,7 +720,7 @@ static MeshRenderData *mesh_render_data_create_ex(
/* Should we store the previous array of `loop_normals` in somewhere? */
rdata->loop_len = bm->totloop;
rdata->loop_normals = MEM_mallocN(sizeof(*rdata->loop_normals) * rdata->loop_len, __func__);
- BM_loops_calc_normal_vcos(bm, NULL, NULL, NULL, true, split_angle, rdata->loop_normals, NULL, NULL, -1);
+ BM_loops_calc_normal_vcos(bm, NULL, NULL, NULL, true, split_angle, rdata->loop_normals, NULL, NULL, -1, false);
}
bool calc_active_tangent = false;
diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h
index 919c8d14db5..b10d915553e 100644
--- a/source/blender/editors/include/ED_screen.h
+++ b/source/blender/editors/include/ED_screen.h
@@ -278,6 +278,7 @@ int ED_operator_object_active_editable_font(struct bContext *C);
int ED_operator_editmesh(struct bContext *C);
int ED_operator_editmesh_view3d(struct bContext *C);
int ED_operator_editmesh_region_view3d(struct bContext *C);
+int ED_operator_editmesh_auto_smooth(struct bContext *C);
int ED_operator_editarmature(struct bContext *C);
int ED_operator_editcurve(struct bContext *C);
int ED_operator_editcurve_3d(struct bContext *C);
diff --git a/source/blender/editors/include/ED_transform.h b/source/blender/editors/include/ED_transform.h
index 1e3c8995314..c005f83b3fb 100644
--- a/source/blender/editors/include/ED_transform.h
+++ b/source/blender/editors/include/ED_transform.h
@@ -89,6 +89,7 @@ enum TfmMode {
TFM_VERT_SLIDE,
TFM_SEQ_SLIDE,
TFM_BONE_ENVELOPE_DIST,
+ TFM_NORMAL_ROTATION,
};
/* TRANSFORM CONTEXTS */
@@ -154,6 +155,7 @@ int BIF_countTransformOrientation(const struct bContext *C);
#define P_CENTER (1 << 12)
#define P_GPENCIL_EDIT (1 << 13)
#define P_CURSOR_EDIT (1 << 14)
+#define P_CLNOR_INVALIDATE (1 << 15)
void Transform_Properties(struct wmOperatorType *ot, int flags);
diff --git a/source/blender/editors/mesh/editmesh_tools.c b/source/blender/editors/mesh/editmesh_tools.c
index 510b3381b7e..07812cefb6b 100644
--- a/source/blender/editors/mesh/editmesh_tools.c
+++ b/source/blender/editors/mesh/editmesh_tools.c
@@ -41,11 +41,16 @@
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
+#include "BLI_bitmap.h"
+#include "BLI_heap.h"
#include "BLI_listbase.h"
+#include "BLI_linklist.h"
+#include "BLI_linklist_stack.h"
#include "BLI_noise.h"
#include "BLI_math.h"
#include "BLI_rand.h"
#include "BLI_sort_utils.h"
+#include "BLI_string.h"
#include "BKE_layer.h"
#include "BKE_material.h"
@@ -54,6 +59,7 @@
#include "BKE_report.h"
#include "BKE_texture.h"
#include "BKE_main.h"
+#include "BKE_mesh.h"
#include "BKE_editmesh.h"
#include "DEG_depsgraph.h"
@@ -6917,3 +6923,1372 @@ void MESH_OT_mark_freestyle_face(wmOperatorType *ot)
/** \} */
#endif /* WITH_FREESTYLE */
+
+/********************** Loop normals editing tools modal map. **********************/
+
+/* NOTE: these defines are saved in keymap files, do not change values but just add new ones */
+/* NOTE: We could add more here, like e.g. a switch between local or global coordinates of target,
+ * use numinput to type in explicit vector values... */
+enum {
+ /* Generic commands. */
+ EDBM_CLNOR_MODAL_CANCEL = 1,
+ EDBM_CLNOR_MODAL_CONFIRM = 2,
+
+ /* Point To operator. */
+ EDBM_CLNOR_MODAL_POINTTO_RESET = 101,
+ EDBM_CLNOR_MODAL_POINTTO_INVERT = 102,
+ EDBM_CLNOR_MODAL_POINTTO_SPHERIZE = 103,
+ EDBM_CLNOR_MODAL_POINTTO_ALIGN = 104,
+
+ EDBM_CLNOR_MODAL_POINTTO_USE_MOUSE = 110,
+ EDBM_CLNOR_MODAL_POINTTO_USE_PIVOT = 111,
+ EDBM_CLNOR_MODAL_POINTTO_USE_OBJECT = 112,
+ EDBM_CLNOR_MODAL_POINTTO_SET_USE_3DCURSOR = 113,
+ EDBM_CLNOR_MODAL_POINTTO_SET_USE_SELECTED = 114,
+};
+
+/* called in transform_ops.c, on each regeneration of keymaps */
+wmKeyMap *point_normals_modal_keymap(wmKeyConfig *keyconf)
+{
+ static const EnumPropertyItem modal_items[] = {
+ {EDBM_CLNOR_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""},
+ {EDBM_CLNOR_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
+
+ /* Point To operator. */
+ {EDBM_CLNOR_MODAL_POINTTO_RESET, "RESET", 0, "Reset", "Reset normals to initial ones"},
+ {EDBM_CLNOR_MODAL_POINTTO_INVERT, "INVERT", 0, "Invert", "Toggle inversion of affected normals"},
+ {EDBM_CLNOR_MODAL_POINTTO_SPHERIZE, "SPHERIZE", 0, "Spherize", "Interpolate between new and original normals"},
+ {EDBM_CLNOR_MODAL_POINTTO_ALIGN, "ALIGN", 0, "Align", "Make all affected normals parallel"},
+
+ {EDBM_CLNOR_MODAL_POINTTO_USE_MOUSE, "USE_MOUSE", 0, "Use Mouse", "Follow mouse cursor position"},
+ {EDBM_CLNOR_MODAL_POINTTO_USE_PIVOT, "USE_PIVOT", 0, "Use Pivot",
+ "Use current rotation/scaling pivot point coordinates"},
+ {EDBM_CLNOR_MODAL_POINTTO_USE_OBJECT, "USE_OBJECT", 0, "Use Object", "Use current edited object's location"},
+ {EDBM_CLNOR_MODAL_POINTTO_SET_USE_3DCURSOR, "SET_USE_3DCURSOR", 0, "Set and Use 3D Cursor",
+ "Set new 3D cursor position and use it"},
+ {EDBM_CLNOR_MODAL_POINTTO_SET_USE_SELECTED, "SET_USE_SELECTED", 0, "Select and Use Mesh Item",
+ "Select new active mesh element and use its location"},
+ {0, NULL, 0, NULL, NULL}
+ };
+ static const char *keymap_name = "Custom Normals Modal Map";
+
+ wmKeyMap *keymap = WM_modalkeymap_get(keyconf, keymap_name);
+
+ /* We only need to add map once */
+ if (keymap && keymap->modal_items)
+ return NULL;
+
+ keymap = WM_modalkeymap_add(keyconf, keymap_name, modal_items);
+
+ /* Generic items for modal map. */
+ WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, EDBM_CLNOR_MODAL_CANCEL);
+ WM_modalkeymap_add_item(keymap, RIGHTMOUSE, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_CANCEL);
+
+ WM_modalkeymap_add_item(keymap, RETKEY, KM_PRESS, KM_ANY, 0, EDBM_CLNOR_MODAL_CONFIRM);
+ WM_modalkeymap_add_item(keymap, PADENTER, KM_PRESS, KM_ANY, 0, EDBM_CLNOR_MODAL_CONFIRM);
+ WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_CONFIRM);
+
+ /* Point To items for modal map */
+ WM_modalkeymap_add_item(keymap, RKEY, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_POINTTO_RESET);
+ WM_modalkeymap_add_item(keymap, IKEY, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_POINTTO_INVERT);
+ WM_modalkeymap_add_item(keymap, SKEY, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_POINTTO_SPHERIZE);
+ WM_modalkeymap_add_item(keymap, AKEY, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_POINTTO_ALIGN);
+
+ WM_modalkeymap_add_item(keymap, MKEY, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_POINTTO_USE_MOUSE);
+ WM_modalkeymap_add_item(keymap, LKEY, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_POINTTO_USE_PIVOT);
+ WM_modalkeymap_add_item(keymap, OKEY, KM_PRESS, KM_NOTHING, 0, EDBM_CLNOR_MODAL_POINTTO_USE_OBJECT);
+
+ WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_CLICK, KM_CTRL, 0, EDBM_CLNOR_MODAL_POINTTO_SET_USE_3DCURSOR);
+ WM_modalkeymap_add_item(keymap, RIGHTMOUSE, KM_CLICK, KM_CTRL, 0, EDBM_CLNOR_MODAL_POINTTO_SET_USE_SELECTED);
+
+ WM_modalkeymap_assign(keymap, "MESH_OT_point_normals");
+
+ return keymap;
+}
+
+#define CLNORS_VALID_VEC_LEN (1e-4f)
+
+/********************** 'Point to' Loop Normals **********************/
+
+enum {
+ EDBM_CLNOR_POINTTO_MODE_COORDINATES = 1,
+ EDBM_CLNOR_POINTTO_MODE_MOUSE = 2,
+};
+
+static EnumPropertyItem clnors_pointto_mode_items[] = {
+ {EDBM_CLNOR_POINTTO_MODE_COORDINATES, "COORDINATES", 0, "Coordinates",
+ "Use static coordinates (defined by various means)"},
+ {EDBM_CLNOR_POINTTO_MODE_MOUSE, "MOUSE", 0, "Mouse", "Follow mouse cursor"},
+ {0, NULL, 0, NULL, NULL}
+};
+
+/* Initialize loop normal data */
+static int point_normals_init(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ Object *obedit = CTX_data_edit_object(C);
+ BMEditMesh *em = BKE_editmesh_from_object(obedit);
+ BMesh *bm = em->bm;
+
+ BKE_editmesh_lnorspace_update(em);
+ BMLoopNorEditDataArray *lnors_ed_arr = BM_loop_normal_editdata_array_init(bm);
+
+ op->customdata = lnors_ed_arr;
+
+ return lnors_ed_arr->totloop;
+}
+
+static void point_normals_free(bContext *C, wmOperator *op)
+{
+ BMLoopNorEditDataArray *lnors_ed_arr = op->customdata;
+ BM_loop_normal_editdata_array_free(lnors_ed_arr);
+ op->customdata = NULL;
+ ED_area_headerprint(CTX_wm_area(C), NULL);
+}
+
+static void point_normals_update_header(bContext *C, wmOperator *op)
+{
+ char header[UI_MAX_DRAW_STR];
+ char buf[UI_MAX_DRAW_STR];
+
+ char *p = buf;
+ int available_len = sizeof(buf);
+
+#define WM_MODALKEY(_id) \
+ WM_modalkeymap_operator_items_to_string_buf(op->type, (_id), true, UI_MAX_SHORTCUT_STR, &available_len, &p)
+
+ BLI_snprintf(header, sizeof(header), IFACE_("%s: confirm, %s: cancel, "
+ "%s: point to mouse (%s), %s: point to Pivot, "
+ "%s: point to object origin, %s: reset normals, "
+ "%s: set & point to 3D cursor, %s: select & point to mesh item, "
+ "%s: invert normals (%s), %s: spherize (%s), %s: align (%s)"),
+ WM_MODALKEY(EDBM_CLNOR_MODAL_CONFIRM), WM_MODALKEY(EDBM_CLNOR_MODAL_CANCEL),
+ WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_USE_MOUSE),
+ WM_bool_as_string(RNA_enum_get(op->ptr, "mode") == EDBM_CLNOR_POINTTO_MODE_MOUSE),
+ WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_USE_PIVOT), WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_USE_OBJECT),
+ WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_RESET), WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_SET_USE_3DCURSOR),
+ WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_SET_USE_SELECTED),
+ WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_INVERT), WM_bool_as_string(RNA_boolean_get(op->ptr, "invert")),
+ WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_SPHERIZE), WM_bool_as_string(RNA_boolean_get(op->ptr, "spherize")),
+ WM_MODALKEY(EDBM_CLNOR_MODAL_POINTTO_ALIGN), WM_bool_as_string(RNA_boolean_get(op->ptr, "align")));
+
+#undef WM_MODALKEY
+
+ ED_area_headerprint(CTX_wm_area(C), header);
+}
+
+/* TODO move that to generic function in BMesh? */
+static void bmesh_selected_verts_center_calc(BMesh *bm, float *r_center)
+{
+ BMVert *v;
+ BMIter viter;
+ int i = 0;
+
+ zero_v3(r_center);
+ BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) {
+ if (BM_elem_flag_test(v, BM_ELEM_SELECT)) {
+ add_v3_v3(r_center, v->co);
+ i++;
+ }
+ }
+ mul_v3_fl(r_center, 1.0f / (float)i);
+}
+
+static void point_normals_apply(bContext *C, wmOperator *op, float target[3], const bool do_reset)
+{
+ Object *obedit = CTX_data_edit_object(C);
+ BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
+ BMLoopNorEditDataArray *lnors_ed_arr = op->customdata;
+
+ const bool do_invert = RNA_boolean_get(op->ptr, "invert");
+ const bool do_spherize = RNA_boolean_get(op->ptr, "spherize");
+ const bool do_align = RNA_boolean_get(op->ptr, "align");
+ float center[3];
+
+ if (do_align && !do_reset) {
+ bmesh_selected_verts_center_calc(bm, center);
+ }
+
+ sub_v3_v3(target, obedit->loc); /* Move target to local coordinates. */
+
+ BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata;
+ for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) {
+ if (do_reset) {
+ copy_v3_v3(lnor_ed->nloc, lnor_ed->niloc);
+ }
+ else if (do_spherize) {
+ /* Note that this is *not* real spherical interpolation. Probably good enough in this case though? */
+ const float strength = RNA_float_get(op->ptr, "spherize_strength");
+ float spherized_normal[3];
+
+ sub_v3_v3v3(spherized_normal, target, lnor_ed->loc);
+ normalize_v3(spherized_normal); /* otherwise, multiplication by strength is meaningless... */
+ mul_v3_fl(spherized_normal, strength);
+ mul_v3_v3fl(lnor_ed->nloc, lnor_ed->niloc, 1.0f - strength);
+ add_v3_v3(lnor_ed->nloc, spherized_normal);
+ }
+ else if (do_align) {
+ sub_v3_v3v3(lnor_ed->nloc, target, center);
+ }
+ else {
+ sub_v3_v3v3(lnor_ed->nloc, target, lnor_ed->loc);
+ }
+
+ if (do_invert && !do_reset) {
+ negate_v3(lnor_ed->nloc);
+ }
+ if (normalize_v3(lnor_ed->nloc) >= CLNORS_VALID_VEC_LEN) {
+ BKE_lnor_space_custom_normal_to_data(
+ bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->nloc, lnor_ed->clnors_data);
+ }
+ }
+}
+
+static int edbm_point_normals_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ View3D *v3d = CTX_wm_view3d(C);
+ Scene *scene = CTX_data_scene(C);
+ Object *obedit = CTX_data_edit_object(C);
+ BMEditMesh *em = BKE_editmesh_from_object(obedit);
+ BMesh *bm = em->bm;
+
+ float target[3];
+
+ int ret = OPERATOR_PASS_THROUGH;
+ int mode = RNA_enum_get(op->ptr, "mode");
+ int new_mode = mode;
+ bool force_mousemove = false;
+ bool do_reset = false;
+
+ PropertyRNA *prop_target = RNA_struct_find_property(op->ptr, "target_location");
+
+ if (event->type == EVT_MODAL_MAP) {
+ switch (event->val) {
+ case EDBM_CLNOR_MODAL_CONFIRM:
+ RNA_property_float_get_array(op->ptr, prop_target, target);
+ ret = OPERATOR_FINISHED;
+ break;
+
+ case EDBM_CLNOR_MODAL_CANCEL:
+ do_reset = true;
+ ret = OPERATOR_CANCELLED;
+ break;
+
+ case EDBM_CLNOR_MODAL_POINTTO_RESET:
+ do_reset = true;
+ ret = OPERATOR_RUNNING_MODAL;
+ break;
+
+ case EDBM_CLNOR_MODAL_POINTTO_INVERT:
+ {
+ PropertyRNA *prop_invert = RNA_struct_find_property(op->ptr, "invert");
+ RNA_property_boolean_set(op->ptr, prop_invert, !RNA_property_boolean_get(op->ptr, prop_invert));
+ RNA_property_float_get_array(op->ptr, prop_target, target);
+ ret = OPERATOR_RUNNING_MODAL;
+ break;
+ }
+
+ case EDBM_CLNOR_MODAL_POINTTO_SPHERIZE:
+ {
+ PropertyRNA *prop_spherize = RNA_struct_find_property(op->ptr, "spherize");
+ RNA_property_boolean_set(op->ptr, prop_spherize, !RNA_property_boolean_get(op->ptr, prop_spherize));
+ RNA_property_float_get_array(op->ptr, prop_target, target);
+ ret = OPERATOR_RUNNING_MODAL;
+ break;
+ }
+
+ case EDBM_CLNOR_MODAL_POINTTO_ALIGN:
+ {
+ PropertyRNA *prop_align = RNA_struct_find_property(op->ptr, "align");
+ RNA_property_boolean_set(op->ptr, prop_align, !RNA_property_boolean_get(op->ptr, prop_align));
+ RNA_property_float_get_array(op->ptr, prop_target, target);
+ ret = OPERATOR_RUNNING_MODAL;
+ break;
+ }
+
+ case EDBM_CLNOR_MODAL_POINTTO_USE_MOUSE:
+ new_mode = EDBM_CLNOR_POINTTO_MODE_MOUSE;
+ force_mousemove = true; /* We want to immediately update to mouse cursor position... */
+ ret = OPERATOR_RUNNING_MODAL;
+ break;
+
+ case EDBM_CLNOR_MODAL_POINTTO_USE_OBJECT:
+ new_mode = EDBM_CLNOR_POINTTO_MODE_COORDINATES;
+ copy_v3_v3(target, obedit->loc);
+ ret = OPERATOR_RUNNING_MODAL;
+ break;
+
+ case EDBM_CLNOR_MODAL_POINTTO_SET_USE_3DCURSOR:
+ new_mode = EDBM_CLNOR_POINTTO_MODE_COORDINATES;
+ ED_view3d_cursor3d_update(C, event->mval);
+ copy_v3_v3(target, ED_view3d_cursor3d_get(scene, v3d)->location);
+ break;
+
+ case EDBM_CLNOR_MODAL_POINTTO_SET_USE_SELECTED:
+ new_mode = EDBM_CLNOR_POINTTO_MODE_COORDINATES;
+ view3d_operator_needs_opengl(C);
+ if (EDBM_select_pick(C, event->mval, false, false, false)) {
+ ED_object_editmode_calc_active_center(obedit, false, target); /* Point to newly selected active. */
+ add_v3_v3(target, obedit->loc);
+ ret = OPERATOR_RUNNING_MODAL;
+ }
+ break;
+
+ case EDBM_CLNOR_MODAL_POINTTO_USE_PIVOT:
+ new_mode = EDBM_CLNOR_POINTTO_MODE_COORDINATES;
+ switch (scene->toolsettings->transform_pivot_point) {
+ case V3D_AROUND_CENTER_BOUNDS: /* calculateCenterBound */
+ {
+ BMVert *v;
+ BMIter viter;
+ float min[3], max[3];
+ int i = 0;
+
+ BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) {
+ if (BM_elem_flag_test(v, BM_ELEM_SELECT)) {
+ if (i) {
+ minmax_v3v3_v3(min, max, v->co);
+ }
+ else {
+ copy_v3_v3(min, v->co);
+ copy_v3_v3(max, v->co);
+ }
+ i++;
+ }
+ }
+ mid_v3_v3v3(target, min, max);
+ add_v3_v3(target, obedit->loc);
+ break;
+ }
+
+ case V3D_AROUND_CENTER_MEAN:
+ {
+ bmesh_selected_verts_center_calc(bm, target);
+ add_v3_v3(target, obedit->loc);
+ break;
+ }
+
+ case V3D_AROUND_CURSOR:
+ copy_v3_v3(target, ED_view3d_cursor3d_get(scene, v3d)->location);
+ break;
+
+ case V3D_AROUND_ACTIVE:
+ if (!ED_object_editmode_calc_active_center(obedit, false, target)) {
+ zero_v3(target);
+ }
+ add_v3_v3(target, obedit->loc);
+ break;
+
+ default:
+ BKE_report(op->reports, RPT_WARNING, "Does not support Individual Origin as pivot");
+ copy_v3_v3(target, obedit->loc);
+ }
+ ret = OPERATOR_RUNNING_MODAL;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (new_mode != mode) {
+ mode = new_mode;
+ RNA_enum_set(op->ptr, "mode", mode);
+ }
+
+ /* Only handle mousemove event in case we are in mouse mode. */
+ if (event->type == MOUSEMOVE || force_mousemove) {
+ if (mode == EDBM_CLNOR_POINTTO_MODE_MOUSE) {
+ ARegion *ar = CTX_wm_region(C);
+ float center[3];
+
+ bmesh_selected_verts_center_calc(bm, center);
+
+ ED_view3d_win_to_3d_int(v3d, ar, center, event->mval, target);
+
+ ret = OPERATOR_RUNNING_MODAL;
+ }
+ }
+
+ if (ret != OPERATOR_PASS_THROUGH) {
+ if (!ELEM(ret, OPERATOR_CANCELLED, OPERATOR_FINISHED)) {
+ RNA_property_float_set_array(op->ptr, prop_target, target);
+ }
+ point_normals_apply(C, op, target, do_reset);
+ EDBM_update_generic(em, true, false); /* Recheck bools. */
+
+ point_normals_update_header(C, op);
+ }
+
+ if (ELEM(ret, OPERATOR_CANCELLED, OPERATOR_FINISHED)) {
+ point_normals_free(C, op);
+ }
+
+ return ret;
+}
+
+static int edbm_point_normals_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ if (!point_normals_init(C, op, event)) {
+ point_normals_free(C, op);
+ return OPERATOR_CANCELLED;
+ }
+
+ WM_event_add_modal_handler(C, op);
+
+ point_normals_update_header(C, op);
+
+ op->flag |= OP_IS_MODAL_GRAB_CURSOR;
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int edbm_point_normals_exec(bContext *C, wmOperator *op)
+{
+ Object *obedit = CTX_data_edit_object(C);
+ BMEditMesh *em = BKE_editmesh_from_object(obedit);
+
+ if (!point_normals_init(C, op, NULL)) {
+ point_normals_free(C, op);
+ return OPERATOR_CANCELLED;
+ }
+
+ /* Note that 'mode' is ignored in exec case, we directly use vector stored in target_location, whatever that is. */
+
+ float target[3];
+ RNA_float_get_array(op->ptr, "target_location", target);
+
+ point_normals_apply(C, op, target, false);
+
+ EDBM_update_generic(em, true, false);
+ point_normals_free(C, op);
+
+ return OPERATOR_FINISHED;
+}
+
+static bool point_normals_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop)
+{
+ const char *prop_id = RNA_property_identifier(prop);
+
+ /* Only show strength option if spherize is enabled. */
+ if (STREQ(prop_id, "spherize_strength")) {
+ return (bool)RNA_boolean_get(ptr, "spherize");
+ }
+
+ /* Else, show it! */
+ return true;
+}
+
+static void edbm_point_normals_ui(bContext *C, wmOperator *op)
+{
+ uiLayout *layout = op->layout;
+ wmWindowManager *wm = CTX_wm_manager(C);
+ PointerRNA ptr;
+
+ RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr);
+
+ /* Main auto-draw call */
+ uiDefAutoButsRNA(layout, &ptr, point_normals_draw_check_prop, '\0', false);
+}
+
+void MESH_OT_point_normals(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Point Normals to Target";
+ ot->description = "Point selected custom normals to specified Target";
+ ot->idname = "MESH_OT_point_normals";
+
+ /* api callbacks */
+ ot->exec = edbm_point_normals_exec;
+ ot->invoke = edbm_point_normals_invoke;
+ ot->modal = edbm_point_normals_modal;
+ ot->poll = ED_operator_editmesh_auto_smooth;
+ ot->ui = edbm_point_normals_ui;
+ ot->cancel = point_normals_free;
+
+ /* flags */
+ ot->flag = OPTYPE_BLOCKING | OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ ot->prop = RNA_def_enum(ot->srna, "mode", clnors_pointto_mode_items, EDBM_CLNOR_POINTTO_MODE_COORDINATES,
+ "Mode", "How to define coordinates to point custom normals to");
+ RNA_def_property_flag(ot->prop, PROP_HIDDEN);
+
+ RNA_def_boolean(ot->srna, "invert", false, "Invert", "Invert affected normals");
+
+ RNA_def_boolean(ot->srna, "align", false, "Align", "Make all affected normals parallel");
+
+ RNA_def_float_vector(ot->srna, "target_location", 3, (float[3]){0.0f, 0.0f, 0.0f}, -FLT_MAX, FLT_MAX,
+ "Target", "Target location to which normals will point", -1000.0f, 1000.0f);
+
+ RNA_def_boolean(ot->srna, "spherize", false,
+ "Spherize", "Interpolate between original and new normals");
+
+ RNA_def_float(ot->srna, "spherize_strength", 0.1, 0.0f, 1.0f,
+ "Spherize Strength", "Ratio of spherized normal to original normal", 0.0f, 1.0f);
+}
+
+/********************** Split/Merge Loop Normals **********************/
+
+static void normals_splitmerge_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);
+}
+
+static void normals_merge(BMesh *bm, BMLoopNorEditDataArray *lnors_ed_arr)
+{
+ BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata;
+
+ BLI_SMALLSTACK_DECLARE(clnors, short *);
+
+ BLI_assert(bm->lnor_spacearr->data_type == MLNOR_SPACEARR_BMLOOP_PTR);
+
+ normals_splitmerge_loops_edges_tag(bm, false);
+
+ for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) {
+ if (BM_elem_flag_test(lnor_ed->loop, BM_ELEM_TAG)) {
+ continue;
+ }
+
+ MLoopNorSpace *lnor_space = bm->lnor_spacearr->lspacearr[lnor_ed->loop_index];
+
+ if ((lnor_space->flags & MLNOR_SPACE_IS_SINGLE) == 0) {
+ LinkNode *loops = lnor_space->loops;
+ float avg_normal[3] = {0.0f, 0.0f, 0.0f};
+ short *clnors_data;
+
+ for (; loops; loops = loops->next) {
+ BMLoop *l = loops->link;
+ const int loop_index = BM_elem_index_get(l);
+
+ BMLoopNorEditData *lnor_ed_tmp = lnors_ed_arr->lidx_to_lnor_editdata[loop_index];
+ BLI_assert(lnor_ed_tmp->loop_index == loop_index && lnor_ed_tmp->loop == l);
+ add_v3_v3(avg_normal, lnor_ed_tmp->nloc);
+ BLI_SMALLSTACK_PUSH(clnors, lnor_ed_tmp->clnors_data);
+ BM_elem_flag_enable(l, BM_ELEM_TAG);
+ }
+ if (normalize_v3(avg_normal) < CLNORS_VALID_VEC_LEN) {
+ /* If avg normal is nearly 0, set clnor to default value. */
+ zero_v3(avg_normal);
+ }
+ while ((clnors_data = BLI_SMALLSTACK_POP(clnors))) {
+ BKE_lnor_space_custom_normal_to_data(lnor_space, avg_normal, clnors_data);
+ }
+ }
+ }
+}
+
+static void normals_split(BMesh *bm)
+{
+ BMFace *f;
+ BMLoop *l, *l_curr, *l_first;
+ BMIter fiter;
+
+ BLI_assert(bm->lnor_spacearr->data_type == MLNOR_SPACEARR_BMLOOP_PTR);
+
+ normals_splitmerge_loops_edges_tag(bm, true);
+
+ const int cd_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL);
+ BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) {
+ l_curr = l_first = BM_FACE_FIRST_LOOP(f);
+ do {
+ if (BM_elem_flag_test(l_curr->v, BM_ELEM_SELECT) && (!BM_elem_flag_test(l_curr->e, BM_ELEM_TAG) ||
+ (!BM_elem_flag_test(l_curr, BM_ELEM_TAG) && BM_loop_check_cyclic_smooth_fan(l_curr))))
+ {
+ if (!BM_elem_flag_test(l_curr->e, BM_ELEM_TAG) && !BM_elem_flag_test(l_curr->prev->e, BM_ELEM_TAG)) {
+ const int loop_index = BM_elem_index_get(l_curr);
+ short *clnors = BM_ELEM_CD_GET_VOID_P(l_curr, cd_clnors_offset);
+ BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[loop_index], f->no, clnors);
+ }
+ else {
+ BMVert *v_pivot = l_curr->v;
+ BMEdge *e_next;
+ const BMEdge *e_org = l_curr->e;
+ BMLoop *lfan_pivot, *lfan_pivot_next;
+
+ lfan_pivot = l_curr;
+ e_next = lfan_pivot->e;
+ BLI_SMALLSTACK_DECLARE(loops, BMLoop *);
+ float avg_normal[3] = { 0.0f };
+
+ while (true) {
+ lfan_pivot_next = BM_vert_step_fan_loop(lfan_pivot, &e_next);
+ if (lfan_pivot_next) {
+ BLI_assert(lfan_pivot_next->v == v_pivot);
+ }
+ else {
+ e_next = (lfan_pivot->e == e_next) ? lfan_pivot->prev->e : lfan_pivot->e;
+ }
+
+ BLI_SMALLSTACK_PUSH(loops, lfan_pivot);
+ add_v3_v3(avg_normal, lfan_pivot->f->no);
+
+ if (!BM_elem_flag_test(e_next, BM_ELEM_TAG) || (e_next == e_org)) {
+ break;
+ }
+ lfan_pivot = lfan_pivot_next;
+ }
+ if (normalize_v3(avg_normal) < CLNORS_VALID_VEC_LEN) {
+ /* If avg normal is nearly 0, set clnor to default value. */
+ zero_v3(avg_normal);
+ }
+ while ((l = BLI_SMALLSTACK_POP(loops))) {
+ const int l_index = BM_elem_index_get(l);
+ short *clnors = BM_ELEM_CD_GET_VOID_P(l, cd_clnors_offset);
+ BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[l_index], avg_normal, clnors);
+ }
+ }
+ }
+ } while ((l_curr = l_curr->next) != l_first);
+ }
+}
+
+static int normals_split_merge(bContext *C, const bool do_merge)
+{
+ Object *obedit = CTX_data_edit_object(C);
+ BMEditMesh *em = BKE_editmesh_from_object(obedit);
+ BMesh *bm = em->bm;
+ BMEdge *e;
+ BMIter eiter;
+
+ BKE_editmesh_lnorspace_update(em);
+
+ BMLoopNorEditDataArray *lnors_ed_arr = do_merge ? BM_loop_normal_editdata_array_init(bm) : NULL;
+
+ mesh_set_smooth_faces(em, do_merge);
+
+ BM_ITER_MESH(e, &eiter, bm, BM_EDGES_OF_MESH) {
+ if (BM_elem_flag_test(e, BM_ELEM_SELECT)) {
+ BM_elem_flag_set(e, BM_ELEM_SMOOTH, do_merge);
+ }
+ }
+ if (do_merge == 0) {
+ Mesh *me = obedit->data;
+ me->drawflag |= ME_DRAWSHARP;
+ }
+
+ bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL;
+ BKE_editmesh_lnorspace_update(em);
+
+ if (do_merge) {
+ normals_merge(bm, lnors_ed_arr);
+ }
+ else {
+ normals_split(bm);
+ }
+
+ if (lnors_ed_arr) {
+ BM_loop_normal_editdata_array_free(lnors_ed_arr);
+ }
+
+ EDBM_update_generic(em, true, false);
+
+ return OPERATOR_FINISHED;
+}
+
+static int edbm_merge_normals_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ return normals_split_merge(C, true);
+}
+
+void MESH_OT_merge_normals(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Merge Normals";
+ ot->description = "Merge custom normals of selected vertices";
+ ot->idname = "MESH_OT_merge_normals";
+
+ /* api callbacks */
+ ot->exec = edbm_merge_normals_exec;
+ ot->poll = ED_operator_editmesh_auto_smooth;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+static int edbm_split_normals_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ return normals_split_merge(C, false);
+}
+
+void MESH_OT_split_normals(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Split Normals";
+ ot->description = "Split custom normals of selected vertices";
+ ot->idname = "MESH_OT_split_normals";
+
+ /* api callbacks */
+ ot->exec = edbm_split_normals_exec;
+ ot->poll = ED_operator_editmesh_auto_smooth;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/********************** Average Loop Normals **********************/
+
+enum {
+ EDBM_CLNOR_AVERAGE_LOOP = 1,
+ EDBM_CLNOR_AVERAGE_FACE_AREA = 2,
+ EDBM_CLNOR_AVERAGE_ANGLE = 3,
+};
+
+static EnumPropertyItem average_method_items[] = {
+ {EDBM_CLNOR_AVERAGE_LOOP, "CUSTOM_NORMAL", 0, "Custom Normal", "Take Average of vert Normals"},
+ {EDBM_CLNOR_AVERAGE_FACE_AREA, "FACE_AREA", 0, "Face Area", "Set all vert normals by Face Area"},
+ {EDBM_CLNOR_AVERAGE_ANGLE, "CORNER_ANGLE", 0, "Corner Angle", "Set all vert normals by Corner Angle"},
+ {0, NULL, 0, NULL, NULL}
+};
+
+static int edbm_average_normals_exec(bContext *C, wmOperator *op)
+{
+ Object *obedit = CTX_data_edit_object(C);
+ BMEditMesh *em = BKE_editmesh_from_object(obedit);
+ BMesh *bm = em->bm;
+ BMFace *f;
+ BMLoop *l, *l_curr, *l_first;
+ BMIter fiter;
+
+ bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL;
+ BKE_editmesh_lnorspace_update(em);
+
+ const int average_type = RNA_enum_get(op->ptr, "average_type");
+ const int cd_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL);
+ const float absweight = (float) RNA_int_get(op->ptr, "weight");
+ const float threshold = RNA_float_get(op->ptr, "threshold");
+
+ float weight = absweight / 50.0f;
+ if (absweight == 100.0f) {
+ weight = (float)SHRT_MAX;
+ }
+ else if (absweight == 1.0f) {
+ weight = 1 / (float)SHRT_MAX;
+ }
+ else if ((weight - 1) * 25 > 1) {
+ weight = (weight - 1) * 25;
+ }
+
+ normals_splitmerge_loops_edges_tag(bm, true);
+
+ Heap *loop_weight = BLI_heap_new();
+
+ BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) {
+ l_curr = l_first = BM_FACE_FIRST_LOOP(f);
+ do {
+ if (BM_elem_flag_test(l_curr->v, BM_ELEM_SELECT) && (!BM_elem_flag_test(l_curr->e, BM_ELEM_TAG) ||
+ (!BM_elem_flag_test(l_curr, BM_ELEM_TAG) && BM_loop_check_cyclic_smooth_fan(l_curr))))
+ {
+ if (!BM_elem_flag_test(l_curr->e, BM_ELEM_TAG) && !BM_elem_flag_test(l_curr->prev->e, BM_ELEM_TAG)) {
+ const int loop_index = BM_elem_index_get(l_curr);
+ short *clnors = BM_ELEM_CD_GET_VOID_P(l_curr, cd_clnors_offset);
+ BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[loop_index], f->no, clnors);
+ }
+ else {
+ BMVert *v_pivot = l_curr->v;
+ BMEdge *e_next;
+ const BMEdge *e_org = l_curr->e;
+ BMLoop *lfan_pivot, *lfan_pivot_next;
+
+ lfan_pivot = l_curr;
+ e_next = lfan_pivot->e;
+
+ while (true) {
+ lfan_pivot_next = BM_vert_step_fan_loop(lfan_pivot, &e_next);
+ if (lfan_pivot_next) {
+ BLI_assert(lfan_pivot_next->v == v_pivot);
+ }
+ else {
+ e_next = (lfan_pivot->e == e_next) ? lfan_pivot->prev->e : lfan_pivot->e;
+ }
+
+ float val = 1.0f;
+ if (average_type == EDBM_CLNOR_AVERAGE_FACE_AREA) {
+ val = 1.0f / BM_face_calc_area(lfan_pivot->f);
+ }
+ else if (average_type == EDBM_CLNOR_AVERAGE_ANGLE) {
+ val = 1.0f / BM_loop_calc_face_angle(lfan_pivot);
+ }
+
+ BLI_heap_insert(loop_weight, val, lfan_pivot);
+
+ if (!BM_elem_flag_test(e_next, BM_ELEM_TAG) || (e_next == e_org)) {
+ break;
+ }
+ lfan_pivot = lfan_pivot_next;
+ }
+
+ BLI_SMALLSTACK_DECLARE(loops, BMLoop *);
+ float wnor[3], avg_normal[3] = { 0.0f }, count = 0;
+ float val = BLI_heap_node_value(BLI_heap_top(loop_weight));
+
+ while (!BLI_heap_is_empty(loop_weight)) {
+ const float cur_val = BLI_heap_node_value(BLI_heap_top(loop_weight));
+ if (!compare_ff(val, cur_val, threshold)) {
+ count++;
+ val = cur_val;
+ }
+ l = BLI_heap_pop_min(loop_weight);
+ BLI_SMALLSTACK_PUSH(loops, l);
+
+ const float n_weight = pow(weight, count);
+
+ if (average_type == EDBM_CLNOR_AVERAGE_LOOP) {
+ const int l_index = BM_elem_index_get(l);
+ short *clnors = BM_ELEM_CD_GET_VOID_P(l, cd_clnors_offset);
+ BKE_lnor_space_custom_data_to_normal(bm->lnor_spacearr->lspacearr[l_index], clnors, wnor);
+ }
+ else {
+ copy_v3_v3(wnor, l->f->no);
+ }
+ mul_v3_fl(wnor, (1.0f / cur_val) * (1.0f / n_weight));
+ add_v3_v3(avg_normal, wnor);
+ }
+
+ if (normalize_v3(avg_normal) < CLNORS_VALID_VEC_LEN) {
+ /* If avg normal is nearly 0, set clnor to default value. */
+ zero_v3(avg_normal);
+ }
+ while ((l = BLI_SMALLSTACK_POP(loops))) {
+ const int l_index = BM_elem_index_get(l);
+ short *clnors = BM_ELEM_CD_GET_VOID_P(l, cd_clnors_offset);
+ BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[l_index], avg_normal, clnors);
+ }
+ }
+ }
+ } while ((l_curr = l_curr->next) != l_first);
+ }
+
+ BLI_heap_free(loop_weight, NULL);
+ EDBM_update_generic(em, true, false);
+
+ return OPERATOR_FINISHED;
+}
+
+static bool average_normals_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop)
+{
+ const char *prop_id = RNA_property_identifier(prop);
+ const int average_type = RNA_enum_get(ptr, "average_type");
+
+ /* Only show weight/threshold options in loop average type. */
+ if (STREQ(prop_id, "weight")) {
+ return (average_type == EDBM_CLNOR_AVERAGE_LOOP);
+ }
+ else if (STREQ(prop_id, "threshold")) {
+ return (average_type == EDBM_CLNOR_AVERAGE_LOOP);
+ }
+
+ /* Else, show it! */
+ return true;
+}
+
+static void edbm_average_normals_ui(bContext *C, wmOperator *op)
+{
+ uiLayout *layout = op->layout;
+ wmWindowManager *wm = CTX_wm_manager(C);
+ PointerRNA ptr;
+
+ RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr);
+
+ /* Main auto-draw call */
+ uiDefAutoButsRNA(layout, &ptr, average_normals_draw_check_prop, '\0', false);
+}
+
+void MESH_OT_average_normals(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Average Normals";
+ ot->description = "Average custom normals of selected vertices";
+ ot->idname = "MESH_OT_average_normals";
+
+ /* api callbacks */
+ ot->exec = edbm_average_normals_exec;
+ ot->poll = ED_operator_editmesh_auto_smooth;
+ ot->ui = edbm_average_normals_ui;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ ot->prop = RNA_def_enum(ot->srna, "average_type", average_method_items, EDBM_CLNOR_AVERAGE_LOOP,
+ "Type", "Averaging method");
+ RNA_def_property_flag(ot->prop, PROP_HIDDEN);
+
+ RNA_def_int(ot->srna, "weight", 50, 1, 100, "Weight", "Weight applied per face", 1, 100);
+
+ RNA_def_float(ot->srna, "threshold", 0.01f, 0, 10, "Threshold",
+ "Threshold value for different weights to be considered equal", 0, 5);
+}
+
+/********************** Custom Normal Interface Tools **********************/
+
+enum {
+ EDBM_CLNOR_TOOLS_COPY = 1,
+ EDBM_CLNOR_TOOLS_PASTE = 2,
+ EDBM_CLNOR_TOOLS_MULTIPLY = 3,
+ EDBM_CLNOR_TOOLS_ADD = 4,
+ EDBM_CLNOR_TOOLS_RESET = 5,
+};
+
+static EnumPropertyItem normal_vector_tool_items[] = {
+ {EDBM_CLNOR_TOOLS_COPY, "COPY", 0, "Copy Normal", "Copy normal to buffer"},
+ {EDBM_CLNOR_TOOLS_PASTE, "PASTE", 0, "Paste Normal", "Paste normal from buffer"},
+ {EDBM_CLNOR_TOOLS_ADD, "ADD", 0, "Add Normal", "Add normal vector with selection"},
+ {EDBM_CLNOR_TOOLS_MULTIPLY, "MULTIPLY", 0, "Multiply Normal", "Multiply normal vector with selection"},
+ {EDBM_CLNOR_TOOLS_RESET, "RESET", 0, "Reset Normal", "Reset buffer and/or normal of selected element"},
+ {0, NULL, 0, NULL, NULL}
+};
+
+static int edbm_normals_tools_exec(bContext *C, wmOperator *op)
+{
+ Object *obedit = CTX_data_edit_object(C);
+ Scene *scene = CTX_data_scene(C);
+ BMEditMesh *em = BKE_editmesh_from_object(obedit);
+ BMesh *bm = em->bm;
+
+ const int mode = RNA_enum_get(op->ptr, "mode");
+ const bool absolute = RNA_boolean_get(op->ptr, "absolute");
+
+ BKE_editmesh_lnorspace_update(em);
+ BMLoopNorEditDataArray *lnors_ed_arr = BM_loop_normal_editdata_array_init(bm);
+ BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata;
+
+ float *normal_vector = scene->toolsettings->normal_vector;
+
+ switch (mode) {
+ case EDBM_CLNOR_TOOLS_COPY:
+ if (bm->totfacesel != 1 && lnors_ed_arr->totloop != 1 && bm->totvertsel != 1) {
+ BKE_report(op->reports, RPT_ERROR, "Can only copy custom normal, vertex normal or face normal");
+ BM_loop_normal_editdata_array_free(lnors_ed_arr);
+ return OPERATOR_CANCELLED;
+ }
+ bool join = true;
+ for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) {
+ if (!compare_v3v3(lnors_ed_arr->lnor_editdata->nloc, lnor_ed->nloc, 1e-4f)) {
+ join = false;
+ }
+ }
+ if (lnors_ed_arr->totloop == 1) {
+ copy_v3_v3(scene->toolsettings->normal_vector, lnors_ed_arr->lnor_editdata->nloc);
+ }
+ else if (bm->totfacesel == 1) {
+ BMFace *f;
+ BMIter fiter;
+ BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) {
+ if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
+ copy_v3_v3(scene->toolsettings->normal_vector, f->no);
+ }
+ }
+ }
+ else if (join) {
+ copy_v3_v3(scene->toolsettings->normal_vector, lnors_ed_arr->lnor_editdata->nloc);
+ }
+ break;
+
+ case EDBM_CLNOR_TOOLS_PASTE:
+ if (!absolute) {
+ if (normalize_v3(normal_vector) < CLNORS_VALID_VEC_LEN) {
+ /* If normal is nearly 0, do nothing. */
+ break;
+ }
+ }
+ for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) {
+ if (absolute) {
+ float abs_normal[3];
+ copy_v3_v3(abs_normal, lnor_ed->loc);
+ negate_v3(abs_normal);
+ add_v3_v3(abs_normal, normal_vector);
+
+ if (normalize_v3(abs_normal) < CLNORS_VALID_VEC_LEN) {
+ /* If abs normal is nearly 0, set clnor to initial value. */
+ copy_v3_v3(abs_normal, lnor_ed->niloc);
+ }
+ BKE_lnor_space_custom_normal_to_data(
+ bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], abs_normal, lnor_ed->clnors_data);
+ }
+ else {
+ BKE_lnor_space_custom_normal_to_data(
+ bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], normal_vector, lnor_ed->clnors_data);
+ }
+ }
+ break;
+
+ case EDBM_CLNOR_TOOLS_MULTIPLY:
+ for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) {
+ mul_v3_v3(lnor_ed->nloc, normal_vector);
+
+ if (normalize_v3(lnor_ed->nloc) < CLNORS_VALID_VEC_LEN) {
+ /* If abs normal is nearly 0, set clnor to initial value. */
+ copy_v3_v3(lnor_ed->nloc, lnor_ed->niloc);
+ }
+ BKE_lnor_space_custom_normal_to_data(
+ bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->nloc, lnor_ed->clnors_data);
+ }
+ break;
+
+ case EDBM_CLNOR_TOOLS_ADD:
+ for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) {
+ add_v3_v3(lnor_ed->nloc, normal_vector);
+
+ if (normalize_v3(lnor_ed->nloc) < CLNORS_VALID_VEC_LEN) {
+ /* If abs normal is nearly 0, set clnor to initial value. */
+ copy_v3_v3(lnor_ed->nloc, lnor_ed->niloc);
+ }
+ BKE_lnor_space_custom_normal_to_data(
+ bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->nloc, lnor_ed->clnors_data);
+ }
+ break;
+
+ case EDBM_CLNOR_TOOLS_RESET:
+ zero_v3(normal_vector);
+ for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) {
+ BKE_lnor_space_custom_normal_to_data(
+ bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], normal_vector, lnor_ed->clnors_data);
+ }
+ break;
+
+ default:
+ BLI_assert(0);
+ break;
+ }
+
+ BM_loop_normal_editdata_array_free(lnors_ed_arr);
+
+ EDBM_update_generic(em, true, false);
+ return OPERATOR_FINISHED;
+}
+
+static bool normals_tools_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop)
+{
+ const char *prop_id = RNA_property_identifier(prop);
+ const int mode = RNA_enum_get(ptr, "mode");
+
+ /* Only show absolute option in paste mode. */
+ if (STREQ(prop_id, "absolute")) {
+ return (mode == EDBM_CLNOR_TOOLS_PASTE);
+ }
+
+ /* Else, show it! */
+ return true;
+}
+
+static void edbm_normals_tools_ui(bContext *C, wmOperator *op)
+{
+ uiLayout *layout = op->layout;
+ wmWindowManager *wm = CTX_wm_manager(C);
+ PointerRNA ptr;
+
+ RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr);
+
+ /* Main auto-draw call */
+ uiDefAutoButsRNA(layout, &ptr, normals_tools_draw_check_prop, '\0', false);
+}
+
+void MESH_OT_normals_tools(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Normals Vector Tools";
+ ot->description = "Custom normals tools using Normal Vector of UI";
+ ot->idname = "MESH_OT_normals_tools";
+
+ /* api callbacks */
+ ot->exec = edbm_normals_tools_exec;
+ ot->poll = ED_operator_editmesh_auto_smooth;
+ ot->ui = edbm_normals_tools_ui;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ ot->prop = RNA_def_enum(ot->srna, "mode", normal_vector_tool_items, EDBM_CLNOR_TOOLS_COPY,
+ "Mode", "Mode of tools taking input from Interface");
+ RNA_def_property_flag(ot->prop, PROP_HIDDEN);
+
+ RNA_def_boolean(ot->srna, "absolute", false, "Absolute Coordinates", "Copy Absolute coordinates or Normal vector");
+}
+
+static int edbm_set_normals_from_faces_exec(bContext *C, wmOperator *op)
+{
+ 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, &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;
+ BMFace *f;
+ BMVert *v;
+ BMEdge *e;
+ BMLoop *l;
+ BMIter fiter, viter, eiter, liter;
+
+ const bool keep_sharp = RNA_boolean_get(op->ptr, "keep_sharp");
+
+ BKE_editmesh_lnorspace_update(em);
+
+ float(*vnors)[3] = MEM_callocN(sizeof(*vnors) * bm->totvert, __func__);
+ BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) {
+ if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
+ BM_ITER_ELEM(v, &viter, f, BM_VERTS_OF_FACE) {
+ const int v_index = BM_elem_index_get(v);
+ add_v3_v3(vnors[v_index], f->no);
+ }
+ }
+ }
+ for (int i = 0; i < bm->totvert; i++) {
+ if (!is_zero_v3(vnors[i]) && normalize_v3(vnors[i]) < CLNORS_VALID_VEC_LEN) {
+ zero_v3(vnors[i]);
+ }
+ }
+
+ BLI_bitmap *loop_set = BLI_BITMAP_NEW(bm->totloop, __func__);
+ const int cd_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL);
+
+ BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) {
+ BM_ITER_ELEM(e, &eiter, f, BM_EDGES_OF_FACE) {
+ if (!keep_sharp || (BM_elem_flag_test(e, BM_ELEM_SMOOTH) && BM_elem_flag_test(e, BM_ELEM_SELECT))) {
+ BM_ITER_ELEM(v, &viter, e, BM_VERTS_OF_EDGE) {
+ l = BM_face_vert_share_loop(f, v);
+ const int l_index = BM_elem_index_get(l);
+ const int v_index = BM_elem_index_get(l->v);
+
+ if (!is_zero_v3(vnors[v_index])) {
+ short *clnors = BM_ELEM_CD_GET_VOID_P(l, cd_clnors_offset);
+ BKE_lnor_space_custom_normal_to_data(
+ bm->lnor_spacearr->lspacearr[l_index], vnors[v_index], clnors);
+
+ if (bm->lnor_spacearr->lspacearr[l_index]->flags & MLNOR_SPACE_IS_SINGLE) {
+ BLI_BITMAP_ENABLE(loop_set, l_index);
+ }
+ else {
+ LinkNode *loops = bm->lnor_spacearr->lspacearr[l_index]->loops;
+ for (; loops; loops = loops->next) {
+ BLI_BITMAP_ENABLE(loop_set, BM_elem_index_get((BMLoop *)loops->link));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ int v_index;
+ BM_ITER_MESH_INDEX(v, &viter, bm, BM_VERTS_OF_MESH, v_index) {
+ BM_ITER_ELEM(l, &liter, v, BM_LOOPS_OF_VERT) {
+ if (BLI_BITMAP_TEST(loop_set, BM_elem_index_get(l))) {
+ const int loop_index = BM_elem_index_get(l);
+ short *clnors = BM_ELEM_CD_GET_VOID_P(l, cd_clnors_offset);
+ BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[loop_index], vnors[v_index], clnors);
+ }
+ }
+ }
+
+ MEM_freeN(loop_set);
+ MEM_freeN(vnors);
+ EDBM_update_generic(em, true, false);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+void MESH_OT_set_normals_from_faces(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Set Normals From Faces";
+ ot->description = "Set the custom normals from the selected faces ones";
+ ot->idname = "MESH_OT_set_normals_from_faces";
+
+ /* api callbacks */
+ ot->exec = edbm_set_normals_from_faces_exec;
+ ot->poll = ED_operator_editmesh_auto_smooth;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_boolean(ot->srna, "keep_sharp", 0, "Keep Sharp Edges", "Do not set sharp edges to face");
+}
+
+static int edbm_smoothen_normals_exec(bContext *C, wmOperator *op)
+{
+ Object *obedit = CTX_data_edit_object(C);
+ BMEditMesh *em = BKE_editmesh_from_object(obedit);
+ BMesh *bm = em->bm;
+ BMFace *f;
+ BMLoop *l;
+ BMIter fiter, liter;
+
+ BKE_editmesh_lnorspace_update(em);
+ BMLoopNorEditDataArray *lnors_ed_arr = BM_loop_normal_editdata_array_init(bm);
+
+ float(*smooth_normal)[3] = MEM_callocN(sizeof(*smooth_normal) * lnors_ed_arr->totloop, __func__);
+
+ /* This is weird choice of operation, taking all loops of faces of current vertex... Could lead to some rather
+ * far away loops weighting as much as very close ones (topologically speaking), with complex polygons.
+ * Using topological distance here (rather than geometrical one) makes sense imho, but would rather go with
+ * a more consistent and flexible code, we could even add max topological distance to take into account,
+ * and a weighting curve...
+ * Would do that later though, think for now we can live with that choice. --mont29 */
+ BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata;
+ for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) {
+ l = lnor_ed->loop;
+ float loop_normal[3];
+
+ BM_ITER_ELEM(f, &fiter, l->v, BM_FACES_OF_VERT) {
+ BMLoop *l_other;
+ BM_ITER_ELEM(l_other, &liter, f, BM_LOOPS_OF_FACE) {
+ const int l_index_other = BM_elem_index_get(l_other);
+ short *clnors = BM_ELEM_CD_GET_VOID_P(l_other, lnors_ed_arr->cd_custom_normal_offset);
+ BKE_lnor_space_custom_data_to_normal(bm->lnor_spacearr->lspacearr[l_index_other], clnors, loop_normal);
+ add_v3_v3(smooth_normal[i], loop_normal);
+ }
+ }
+ }
+
+ const float factor = RNA_float_get(op->ptr, "factor");
+
+ lnor_ed = lnors_ed_arr->lnor_editdata;
+ for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) {
+ float current_normal[3];
+
+ if (normalize_v3(smooth_normal[i]) < CLNORS_VALID_VEC_LEN) {
+ /* Skip in case smoothen normal is invalid... */
+ continue;
+ }
+
+ BKE_lnor_space_custom_data_to_normal(
+ bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->clnors_data, current_normal);
+
+ /* Note: again, this is not true spherical interpolation that normals would need...
+ * But it's probably good enough for now. */
+ mul_v3_fl(current_normal, 1.0f - factor);
+ mul_v3_fl(smooth_normal[i], factor);
+ add_v3_v3(current_normal, smooth_normal[i]);
+
+ if (normalize_v3(current_normal) < CLNORS_VALID_VEC_LEN) {
+ /* Skip in case smoothen normal is invalid... */
+ continue;
+ }
+
+ BKE_lnor_space_custom_normal_to_data(
+ bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], current_normal, lnor_ed->clnors_data);
+ }
+
+ BM_loop_normal_editdata_array_free(lnors_ed_arr);
+ MEM_freeN(smooth_normal);
+
+ EDBM_update_generic(em, true, false);
+
+ return OPERATOR_FINISHED;
+}
+
+void MESH_OT_smoothen_normals(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Smoothen Normals";
+ ot->description = "Smoothen custom normals based on adjacent vertex normals";
+ ot->idname = "MESH_OT_smoothen_normals";
+
+ /* api callbacks */
+ ot->exec = edbm_smoothen_normals_exec;
+ ot->poll = ED_operator_editmesh_auto_smooth;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_float(ot->srna, "factor", 0.5f, 0.0f, 1.0f, "Factor",
+ "Specifies weight of smooth vs original normal", 0.0f, 1.0f);
+}
+
+/********************** Weighted Normal Modifier Face Strength **********************/
+
+static int edbm_mod_weighted_strength_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;
+ BMFace *f;
+ BMIter fiter;
+
+ BM_select_history_clear(bm);
+
+ const char *layer_id = MOD_WEIGHTEDNORMALS_FACEWEIGHT_CDLAYER_ID;
+ int cd_prop_int_index = CustomData_get_named_layer_index(&bm->pdata, CD_PROP_INT, layer_id);
+ if (cd_prop_int_index == -1) {
+ BM_data_layer_add_named(bm, &bm->pdata, CD_PROP_INT, layer_id);
+ cd_prop_int_index = CustomData_get_named_layer_index(&bm->pdata, CD_PROP_INT, layer_id);
+ }
+ cd_prop_int_index -= CustomData_get_layer_index(&bm->pdata, CD_PROP_INT);
+ const int cd_prop_int_offset = CustomData_get_n_offset(&bm->pdata, CD_PROP_INT, cd_prop_int_index);
+
+ const int face_strength = scene->toolsettings->face_strength;
+ const bool set = RNA_boolean_get(op->ptr, "set");
+ BM_mesh_elem_index_ensure(bm, BM_FACE);
+
+ if (set) {
+ BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) {
+ if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
+ int *strength = BM_ELEM_CD_GET_VOID_P(f, cd_prop_int_offset);
+ *strength = face_strength;
+ }
+ }
+ }
+ else {
+ BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) {
+ int *strength = BM_ELEM_CD_GET_VOID_P(f, cd_prop_int_offset);
+ if (*strength == face_strength) {
+ BM_face_select_set(bm, f, true);
+ BM_select_history_store(bm, f);
+ }
+ else {
+ BM_face_select_set(bm, f, false);
+ }
+ }
+ }
+
+ EDBM_update_generic(em, false, false);
+ return OPERATOR_FINISHED;
+}
+
+void MESH_OT_mod_weighted_strength(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Face Strength";
+ ot->description = "Set/Get strength of face (used in Weighted Normal modifier)";
+ ot->idname = "MESH_OT_mod_weighted_strength";
+
+ /* api callbacks */
+ ot->exec = edbm_mod_weighted_strength_exec;
+ ot->poll = ED_operator_editmesh_auto_smooth;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ ot->prop = RNA_def_boolean(ot->srna, "set", 0, "Set value", "Set Value of faces");
+ RNA_def_property_flag(ot->prop, PROP_HIDDEN);
+} \ No newline at end of file
diff --git a/source/blender/editors/mesh/editmesh_undo.c b/source/blender/editors/mesh/editmesh_undo.c
index 4d4b7a098b0..6fb7a713ce0 100644
--- a/source/blender/editors/mesh/editmesh_undo.c
+++ b/source/blender/editors/mesh/editmesh_undo.c
@@ -598,6 +598,8 @@ static void undomesh_to_editmesh(UndoMesh *um, BMEditMesh *em, Mesh *obmesh)
bm->selectmode = um->selectmode;
em->ob = ob;
+ bm->spacearr_dirty = BM_SPACEARR_DIRTY_ALL;
+
/* T35170: Restore the active key on the RealMesh. Otherwise 'fake' offset propagation happens
* if the active is a basis for any other. */
if (key && (key->type == KEY_RELATIVE)) {
diff --git a/source/blender/editors/mesh/editmesh_utils.c b/source/blender/editors/mesh/editmesh_utils.c
index ab44e017fc6..16c1a2c45ec 100644
--- a/source/blender/editors/mesh/editmesh_utils.c
+++ b/source/blender/editors/mesh/editmesh_utils.c
@@ -1343,7 +1343,10 @@ void EDBM_update_generic(BMEditMesh *em, const bool do_tessface, const bool is_d
/* in debug mode double check we didn't need to recalculate */
BLI_assert(BM_mesh_elem_table_check(em->bm) == true);
}
-
+ if (em->bm->spacearr_dirty & BM_SPACEARR_BMO_SET) {
+ BM_lnorspace_invalidate(em->bm, false);
+ em->bm->spacearr_dirty &= ~BM_SPACEARR_BMO_SET;
+ }
/* don't keep stale derivedMesh data around, see: [#38872] */
BKE_editmesh_free_derivedmesh(em);
diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h
index b9e9ae9c1fd..e585f407052 100644
--- a/source/blender/editors/mesh/mesh_intern.h
+++ b/source/blender/editors/mesh/mesh_intern.h
@@ -240,6 +240,16 @@ void MESH_OT_duplicate(struct wmOperatorType *ot);
void MESH_OT_merge(struct wmOperatorType *ot);
void MESH_OT_remove_doubles(struct wmOperatorType *ot);
void MESH_OT_poke(struct wmOperatorType *ot);
+void MESH_OT_point_normals(struct wmOperatorType *ot);
+void MESH_OT_merge_normals(struct wmOperatorType *ot);
+void MESH_OT_split_normals(struct wmOperatorType *ot);
+void MESH_OT_normals_tools(struct wmOperatorType *ot);
+void MESH_OT_set_normals_from_faces(struct wmOperatorType *ot);
+void MESH_OT_average_normals(struct wmOperatorType *ot);
+void MESH_OT_smoothen_normals(struct wmOperatorType *ot);
+void MESH_OT_mod_weighted_strength(struct wmOperatorType *ot);
+
+struct wmKeyMap *point_normals_modal_keymap(wmKeyConfig *keyconf);
#ifdef WITH_FREESTYLE
void MESH_OT_mark_freestyle_edge(struct wmOperatorType *ot);
diff --git a/source/blender/editors/mesh/mesh_ops.c b/source/blender/editors/mesh/mesh_ops.c
index 9f3ef6f958d..60200313b8c 100644
--- a/source/blender/editors/mesh/mesh_ops.c
+++ b/source/blender/editors/mesh/mesh_ops.c
@@ -200,6 +200,15 @@ void ED_operatortypes_mesh(void)
WM_operatortype_append(MESH_OT_bisect);
WM_operatortype_append(MESH_OT_symmetrize);
WM_operatortype_append(MESH_OT_symmetry_snap);
+
+ WM_operatortype_append(MESH_OT_point_normals);
+ WM_operatortype_append(MESH_OT_merge_normals);
+ WM_operatortype_append(MESH_OT_split_normals);
+ WM_operatortype_append(MESH_OT_normals_tools);
+ WM_operatortype_append(MESH_OT_set_normals_from_faces);
+ WM_operatortype_append(MESH_OT_average_normals);
+ WM_operatortype_append(MESH_OT_smoothen_normals);
+ WM_operatortype_append(MESH_OT_mod_weighted_strength);
}
#if 0 /* UNUSED, remove? */
@@ -446,6 +455,8 @@ void ED_keymap_mesh(wmKeyConfig *keyconf)
WM_keymap_add_item(keymap, "MESH_OT_split", YKEY, KM_PRESS, 0, 0);
WM_keymap_add_item(keymap, "MESH_OT_vert_connect_path", JKEY, KM_PRESS, 0, 0);
+ WM_keymap_add_item(keymap, "MESH_OT_point_normals", LKEY, KM_PRESS, KM_ALT, 0);
+
/* Vertex Slide */
WM_keymap_add_item(keymap, "TRANSFORM_OT_vert_slide", VKEY, KM_PRESS, KM_SHIFT, 0);
/* use KM_CLICK because same key is used for tweaks */
@@ -489,5 +500,6 @@ void ED_keymap_mesh(wmKeyConfig *keyconf)
ED_keymap_proportional_editmode(keyconf, keymap, true);
knifetool_modal_keymap(keyconf);
+ point_normals_modal_keymap(keyconf);
}
diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c
index 5efbcd8a3f4..9b7f767798f 100644
--- a/source/blender/editors/screen/screen_ops.c
+++ b/source/blender/editors/screen/screen_ops.c
@@ -47,6 +47,7 @@
#include "DNA_gpencil_types.h"
#include "DNA_scene_types.h"
#include "DNA_meta_types.h"
+#include "DNA_mesh_types.h"
#include "DNA_mask_types.h"
#include "DNA_node_types.h"
#include "DNA_workspace_types.h"
@@ -359,6 +360,15 @@ int ED_operator_editmesh_region_view3d(bContext *C)
return 0;
}
+int ED_operator_editmesh_auto_smooth(bContext *C)
+{
+ Object *obedit = CTX_data_edit_object(C);
+ if (obedit && obedit->type == OB_MESH && (((Mesh *)(obedit->data))->flag & ME_AUTOSMOOTH)) {
+ return NULL != BKE_editmesh_from_object(obedit);
+ }
+ return 0;
+}
+
int ED_operator_editarmature(bContext *C)
{
Object *obedit = CTX_data_edit_object(C);
diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c
index f4a8d7af28b..172669ed122 100644
--- a/source/blender/editors/space_outliner/outliner_draw.c
+++ b/source/blender/editors/space_outliner/outliner_draw.c
@@ -1006,6 +1006,7 @@ static void tselem_draw_icon(uiBlock *block, int xmax, float x, float y, TreeSto
ICON_DRAW(ICON_MOD_DATA_TRANSFER);
break;
case eModifierType_NormalEdit:
+ case eModifierType_WeightedNormal:
ICON_DRAW(ICON_MOD_NORMALEDIT);
break;
/* Default */
diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c
index a4d08b15a6d..b713f18da4c 100644
--- a/source/blender/editors/transform/transform.c
+++ b/source/blender/editors/transform/transform.c
@@ -41,6 +41,7 @@
#include "DNA_armature_types.h"
#include "DNA_constraint_types.h"
#include "DNA_mask_types.h"
+#include "DNA_mesh_types.h"
#include "DNA_movieclip_types.h"
#include "DNA_scene_types.h" /* PET modes */
#include "DNA_workspace_types.h"
@@ -64,6 +65,7 @@
#include "BKE_unit.h"
#include "BKE_scene.h"
#include "BKE_mask.h"
+#include "BKE_mesh.h"
#include "BKE_report.h"
#include "BKE_workspace.h"
@@ -94,6 +96,7 @@
#include "UI_resources.h"
#include "RNA_access.h"
+#include "RNA_define.h"
#include "BLF_api.h"
#include "BLT_translation.h"
@@ -114,6 +117,7 @@ static void postInputRotation(TransInfo *t, float values[3]);
static void ElementRotation(TransInfo *t, TransDataContainer *tc, TransData *td, float mat[3][3], const short around);
static void initSnapSpatial(TransInfo *t, float r_snap[3]);
+static void storeCustomLNorValue(TransDataContainer *t, BMesh *bm);
/* Transform Callbacks */
static void initBend(TransInfo *t);
@@ -139,6 +143,9 @@ static void applyToSphere(TransInfo *t, const int mval[2]);
static void initRotation(TransInfo *t);
static void applyRotation(TransInfo *t, const int mval[2]);
+static void initNormalRotation(TransInfo *t);
+static void applyNormalRotation(TransInfo *t, const int mval[2]);
+
static void initShrinkFatten(TransInfo *t);
static void applyShrinkFatten(TransInfo *t, const int mval[2]);
@@ -1481,6 +1488,18 @@ int transformEvent(TransInfo *t, const wmEvent *event)
handled = true;
}
break;
+ case NKEY:
+ if (ELEM(t->mode, TFM_ROTATION)) {
+ if ((t->flag & T_EDIT) && t->obedit_type == OB_MESH) {
+ restoreTransObjects(t);
+ resetTransModal(t);
+ resetTransRestrictions(t);
+ initNormalRotation(t);
+ t->redraw = TREDRAW_HARD;
+ handled = true;
+ }
+ }
+ break;
default:
break;
}
@@ -2436,6 +2455,9 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
case TFM_SEQ_SLIDE:
initSeqSlide(t);
break;
+ case TFM_NORMAL_ROTATION:
+ initNormalRotation(t);
+ break;
}
if (t->state == TRANS_CANCEL) {
@@ -2443,6 +2465,39 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
return 0;
}
+ if ((prop = RNA_struct_find_property(op->ptr, "preserve_clnor"))) {
+ if ((t->flag & T_EDIT) && t->obedit_type == OB_MESH) {
+
+ FOREACH_TRANS_DATA_CONTAINER(t, tc) {
+ if ((((Mesh *)(tc->obedit->data))->flag & ME_AUTOSMOOTH)) {
+ BMEditMesh *em = NULL;// BKE_editmesh_from_object(t->obedit);
+ bool do_skip = false;
+
+ /* Currently only used for two of three most frequent transform ops, can include more ops.
+ * Note that scaling cannot be included here, non-uniform scaling will affect normals. */
+ if (ELEM(t->mode, TFM_TRANSLATION, TFM_ROTATION)) {
+ if (em->bm->totvertsel == em->bm->totvert) {
+ /* No need to invalidate if whole mesh is selected. */
+ do_skip = true;
+ }
+ }
+
+ if (t->flag & T_MODAL) {
+ RNA_property_boolean_set(op->ptr, prop, false);
+ }
+ else if (!do_skip) {
+ const bool preserve_clnor = RNA_property_boolean_get(op->ptr, prop);
+ if (preserve_clnor) {
+ BKE_editmesh_lnorspace_update(em);
+ t->flag |= T_CLNOR_REBUILD;
+ }
+ BM_lnorspace_invalidate(em->bm, true);
+ }
+ }
+ }
+ }
+ }
+
t->context = NULL;
return 1;
@@ -2503,6 +2558,12 @@ int transformEnd(bContext *C, TransInfo *t)
restoreTransObjects(t); // calls recalcData()
}
else {
+ if (t->flag & T_CLNOR_REBUILD) {
+ FOREACH_TRANS_DATA_CONTAINER(t, tc) {
+ BMEditMesh *em = BKE_editmesh_from_object(tc->obedit);
+ BM_lnorspace_rebuild(em->bm, true);
+ }
+ }
exit_code = OPERATOR_FINISHED;
}
@@ -3821,6 +3882,28 @@ static void initRotation(TransInfo *t)
copy_v3_v3(t->axis_orig, t->axis);
}
+/* Used by Transform Rotation and Transform Normal Rotation */
+static void headerRotation(TransInfo *t, char str[UI_MAX_DRAW_STR], float final)
+{
+ size_t ofs = 0;
+
+ if (hasNumInput(&t->num)) {
+ char c[NUM_STR_REP_LEN];
+
+ outputNumInput(&(t->num), c, &t->scene->unit);
+
+ ofs += BLI_snprintf(str + ofs, UI_MAX_DRAW_STR - ofs, IFACE_("Rot: %s %s %s"), &c[0], t->con.text, t->proptext);
+ }
+ else {
+ ofs += BLI_snprintf(str + ofs, UI_MAX_DRAW_STR - ofs, IFACE_("Rot: %.2f%s %s"),
+ RAD2DEGF(final), t->con.text, t->proptext);
+ }
+
+ if (t->flag & T_PROP_EDIT_ALL) {
+ ofs += BLI_snprintf(str + ofs, UI_MAX_DRAW_STR - ofs, IFACE_(" Proportional size: %.2f"), t->prop_size);
+ }
+}
+
/**
* Applies values of rotation to `td->loc` and `td->ext->quat`
* based on a rotation matrix (mat) and a pivot (center).
@@ -4107,21 +4190,7 @@ static void applyRotation(TransInfo *t, const int UNUSED(mval[2]))
t->values[0] = final;
- if (hasNumInput(&t->num)) {
- char c[NUM_STR_REP_LEN];
-
- outputNumInput(&(t->num), c, &t->scene->unit);
-
- ofs += BLI_snprintf(str + ofs, sizeof(str) - ofs, IFACE_("Rot: %s %s %s"), &c[0], t->con.text, t->proptext);
- }
- else {
- ofs += BLI_snprintf(str + ofs, sizeof(str) - ofs, IFACE_("Rot: %.2f%s %s"),
- RAD2DEGF(final), t->con.text, t->proptext);
- }
-
- if (t->flag & T_PROP_EDIT_ALL) {
- ofs += BLI_snprintf(str + ofs, sizeof(str) - ofs, IFACE_(" Proportional size: %.2f"), t->prop_size);
- }
+ headerRotation(t, str, final);
applyRotationValue(t, final, t->axis);
@@ -4250,6 +4319,127 @@ static void applyTrackball(TransInfo *t, const int UNUSED(mval[2]))
/* -------------------------------------------------------------------- */
+/* Transform (Normal Rotation) */
+
+/** \name Transform Normal Rotation
+* \{ */
+
+static void storeCustomLNorValue(TransDataContainer *tc, BMesh *bm)
+{
+ BMLoopNorEditDataArray *lnors_ed_arr = BM_loop_normal_editdata_array_init(bm);
+ BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata;
+
+ tc->custom.mode.data = lnors_ed_arr;
+ tc->custom.mode.free_cb = freeCustomNormalArray;
+}
+
+void freeCustomNormalArray(TransInfo *t, TransDataContainer *tc, TransCustomData *custom_data)
+{
+ BMLoopNorEditDataArray *lnors_ed_arr = custom_data->data;
+
+ if (t->state == TRANS_CANCEL) {
+ BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata;
+ BMEditMesh *em = BKE_editmesh_from_object(tc->obedit);
+ BMesh *bm = em->bm;
+
+ for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { /* Restore custom loop normal on cancel */
+ BKE_lnor_space_custom_normal_to_data(
+ bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->niloc, lnor_ed->clnors_data);
+ }
+ }
+
+ BM_loop_normal_editdata_array_free(lnors_ed_arr);
+
+ tc->custom.mode.data = NULL;
+ tc->custom.mode.free_cb = NULL;
+}
+
+static void initNormalRotation(TransInfo *t)
+{
+ t->mode = TFM_NORMAL_ROTATION;
+ t->transform = applyNormalRotation;
+
+ setInputPostFct(&t->mouse, postInputRotation);
+ initMouseInputMode(t, &t->mouse, INPUT_ANGLE);
+
+ t->idx_max = 0;
+ t->num.idx_max = 0;
+ t->snap[0] = 0.0f;
+ t->snap[1] = DEG2RAD(5.0);
+ t->snap[2] = DEG2RAD(1.0);
+
+ copy_v3_fl(t->num.val_inc, t->snap[2]);
+ t->num.unit_sys = t->scene->unit.system;
+ t->num.unit_use_radians = (t->scene->unit.system_rotation == USER_UNIT_ROT_RADIANS);
+ t->num.unit_type[0] = B_UNIT_ROTATION;
+
+ FOREACH_TRANS_DATA_CONTAINER(t, tc) {
+ BMEditMesh *em = BKE_editmesh_from_object(tc->obedit);
+ BMesh *bm = em->bm;
+
+ BKE_editmesh_lnorspace_update(em);
+
+ storeCustomLNorValue(tc, bm);
+ }
+
+ negate_v3_v3(t->axis, t->viewinv[2]);
+ normalize_v3(t->axis);
+
+ copy_v3_v3(t->axis_orig, t->axis);
+}
+
+/* Works by getting custom normal from clnor_data, transform, then store */
+static void applyNormalRotation(TransInfo *t, const int UNUSED(mval[2]))
+{
+ char str[UI_MAX_DRAW_STR];
+
+ if ((t->con.mode & CON_APPLY) && t->con.applyRot) {
+ t->con.applyRot(t, NULL, NULL, t->axis, NULL);
+ }
+ else {
+ /* reset axis if constraint is not set */
+ copy_v3_v3(t->axis, t->axis_orig);
+ }
+
+ FOREACH_TRANS_DATA_CONTAINER(t, tc) {
+ BMEditMesh *em = BKE_editmesh_from_object(tc->obedit);
+ BMesh *bm = em->bm;
+
+ BMLoopNorEditDataArray *lnors_ed_arr = tc->custom.mode.data;
+ BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata;
+
+ float axis[3];
+ float mat[3][3];
+ float angle = t->values[0];
+ copy_v3_v3(axis, t->axis);
+
+ snapGridIncrement(t, &angle);
+
+ applySnapping(t, &angle);
+
+ applyNumInput(&t->num, &angle);
+
+ headerRotation(t, str, angle);
+
+ axis_angle_normalized_to_mat3(mat, axis, angle);
+
+ for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) {
+ mul_v3_m3v3(lnor_ed->nloc, mat, lnor_ed->niloc);
+
+ BKE_lnor_space_custom_normal_to_data(
+ bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->nloc, lnor_ed->clnors_data);
+ }
+ }
+
+ recalcData(t);
+
+ ED_area_headerprint(t->sa, str);
+}
+
+/** \} */
+
+
+/* -------------------------------------------------------------------- */
/* Transform (Translation) */
static void initSnapSpatial(TransInfo *t, float r_snap[3])
diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h
index b9f42a0d9fc..3718f1be9a8 100644
--- a/source/blender/editors/transform/transform.h
+++ b/source/blender/editors/transform/transform.h
@@ -605,6 +605,8 @@ typedef struct TransInfo {
#define T_MODAL_CURSOR_SET (1 << 26)
+#define T_CLNOR_REBUILD (1 << 27)
+
/* TransInfo->modifiers */
#define MOD_CONSTRAINT_SELECT 0x01
#define MOD_PRECISION 0x02
@@ -873,6 +875,8 @@ bool applyTransformOrientation(const struct TransformOrientation *ts, float r_ma
int getTransformOrientation_ex(const struct bContext *C, float normal[3], float plane[3], const short around);
int getTransformOrientation(const struct bContext *C, float normal[3], float plane[3]);
+void freeCustomNormalArray(TransInfo *t, TransCustomData *custom_data);
+
void freeEdgeSlideTempFaces(EdgeSlideData *sld);
void freeEdgeSlideVerts(TransInfo *t, TransDataContainer *tc, TransCustomData *custom_data);
void projectEdgeSlideData(TransInfo *t, bool is_final);
diff --git a/source/blender/editors/transform/transform_ops.c b/source/blender/editors/transform/transform_ops.c
index 73dd0392a25..a1b78403fc4 100644
--- a/source/blender/editors/transform/transform_ops.c
+++ b/source/blender/editors/transform/transform_ops.c
@@ -81,6 +81,7 @@ static const char OP_VERT_SLIDE[] = "TRANSFORM_OT_vert_slide";
static const char OP_EDGE_CREASE[] = "TRANSFORM_OT_edge_crease";
static const char OP_EDGE_BWEIGHT[] = "TRANSFORM_OT_edge_bevelweight";
static const char OP_SEQ_SLIDE[] = "TRANSFORM_OT_seq_slide";
+static const char OP_NORMAL_ROTATION[] = "TRANSFORM_OT_rotate_normal";
static void TRANSFORM_OT_translate(struct wmOperatorType *ot);
static void TRANSFORM_OT_rotate(struct wmOperatorType *ot);
@@ -99,6 +100,7 @@ static void TRANSFORM_OT_vert_slide(struct wmOperatorType *ot);
static void TRANSFORM_OT_edge_crease(struct wmOperatorType *ot);
static void TRANSFORM_OT_edge_bevelweight(struct wmOperatorType *ot);
static void TRANSFORM_OT_seq_slide(struct wmOperatorType *ot);
+static void TRANSFORM_OT_rotate_normal(struct wmOperatorType *ot);
static TransformModeItem transform_modes[] =
{
@@ -119,6 +121,7 @@ static TransformModeItem transform_modes[] =
{OP_EDGE_CREASE, TFM_CREASE, TRANSFORM_OT_edge_crease},
{OP_EDGE_BWEIGHT, TFM_BWEIGHT, TRANSFORM_OT_edge_bevelweight},
{OP_SEQ_SLIDE, TFM_SEQ_SLIDE, TRANSFORM_OT_seq_slide},
+ {OP_NORMAL_ROTATION, TFM_NORMAL_ROTATION, TRANSFORM_OT_rotate_normal},
{NULL, 0}
};
@@ -1027,6 +1030,27 @@ static void TRANSFORM_OT_seq_slide(struct wmOperatorType *ot)
Transform_Properties(ot, P_SNAP);
}
+static void TRANSFORM_OT_rotate_normal(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Normal Rotate";
+ ot->description = "Rotate split normal of selected items";
+ ot->idname = OP_NORMAL_ROTATION;
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
+
+ /* api callbacks */
+ ot->invoke = transform_invoke;
+ ot->exec = transform_exec;
+ ot->modal = transform_modal;
+ ot->cancel = transform_cancel;
+ ot->poll = ED_operator_editmesh_auto_smooth;
+
+ RNA_def_float_rotation(ot->srna, "value", 0, NULL, -FLT_MAX, FLT_MAX, "Angle", "", -M_PI * 2, M_PI * 2);
+
+ Transform_Properties(ot, P_AXIS | P_CONSTRAINT | P_MIRROR);
+}
+
+
static void TRANSFORM_OT_transform(struct wmOperatorType *ot)
{
PropertyRNA *prop;
diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h
index 93ab7a035bc..1d7d1b34f6b 100644
--- a/source/blender/makesdna/DNA_modifier_types.h
+++ b/source/blender/makesdna/DNA_modifier_types.h
@@ -89,6 +89,7 @@ typedef enum ModifierType {
eModifierType_CorrectiveSmooth = 51,
eModifierType_MeshSequenceCache = 52,
eModifierType_SurfaceDeform = 53,
+ eModifierType_WeightedNormal = 54,
NUM_MODIFIER_TYPES
} ModifierType;
@@ -1639,6 +1640,32 @@ enum {
MOD_SDEF_MODE_CENTROID = 2,
};
+typedef struct WeightedNormalModifierData {
+ ModifierData modifier;
+
+ char defgrp_name[64]; /* MAX_VGROUP_NAME */
+ char mode, flag;
+ short weight;
+ float thresh;
+} WeightedNormalModifierData;
+
+/* Name/id of the generic PROP_INT cdlayer storing face weights. */
+#define MOD_WEIGHTEDNORMALS_FACEWEIGHT_CDLAYER_ID "__mod_weightednormals_faceweight"
+
+/* WeightedNormalModifierData.mode */
+enum {
+ MOD_WEIGHTEDNORMAL_MODE_FACE = 0,
+ MOD_WEIGHTEDNORMAL_MODE_ANGLE = 1,
+ MOD_WEIGHTEDNORMAL_MODE_FACE_ANGLE = 2,
+};
+
+/* WeightedNormalModifierData.flag */
+enum {
+ MOD_WEIGHTEDNORMAL_KEEP_SHARP = (1 << 0),
+ MOD_WEIGHTEDNORMAL_INVERT_VGROUP = (1 << 1),
+ MOD_WEIGHTEDNORMAL_FACE_INFLUENCE = (1 << 2),
+};
+
#define MOD_MESHSEQ_READ_ALL \
(MOD_MESHSEQ_READ_VERT | MOD_MESHSEQ_READ_POLY | MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)
diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h
index cbd863f706f..0559df7950d 100644
--- a/source/blender/makesdna/DNA_scene_types.h
+++ b/source/blender/makesdna/DNA_scene_types.h
@@ -1309,6 +1309,10 @@ typedef struct ToolSettings {
struct CurvePaintSettings curve_paint_settings;
struct MeshStatVis statvis;
+
+ /* Normal Editing */
+ float normal_vector[3];
+ int face_strength;
} ToolSettings;
/* *************************************************************** */
@@ -1837,6 +1841,13 @@ enum {
OB_DRAW_GROUPUSER_ALL = 2
};
+/* toolsettings->face_strength */
+enum {
+ FACE_STRENGTH_WEAK = -16384,
+ FACE_STRENGTH_MEDIUM = 0,
+ FACE_STRENGTH_STRONG = 16384,
+};
+
/* object_vgroup.c */
/* ToolSettings.vgroupsubset */
typedef enum eVGroupSelect {
diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h
index 1cd907aeb91..7431f48367c 100644
--- a/source/blender/makesrna/RNA_access.h
+++ b/source/blender/makesrna/RNA_access.h
@@ -692,6 +692,7 @@ extern StructRNA RNA_WaveModifier;
extern StructRNA RNA_VertexWeightEditModifier;
extern StructRNA RNA_VertexWeightMixModifier;
extern StructRNA RNA_VertexWeightProximityModifier;
+extern StructRNA RNA_WeightedNormalModifier;
extern StructRNA RNA_View3DOverlay;
extern StructRNA RNA_View3DShading;
extern StructRNA RNA_ViewLayer;
diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c
index b1716f6a176..c665442a82f 100644
--- a/source/blender/makesrna/intern/rna_modifier.c
+++ b/source/blender/makesrna/intern/rna_modifier.c
@@ -68,6 +68,7 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = {
{eModifierType_MeshCache, "MESH_CACHE", ICON_MOD_MESHDEFORM, "Mesh Cache", ""},
{eModifierType_MeshSequenceCache, "MESH_SEQUENCE_CACHE", ICON_MOD_MESHDEFORM, "Mesh Sequence Cache", ""},
{eModifierType_NormalEdit, "NORMAL_EDIT", ICON_MOD_NORMALEDIT, "Normal Edit", ""},
+ {eModifierType_WeightedNormal, "WEIGHTED_NORMAL", ICON_MOD_NORMALEDIT, "Weighted Normal", ""},
{eModifierType_UVProject, "UV_PROJECT", ICON_MOD_UVPROJECT, "UV Project", ""},
{eModifierType_UVWarp, "UV_WARP", ICON_MOD_UVPROJECT, "UV Warp", ""},
{eModifierType_WeightVGEdit, "VERTEX_WEIGHT_EDIT", ICON_MOD_VERTEX_WEIGHT, "Vertex Weight Edit", ""},
@@ -413,6 +414,8 @@ static StructRNA *rna_Modifier_refine(struct PointerRNA *ptr)
return &RNA_MeshSequenceCacheModifier;
case eModifierType_SurfaceDeform:
return &RNA_SurfaceDeformModifier;
+ case eModifierType_WeightedNormal:
+ return &RNA_WeightedNormalModifier;
/* Default */
case eModifierType_None:
case eModifierType_ShapeKey:
@@ -503,6 +506,7 @@ RNA_MOD_VGROUP_NAME_SET(WeightVGMix, defgrp_name_b);
RNA_MOD_VGROUP_NAME_SET(WeightVGMix, mask_defgrp_name);
RNA_MOD_VGROUP_NAME_SET(WeightVGProximity, defgrp_name);
RNA_MOD_VGROUP_NAME_SET(WeightVGProximity, mask_defgrp_name);
+RNA_MOD_VGROUP_NAME_SET(WeightedNormal, defgrp_name);
RNA_MOD_VGROUP_NAME_SET(Wireframe, defgrp_name);
static void rna_ExplodeModifier_vgroup_get(PointerRNA *ptr, char *value)
@@ -4892,6 +4896,68 @@ static void rna_def_modifier_surfacedeform(BlenderRNA *brna)
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
}
+static void rna_def_modifier_weightednormal(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ static EnumPropertyItem prop_weighting_mode_items[] = {
+ {MOD_WEIGHTEDNORMAL_MODE_FACE, "FACE_AREA", 0, "Face Area", "Generate face area weighted normals"},
+ {MOD_WEIGHTEDNORMAL_MODE_ANGLE, "CORNER_ANGLE", 0, "Corner Angle", "Generate corner angle weighted normals"},
+ {MOD_WEIGHTEDNORMAL_MODE_FACE_ANGLE, "FACE_AREA_WITH_ANGLE", 0, "Face Area And Angle",
+ "Generated normals weighted by both face area and angle"},
+ {0, NULL, 0, NULL, NULL}
+ };
+
+ srna = RNA_def_struct(brna, "WeightedNormalModifier", "Modifier");
+ RNA_def_struct_ui_text(srna, "WeightedNormal Modifier", "");
+ RNA_def_struct_sdna(srna, "WeightedNormalModifierData");
+ RNA_def_struct_ui_icon(srna, ICON_MOD_NORMALEDIT);
+
+ prop = RNA_def_property(srna, "weight", PROP_INT, PROP_NONE);
+ RNA_def_property_range(prop, 1, 100);
+ RNA_def_property_ui_range(prop, 1, 100, 1, -1);
+ RNA_def_property_ui_text(prop, "Weight",
+ "Corrective factor applied to faces' weights, 50 is neutral, "
+ "lower values increase weight of weak faces, "
+ "higher values increase weight of strong faces");
+ RNA_def_property_update(prop, 0, "rna_Modifier_update");
+
+ prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, prop_weighting_mode_items);
+ RNA_def_property_ui_text(prop, "Weighting Mode", "Weighted vertex normal mode to use");
+ RNA_def_property_update(prop, 0, "rna_Modifier_update");
+
+ prop = RNA_def_property(srna, "thresh", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_range(prop, 0, 10);
+ RNA_def_property_ui_range(prop, 0, 10, 1, 2);
+ RNA_def_property_ui_text(prop, "Threshold", "Threshold value for different weights to be considered equal");
+ RNA_def_property_update(prop, 0, "rna_Modifier_update");
+
+ prop = RNA_def_property(srna, "keep_sharp", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", MOD_WEIGHTEDNORMAL_KEEP_SHARP);
+ RNA_def_property_ui_text(prop, "Keep Sharp",
+ "Keep sharp edges as computed for default split normals, "
+ "instead of setting a single weighted normal for each vertex.");
+ RNA_def_property_update(prop, 0, "rna_Modifier_update");
+
+ prop = RNA_def_property(srna, "vertex_group", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_sdna(prop, NULL, "defgrp_name");
+ RNA_def_property_ui_text(prop, "Vertex Group", "Vertex group name for modifying the selected areas");
+ RNA_def_property_string_funcs(prop, NULL, NULL, "rna_WeightedNormalModifier_defgrp_name_set");
+ RNA_def_property_update(prop, 0, "rna_Modifier_update");
+
+ prop = RNA_def_property(srna, "invert_vertex_group", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", MOD_WEIGHTEDNORMAL_INVERT_VGROUP);
+ RNA_def_property_ui_text(prop, "Invert", "Invert vertex group influence");
+ RNA_def_property_update(prop, 0, "rna_Modifier_update");
+
+ prop = RNA_def_property(srna, "face_influence", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", MOD_WEIGHTEDNORMAL_FACE_INFLUENCE);
+ RNA_def_property_ui_text(prop, "Face Influence", "Use influence of face for weighting");
+ RNA_def_property_update(prop, 0, "rna_Modifier_update");
+}
+
void RNA_def_modifier(BlenderRNA *brna)
{
StructRNA *srna;
@@ -5012,6 +5078,7 @@ void RNA_def_modifier(BlenderRNA *brna)
rna_def_modifier_normaledit(brna);
rna_def_modifier_meshseqcache(brna);
rna_def_modifier_surfacedeform(brna);
+ rna_def_modifier_weightednormal(brna);
}
#endif
diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c
index d60c9b2d99b..4599ec0f9c5 100644
--- a/source/blender/makesrna/intern/rna_scene.c
+++ b/source/blender/makesrna/intern/rna_scene.c
@@ -2415,6 +2415,13 @@ static void rna_def_tool_settings(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL}
};
+ static EnumPropertyItem mod_weighted_strength[] = {
+ {FACE_STRENGTH_WEAK, "Weak", 0, "Weak", ""},
+ {FACE_STRENGTH_MEDIUM, "Medium", 0, "Medium", ""},
+ {FACE_STRENGTH_STRONG, "Strong", 0, "Strong", ""},
+ {0, NULL, 0, NULL, NULL},
+ };
+
static const EnumPropertyItem draw_groupuser_items[] = {
{OB_DRAW_GROUPUSER_NONE, "NONE", 0, "None", ""},
{OB_DRAW_GROUPUSER_ACTIVE, "ACTIVE", 0, "Active", "Show vertices with no weights in the active group"},
@@ -2815,6 +2822,14 @@ static void rna_def_tool_settings(BlenderRNA *brna)
RNA_def_property_boolean_sdna(prop, NULL, "edge_mode_live_unwrap", 1);
RNA_def_property_ui_text(prop, "Live Unwrap", "Changing edges seam re-calculates UV unwrap");
+ prop = RNA_def_property(srna, "normal_vector", PROP_FLOAT, PROP_XYZ);
+ RNA_def_property_ui_text(prop, "Normal Vector", "Normal Vector used to copy, add or multiply");
+ RNA_def_property_ui_range(prop, -10000.0, 10000.0, 1, 3);
+
+ prop = RNA_def_property(srna, "face_strength", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, mod_weighted_strength);
+ RNA_def_property_ui_text(prop, "Face Strength", "Set strength of face to specified value");
+
/* Unified Paint Settings */
prop = RNA_def_property(srna, "unified_paint_settings", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_NEVER_NULL);
diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt
index 397a3263e22..0a619ecf9c7 100644
--- a/source/blender/modifiers/CMakeLists.txt
+++ b/source/blender/modifiers/CMakeLists.txt
@@ -100,6 +100,7 @@ set(SRC
intern/MOD_uvproject.c
intern/MOD_warp.c
intern/MOD_wave.c
+ intern/MOD_weighted_normal.c
intern/MOD_weightvg_util.c
intern/MOD_weightvgedit.c
intern/MOD_weightvgmix.c
diff --git a/source/blender/modifiers/MOD_modifiertypes.h b/source/blender/modifiers/MOD_modifiertypes.h
index bf121af2bd1..3511b0edbec 100644
--- a/source/blender/modifiers/MOD_modifiertypes.h
+++ b/source/blender/modifiers/MOD_modifiertypes.h
@@ -86,6 +86,7 @@ extern ModifierTypeInfo modifierType_NormalEdit;
extern ModifierTypeInfo modifierType_CorrectiveSmooth;
extern ModifierTypeInfo modifierType_MeshSequenceCache;
extern ModifierTypeInfo modifierType_SurfaceDeform;
+extern ModifierTypeInfo modifierType_WeightedNormal;
/* MOD_util.c */
void modifier_type_init(ModifierTypeInfo *types[]);
diff --git a/source/blender/modifiers/intern/MOD_util.c b/source/blender/modifiers/intern/MOD_util.c
index 4a62d17fa27..5d740ae9e20 100644
--- a/source/blender/modifiers/intern/MOD_util.c
+++ b/source/blender/modifiers/intern/MOD_util.c
@@ -444,5 +444,6 @@ void modifier_type_init(ModifierTypeInfo *types[])
INIT_TYPE(CorrectiveSmooth);
INIT_TYPE(MeshSequenceCache);
INIT_TYPE(SurfaceDeform);
+ INIT_TYPE(WeightedNormal);
#undef INIT_TYPE
}
diff --git a/source/blender/modifiers/intern/MOD_weighted_normal.c b/source/blender/modifiers/intern/MOD_weighted_normal.c
new file mode 100644
index 00000000000..b43ef698cc4
--- /dev/null
+++ b/source/blender/modifiers/intern/MOD_weighted_normal.c
@@ -0,0 +1,670 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * 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.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ *
+ */
+
+/** \file blender/modifiers/intern/MOD_weighted_normal.c
+ * \ingroup modifiers
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_cdderivedmesh.h"
+#include "BKE_deform.h"
+#include "BKE_library.h"
+#include "BKE_library_query.h"
+#include "BKE_mesh.h"
+
+#include "BLI_math.h"
+#include "BLI_linklist.h"
+
+#include "MOD_modifiertypes.h"
+#include "MOD_util.h"
+
+#define CLNORS_VALID_VEC_LEN (1e-6f)
+
+typedef struct ModePair {
+ float val; /* Contains mode based value (face area / corner angle). */
+ int index; /* Index value per poly or per loop. */
+} ModePair;
+
+/* Sorting function used in modifier, sorts in decreasing order. */
+static int modepair_cmp_by_val_inverse(const void *p1, const void *p2)
+{
+ ModePair *r1 = (ModePair *)p1;
+ ModePair *r2 = (ModePair *)p2;
+
+ return (r1->val < r2->val) ? 1 : ((r1->val > r2->val) ? -1 : 0);
+}
+
+/* There will be one of those per vertex (simple case, computing one normal per vertex), or per smooth fan. */
+typedef struct WeightedNormalDataAggregateItem {
+ float normal[3];
+
+ int num_loops; /* Count number of loops using this item so far. */
+ float curr_val; /* Current max val for this item. */
+ int curr_strength; /* Current max strength encountered for this item. */
+} WeightedNormalDataAggregateItem;
+
+#define NUM_CACHED_INVERSE_POWERS_OF_WEIGHT 128
+
+typedef struct WeightedNormalData {
+ const int numVerts;
+ const int numEdges;
+ const int numLoops;
+ const int numPolys;
+
+ MVert *mvert;
+ MEdge *medge;
+
+ MLoop *mloop;
+ short (*clnors)[2];
+ const bool has_clnors; /* True if clnors already existed, false if we had to create them. */
+ const float split_angle;
+
+ MPoly *mpoly;
+ float (*polynors)[3];
+ int *poly_strength;
+
+ MDeformVert *dvert;
+ const int defgrp_index;
+ const bool use_invert_vgroup;
+
+ const float weight;
+ const short mode;
+
+ /* Lower-level, internal processing data. */
+ float cached_inverse_powers_of_weight[NUM_CACHED_INVERSE_POWERS_OF_WEIGHT];
+
+ WeightedNormalDataAggregateItem *items_data;
+
+ ModePair *mode_pair;
+
+ int *loop_to_poly;
+} WeightedNormalData;
+
+/* Check strength of given poly compared to those found so far for that given item (vertex or smooth fan),
+ * and reset matching item_data in case we get a stronger new strength. */
+static bool check_item_poly_strength(
+ WeightedNormalData *wn_data, WeightedNormalDataAggregateItem *item_data, const int mp_index)
+{
+ BLI_assert (wn_data->poly_strength != NULL);
+
+ const int mp_strength = wn_data->poly_strength[mp_index];
+
+ if (mp_strength > item_data->curr_strength) {
+ item_data->curr_strength = mp_strength;
+ item_data->curr_val = 0.0f;
+ item_data->num_loops = 0;
+ zero_v3(item_data->normal);
+ }
+
+ return mp_strength == item_data->curr_strength;
+}
+
+static void aggregate_item_normal(
+ WeightedNormalModifierData *wnmd, WeightedNormalData *wn_data,
+ WeightedNormalDataAggregateItem *item_data,
+ const int mv_index, const int mp_index,
+ const float curr_val, const bool use_face_influence)
+{
+ float (*polynors)[3] = wn_data->polynors;
+
+ MDeformVert *dvert = wn_data->dvert;
+ const int defgrp_index = wn_data->defgrp_index;
+ const bool use_invert_vgroup = wn_data->use_invert_vgroup;
+
+ const float weight = wn_data->weight;
+
+ float *cached_inverse_powers_of_weight = wn_data->cached_inverse_powers_of_weight;
+
+ const bool has_vgroup = dvert != NULL;
+ const bool vert_of_group = has_vgroup && defvert_find_index(&dvert[mv_index], defgrp_index) != NULL;
+
+ if (has_vgroup && ((vert_of_group && use_invert_vgroup) || (!vert_of_group && !use_invert_vgroup))) {
+ return;
+ }
+
+ if (use_face_influence && !check_item_poly_strength(wn_data, item_data, mp_index)) {
+ return;
+ }
+
+ /* If item's curr_val is 0 init it to present value. */
+ if (item_data->curr_val == 0.0f) {
+ item_data->curr_val = curr_val;
+ }
+ if (!compare_ff(item_data->curr_val, curr_val, wnmd->thresh)) {
+ /* item's curr_val and present value differ more than threshold, update. */
+ item_data->num_loops++;
+ item_data->curr_val = curr_val;
+ }
+
+ /* Exponentially divided weight for each normal (since a few values will be used by most cases, we cache those). */
+ const int num_loops = item_data->num_loops;
+ if (num_loops < NUM_CACHED_INVERSE_POWERS_OF_WEIGHT && cached_inverse_powers_of_weight[num_loops] == 0.0f) {
+ cached_inverse_powers_of_weight[num_loops] = 1.0f / powf(weight, num_loops);
+ }
+ const float inverted_n_weight = num_loops < NUM_CACHED_INVERSE_POWERS_OF_WEIGHT ?
+ cached_inverse_powers_of_weight[num_loops] : 1.0f / powf(weight, num_loops);
+
+ madd_v3_v3fl(item_data->normal, polynors[mp_index], curr_val * inverted_n_weight);
+}
+
+static void apply_weights_vertex_normal(WeightedNormalModifierData *wnmd, WeightedNormalData *wn_data)
+{
+ const int numVerts = wn_data->numVerts;
+ const int numEdges = wn_data->numEdges;
+ const int numLoops = wn_data->numLoops;
+ const int numPolys = wn_data->numPolys;
+
+ MVert *mvert = wn_data->mvert;
+ MEdge *medge = wn_data->medge;
+
+ MLoop *mloop = wn_data->mloop;
+ short (*clnors)[2] = wn_data->clnors;
+ int *loop_to_poly = wn_data->loop_to_poly;
+
+ MPoly *mpoly = wn_data->mpoly;
+ float (*polynors)[3] = wn_data->polynors;
+ int *poly_strength = wn_data->poly_strength;
+
+ MDeformVert *dvert = wn_data->dvert;
+
+ const short mode = wn_data->mode;
+ ModePair *mode_pair = wn_data->mode_pair;
+
+ const bool has_clnors = wn_data->has_clnors;
+ const float split_angle = wn_data->split_angle;
+ MLoopNorSpaceArray lnors_spacearr = {NULL};
+
+ const bool keep_sharp = (wnmd->flag & MOD_WEIGHTEDNORMAL_KEEP_SHARP) != 0;
+ const bool use_face_influence = (wnmd->flag & MOD_WEIGHTEDNORMAL_FACE_INFLUENCE) != 0 && poly_strength != NULL;
+ const bool has_vgroup = dvert != NULL;
+
+ float (*loop_normals)[3] = NULL;
+
+ WeightedNormalDataAggregateItem *items_data = NULL;
+ int num_items = 0;
+ if (keep_sharp) {
+ BLI_bitmap *done_loops = BLI_BITMAP_NEW(numLoops, __func__);
+
+ /* This will give us loop normal spaces, we do not actually care about computed loop_normals for now... */
+ loop_normals = MEM_calloc_arrayN((size_t)numLoops, sizeof(*loop_normals), __func__);
+ BKE_mesh_normals_loop_split(mvert, numVerts, medge, numEdges,
+ mloop, loop_normals, numLoops, mpoly, polynors, numPolys,
+ true, split_angle, &lnors_spacearr, has_clnors ? clnors : NULL, loop_to_poly);
+
+ num_items = lnors_spacearr.num_spaces;
+ items_data = MEM_calloc_arrayN((size_t)num_items, sizeof(*items_data), __func__);
+
+ /* In this first loop, we assign each WeightedNormalDataAggregateItem to its smooth fan of loops (aka lnor space). */
+ MPoly *mp;
+ int mp_index;
+ int item_index;
+ for (mp = mpoly, mp_index = 0, item_index = 0; mp_index < numPolys; mp++, mp_index++) {
+ int ml_index = mp->loopstart;
+ const int ml_end_index = ml_index + mp->totloop;
+
+ for (; ml_index < ml_end_index; ml_index++) {
+ if (BLI_BITMAP_TEST(done_loops, ml_index)) {
+ /* Smooth fan of this loop has already been processed, skip it. */
+ continue;
+ }
+ BLI_assert(item_index < num_items);
+
+ WeightedNormalDataAggregateItem *itdt = &items_data[item_index];
+ itdt->curr_strength = FACE_STRENGTH_WEAK;
+
+ MLoopNorSpace *lnor_space = lnors_spacearr.lspacearr[ml_index];
+ lnor_space->user_data = itdt;
+
+ if (!(lnor_space->flags & MLNOR_SPACE_IS_SINGLE)) {
+ for (LinkNode *lnode = lnor_space->loops; lnode; lnode = lnode->next) {
+ const int ml_fan_index = GET_INT_FROM_POINTER(lnode->link);
+ BLI_BITMAP_ENABLE(done_loops, ml_fan_index);
+ }
+ }
+ else {
+ BLI_BITMAP_ENABLE(done_loops, ml_index);
+ }
+
+ item_index++;
+ }
+ }
+
+ MEM_freeN(done_loops);
+ }
+ else {
+ num_items = numVerts;
+ items_data = MEM_calloc_arrayN((size_t)num_items, sizeof(*items_data), __func__);
+ if (use_face_influence) {
+ for (int item_index = 0; item_index < num_items; item_index++) {
+ items_data[item_index].curr_strength = FACE_STRENGTH_WEAK;
+ }
+ }
+ }
+ wn_data->items_data = items_data;
+
+ switch (mode) {
+ case MOD_WEIGHTEDNORMAL_MODE_FACE:
+ for (int i = 0; i < numPolys; i++) {
+ const int mp_index = mode_pair[i].index;
+ const float mp_val = mode_pair[i].val;
+
+ int ml_index = mpoly[mp_index].loopstart;
+ const int ml_index_end = ml_index + mpoly[mp_index].totloop;
+ for (; ml_index < ml_index_end; ml_index++) {
+ const int mv_index = mloop[ml_index].v;
+ WeightedNormalDataAggregateItem *item_data = keep_sharp ?
+ lnors_spacearr.lspacearr[ml_index]->user_data:
+ &items_data[mv_index];
+
+ aggregate_item_normal(wnmd, wn_data, item_data, mv_index, mp_index, mp_val, use_face_influence);
+ }
+ }
+ break;
+ case MOD_WEIGHTEDNORMAL_MODE_ANGLE:
+ case MOD_WEIGHTEDNORMAL_MODE_FACE_ANGLE:
+ BLI_assert(loop_to_poly != NULL);
+
+ for (int i = 0; i < numLoops; i++) {
+ const int ml_index = mode_pair[i].index;
+ const float ml_val = mode_pair[i].val;
+
+ const int mp_index = loop_to_poly[ml_index];
+ const int mv_index = mloop[ml_index].v;
+ WeightedNormalDataAggregateItem *item_data = keep_sharp ?
+ lnors_spacearr.lspacearr[ml_index]->user_data:
+ &items_data[mv_index];
+
+ aggregate_item_normal(wnmd, wn_data, item_data, mv_index, mp_index, ml_val, use_face_influence);
+ }
+ break;
+ default:
+ BLI_assert(0);
+ }
+
+ /* Validate computed weighted normals. */
+ for (int item_index = 0; item_index < num_items; item_index++) {
+ if (normalize_v3(items_data[item_index].normal) < CLNORS_VALID_VEC_LEN) {
+ zero_v3(items_data[item_index].normal);
+ }
+ }
+
+ if (keep_sharp) {
+ /* Set loop normals for normal computed for each lnor space (smooth fan).
+ * Note that loop_normals is already populated with clnors (before this modifier is applied, at start of
+ * this function), so no need to recompute them here. */
+ for (int ml_index = 0; ml_index < numLoops; ml_index++) {
+ WeightedNormalDataAggregateItem *item_data = lnors_spacearr.lspacearr[ml_index]->user_data;
+ if (!is_zero_v3(item_data->normal)) {
+ copy_v3_v3(loop_normals[ml_index], item_data->normal);
+ }
+ }
+
+ BKE_mesh_normals_loop_custom_set(mvert, numVerts, medge, numEdges,
+ mloop, loop_normals, numLoops, mpoly, polynors, numPolys, clnors);
+ }
+ else {
+ /* TODO: Ideally, we could add an option to BKE_mesh_normals_loop_custom_[from_vertices_]set() to keep current
+ * clnors instead of resetting them to default autocomputed ones, when given new custom normal is zero-vec.
+ * But this is not exactly trivial change, better to keep this optimization for later...
+ */
+ if (!has_vgroup) {
+ /* Note: in theory, we could avoid this extra allocation & copying... But think we can live with it for now,
+ * and it makes code simpler & cleaner. */
+ float (*vert_normals)[3] = MEM_calloc_arrayN((size_t)numVerts, sizeof(*loop_normals), __func__);
+
+ for (int ml_index = 0; ml_index < numLoops; ml_index++) {
+ const int mv_index = mloop[ml_index].v;
+ copy_v3_v3(vert_normals[mv_index], items_data[mv_index].normal);
+ }
+
+ BKE_mesh_normals_loop_custom_from_vertices_set(mvert, vert_normals, numVerts, medge, numEdges,
+ mloop, numLoops, mpoly, polynors, numPolys, clnors);
+
+ MEM_freeN(vert_normals);
+ }
+ else {
+ loop_normals = MEM_calloc_arrayN((size_t)numLoops, sizeof(*loop_normals), __func__);
+
+ BKE_mesh_normals_loop_split(mvert, numVerts, medge, numEdges,
+ mloop, loop_normals, numLoops, mpoly, polynors, numPolys,
+ true, split_angle, NULL, has_clnors ? clnors : NULL, loop_to_poly);
+
+ for (int ml_index = 0; ml_index < numLoops; ml_index++) {
+ const int item_index = mloop[ml_index].v;
+ if (!is_zero_v3(items_data[item_index].normal)) {
+ copy_v3_v3(loop_normals[ml_index], items_data[item_index].normal);
+ }
+ }
+
+ BKE_mesh_normals_loop_custom_set(mvert, numVerts, medge, numEdges,
+ mloop, loop_normals, numLoops, mpoly, polynors, numPolys, clnors);
+ }
+ }
+
+ if (keep_sharp) {
+ BKE_lnor_spacearr_free(&lnors_spacearr);
+ }
+ MEM_SAFE_FREE(loop_normals);
+}
+
+static void wn_face_area(WeightedNormalModifierData *wnmd, WeightedNormalData *wn_data)
+{
+ const int numPolys = wn_data->numPolys;
+
+ MVert *mvert = wn_data->mvert;
+ MLoop *mloop = wn_data->mloop;
+ MPoly *mpoly = wn_data->mpoly;
+
+ MPoly *mp;
+ int mp_index;
+
+ ModePair *face_area = MEM_malloc_arrayN((size_t)numPolys, sizeof(*face_area), __func__);
+
+ ModePair *f_area = face_area;
+ for (mp_index = 0, mp = mpoly; mp_index < numPolys; mp_index++, mp++, f_area++) {
+ f_area->val = BKE_mesh_calc_poly_area(mp, &mloop[mp->loopstart], mvert);
+ f_area->index = mp_index;
+ }
+
+ qsort(face_area, numPolys, sizeof(*face_area), modepair_cmp_by_val_inverse);
+
+ wn_data->mode_pair = face_area;
+ apply_weights_vertex_normal(wnmd, wn_data);
+}
+
+static void wn_corner_angle(WeightedNormalModifierData *wnmd, WeightedNormalData *wn_data)
+{
+ const int numLoops = wn_data->numLoops;
+ const int numPolys = wn_data->numPolys;
+
+ MVert *mvert = wn_data->mvert;
+ MLoop *mloop = wn_data->mloop;
+ MPoly *mpoly = wn_data->mpoly;
+
+ MPoly *mp;
+ int mp_index;
+
+ int *loop_to_poly = MEM_malloc_arrayN((size_t)numLoops, sizeof(*loop_to_poly), __func__);
+
+ ModePair *corner_angle = MEM_malloc_arrayN((size_t)numLoops, sizeof(*corner_angle), __func__);
+
+ for (mp_index = 0, mp = mpoly; mp_index < numPolys; mp_index++, mp++) {
+ MLoop *ml_start = &mloop[mp->loopstart];
+
+ float *index_angle = MEM_malloc_arrayN((size_t)mp->totloop, sizeof(*index_angle), __func__);
+ BKE_mesh_calc_poly_angles(mp, ml_start, mvert, index_angle);
+
+ ModePair *c_angl = &corner_angle[mp->loopstart];
+ float *angl = index_angle;
+ for (int ml_index = mp->loopstart; ml_index < mp->loopstart + mp->totloop; ml_index++, c_angl++, angl++) {
+ c_angl->val = (float)M_PI - *angl;
+ c_angl->index = ml_index;
+
+ loop_to_poly[ml_index] = mp_index;
+ }
+ MEM_freeN(index_angle);
+ }
+
+ qsort(corner_angle, numLoops, sizeof(*corner_angle), modepair_cmp_by_val_inverse);
+
+ wn_data->loop_to_poly = loop_to_poly;
+ wn_data->mode_pair = corner_angle;
+ apply_weights_vertex_normal(wnmd, wn_data);
+}
+
+static void wn_face_with_angle(WeightedNormalModifierData *wnmd, WeightedNormalData *wn_data)
+{
+ const int numLoops = wn_data->numLoops;
+ const int numPolys = wn_data->numPolys;
+
+ MVert *mvert = wn_data->mvert;
+ MLoop *mloop = wn_data->mloop;
+ MPoly *mpoly = wn_data->mpoly;
+
+ MPoly *mp;
+ int mp_index;
+
+ int *loop_to_poly = MEM_malloc_arrayN((size_t)numLoops, sizeof(*loop_to_poly), __func__);
+
+ ModePair *combined = MEM_malloc_arrayN((size_t)numLoops, sizeof(*combined), __func__);
+
+ for (mp_index = 0, mp = mpoly; mp_index < numPolys; mp_index++, mp++) {
+ MLoop *ml_start = &mloop[mp->loopstart];
+
+ float face_area = BKE_mesh_calc_poly_area(mp, ml_start, mvert);
+ float *index_angle = MEM_malloc_arrayN((size_t)mp->totloop, sizeof(*index_angle), __func__);
+ BKE_mesh_calc_poly_angles(mp, ml_start, mvert, index_angle);
+
+ ModePair *cmbnd = &combined[mp->loopstart];
+ float *angl = index_angle;
+ for (int ml_index = mp->loopstart; ml_index < mp->loopstart + mp->totloop; ml_index++, cmbnd++, angl++) {
+ /* In this case val is product of corner angle and face area. */
+ cmbnd->val = ((float)M_PI - *angl) * face_area;
+ cmbnd->index = ml_index;
+
+ loop_to_poly[ml_index] = mp_index;
+ }
+ MEM_freeN(index_angle);
+ }
+
+ qsort(combined, numLoops, sizeof(*combined), modepair_cmp_by_val_inverse);
+
+ wn_data->loop_to_poly = loop_to_poly;
+ wn_data->mode_pair = combined;
+ apply_weights_vertex_normal(wnmd, wn_data);
+}
+
+static Mesh *applyModifier(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh)
+{
+ WeightedNormalModifierData *wnmd = (WeightedNormalModifierData *)md;
+ Object *ob = ctx->object;
+
+ /* XXX TODO ARG GRRR XYQWNMPRXTYY
+ * Once we fully switch to Mesh evaluation of modifiers, we can expect to get that flag from the COW copy.
+ * But for now, it is lost in the DM intermediate step, so we need to directly check orig object's data. */
+#if 0
+ if (!(mesh->flag & ME_AUTOSMOOTH)) {
+#else
+ if (!(((Mesh *)ob->data)->flag & ME_AUTOSMOOTH)) {
+#endif
+ modifier_setError((ModifierData *)wnmd, "Enable 'Auto Smooth' option in mesh settings");
+ return mesh;
+ }
+
+ Mesh *result;
+ BKE_id_copy_ex(
+ NULL, &mesh->id, (ID **)&result,
+ LIB_ID_CREATE_NO_MAIN |
+ LIB_ID_CREATE_NO_USER_REFCOUNT |
+ LIB_ID_CREATE_NO_DEG_TAG |
+ LIB_ID_COPY_NO_PREVIEW,
+ false);
+
+ const int numVerts = result->totvert;
+ const int numEdges = result->totedge;
+ const int numLoops = result->totloop;
+ const int numPolys = result->totpoly;
+
+ MEdge *medge = result->medge;
+ MPoly *mpoly = result->mpoly;
+ MVert *mvert = result->mvert;
+ MLoop *mloop = result->mloop;
+
+ bool free_polynors = false;
+
+ /* Right now:
+ * If weight = 50 then all faces are given equal weight.
+ * If weight > 50 then more weight given to faces with larger vals (face area / corner angle).
+ * If weight < 50 then more weight given to faces with lesser vals. However current calculation
+ * does not converge to min/max.
+ */
+ float weight = ((float)wnmd->weight) / 50.0f;
+ if (wnmd->weight == 100) {
+ weight = (float)SHRT_MAX;
+ }
+ else if (wnmd->weight == 1) {
+ weight = 1 / (float)SHRT_MAX;
+ }
+ else if ((weight - 1) * 25 > 1) {
+ weight = (weight - 1) * 25;
+ }
+
+ CustomData *pdata = &result->pdata;
+ float (*polynors)[3] = CustomData_get_layer(pdata, CD_NORMAL);
+ if (!polynors) {
+ polynors = CustomData_add_layer(pdata, CD_NORMAL, CD_CALLOC, NULL, numPolys);
+ }
+ BKE_mesh_calc_normals_poly(mvert, NULL, numVerts, mloop, mpoly, numLoops, numPolys, polynors, false);
+
+
+ const float split_angle = mesh->smoothresh;
+ short (*clnors)[2];
+ CustomData *ldata = &result->ldata;
+ clnors = CustomData_get_layer(ldata, CD_CUSTOMLOOPNORMAL);
+
+ /* Keep info whether we had clnors, it helps when generating clnor spaces and default normals. */
+ const bool has_clnors = clnors != NULL;
+ if (!clnors) {
+ clnors = CustomData_add_layer(ldata, CD_CUSTOMLOOPNORMAL, CD_CALLOC, NULL, numLoops);
+ clnors = CustomData_get_layer(ldata, CD_CUSTOMLOOPNORMAL);
+ }
+
+ MDeformVert *dvert;
+ int defgrp_index;
+ modifier_get_vgroup_mesh(ob, result, wnmd->defgrp_name, &dvert, &defgrp_index);
+
+ WeightedNormalData wn_data = {
+ .numVerts = numVerts,
+ .numEdges = numEdges,
+ .numLoops = numLoops,
+ .numPolys = numPolys,
+
+ .mvert = mvert,
+ .medge = medge,
+
+ .mloop = mloop,
+ .clnors = clnors,
+ .has_clnors = has_clnors,
+ .split_angle = split_angle,
+
+ .mpoly = mpoly,
+ .polynors = polynors,
+ .poly_strength = CustomData_get_layer_named(&result->pdata, CD_PROP_INT, MOD_WEIGHTEDNORMALS_FACEWEIGHT_CDLAYER_ID),
+
+ .dvert = dvert,
+ .defgrp_index = defgrp_index,
+ .use_invert_vgroup = (wnmd->flag & MOD_WEIGHTEDNORMAL_INVERT_VGROUP) != 0,
+
+ .weight = weight,
+ .mode = wnmd->mode,
+ };
+
+ switch (wnmd->mode) {
+ case MOD_WEIGHTEDNORMAL_MODE_FACE:
+ wn_face_area(wnmd, &wn_data);
+ break;
+ case MOD_WEIGHTEDNORMAL_MODE_ANGLE:
+ wn_corner_angle(wnmd, &wn_data);
+ break;
+ case MOD_WEIGHTEDNORMAL_MODE_FACE_ANGLE:
+ wn_face_with_angle(wnmd, &wn_data);
+ break;
+ }
+
+ MEM_SAFE_FREE(wn_data.loop_to_poly);
+ MEM_SAFE_FREE(wn_data.mode_pair);
+ MEM_SAFE_FREE(wn_data.items_data);
+
+ return result;
+}
+
+static void initData(ModifierData *md)
+{
+ WeightedNormalModifierData *wnmd = (WeightedNormalModifierData *)md;
+ wnmd->mode = MOD_WEIGHTEDNORMAL_MODE_FACE;
+ wnmd->weight = 50;
+ wnmd->thresh = 1e-2f;
+ wnmd->flag = 0;
+}
+
+static CustomDataMask requiredDataMask(Object *UNUSED(ob), ModifierData *md)
+{
+ WeightedNormalModifierData *wnmd = (WeightedNormalModifierData *)md;
+ CustomDataMask dataMask = CD_CUSTOMLOOPNORMAL;
+
+ if (wnmd->defgrp_name[0]) {
+ dataMask |= CD_MASK_MDEFORMVERT;
+ }
+
+ if (wnmd->flag & MOD_WEIGHTEDNORMAL_FACE_INFLUENCE) {
+ dataMask |= CD_MASK_PROP_INT;
+ }
+
+ return dataMask;
+}
+
+static bool dependsOnNormals(ModifierData *UNUSED(md))
+{
+ return true;
+}
+
+ModifierTypeInfo modifierType_WeightedNormal = {
+ /* name */ "Weighted Normal",
+ /* structName */ "WeightedNormalModifierData",
+ /* structSize */ sizeof(WeightedNormalModifierData),
+ /* type */ eModifierTypeType_Constructive,
+ /* flags */ eModifierTypeFlag_AcceptsMesh |
+ eModifierTypeFlag_SupportsMapping |
+ eModifierTypeFlag_SupportsEditmode |
+ eModifierTypeFlag_EnableInEditmode,
+
+ /* copyData */ modifier_copyData_generic,
+
+ /* deformVerts_DM */ NULL,
+ /* deformMatrices_DM */ NULL,
+ /* deformVertsEM_DM */ NULL,
+ /* deformMatricesEM_DM*/NULL,
+ /* applyModifier_DM */ NULL,
+ /* applyModifierEM_DM */NULL,
+
+ /* deformVerts */ NULL,
+ /* deformMatrices */ NULL,
+ /* deformVertsEM */ NULL,
+ /* deformMatricesEM */ NULL,
+ /* applyModifier */ applyModifier,
+ /* applyModifierEM */ NULL,
+
+ /* initData */ initData,
+ /* requiredDataMask */ requiredDataMask,
+ /* freeData */ NULL,
+ /* isDisabled */ NULL,
+ /* updateDepsgraph */ NULL,
+ /* dependsOnTime */ NULL,
+ /* dependsOnNormals */ dependsOnNormals,
+ /* foreachObjectLink */ NULL,
+ /* foreachIDLink */ NULL,
+ /* foreachTexLink */ NULL,
+};
diff --git a/source/tools b/source/tools
-Subproject 6bcd05cf6aaafae07b8a15313d7fdda1471ff59
+Subproject 88a1758d2d2e862cc69c08b5b40a4e75f71592d