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: intNote 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
[...]
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
[...]
#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
[...]
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/
/*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
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;
[...]
}
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
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を持っている。
/** 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周りは、やりたいことは分かるけど若干怪しげ (コンテキスト周りからしてアレだと思う)。