/* SPDX-FileCopyrightText: 2023 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once /** \file * \ingroup bmesh * * #BMesh data structures, used for mesh editing operations * that benefit from accessing connectivity information. */ #include "BLI_assert.h" #include "BLI_sys_types.h" #include "DNA_customdata_types.h" #include "DNA_listBase.h" namespace blender { /* disable holes for now, * these are ifdef'd because they use more memory and can't be saved in DNA currently */ // #define USE_BMESH_HOLES struct BMEditSelection; struct BMEdge; struct BMFace; struct BMLoop; struct BMOpError; struct BMVert; struct BMesh; struct MLoopNorSpaceArray; struct BLI_mempool; // #pragma GCC diagnostic push // #pragma GCC diagnostic error "-Wpadded" /** * #BMHeader * * All mesh elements begin with a #BMHeader. This structure * hold several types of data * * 1: The type of the element (vert, edge, loop or face) * 2: Persistent "header" flags/markings (smooth, seam, select, hidden, etc) * note that this is different from the "tool" flags. * 3: Unique ID in the #BMesh. * 4: some elements for internal record keeping. */ struct BMHeader { /* NOTE: it its essential the #BMHeader is at least the size of two pointers. * This is a requirement of mempool's method of iteration. * * Even though there is only a single pointer, the struct will be padded to two. */ /** CustomData layers. */ void *data; /** * \note * - Use BM_elem_index_get/set macros for index * - Uninitialized to -1 so we can easily tell its not set. * - Used for edge/vert/face/loop, check BMesh.elem_index_dirty for valid index values, * this is abused by various tools which set it dirty. * - For loops this is used for sorting during tessellation. */ int index; /** Element geometric type (verts/edges/loops/faces). */ char htype; /** This would be a CD layer, see below. */ char hflag; /** * Internal use only! * \note We are very picky about not bloating this struct * but in this case its padded up to 16 bytes anyway, * so adding a flag here gives no increase in size. */ char api_flag; // char _pad; }; BLI_STATIC_ASSERT((sizeof(BMHeader) <= 16), "BMHeader size has grown!"); /* NOTE: need some way to specify custom locations for custom data layers. so we can * make them point directly into structs. and some way to make it only happen to the * active layer, and properly update when switching active layers. */ struct BMVert { BMHeader head; /** Vertex coordinate. */ float co[3]; /** Vertex normal. */ float no[3]; /** * Pointer to (any) edge using this vertex (for disk cycles). * * \note Some higher level functions set this to different edges that use this vertex, * which is a bit of an abuse of internal #BMesh data but also works OK for now * (use with care!). */ struct BMEdge *e; }; struct BMVert_OFlag { BMVert base; struct BMFlagLayer *oflags; }; /** * Disk link structure (the element in a circular linked list), * only used by edges to reference connected edges for the first & second vertices. */ struct BMDiskLink { struct BMEdge *next, *prev; }; struct BMEdge { BMHeader head; /** * Vertices (unordered), * * Although the order can be used at times, * when extruding a face from a wire-edge for example. * * Operations that create/subdivide edges shouldn't flip the order * unless there is a good reason to do so. */ BMVert *v1, *v2; /** * The list of loops around the edge, see docstring for #BMLoop.radial_next * for an example of using this to loop over all faces used by an edge. */ struct BMLoop *l; /** * Disk Cycle Pointers * * relative data: d1 indicates the next/prev * edge around vertex v1 and d2 does the same for v2. */ BMDiskLink v1_disk_link, v2_disk_link; }; struct BMEdge_OFlag { BMEdge base; struct BMFlagLayer *oflags; }; struct BMLoop { BMHeader head; /* Notice no #BMFlagLayer, making this different from other elements. */ /** * The vertex this loop points to. * * - This vertex must be unique within the cycle. */ struct BMVert *v; /** * The edge this loop uses. * * Vertices (#BMLoop.v & #BMLoop.next.v) always contain vertices from (#BMEdge.v1 & #BMEdge.v2). * Although no assumptions can be made about the order, * as this isn't meaningful for mesh topology. * * - This edge must be unique within the cycle (defined by #BMLoop.next & #BMLoop.prev links). */ struct BMEdge *e; /** * The face this loop is part of. * * - This face must be shared by all within the cycle. * Used as a back-pointer so loops can know the face they define. */ struct BMFace *f; /** * Other loops connected to this edge. * * This is typically use for accessing an edges faces, * however this is done by stepping over it's loops. * * - This is a circular list, so there are no first/last storage of the "radial" data. * Instead #BMEdge.l points to any one of the loops that use it. * * - Since the list is circular, the particular loop referenced doesn't matter, * as all other loops can be accessed from it. * * - Every loop in this radial list has the same value for #BMLoop.e. * * - The value for #BMLoop.v might not match the radial next/previous * as this depends on the face-winding. * You can be sure #BMLoop.v will either #BMEdge.v1 or #BMEdge.v2 of #BMLoop.e, * * - Unlike face-winding (which defines if the direction the face points), * next and previous are insignificant. The list could be reversed for example, * without any impact on the topology. * * This is an example of looping over an edges faces using #BMLoop.radial_next. * * \code{.c} * BMLoop *l_iter = edge->l; * do { * operate_on_face(l_iter->f); * } while ((l_iter = l_iter->radial_next) != edge->l); * \endcode */ struct BMLoop *radial_next, *radial_prev; /** * Other loops that are part of this face. * * This is typically used for accessing all vertices/edges in a faces. * * - This is a circular list, so there are no first/last storage of the "cycle" data. * Instead #BMFace.l_first points to any one of the loops that are part of this face. * * - Since the list is circular, the particular loop referenced doesn't matter, * as all other loops can be accessed from it. * * - Every loop in this "cycle" list has the same value for #BMLoop.f. * * - The direction of this list defines the face winding. * Reversing the list flips the face. * * This is an example loop over all vertices and edges of a face. * * \code{.c} * BMLoop *l_first, *l_iter; * l_iter = l_first = BM_FACE_FIRST_LOOP(f); * do { * operate_on_vert(l_iter->v); * operate_on_edge(l_iter->e); * } while ((l_iter = l_iter->next) != l_first); * \endcode */ struct BMLoop *next, *prev; }; /** * A struct which only (#BMFace, #BMEdge, #BMVert) can be cast to. * But *not* #BMLoop, since these don't have a flag layer. */ struct BMElemF { BMHeader head; }; /** * A struct which any element type can be cast to: * (#BMFace, #BMLoop, #BMEdge, #BMVert). */ struct BMElem { BMHeader head; }; #ifdef USE_BMESH_HOLES /** * NOTE(@ideasman42): this structure was planned for supporting holes in faces. * although there are no near term plans for this. */ struct BMLoopList { struct BMLoopList *next, *prev; struct BMLoop *first, *last; }; #endif struct BMFace { BMHeader head; #ifdef USE_BMESH_HOLES /** Total boundaries, is one plus the number of holes in the face. */ int totbounds; ListBaseT loops; #else BMLoop *l_first; #endif /** * Number of vertices in the face * (the length of #BMFace.l_first circular linked list). */ int len; /** * Face normal, see #BM_face_calc_normal. */ float no[3]; /** * Material index, typically >= 0 and < #Mesh.totcol although this isn't enforced * Python for example can set this to any positive value since scripts may create * mesh data first and setup material slots later. * * When using to index into a material array it's range should be checked first, * values exceeding the range should be ignored or treated as zero * (if a material slot needs to be used - when drawing for example) */ short mat_nr; // short _pad[3]; }; struct BMFace_OFlag { BMFace base; struct BMFlagLayer *oflags; }; struct BMFlagLayer { short f; /* flags */ }; // #pragma GCC diagnostic pop struct BMesh { int totvert = 0; int totedge = 0; int totloop = 0; int totface = 0; int totvertsel = 0; int totedgesel = 0; int totfacesel = 0; /** * Flag index arrays as being dirty so we can check if they are clean and * avoid looping over the entire vert/edge/face/loop array in those cases. * valid flags are: `(BM_VERT | BM_EDGE | BM_FACE | BM_LOOP)` */ char elem_index_dirty = 0; /** * Flag array table as being dirty so we know when its safe to use it, * or when it needs to be re-created. */ char elem_table_dirty = 0; /** Element pools. */ BLI_mempool *vpool = nullptr; BLI_mempool *epool = nullptr; BLI_mempool *lpool = nullptr; BLI_mempool *fpool = nullptr; /* #BLI_mempool lookup tables (optional). * Map indices to elements via #BM_mesh_elem_table_ensure and associated functions. * Don't touch this or read it directly. * Use #BM_mesh_elem_table_ensure(), `BM_vert/edge/face_at_index()`. */ /** Vertex table. */ BMVert **vtable = nullptr; /** Edge table. */ BMEdge **etable = nullptr; /** Face table. */ BMFace **ftable = nullptr; /* Size of allocated tables. */ int vtable_tot = 0; int etable_tot = 0; int ftable_tot = 0; /** Operator API stuff (must be all null or all allocated). */ BLI_mempool *vtoolflagpool = nullptr; BLI_mempool *etoolflagpool = nullptr; BLI_mempool *ftoolflagpool = nullptr; bool use_toolflags = false; /** * Used when the UV select sync tool-setting is enabled (see: #UV_FLAG_SELECT_SYNC). * * When true, UV selection flags are "valid" (see: #BM_ELEM_SELECT_UV & #BM_ELEM_SELECT_UV_EDGE). * Otherwise UV selection is read from vertex/edge/face selection flags used in the viewport. * * Notes: * - This should be cleared aggressively when there is no need * to store a separate UV selection to avoid unnecessary overhead. * - Clear using #BM_mesh_uvselect_clear (instead of setting directly). * * - See `bmesh_uvselect.hh` for a more comprehensive explanation. */ bool uv_select_sync_valid = false; int toolflag_index = 0; CustomData vdata; CustomData edata; CustomData ldata; CustomData pdata; #ifdef USE_BMESH_HOLES BLI_mempool *looplistpool = nullptr; #endif struct MLoopNorSpaceArray *lnor_spacearr = nullptr; char spacearr_dirty = 0; /** * Should be copy of scene select mode. * * NOTE(@ideasman42): Stored in #BMEditMesh too, a bit confusing, make sure they're in sync! * Only use when the edit mesh can't be accessed. */ short selectmode = 0; /** 1-based index of the shape key's #Key::block this #BMesh came from. */ int shapenr = 0; int totflags = 0; ListBaseT selected = {}; /** * The active face. * This is kept even when unselected, mainly so UV editing can keep showing the * active faces image while the selection is being modified in the 3D viewport. * * Without this the active image in the UV editor would flicker in a distracting way * while changing selection in the 3D viewport. */ BMFace *act_face = nullptr; /** List of #BMOpError, used for operator error handling. */ ListBaseT errorstack = {}; /** * Keep a single reference to the Python instance of this #BMesh (if any exists). * * This allows save invalidation of a #BMesh when it's freed, * so the Python object will report it as having been removed, * instead of crashing on invalid memory access. * * Doesn't hold a #PyObject reference, cleared when the last object is de-referenced. */ void *py_handle = nullptr; }; /** #BMHeader.htype (char) */ enum { BM_VERT = 1, BM_EDGE = 2, BM_LOOP = 4, BM_FACE = 8, }; struct BMLoopNorEditData { int loop_index; BMLoop *loop; float niloc[3]; float nloc[3]; float *loc; short *clnors_data; }; 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; }; #define BM_ALL (BM_VERT | BM_EDGE | BM_LOOP | BM_FACE) #define BM_ALL_NOLOOP (BM_VERT | BM_EDGE | BM_FACE) /** #BMesh.spacearr_dirty */ 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 *, BMVert_OFlag *, BMEdge_OFlag *, BMFace_OFlag *, \ BMElem *, BMElemF *, BMHeader * #define _BM_GENERIC_TYPE_ELEM_CONST \ const void *, const BMVert *, const BMEdge *, const BMLoop *, const BMFace *, \ const BMVert_OFlag *, const BMEdge_OFlag *, const BMFace_OFlag *, const BMElem *, \ const BMElemF *, const BMHeader * #define BM_CHECK_TYPE_ELEM_CONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPES_CONST) #define BM_CHECK_TYPE_ELEM_NONCONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_ELEM_NONCONST) #define BM_CHECK_TYPE_ELEM(ele) \ CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_ELEM_NONCONST, _BM_GENERIC_TYPE_ELEM_CONST) /* vert */ #define _BM_GENERIC_TYPE_VERT_NONCONST BMVert *, BMVert_OFlag * #define _BM_GENERIC_TYPE_VERT_CONST const BMVert *, const BMVert_OFlag * #define BM_CHECK_TYPE_VERT_CONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_VERT_CONST) #define BM_CHECK_TYPE_VERT_NONCONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_ELEM_NONCONST) #define BM_CHECK_TYPE_VERT(ele) \ CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_VERT_NONCONST, _BM_GENERIC_TYPE_VERT_CONST) /* edge */ #define _BM_GENERIC_TYPE_EDGE_NONCONST BMEdge *, BMEdge_OFlag * #define _BM_GENERIC_TYPE_EDGE_CONST const BMEdge *, const BMEdge_OFlag * #define BM_CHECK_TYPE_EDGE_CONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_EDGE_CONST) #define BM_CHECK_TYPE_EDGE_NONCONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_ELEM_NONCONST) #define BM_CHECK_TYPE_EDGE(ele) \ CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_EDGE_NONCONST, _BM_GENERIC_TYPE_EDGE_CONST) /* face */ #define _BM_GENERIC_TYPE_FACE_NONCONST BMFace *, BMFace_OFlag * #define _BM_GENERIC_TYPE_FACE_CONST const BMFace *, const BMFace_OFlag * #define BM_CHECK_TYPE_FACE_CONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_FACE_CONST) #define BM_CHECK_TYPE_FACE_NONCONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_ELEM_NONCONST) #define BM_CHECK_TYPE_FACE(ele) \ CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_FACE_NONCONST, _BM_GENERIC_TYPE_FACE_CONST) /** * Assignment from a void* to a typed pointer is not allowed in C++, * casting the LHS to void works fine though. */ #define BM_CHECK_TYPE_ELEM_ASSIGN(ele) (BM_CHECK_TYPE_ELEM(ele)), *((void **)&ele) /** #BMHeader.hflag (char) */ enum { BM_ELEM_SELECT = (1 << 0), BM_ELEM_HIDDEN = (1 << 1), BM_ELEM_SEAM = (1 << 2), /** Used for faces and edges, note from the user POV, this is a sharp edge when disabled. */ BM_ELEM_SMOOTH = (1 << 3), /** * Internal flag, used for ensuring correct normals * during multi-resolution interpolation, and any other time * when temp tagging is handy. * always assume dirty & clear before use. */ BM_ELEM_TAG = (1 << 4), /** * Used for #BMLoop for loop-vertex selection & #BMFace when the face is selected. * The #BMLoop also stores edge selection: #BM_ELEM_SELECT_UV_EDGE. */ BM_ELEM_SELECT_UV = (1 << 5), /** Spare tag, assumed dirty, use define in each function to name based on use. */ BM_ELEM_TAG_ALT = (1 << 6), /** * For low level internal API tagging, * since tools may want to tag verts and not have functions clobber them. * Leave cleared! */ BM_ELEM_INTERNAL_TAG = (1 << 7), }; /* Only for #BMLoop to select an edge. */ #define BM_ELEM_SELECT_UV_EDGE BM_ELEM_SEAM struct BPy_BMGeneric; extern void bpy_bm_generic_invalidate(struct BPy_BMGeneric *self); using BMElemFilterFunc = bool (*)(const BMElem *, void *user_data); using BMVertFilterFunc = bool (*)(const BMVert *, void *user_data); using BMEdgeFilterFunc = bool (*)(const BMEdge *, void *user_data); using BMFaceFilterFunc = bool (*)(const BMFace *, void *user_data); using BMLoopFilterFunc = bool (*)(const BMLoop *, void *user_data); using BMLoopPairFilterFunc = bool (*)(const BMLoop *, const BMLoop *, void *user_data); /* defines */ #define BM_ELEM_CD_SET_INT(ele, offset, f) \ { \ CHECK_TYPE_NONCONST(ele); \ BLI_assert(offset != -1); \ *((int *)((char *)(ele)->head.data + (offset))) = (f); \ } \ (void)0 #define BM_ELEM_CD_GET_INT(ele, offset) \ (BLI_assert(offset != -1), *((int *)((char *)(ele)->head.data + (offset)))) #define BM_ELEM_CD_SET_BOOL(ele, offset, f) \ { \ CHECK_TYPE_NONCONST(ele); \ BLI_assert(offset != -1); \ *((bool *)((char *)(ele)->head.data + (offset))) = (f); \ } \ (void)0 #define BM_ELEM_CD_GET_BOOL(ele, offset) \ (BLI_assert(offset != -1), *((bool *)((char *)(ele)->head.data + (offset)))) #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) # define BM_ELEM_CD_GET_BOOL_P(ele, offset) \ (BLI_assert(offset != -1), \ _Generic(ele, \ GENERIC_TYPE_ANY((bool *)POINTER_OFFSET((ele)->head.data, offset), \ _BM_GENERIC_TYPE_ELEM_NONCONST), \ GENERIC_TYPE_ANY((const bool *)POINTER_OFFSET((ele)->head.data, offset), \ _BM_GENERIC_TYPE_ELEM_CONST))) #else # define BM_ELEM_CD_GET_BOOL_P(ele, offset) \ (BLI_assert(offset != -1), (bool *)((char *)(ele)->head.data + (offset))) #endif #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) # define BM_ELEM_CD_GET_VOID_P(ele, offset) \ (BLI_assert(offset != -1), \ _Generic(ele, \ GENERIC_TYPE_ANY(POINTER_OFFSET((ele)->head.data, offset), \ _BM_GENERIC_TYPE_ELEM_NONCONST), \ GENERIC_TYPE_ANY((const void *)POINTER_OFFSET((ele)->head.data, offset), \ _BM_GENERIC_TYPE_ELEM_CONST))) #else # define BM_ELEM_CD_GET_VOID_P(ele, offset) \ (BLI_assert(offset != -1), (void *)((char *)(ele)->head.data + (offset))) #endif #define BM_ELEM_CD_SET_FLOAT(ele, offset, f) \ { \ CHECK_TYPE_NONCONST(ele); \ BLI_assert(offset != -1); \ *((float *)((char *)(ele)->head.data + (offset))) = (f); \ } \ (void)0 #define BM_ELEM_CD_GET_FLOAT(ele, offset) \ (BLI_assert(offset != -1), *((float *)((char *)(ele)->head.data + (offset)))) #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) # define BM_ELEM_CD_GET_FLOAT_P(ele, offset) \ (BLI_assert(offset != -1), \ _Generic(ele, \ GENERIC_TYPE_ANY((float *)POINTER_OFFSET((ele)->head.data, offset), \ _BM_GENERIC_TYPE_ELEM_NONCONST), \ GENERIC_TYPE_ANY((const float *)POINTER_OFFSET((ele)->head.data, offset), \ _BM_GENERIC_TYPE_ELEM_CONST))) # define BM_ELEM_CD_GET_FLOAT2_P(ele, offset) \ (BLI_assert(offset != -1), \ _Generic(ele, \ GENERIC_TYPE_ANY((float (*)[2])POINTER_OFFSET((ele)->head.data, offset), \ _BM_GENERIC_TYPE_ELEM_NONCONST), \ GENERIC_TYPE_ANY((const float (*)[2])POINTER_OFFSET((ele)->head.data, offset), \ _BM_GENERIC_TYPE_ELEM_CONST))) # define BM_ELEM_CD_GET_FLOAT3_P(ele, offset) \ (BLI_assert(offset != -1), \ _Generic(ele, \ GENERIC_TYPE_ANY((float (*)[3])POINTER_OFFSET((ele)->head.data, offset), \ _BM_GENERIC_TYPE_ELEM_NONCONST), \ GENERIC_TYPE_ANY((const float (*)[3])POINTER_OFFSET((ele)->head.data, offset), \ _BM_GENERIC_TYPE_ELEM_CONST))) #else # define BM_ELEM_CD_GET_FLOAT_P(ele, offset) \ (BLI_assert(offset != -1), (float *)((char *)(ele)->head.data + (offset))) # define BM_ELEM_CD_GET_FLOAT2_P(ele, offset) \ (BLI_assert(offset != -1), (float (*)[2])((char *)(ele)->head.data + (offset))) # define BM_ELEM_CD_GET_FLOAT3_P(ele, offset) \ (BLI_assert(offset != -1), (float (*)[3])((char *)(ele)->head.data + (offset))) #endif #define BM_ELEM_CD_SET_FLOAT2(ele, offset, f) \ { \ CHECK_TYPE_NONCONST(ele); \ BLI_assert(offset != -1); \ ((float *)((char *)(ele)->head.data + (offset)))[0] = (f)[0]; \ ((float *)((char *)(ele)->head.data + (offset)))[1] = (f)[1]; \ } \ (void)0 #define BM_ELEM_CD_SET_FLOAT3(ele, offset, f) \ { \ CHECK_TYPE_NONCONST(ele); \ BLI_assert(offset != -1); \ ((float *)((char *)(ele)->head.data + (offset)))[0] = (f)[0]; \ ((float *)((char *)(ele)->head.data + (offset)))[1] = (f)[1]; \ ((float *)((char *)(ele)->head.data + (offset)))[2] = (f)[2]; \ } \ (void)0 #define BM_ELEM_CD_GET_FLOAT_AS_UCHAR(ele, offset) \ (BLI_assert(offset != -1), (uchar)(BM_ELEM_CD_GET_FLOAT(ele, offset) * 255.0f)) /* Forward declarations. */ #ifdef USE_BMESH_HOLES # define BM_FACE_FIRST_LOOP(p) (((BMLoopList *)((p)->loops.first))->first) #else # define BM_FACE_FIRST_LOOP(p) ((p)->l_first) #endif #define BM_DISK_EDGE_NEXT(e, v) \ (CHECK_TYPE_INLINE(e, BMEdge *), \ CHECK_TYPE_INLINE(v, BMVert *), \ BLI_assert(BM_vert_in_edge(e, v)), \ (((&e->v1_disk_link)[v == e->v2]).next)) #define BM_DISK_EDGE_PREV(e, v) \ (CHECK_TYPE_INLINE(e, BMEdge *), \ CHECK_TYPE_INLINE(v, BMVert *), \ BLI_assert(BM_vert_in_edge(e, v)), \ (((&e->v1_disk_link)[v == e->v2]).prev)) /** * Size to use for stack arrays when dealing with NGons, allocate after this limit is reached. * this value is rather arbitrary. */ #define BM_DEFAULT_NGON_STACK_SIZE 32 /** * Size to use for stack arrays dealing with connected mesh data * verts of faces, edges of vert - etc. * often used with #BM_iter_as_arrayN(). */ #define BM_DEFAULT_ITER_STACK_SIZE 16 /** * Size to use for stack arrays when gathering topology-related data * (e.g. collecting edges, faces, or vertices during mesh operations). * Prefer more specific defines (such as #BM_DEFAULT_NGON_STACK_SIZE) when applicable. */ #define BM_DEFAULT_TOPOLOGY_STACK_SIZE 64 /** Avoid an eternal loop, this value is arbitrary but should not error on valid cases. */ #define BM_LOOP_RADIAL_MAX 10000 #define BM_NGON_MAX 100000 /** Minimum number of elements before using threading. */ #define BM_THREAD_LIMIT 10000 } // namespace blender