Exporting Triangles API Challenges

I’m having difficulty scripting the export of a mesh to simple triangles. This seems to have been an easier task in older versions of Blender, but the change to polygons appears to have left this capability not well-defined.

Since 2.63, NGons have been used and there is some documentation here: https://docs.blender.org/api/blender_python_api_2_63_2/info_gotcha.html#ngons-and-tessellation-faces

It states:Since 2.63 NGons are supported, this adds some complexity since in some cases you need to access triangles still (some exporters for example).

Ironically, this is exactly what I’d like to do (export triangles). I’m pretty much stuck at the code below:

import bpy
obj = bpy.context.active_object
mesh = obj.data
mesh.update(True, True)

Any assistance you can provide is appreciated.

You can transform the object into bmesh and use bmesh.calc_tessfaces.
Here is a code in which I tested the time of some ways to get triangles:


import ctypes    
class C_BMHeader(ctypes.Structure):
    _fields_ = [
        ("data", ctypes.c_void_p),
        ("index", ctypes.c_int),
        ("htype", ctypes.c_char),
        ("hflag", ctypes.c_char),
        ("api_flag", ctypes.c_char),
    ]


class C_BMLoop(ctypes.Structure):
    pass


C_BMLoop._fields_ = [
    ("head", C_BMHeader),
    ("v", ctypes.POINTER(C_BMHeader)),
    ("e", ctypes.POINTER(C_BMHeader)),
    ("f", ctypes.POINTER(C_BMHeader)),
    ("radial_next", ctypes.POINTER(C_BMLoop)),
    ("radial_prev", ctypes.POINTER(C_BMLoop)),
    ("next", ctypes.POINTER(C_BMLoop)),
    ("prev", ctypes.POINTER(C_BMLoop)),
]


def get_looptris_ctypes(me):
    # figure out side of _Py_ssize_t
    if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
        _Py_ssize_t = ctypes.c_int64
    else:
        _Py_ssize_t = ctypes.c_int


    class _PyObject(ctypes.Structure):
        pass


    _PyObject._fields_ = [
        ('ob_refcnt', _Py_ssize_t),
        ('ob_type', ctypes.POINTER(_PyObject)),
    ]


    if object.__basicsize__ != ctypes.sizeof(_PyObject):
        # python with trace
        class _PyObject(ctypes.Structure):
            _fields_ = [
                ('_ob_next', ctypes.POINTER(_PyObject)),
                ('_ob_prev', ctypes.POINTER(_PyObject)),
                ('ob_refcnt', _Py_ssize_t),
                ('ob_type', ctypes.POINTER(_PyObject)),
            ]


    class _PyVarObject(_PyObject):
        _fields_ = [
            ('ob_size', _Py_ssize_t),
        ]


    class C_BPy_BMLoop(_PyVarObject):
        _fields_ = [
            ("bm", ctypes.c_void_p),
            ("l", ctypes.POINTER(C_BMLoop)),
        ]
    #(int)(&((struct Mesh *)0)->edit_btmesh) == 272
    #(int)(&((struct BMEditMesh *)0)->bm) == 0
    #(int)(&((struct BMEditMesh *)0)->tottris) == 32
    #(int)(&((struct BMEditMesh *)0)->looptris) == 24
    edit_btmesh = ctypes.c_void_p.from_address(me.as_pointer() + 272)
    if edit_btmesh.value:
        ctypes.pythonapi._PyObject_New.argtypes = [ctypes.py_object]
        ctypes.pythonapi._PyObject_New.restype = ctypes.py_object


        import bmesh
        py_loop = ctypes.pythonapi._PyObject_New(bmesh.types.BMLoop)
        cpy_loop = C_BPy_BMLoop.from_address(id(py_loop))


        cpy_loop.bm = ctypes.c_void_p.from_address(edit_btmesh.value + 0)


        tottris = ctypes.c_int.from_address(edit_btmesh.value + 32).value
        looptris = ctypes.POINTER(ctypes.POINTER(C_BMLoop) * 3 * tottris).from_address(edit_btmesh.value + 24).contents
        tris = bgl.Buffer(bgl.GL_INT, (tottris, 3))
        for i, ltri in enumerate(looptris):
            cpy_loop.l = ltri[0]
            tris[i][0] = py_loop.vert.index
            cpy_loop.l = ltri[1]
            tris[i][1] = py_loop.vert.index
            cpy_loop.l = ltri[2]
            tris[i][2] = py_loop.vert.index
        return tris


