diff options
-rw-r--r-- | source/blender/blenkernel/BKE_mesh.h | 3 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/mesh_evaluate.c | 15 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_math_base.h | 4 | ||||
-rw-r--r-- | source/blender/blenlib/intern/math_base_inline.c | 12 | ||||
-rw-r--r-- | source/blender/bmesh/intern/bmesh_mesh.c | 328 | ||||
-rw-r--r-- | source/blender/bmesh/intern/bmesh_mesh.h | 10 | ||||
-rw-r--r-- | source/blender/python/bmesh/bmesh_py_types_customdata.c | 247 | ||||
-rw-r--r-- | source/blender/python/bmesh/bmesh_py_utils.c | 3 |
8 files changed, 607 insertions, 15 deletions
diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index d8d869015a3..3ec597fa1a3 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -194,6 +194,9 @@ void BKE_mesh_loop_tangents_ex( void BKE_mesh_loop_tangents( struct Mesh *mesh, const char *uvmap, float (*r_looptangents)[4], struct ReportList *reports); +/* This threshold is a bit touchy (usual float precision issue), this value seems OK. */ +#define LNOR_SPACE_TRIGO_THRESHOLD (1.0f - 1e-6f) + /** * References a contiguous loop-fan with normal offset vars. */ diff --git a/source/blender/blenkernel/intern/mesh_evaluate.c b/source/blender/blenkernel/intern/mesh_evaluate.c index 1c86fbcfe8e..899eaf6a759 100644 --- a/source/blender/blenkernel/intern/mesh_evaluate.c +++ b/source/blender/blenkernel/intern/mesh_evaluate.c @@ -433,9 +433,6 @@ MLoopNorSpace *BKE_lnor_space_create(MLoopNorSpaceArray *lnors_spacearr) return BLI_memarena_calloc(lnors_spacearr->mem, sizeof(MLoopNorSpace)); } -/* This threshold is a bit touchy (usual float precision issue), this value seems OK. */ -#define LNOR_SPACE_TRIGO_THRESHOLD (1.0f - 1e-6f) - /* Should only be called once. * Beware, this modifies ref_vec and other_vec in place! * In case no valid space can be generated, ref_alpha and ref_beta are set to zero (which means 'use auto lnors'). @@ -513,17 +510,6 @@ void BKE_lnor_space_add_loop(MLoopNorSpaceArray *lnors_spacearr, MLoopNorSpace * } } -MINLINE float unit_short_to_float(const short val) -{ - return (float)val / (float)SHRT_MAX; -} - -MINLINE short unit_float_to_short(const float val) -{ - /* Rounding... */ - return (short)floorf(val * (float)SHRT_MAX + 0.5f); -} - void BKE_lnor_space_custom_data_to_normal(MLoopNorSpace *lnor_space, const short clnor_data[2], float r_custom_lnor[3]) { /* NOP custom normal data or invalid lnor space, return. */ @@ -1370,6 +1356,7 @@ void BKE_mesh_normals_loop_split( * r_custom_loopnors is expected to have normalized normals, or zero ones, in which case they will be replaced * by default loop/vertex normal. */ +/* XXX Keep in sync with BMesh's bm_mesh_loops_normals_custom_set(). */ static void mesh_normals_loop_custom_set( const MVert *mverts, const int numVerts, MEdge *medges, const int numEdges, MLoop *mloops, float (*r_custom_loopnors)[3], const int numLoops, diff --git a/source/blender/blenlib/BLI_math_base.h b/source/blender/blenlib/BLI_math_base.h index e97a250cd24..e6c98cb7173 100644 --- a/source/blender/blenlib/BLI_math_base.h +++ b/source/blender/blenlib/BLI_math_base.h @@ -210,6 +210,10 @@ MINLINE int mod_i(int i, int n); int pow_i(int base, int exp); double double_round(double x, int ndigits); +MINLINE float unit_short_to_float(const short val); +MINLINE short unit_float_to_short(const float val); + + #ifdef BLI_MATH_GCC_WARN_PRAGMA # pragma GCC diagnostic pop #endif diff --git a/source/blender/blenlib/intern/math_base_inline.c b/source/blender/blenlib/intern/math_base_inline.c index 8d2d80c2a35..f8cde6e5c4c 100644 --- a/source/blender/blenlib/intern/math_base_inline.c +++ b/source/blender/blenlib/intern/math_base_inline.c @@ -33,6 +33,7 @@ #include <float.h> #include <stdio.h> #include <stdlib.h> +#include <limits.h> #ifdef __SSE2__ # include <emmintrin.h> @@ -314,6 +315,17 @@ MINLINE int signum_i(float a) else return 0; } +MINLINE float unit_short_to_float(const short val) +{ + return (float)val / (float)SHRT_MAX; +} + +MINLINE short unit_float_to_short(const float val) +{ + /* Rounding... */ + return (short)floorf(val * (float)SHRT_MAX + 0.5f); +} + /* Internal helpers for SSE2 implementation. * * NOTE: Are to be called ONLY from inside `#ifdef __SSE2__` !!! diff --git a/source/blender/bmesh/intern/bmesh_mesh.c b/source/blender/bmesh/intern/bmesh_mesh.c index ed1bd16b2e4..9147b226922 100644 --- a/source/blender/bmesh/intern/bmesh_mesh.c +++ b/source/blender/bmesh/intern/bmesh_mesh.c @@ -26,11 +26,14 @@ * BM mesh level functions. */ +#include <limits.h> + #include "MEM_guardedalloc.h" #include "DNA_listBase.h" #include "DNA_object_types.h" +#include "BLI_bitmap.h" #include "BLI_linklist_stack.h" #include "BLI_listbase.h" #include "BLI_math.h" @@ -39,6 +42,7 @@ #include "BKE_cdderivedmesh.h" #include "BKE_editmesh.h" +#include "BKE_global.h" #include "BKE_mesh.h" #include "BKE_multires.h" @@ -903,6 +907,330 @@ void BM_loops_calc_normal_vcos( } } +/* BMesh version of mesh_normals_loop_custom_set() in mesh_evaluate.c, keep it in sync. + * 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_normals_custom_set( + BMesh *bm, const float (*vcos)[3], const float (*vnos)[3], const float (*fnos)[3], float (*r_lnos)[3], + short (*r_clnors_data)[2], const int cd_loop_clnors_offset, const bool use_vertices) +{ + BMIter viter; + BMVert *v_curr; + BMIter fiter; + BMFace *f_curr; + BMIter liter; + BMLoop *l_curr; + + MLoopNorSpaceArray lnors_spacearr = {NULL}; + BLI_bitmap *done_loops = BLI_BITMAP_NEW((size_t)bm->totloop, __func__); + float (*lnos)[3] = MEM_callocN(sizeof(*lnos) * (size_t)bm->totloop, __func__); + /* In this case we always consider split nors as ON, and do not want to use angle to define smooth fans! */ + const bool use_split_normals = true; + const float split_angle = (float)M_PI; + int i; + + { + char htype = BM_LOOP; + if (vcos || use_vertices) { + htype |= BM_VERT; + } + if (fnos) { + htype |= BM_FACE; + } + BM_mesh_elem_index_ensure(bm, htype); + } + + /* Compute current lnor spacearr. */ + BM_loops_calc_normal_vcos(bm, vcos, vnos, fnos, use_split_normals, split_angle, lnos, &lnors_spacearr, NULL, -1); + + /* Set all given zero vectors to their default value. */ + if (use_vertices) { + BM_ITER_MESH_INDEX(v_curr, &viter, bm, BM_VERTS_OF_MESH, i) { + if (is_zero_v3(r_lnos[i])) { + copy_v3_v3(r_lnos[i], v_curr->no); + } + } + } + else { + BM_ITER_MESH(f_curr, &fiter, bm, BM_FACES_OF_MESH) { + BM_ITER_ELEM(l_curr, &liter, f_curr, BM_LOOPS_OF_FACE) { + i = BM_elem_index_get(l_curr); + if (is_zero_v3(r_lnos[i])) { + copy_v3_v3(r_lnos[i], lnos[i]); + } + } + } + } + + /* Now, check each current smooth fan (one lnor space per smooth fan!), and if all its matching custom lnors + * are not (enough) equal, add sharp edges as needed. + * This way, next time we run BM_mesh_loop_normals_update(), we'll get lnor spacearr/smooth fans matching + * given custom lnors. + * Note this code *will never* unsharp edges! + * And quite obviously, when we set custom normals per vertices, running this is absolutely useless. + */ + if (!use_vertices) { + BM_ITER_MESH(f_curr, &fiter, bm, BM_FACES_OF_MESH) { + BM_ITER_ELEM(l_curr, &liter, f_curr, BM_LOOPS_OF_FACE) { + i = BM_elem_index_get(l_curr); + if (!lnors_spacearr.lspacearr[i]) { + /* This should not happen in theory, but in some rare case (probably ugly geometry) + * we can get some NULL loopspacearr at this point. :/ + * Maybe we should set those loops' edges as sharp? + */ + BLI_BITMAP_ENABLE(done_loops, i); + if (G.debug & G_DEBUG) { + printf("WARNING! Getting invalid NULL loop space for loop %d!\n", i); + } + continue; + } + + if (!BLI_BITMAP_TEST(done_loops, i)) { + /* Notes: + * * In case of mono-loop smooth fan, loops is NULL, so everything is fine (we have nothing to do). + * * Loops in this linklist are ordered (in reversed order compared to how they were discovered by + * BKE_mesh_normals_loop_split(), but this is not a problem). Which means if we find a + * mismatching clnor, we know all remaining loops will have to be in a new, different smooth fan/ + * lnor space. + * * In smooth fan case, we compare each clnor against a ref one, to avoid small differences adding + * up into a real big one in the end! + */ + LinkNode *loops = lnors_spacearr.lspacearr[i]->loops; + BMLoop *l_prev = NULL; + const float *org_no = NULL; + + if (loops) { + if (BM_elem_index_get(l_curr) != GET_INT_FROM_POINTER(loops->link)) { + /* l_curr is not first loop of our loop fan, just skip until we find that one. + * Simpler than to go searching for the first loop immediately, since getting a BMLoop + * from its index is not trivial currently.*/ + continue; + } + + BLI_assert(loops->next); + + BMLoop *l = l_curr; + BMEdge *e_next; /* Used to handle fan loop direction... */ + + /* Set e_next to loop along fan in matching direction with loop space's fan. */ + { + const int lidx_next = GET_INT_FROM_POINTER(loops->next->link); + if (BM_elem_index_get(l->radial_next) == lidx_next) { + e_next = ELEM(l->e, l->radial_next->e, l->radial_next->prev->e) ? l->prev->e : l->e; + } + else { + BLI_assert(BM_elem_index_get(l->radial_prev) == lidx_next); + e_next = ELEM(l->e, l->radial_prev->e, l->radial_prev->prev->e) ? l->prev->e : l->e; + } + } + + while (loops) { + const int lidx = GET_INT_FROM_POINTER(loops->link); + const int nidx = lidx; + float *no = r_lnos[nidx]; + + BLI_assert(l != NULL); + BLI_assert(BM_elem_index_get(l) == lidx); + + if (!org_no) { + org_no = no; + } + else if (dot_v3v3(org_no, no) < LNOR_SPACE_TRIGO_THRESHOLD) { + /* Current normal differs too much from org one, we have to tag the edge between + * previous loop's face and current's one as sharp. + * We know those two loops do not point to the same edge, since we do not allow reversed winding + * in a same smooth fan. + */ + const BMLoop *lp = l->prev; + + BLI_assert(((l_prev->e == lp->e) ? l_prev->e : l->e) == e_next); + + BM_elem_flag_disable(e_next, BM_ELEM_SMOOTH); + org_no = no; + } + + l_prev = l; + l = BM_vert_step_fan_loop(l, &e_next); + loops = loops->next; + BLI_BITMAP_ENABLE(done_loops, lidx); + } + + /* We also have to check between last and first loops, otherwise we may miss some sharp edges here! + * This is just a simplified version of above while loop. + * See T45984. */ + loops = lnors_spacearr.lspacearr[i]->loops; + if (org_no) { + const int lidx = GET_INT_FROM_POINTER(loops->link); + l = l_curr; + const int nidx = lidx; + float *no = r_lnos[nidx]; + + if (dot_v3v3(org_no, no) < LNOR_SPACE_TRIGO_THRESHOLD) { + const BMLoop *lp = l->prev; + BM_elem_flag_disable((l_prev->e == lp->e) ? l_prev->e : l->e, BM_ELEM_SMOOTH); + } + } + } + + /* For single loops, where lnors_spacearr.lspacearr[i]->loops is NULL. */ + BLI_BITMAP_ENABLE(done_loops, i); + } + } + } + + /* And now, recompute our new auto lnors and lnor spacearr! */ + BKE_lnor_spacearr_clear(&lnors_spacearr); + BM_loops_calc_normal_vcos(bm, vcos, vnos, fnos, use_split_normals, split_angle, lnos, &lnors_spacearr, NULL, -1); + } + else { + BLI_BITMAP_SET_ALL(done_loops, true, (size_t)bm->totloop); + } + + /* And we just have to convert plain object-space custom normals to our lnor space-encoded ones. */ + BM_ITER_MESH(f_curr, &fiter, bm, BM_FACES_OF_MESH) { + BM_ITER_ELEM(l_curr, &liter, f_curr, BM_LOOPS_OF_FACE) { + i = BM_elem_index_get(l_curr); + + if (!lnors_spacearr.lspacearr[i]) { + BLI_BITMAP_DISABLE(done_loops, i); + if (G.debug & G_DEBUG) { + printf("WARNING! Still getting invalid NULL loop space in second loop for loop %d!\n", i); + } + continue; + } + + if (BLI_BITMAP_TEST_BOOL(done_loops, i)) { + /* Note we accumulate and average all custom normals in current smooth fan, to avoid getting different + * clnors data (tiny differences in plain custom normals can give rather huge differences in + * computed 2D factors). + */ + LinkNode *loops = lnors_spacearr.lspacearr[i]->loops; + if (loops) { + if (BM_elem_index_get(l_curr) != GET_INT_FROM_POINTER(loops->link)) { + /* l_curr is not first loop of our loop fan, just skip until we find that one. + * Simpler than to go searching for the first loop immediately, since getting a BMLoop + * from its index is not trivial currently.*/ + continue; + } + + BLI_assert(loops->next); + + int nbr_nos = 0; + float avg_no[3] = {0.0f}; + short clnor_data_tmp[2], *clnor_data; + + BLI_SMALLSTACK_DECLARE(clnors_data, short *); + + BMLoop *l = l_curr; + BMEdge *e_next; /* Used to handle fan loop direction... */ + + /* Set e_next to loop along fan in matching direction with loop space's fan. */ + { + const int lidx_next = GET_INT_FROM_POINTER(loops->next->link); + if (BM_elem_index_get(l->radial_next) == lidx_next) { + e_next = ELEM(l->e, l->radial_next->e, l->radial_next->prev->e) ? l->prev->e : l->e; + } + else { + BLI_assert(BM_elem_index_get(l->radial_prev) == lidx_next); + e_next = ELEM(l->e, l->radial_prev->e, l->radial_prev->prev->e) ? l->prev->e : l->e; + } + } + + while (loops) { + const int lidx = GET_INT_FROM_POINTER(loops->link); + + BLI_assert(l != NULL); + BLI_assert(BM_elem_index_get(l) == lidx); + + const int nidx = use_vertices ? BM_elem_index_get(l->v) : lidx; + float *no = r_lnos[nidx]; + clnor_data = r_clnors_data ? r_clnors_data[lidx] : + BM_ELEM_CD_GET_VOID_P(l, cd_loop_clnors_offset); + + nbr_nos++; + add_v3_v3(avg_no, no); + BLI_SMALLSTACK_PUSH(clnors_data, clnor_data); + + l = BM_vert_step_fan_loop(l, &e_next); + loops = loops->next; + BLI_BITMAP_DISABLE(done_loops, lidx); + } + + mul_v3_fl(avg_no, 1.0f / (float)nbr_nos); + BKE_lnor_space_custom_normal_to_data(lnors_spacearr.lspacearr[i], avg_no, clnor_data_tmp); + + while ((clnor_data = BLI_SMALLSTACK_POP(clnors_data))) { + clnor_data[0] = clnor_data_tmp[0]; + clnor_data[1] = clnor_data_tmp[1]; + } + } + else { + const int nidx = use_vertices ? BM_elem_index_get(l_curr->v) : i; + float *no = r_lnos[nidx]; + short *clnor_data = r_clnors_data ? r_clnors_data[i] : + BM_ELEM_CD_GET_VOID_P(l_curr, cd_loop_clnors_offset); + + BKE_lnor_space_custom_normal_to_data(lnors_spacearr.lspacearr[i], no, clnor_data); + BLI_BITMAP_DISABLE(done_loops, i); + } + } + } + } + + MEM_freeN(lnos); + MEM_freeN(done_loops); + BKE_lnor_spacearr_free(&lnors_spacearr); +} + +/** + * \brief BMesh Set custom Loop Normals. + * + * Store given custom per-loop normals. + * Caller must ensure a matching CD layer is already available, or feature its own array of clnors. + */ +void BM_loops_normal_custom_set( + BMesh *bm, float (*r_lnos)[3], short (*r_clnors_data)[2], const int cd_loop_clnors_offset) +{ + bm_mesh_loops_normals_custom_set(bm, NULL, NULL, NULL, r_lnos, r_clnors_data, cd_loop_clnors_offset, false); +} + +/** + * \brief BMesh Set custom Loop Normals. + * + * Store given custom per-loop normals. + * Caller must ensure a matching CD layer is already available, or feature its own array of clnors. + */ +void BM_loops_normal_custom_set_vcos( + BMesh *bm, const float (*vcos)[3], const float (*vnos)[3], const float (*fnos)[3], float (*r_lnos)[3], + short (*r_clnors_data)[2], const int cd_loop_clnors_offset) +{ + bm_mesh_loops_normals_custom_set(bm, vcos, vnos, fnos, r_lnos, r_clnors_data, cd_loop_clnors_offset, false); +} + +/** + * \brief BMesh Set custom Loop Normals from vertex normals. + * + * Store given custom per-vertex normals. + * Caller must ensure a matching CD layer is already available, or feature its own array of clnors. + */ +void BM_loops_normal_custom_set_from_vertices( + BMesh *bm, float (*r_lnos)[3], short (*r_clnors_data)[2], const int cd_loop_clnors_offset) +{ + bm_mesh_loops_normals_custom_set(bm, NULL, NULL, NULL, r_lnos, r_clnors_data, cd_loop_clnors_offset, true); +} + +/** + * \brief BMesh Set custom Loop Normals from vertex normals. + * + * Store given custom per-vertex normals. + * Caller must ensure a matching CD layer is already available, or feature its own array of clnors. + */ +void BM_loops_normal_custom_set_from_vertices_vcos( + BMesh *bm, const float (*vcos)[3], const float (*vnos)[3], const float (*fnos)[3], float (*r_lnos)[3], + short (*r_clnors_data)[2], const int cd_loop_clnors_offset) +{ + bm_mesh_loops_normals_custom_set(bm, vcos, vnos, fnos, r_lnos, r_clnors_data, cd_loop_clnors_offset, true); +} + static void UNUSED_FUNCTION(bm_mdisps_space_set)(Object *ob, BMesh *bm, int from, int to) { /* switch multires data out of tangent space */ diff --git a/source/blender/bmesh/intern/bmesh_mesh.h b/source/blender/bmesh/intern/bmesh_mesh.h index b9cdc4ccf66..b982b14340f 100644 --- a/source/blender/bmesh/intern/bmesh_mesh.h +++ b/source/blender/bmesh/intern/bmesh_mesh.h @@ -44,6 +44,16 @@ 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); +void BM_loops_normal_custom_set( + BMesh *bm, float (*r_lnos)[3], short (*r_clnors_data)[2], const int cd_loop_clnors_offset); +void BM_loops_normal_custom_set_vcos( + BMesh *bm, const float (*vcos)[3], const float (*vnos)[3], const float (*fnos)[3], float (*r_lnos)[3], + short (*r_clnors_data)[2], const int cd_loop_clnors_offset); +void BM_loops_normal_custom_set_from_vertices( + BMesh *bm, float (*r_lnos)[3], short (*r_clnors_data)[2], const int cd_loop_clnors_offset); +void BM_loops_normal_custom_set_from_vertices_vcos( + BMesh *bm, const float (*vcos)[3], const float (*vnos)[3], const float (*fnos)[3], float (*r_lnos)[3], + short (*r_clnors_data)[2], const int cd_loop_clnors_offset); void bmesh_edit_begin(BMesh *bm, const BMOpTypeFlag type_flag); void bmesh_edit_end(BMesh *bm, const BMOpTypeFlag type_flag); diff --git a/source/blender/python/bmesh/bmesh_py_types_customdata.c b/source/blender/python/bmesh/bmesh_py_types_customdata.c index 908f6b5a734..21a70c93e0a 100644 --- a/source/blender/python/bmesh/bmesh_py_types_customdata.c +++ b/source/blender/python/bmesh/bmesh_py_types_customdata.c @@ -44,6 +44,8 @@ #include "../mathutils/mathutils.h" #include "../generic/python_utildefines.h" +#include "MEM_guardedalloc.h" + #include "BKE_customdata.h" #include "DNA_meshdata_types.h" @@ -113,6 +115,9 @@ PyDoc_STRVAR(bpy_bmlayeraccess_collection__uv_doc, PyDoc_STRVAR(bpy_bmlayeraccess_collection__color_doc, "Accessor for vertex color layer.\n\ntype: :class:`BMLayerCollection`" ); +PyDoc_STRVAR(bpy_bmlayeraccess_collection__clnor_doc, +"Accessor for custom loop normal layer.\n\ntype: :class:`BMLayerCollection`" +); PyDoc_STRVAR(bpy_bmlayeraccess_collection__skin_doc, "Accessor for skin layer.\n\ntype: :class:`BMLayerCollection`" ); @@ -239,6 +244,8 @@ static PyGetSetDef bpy_bmlayeraccess_loop_getseters[] = { {(char *)"uv", (getter)bpy_bmlayeraccess_collection_get, (setter)NULL, (char *)bpy_bmlayeraccess_collection__uv_doc, (void *)CD_MLOOPUV}, {(char *)"color", (getter)bpy_bmlayeraccess_collection_get, (setter)NULL, (char *)bpy_bmlayeraccess_collection__color_doc, (void *)CD_MLOOPCOL}, + {(char *)"clnor", (getter)bpy_bmlayeraccess_collection_get, (setter)NULL, (char *)bpy_bmlayeraccess_collection__clnor_doc, (void *)CD_CUSTOMLOOPNORMAL}, + {NULL, NULL, NULL, NULL, NULL} /* Sentinel */ }; @@ -311,6 +318,216 @@ static PyObject *bpy_bmlayeritem_copy_from(BPy_BMLayerItem *self, BPy_BMLayerIte Py_RETURN_NONE; } +#define bpy_bmlayeritem_from_array__clnors_doc \ +" clnor layer: Array may be either:\n" \ +" - A sequence of num_loop tuples (float, float):\n" \ +" Raw storage of custom normals, as (alpha, beta) factors in [-1.0, 1.0] range.\n" \ +" - A sequence of num_loop vectors (float, float, float):\n" \ +" Custom normals per loop, as (x, y, z) components (normalization is ensured internaly).\n" \ +" - A sequence of num_vert vectors (float, float, float):\n" \ +" Custom normals per vertex, as (x, y, z) components (normalization is ensured internaly).\n" \ +"\n" \ +" In all cases, items which are None or null vectors will use default auto-computed normal.\n" \ +"\n" \ +" Returns an array of the same type as given one, with None/null-vector values replaced by actual ones.\n" +static PyObject *bpy_bmlayeritem_from_array__clnors(BPy_BMLayerItem *self, PyObject *value) +{ + PyObject *ret; + + float (*nors)[3] = NULL; + float (*clnors)[2] = NULL; + Py_ssize_t vec_size; + int cd_loop_clnors_offset; + + BMesh *bm = self->bm; + + if ((cd_loop_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL)) == -1) { + /* Should never ever happen! */ + PyErr_Format(PyExc_SystemError, + "clnor's from_array(): No custom normal data layer in the bmesh"); + return NULL; + } + + value = PySequence_Fast(value, "normals must be an iterable"); + if (!value) { + return NULL; + } + + const int value_len = PySequence_Fast_GET_SIZE(value); + + if (!ELEM(value_len, bm->totloop, bm->totvert)) { + PyErr_Format(PyExc_TypeError, + "clnor's from_array(): " + "There must be either one data per vertex or one per loop"); + Py_DECREF(value); + return NULL; + } + + PyObject **value_items = PySequence_Fast_ITEMS(value); + + vec_size = 3; /* In case value is an array of None's only. */ + for (Py_ssize_t i = 0; i < value_len; i++) { + PyObject *py_vec = value_items[i]; + + if (py_vec == Py_None) { + continue; + } + py_vec = PySequence_Fast(py_vec, ""); + if (py_vec) { + vec_size = PySequence_Fast_GET_SIZE(py_vec); + } + if (!py_vec || (vec_size == 2 && value_len != bm->totloop) || vec_size != 3) { + PyErr_Format(PyExc_TypeError, + "clnor's from_array(): array items must be either triplets of floats, " + "or pair of floats factors for raw clnor data, first item is neither " + "(or total number of items does match expected one, %d verts/%d loops)", + bm->totvert, bm->totloop); + MEM_freeN(nors); + Py_DECREF(value); + Py_XDECREF(py_vec); + return NULL; + } + break; + } + + if (vec_size == 2) { + clnors = MEM_mallocN(sizeof(*clnors) * value_len, __func__); + for (Py_ssize_t i = 0; i < value_len; i++) { + PyObject *py_vec = value_items[i]; + + if (py_vec == Py_None) { + zero_v2(clnors[i]); + } + else { + if (mathutils_array_parse( + clnors[i], 2, 2, py_vec, + "clnor's from_array(): clnors are expected to be pairs of floats " + "in [-1.0, 1.0] range") == -1) + { + MEM_freeN(clnors); + Py_DECREF(value); + return NULL; + } + + for (int j = 0; j < 2; j++) { + CLAMP(clnors[i][j], -1.0, 1.0f); + } + } + } + } + else { + nors = MEM_mallocN(sizeof(*nors) * value_len, __func__); + for (Py_ssize_t i = 0; i < value_len; i++) { + PyObject *py_vec = value_items[i]; + if (py_vec == Py_None) { + zero_v3(nors[i]); + } + else { + if (mathutils_array_parse( + nors[i], 3, 3, py_vec, + "clnor's from_array(): clnors are expected to be triplets of floats") == -1) + { + MEM_freeN(clnors); + Py_DECREF(value); + return NULL; + } + normalize_v3(nors[i]); /* Just in case... */ + } + } + } + + Py_DECREF(value); + ret = PyTuple_New(value_len); + + if (vec_size == 2) { + BMIter fiter; + BMIter liter; + BMFace *f; + BMLoop *l; + + BM_mesh_elem_index_ensure(bm, BM_LOOP); + + BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { + BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { + const int lidx = BM_elem_index_get(l); + short *clnor = BM_ELEM_CD_GET_VOID_P(l, cd_loop_clnors_offset); + clnor[0] = unit_float_to_short(clnors[lidx][0]); + clnor[1] = unit_float_to_short(clnors[lidx][1]); + + PyObject *py_vec = PyTuple_New(2); + PyTuple_SET_ITEMS(py_vec, + PyFloat_FromDouble((double)clnors[lidx][0]), + PyFloat_FromDouble((double)clnors[lidx][1])); + + PyTuple_SET_ITEM(ret, lidx, py_vec); + } + } + } + else { + if (value_len == bm->totloop) { + BM_loops_normal_custom_set(bm, nors, NULL, cd_loop_clnors_offset); + } + else { + BM_loops_normal_custom_set_from_vertices(bm, nors, NULL, cd_loop_clnors_offset); + } + + for (Py_ssize_t i = 0; i < value_len; i++) { + PyObject *py_vec = PyTuple_New(3); + PyTuple_SET_ITEMS(py_vec, + PyFloat_FromDouble((double)nors[i][0]), + PyFloat_FromDouble((double)nors[i][1]), + PyFloat_FromDouble((double)nors[i][2])); + + PyTuple_SET_ITEM(ret, i, py_vec); + } + + MEM_freeN(nors); + } + + return ret; +} + +PyDoc_STRVAR(bpy_bmlayeritem_from_array_doc, +".. method:: from_array(array)\n" +"\n" +" Set whole layer data from values in given array (or any type of iterable).\n" +"\n" +"\n" +bpy_bmlayeritem_from_array__clnors_doc +"\n" +"\n" +" :arg array: Iterable of data to set from.\n" +); +static PyObject *bpy_bmlayeritem_from_array(BPy_BMLayerItem *self, PyObject *value) +{ + PyObject *ret; + + BPY_BM_CHECK_OBJ(self); + + switch (self->htype) { + case BM_LOOP: + switch (self->type) { + case CD_CUSTOMLOOPNORMAL: + ret = bpy_bmlayeritem_from_array__clnors(self, value); + break; + default: + ret = Py_NotImplemented; /* TODO */ + Py_INCREF(ret); + } + break; + case BM_VERT: + case BM_EDGE: + case BM_FACE: + ret = Py_NotImplemented; /* TODO */ + Py_INCREF(ret); + break; + default: + ret = NULL; + } + + return ret; +} + /* similar to new(), but no name arg. */ PyDoc_STRVAR(bpy_bmlayercollection_verify_doc, ".. method:: verify()\n" @@ -564,7 +781,10 @@ static PyObject *bpy_bmlayercollection_get(BPy_BMLayerCollection *self, PyObject } static struct PyMethodDef bpy_bmlayeritem_methods[] = { - {"copy_from", (PyCFunction)bpy_bmlayeritem_copy_from, METH_O, bpy_bmlayeritem_copy_from_doc}, + {"copy_from", (PyCFunction)bpy_bmlayeritem_copy_from, METH_O, bpy_bmlayeritem_copy_from_doc}, + + {"from_array", (PyCFunction)bpy_bmlayeritem_from_array, METH_O, bpy_bmlayeritem_from_array_doc}, + {NULL, NULL, 0, NULL} }; @@ -1013,6 +1233,14 @@ PyObject *BPy_BMLayerItem_GetItem(BPy_BMElem *py_ele, BPy_BMLayerItem *py_layer) ret = BPy_BMLoopColor_CreatePyObject(value); break; } + case CD_CUSTOMLOOPNORMAL: + { + float vec[2]; + vec[0] = unit_short_to_float(((short *)value)[0]); + vec[1] = unit_short_to_float(((short *)value)[1]); + ret = Vector_CreatePyObject(vec, 2, NULL); + break; + } case CD_SHAPEKEY: { ret = Vector_CreatePyObject_wrap((float *)value, 3, NULL); @@ -1116,6 +1344,23 @@ int BPy_BMLayerItem_SetItem(BPy_BMElem *py_ele, BPy_BMLayerItem *py_layer, PyObj ret = BPy_BMLoopColor_AssignPyObject(value, py_value); break; } + case CD_CUSTOMLOOPNORMAL: + { + float vec[2]; + if (UNLIKELY(mathutils_array_parse(vec, 2, 2, py_value, "BMLoop[clnor] = value") == -1)) { + ret = -1; + break; + } + if (vec[0] < -1.0f || vec[0] > 1.0f || vec[1] < -1.0f || vec[1] > 1.0f) { + PyErr_Format(PyExc_ValueError, "expected float values between -1.0 and 1.0, not (%f, %f)", + vec[0], vec[1]); + ret = -1; + break; + } + ((short *)value)[0] = unit_float_to_short(vec[0]); + ((short *)value)[1] = unit_float_to_short(vec[1]); + break; + } case CD_SHAPEKEY: { float tmp_val[3]; diff --git a/source/blender/python/bmesh/bmesh_py_utils.c b/source/blender/python/bmesh/bmesh_py_utils.c index 89c196dbcad..df3bfb1f545 100644 --- a/source/blender/python/bmesh/bmesh_py_utils.c +++ b/source/blender/python/bmesh/bmesh_py_utils.c @@ -33,6 +33,9 @@ #include <Python.h> #include "BLI_utildefines.h" +#include "BLI_math.h" + +#include "BKE_customdata.h" #include "MEM_guardedalloc.h" |