diff options
author | Bastien Montagne <montagne29@wanadoo.fr> | 2016-06-07 22:55:28 +0300 |
---|---|---|
committer | Bastien Montagne <montagne29@wanadoo.fr> | 2016-06-07 22:55:28 +0300 |
commit | d3f578c7e772e65189bcc94ba42ee75e7c7a75a5 (patch) | |
tree | d1961da15c54abca531bfdde51fa420ceb8bea38 | |
parent | 9081adf097df9022179c9d3766e26ff7d5ee29e8 (diff) |
Initial work to allow custom normals setting in BMesh, with quick bmesh py API call too.
Seems to be working OK from quick tests...
Some notes:
- This is mimicking BKE_mesh_evaluate code. While this is OK for some cases,
we'll likely want some more integrated and fine-grained ways to set/edit those
normals in Edit mode once we create real editing tools for those (most likely based
on some BMesh operators).
This implies some kind of dynamic caching/update of clnors spaces though, which
is not trivial. So kept for later, for now you have to set all custom normals at once.
- Not sure where/how to expose this in py API, for now just added a func in bmesh.utils.
-rw-r--r-- | source/blender/blenkernel/BKE_mesh.h | 3 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/mesh_evaluate.c | 4 | ||||
-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_utils.c | 126 |
5 files changed, 468 insertions, 3 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..2c448aaa109 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'). @@ -1370,6 +1367,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/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_utils.c b/source/blender/python/bmesh/bmesh_py_utils.c index 89c196dbcad..266a9250dc1 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" @@ -799,6 +802,128 @@ static PyObject *bpy_bm_utils_loop_separate(PyObject *UNUSED(self), BPy_BMLoop * } +PyDoc_STRVAR(bpy_bm_utils_custom_normals_set_doc, +".. method:: custom_normals_set(bm, normals)\n" +"\n" +" Set given normals into bmesh (can be per-loop or per-vertex normals).\n" +"\n" +" :arg bm: The bmesh to set custom normals.\n" +" :type bm: :class:`bmesh.types.BMesh`\n" +" :arg normals: Normals to set (for each loop or each vertex, use null vectors or None to use default normal).\n" +" :type normals: sequence of None and/or float triplets, either one per loop or one per vertex\n" +" :return: The normals (with 'auto' ones set to their actual values).\n" +" :rtype: sequence of float triplets, either one per loop or one per vertex\n" +); +static PyObject *bpy_bm_utils_custom_normals_set(PyObject *UNUSED(self), PyObject *args, PyObject *kw) +{ + static const char *kwlist[] = {"bm", "normals", NULL}; + + BPy_BMesh *py_bm; + PyObject *py_nors = NULL; + + float (*nors)[3]; + Py_ssize_t nbr_nors; + int loop_clnors_offset; + + BMesh *bm; + + if (!PyArg_ParseTupleAndKeywords( + args, kw, + "O!O:custom_normals_set", (char **)kwlist, + &BPy_BMesh_Type, &py_bm, + &py_nors)) + { + return NULL; + } + + bm = py_bm->bm; + + py_nors = PySequence_Fast(py_nors, "normals must be an iterable"); + if (!py_nors) { + return NULL; + } + + nbr_nors = PySequence_Fast_GET_SIZE(py_nors); + + if (!ELEM(nbr_nors, bm->totloop, bm->totvert)) { + PyErr_Format(PyExc_TypeError, "custom_normals_set: There must be either one normal per vertex or one per loop"); + Py_DECREF(py_nors); + return NULL; + } + + nors = MEM_mallocN(sizeof(*nors) * nbr_nors, __func__); + for (Py_ssize_t i = 0; i < nbr_nors; i++) { + PyObject *py_vec = PySequence_Fast_GET_ITEM(py_nors, i); + + if (py_vec == Py_None) { + zero_v3(nors[i]); + } + else { + py_vec = PySequence_Fast(py_vec, ""); + if (!py_vec || PySequence_Fast_GET_SIZE(py_vec) != 3) { + PyErr_Format(PyExc_TypeError, + "custom_normals_set: normals are expected to be triplets of floats, normal %d is not", i); + MEM_freeN(nors); + Py_DECREF(py_nors); + Py_XDECREF(py_vec); + return NULL; + } + + for (int j = 0; j < 3; j++) { + PyObject *py_float = PyNumber_Float(PySequence_Fast_GET_ITEM(py_vec, j)); + + if (!py_float || !PyFloat_Check(py_float)) { + PyErr_Format(PyExc_TypeError, + "custom_normals_set: normals are expected to be triplets of floats, normal %d is not", i); + MEM_freeN(nors); + Py_DECREF(py_nors); + Py_DECREF(py_vec); + Py_XDECREF(py_float); + return NULL; + } + + nors[i][j] = (float)PyFloat_AS_DOUBLE(py_float); + Py_DECREF(py_float); + } + normalize_v3(nors[i]); /* Just in case... */ + } + Py_DECREF(py_vec); + } + Py_DECREF(py_nors); + + if ((loop_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL)) == -1) { + BM_data_layer_add(bm, &bm->ldata, CD_CUSTOMLOOPNORMAL); + loop_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL); + + if (loop_clnors_offset == -1) { + PyErr_Format(PyExc_TypeError, + "custom_normals_set: Impossible to add a custom normal data layer to the bmesh"); + MEM_freeN(nors); + return NULL; + } + } + + if (nbr_nors == bm->totloop) { + BM_loops_normal_custom_set(bm, nors, NULL, loop_clnors_offset); + } + else { + BM_loops_normal_custom_set_from_vertices(bm, nors, NULL, loop_clnors_offset); + } + + py_nors = PyTuple_New(nbr_nors); + for (Py_ssize_t i = 0; i < nbr_nors; i++) { + PyObject *py_vec = PyTuple_Pack(3, + PyFloat_FromDouble((double)nors[i][0]), + PyFloat_FromDouble((double)nors[i][1]), + PyFloat_FromDouble((double)nors[i][2])); + + PyTuple_SET_ITEM(py_nors, i, py_vec); + } + + MEM_freeN(nors); + return py_nors; +} + static struct PyMethodDef BPy_BM_utils_methods[] = { {"vert_collapse_edge", (PyCFunction)bpy_bm_utils_vert_collapse_edge, METH_VARARGS, bpy_bm_utils_vert_collapse_edge_doc}, {"vert_collapse_faces", (PyCFunction)bpy_bm_utils_vert_collapse_faces, METH_VARARGS, bpy_bm_utils_vert_collapse_faces_doc}, @@ -813,6 +938,7 @@ static struct PyMethodDef BPy_BM_utils_methods[] = { {"face_vert_separate", (PyCFunction)bpy_bm_utils_face_vert_separate, METH_VARARGS, bpy_bm_utils_face_vert_separate_doc}, {"face_flip", (PyCFunction)bpy_bm_utils_face_flip, METH_O, bpy_bm_utils_face_flip_doc}, {"loop_separate", (PyCFunction)bpy_bm_utils_loop_separate, METH_O, bpy_bm_utils_loop_separate_doc}, + {"custom_normals_set", (PyCFunction)bpy_bm_utils_custom_normals_set, METH_VARARGS | METH_KEYWORDS, bpy_bm_utils_custom_normals_set_doc}, {NULL, NULL, 0, NULL} }; |