def get_looptris_ctypes2(me):
    #(int)(&((struct Mesh *)0)->edit_btmesh) == 272
    #(int)(&((struct BMEditMesh *)0)->tottris) == 32
    #(int)(&((struct BMEditMesh *)0)->looptris) == 24
    edit_btmesh = ctypes.c_void_p.from_address(me.as_pointer() + 272)
    if edit_btmesh.value:
        tottris = ctypes.c_int.from_address(edit_btmesh.value + 32).value
        looptris = ctypes.POINTER(ctypes.POINTER(C_BMLoop) * 3 * tottris).from_address(edit_btmesh.value + 24).contents
        tris = bgl.Buffer(bgl.GL_INT, (tottris, 3))
        for i, ltri in enumerate(looptris):
            tris[i] = ltri[0].contents.v.contents.index, ltri[1].contents.v.contents.index, ltri[2].contents.v.contents.index
        return tris


def get_looptris_bmesh(me):
    if me.is_editmode:
        bm = bmesh.from_edit_mesh(me)
        looptris = bm.calc_tessface()
        tris = bgl.Buffer(bgl.GL_INT, (len(looptris), 3))
        for i, ltri in enumerate(looptris):
            tris[i] = ltri[0].vert.index, ltri[1].vert.index, ltri[2].vert.index
        return tris


def get_looptris_bmesh_numpy(me):
    if me.is_editmode:
        import numpy as np
        bm = bmesh.from_edit_mesh(me)
        looptris = bm.calc_tessface()


        get_v_index = np.vectorize(lambda l: l.vert.index, otypes = ['i4'], cache = True)
        tris = get_v_index(looptris)


        tris = bgl.Buffer(bgl.GL_INT, tris.shape, tris)
        return tris


def get_looptris_ctypes_numpy(me):
    #(int)(&((struct Mesh *)0)->edit_btmesh) == 272
    #(int)(&((struct BMEditMesh *)0)->tottris) == 32
    #(int)(&((struct BMEditMesh *)0)->looptris) == 24
    edit_btmesh_ptr = ctypes.c_void_p.from_address(me.as_pointer() + 272).value
    if edit_btmesh_ptr:
        bm_ltri = ctypes.POINTER(C_BMLoop) * 3
        tottris = ctypes.c_int.from_address(edit_btmesh_ptr + 32).value
        looptris = ctypes.POINTER(tottris * bm_ltri).from_address(edit_btmesh_ptr + 24).contents


        import numpy as np        
        array = np.frombuffer(looptris, dtype = 'u8')


        get_v_index = np.vectorize(lambda l: C_BMLoop.from_address(l).v.contents.index, otypes = ['i4'], cache = True)
        tris = get_v_index(array)
        tris.shape = (tottris, 3)


        tris = bgl.Buffer(bgl.GL_INT, tris.shape, tris)
        return tris


if __name__ == '__main__':
    import bmesh, bgl, timeit
    import bpy
    me = bpy.context.object.data
    get_looptris_ctypes_numpy(me)


    print("START")
    ##print(timeit.timeit("get_looptris_ctypes(me)", setup="from __main__ import (get_looptris_ctypes, me)", number=5))
    print(timeit.timeit("get_looptris_ctypes2(me)", setup="from __main__ import (get_looptris_ctypes2, me)", number=5))
    print(timeit.timeit("get_looptris_bmesh(me)", setup="from __main__ import (get_looptris_bmesh, me)", number=5))
    print(timeit.timeit("get_looptris_bmesh_numpy(me)", setup="from __main__ import (get_looptris_bmesh_numpy, me)", number=5))
    print(timeit.timeit("get_looptris_ctypes_numpy(me)", setup="from __main__ import (get_looptris_ctypes_numpy, me)", number=5))



If someone has a better solution, I’d be happy to see :wink: