Python
C++
Blender

BlenderのC++アドオンで高速にデータを受け渡す (LuxBlendにおける手法)

More than 1 year has passed since last update.

LuxRenderのBlender版アドオンであるLuxBlendにはダイナミックライブラリ版のLuxCoreが存在し、高速なデータの受け渡しに対応しています。この仕組みをちょっと解説します。
なお、ソースコードのライセンスはLuxBlendがGPL、LuxRaysがApache Licenseであるため、コードの引用は最小限に留めておきます。

まず、BlenderのPython構造体のベースクラスであるbpy_structにはas_pointer関数があり、多くのデータのポインターを取得することができます。
https://docs.blender.org/api/2.79/bpy.types.bpy_struct.html

as_pointer()

Returns the memory address which holds a pointer to blenders internal data

Returns: int (memory address).
Return type: int

Note This is intended only for advanced script writers who need to pass blender data to their own C/Python modules.

例えば、Blenderのコンソールでbpy.context.active_object.as_pointer()と打つと、アクティブオブジェクトのポインタを取って来れます。

>>> bpy.context.active_object.as_pointer()
140128802162184

LuxBlendでは、このas_pointer関数を使ってメッシュや頂点データのポインタを取得して、C++で実装されたLuxRays側のメンバー関数に渡しています。
https://bitbucket.org/luxrender/luxblend25/src/10dd12c045d79530acf763642af28e1180903e11/src/luxrender/export/luxcore/meshes.py?at=default&fileviewer=file-view-default

meshes.py
[...]

    def __export_mesh_to_shape(self, name, mesh, luxcore_scene):
        faces = mesh.tessfaces[0].as_pointer()
[...]

        mesh_definitions = luxcore_scene.DefineBlenderMesh(name, len(mesh.tessfaces), faces, len(mesh.vertices),
                                                                 vertices, texCoords, vertexColors, transformation)
[...]

LuxRaysのC++側では、Boost.Pythonを使ってPythonのメンバー関数を定義しています。
https://bitbucket.org/luxrender/luxrays/src/ceb10f7963250be95af709f98633907c13da7830/src/luxcore/pyluxcore.cpp?at=default&fileviewer=file-view-default

pyluxcore.cpp
[...]
#include <boost/python.hpp>
[...]
BOOST_PYTHON_MODULE(pyluxcore) {
[...]
    class_<luxcore::detail::SceneImpl>("Scene", init<optional<float> >())
[...]
        .def("DefineBlenderMesh", &blender::Scene_DefineBlenderMesh1)
        .def("DefineBlenderMesh", &blender::Scene_DefineBlenderMesh2)
[...]
}
[...]

関数の実体では、ポインタをsize_tで受けて、それを実際の構造体のポインタ型にキャストしています。構造体の定義はBlender側のヘッダを使わず、独自に定義していますね。
https://bitbucket.org/luxrender/luxrays/src/ceb10f7963250be95af709f98633907c13da7830/src/luxcore/pyluxcoreforblender.cpp?at=default&fileviewer=file-view-default

pyluxcoreforblender.cpp
[...]

struct MFace {
    u_int v[4];
    short mat_nr;
    char edcode, flag;
};

[...]

boost::python::list Scene_DefineBlenderMesh1(luxcore::detail::SceneImpl *scene, const string &name,
        const size_t blenderFaceCount, const size_t blenderFacesPtr,
        const size_t blenderVertCount, const size_t blenderVerticesPtr,
        const size_t blenderUVsPtr, const size_t blenderColsPtr,
        const boost::python::object &transformation) {
[...]
    MFace *blenderFaces = reinterpret_cast<MFace *>(blenderFacesPtr);
[...]
}

この構造体の定義がBlender側ではどこにあるかというと、makesdnaディレクトリの下にあるようです。
https://git.blender.org/gitweb/gitweb.cgi/blender.git/tree/HEAD:/source/blender/makesdna/

DNA_meshdata_types.h
 /*tessellation face, see MLoop/MPoly for the real face data*/
typedef struct MFace {
        unsigned int v1, v2, v3, v4;
        short mat_nr;
        char edcode, flag;  /* we keep edcode, for conversion to edges draw flags in old files */
} MFace; 

TODO: bpy_prop_collectionについて纏める?

追記
この記事はMeshSync for Blender開発中のi_saintさんの下記ツイートを見て即席で書いたものですが、
https://twitter.com/i_saint/status/947955269375377408
https://twitter.com/i_saint/status/947955773841145856

既に別の解法を見つけたとのこと。
https://twitter.com/i_saint/status/948227910380294145
https://github.com/unity3d-jp/MeshSync/blob/e255f9c/Plugin/MeshSyncClientBlender/msbContext.cpp#L252

msbContext.cpp
void msbContext::extractMeshData(ms::MeshPtr dst_, py::object src_)
{
    auto rna = (BPy_StructRNA*)src_.ptr();
    auto& obj = *(Object*)rna->ptr.id.data;
    auto& src = *(Mesh*)obj.data;
[...]
}

https://github.com/unity3d-jp/MeshSync/blob/e255f9c/Plugin/MeshSyncClientBlender/python/unity_mesh_sync.py

unity_mesh_sync.py
def msb_add_mesh(ctx, obj):
[...]
        ctx.extractMeshData(dst, obj)
[...]

C++11とPythonのやりとりにはpybind11を使用しており、C++11側はPythonオブジェクトをpy::objectで直接受け取って、そこからポインタを得ている模様。namespace py = pybind11;されているので、py::objectはpybind11::objectクラスのことで、この辺に定義されている。
https://github.com/pybind/pybind11/blob/086d53e8c66a84d0ec723d5435918c76edd878e8/include/pybind11/pytypes.h

派生先→派生元の関係は、pybind11::object→pybind11::handle→pybind11::detail::object_api<handle>→pybind11::detail::pyobject_tag。src_.ptr()はPyObjectのポインタを返していて、それをBPy_StructRNAのポインタにキャストしている。
https://git.blender.org/gitweb/gitweb.cgi/blender.git/blob/HEAD:/source/blender/python/intern/bpy_rna.h

bpy_rna.h
 typedef struct {
        PyObject_HEAD /* required python macro   */
#ifdef USE_WEAKREFS
        PyObject *in_weakreflist;
#endif
        PointerRNA ptr;
#ifdef USE_PYRNA_STRUCT_REFERENCE
        /* generic PyObject we hold a reference to, example use:
         * hold onto the collection iterator to prevent it from freeing allocated data we may use */
        PyObject *reference;
#endif /* !USE_PYRNA_STRUCT_REFERENCE */

#ifdef PYRNA_FREE_SUPPORT
        bool freeptr;  /* needed in some cases if ptr.data is created on the fly, free when deallocing */
#endif /* PYRNA_FREE_SUPPORT */
} BPy_StructRNA;

BPy_StructRNAはPyObjectの上位互換で、RNAへのポインタ構造体であるPointerRNAを持っている。

https://git.blender.org/gitweb/gitweb.cgi/blender.git/blob/HEAD:/source/blender/makesrna/RNA_types.h

RNA_types.h
/** Pointer
 *
 * RNA pointers are not a single C pointer but include the type,
 * and a pointer to the ID struct that owns the struct, since
 * in some cases this information is needed to correctly get/set
 * the properties and validate them. */

typedef struct PointerRNA {
        struct {
                void *data;
        } id;

        struct StructRNA *type;
        void *data;
} PointerRNA;

https://git.blender.org/gitweb/gitweb.cgi/blender.git/blob/HEAD:/source/blender/makesrna/intern/rna_rna.c
PointerRNA.typeは&RNA_Property (値型) や&RNA_Struct (構造体型) などが入ってる模様。
PointerRNA.dataは、typeが前者ならPropertyRNA*型で、typeが後者ならStructRNA*型な模様。
PointerRNA.id.dataは、PointerRNA.dataがID structの時に、その参照先のデータのポインタが入る模様。ID structというのはオブジェクトやマテリアルなどのBlender内部で参照として扱う型のこと…だと思う。ID structの一覧は下にある。
https://git.blender.org/gitweb/gitweb.cgi/blender.git/blob/HEAD:/source/blender/makesrna/intern/rna_ID.c

…RNA周りは、やりたいことは分かるけど若干怪しげ (コンテキスト周りからしてアレだと思う)。