英文:
Accessing C pointers to vertices in Blender's Python API
问题
I'm currently making a render engine in C and C++ for Blender. I want to access the vertices of a mesh from C via a pointer, to reduce the time spent in Python and avoid unneeded data duplication.
I am aware that objects derived from the ID
class, there is an as_pointer()
method available. However, when I tried to use as_pointer()
on the vertices collection of a mesh like so:
mesh = bpy.context.object.data
pointer = mesh.vertices.as_pointer()
I received an error stating that bpy_prop_collection
object has no attribute as_pointer
, which makes sense as bpy_prop_collection
isn't derived from ID
.
The documentation mentions states that the type of vertices
is "MeshVertices bpy_prop_collection of MeshVertex, (readonly)" (doc), and MeshVertices
should be abe to return a pointer, but this isn't the type of neither vertices
nor it elements.
As a workaround, I've been retrieving the vertices data into a numpy array which is then passed onto my C library, as shown in the following example code:
import bpy
import numpy as np
import ctypes
obj = bpy.context.object # Suppose 'obj' is the mesh object
mesh = obj.data
# Allocate an array and copy the data, then set the pointer
# of the struct to the array
vert_array = np.zeros((len(mesh.vertices) * 3), dtype=np.float32)
mesh.vertices.foreach_get("co", vert_array)
vert_ptr = vert_array.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
# Pass the pointer
lib = ctypes.CDLL("bin/libengine.so")
lib.load_vert.argtypes = [ctypes.POINTER(ctypes.float)]
lib.load_vert(vert_ptr)
However, this approach duplicates the vertex data in memory (once in Blender's internal data structures, and once in the numpy array) and require processing which could be avoided.
I've looked into Blender's source code and noticed that the underlying C/C++ API does allow direct memory access. By looking at the BKE_mesh_vert_positions
and CustomData_get_layer_named
functions, we see that the vertices are stored in a contiguous data block:
BLI_INLINE const float (*BKE_mesh_vert_positions(const Mesh *mesh))[3]
{
return (const float(*)[3])CustomData_get_layer_named(&mesh->vdata, CD_PROP_FLOAT3, "position");
}
const void *CustomData_get_layer_named(const CustomData *data,
const eCustomDataType type,
const char *name)
{
int layer_index = CustomData_get_named_layer_index(data, type, name);
if (layer_index == -1) {
return nullptr;
}
return data->layers[layer_index].data;
}
This means that we could have, at least in theory, a pointer to the data.
Is there a method in the Python API to expose these pointers or a way to work with the C/C++ API from Python to get this memory directly, without having to compile a custom version of Blender?
Any guidance on how to directly access these pointers or alternative solutions that avoid memory duplication would be highly appreciated.
英文:
I'm currently making a render engine in C and C++ for Blender. I want to access the vertices of a mesh from C via a pointer, to reduce the time spent in Python and avoid unneeded data duplication.
I am aware that objects derived from the ID
class, there is an as_pointer()
method available. However, when I tried to use as_pointer()
on the vertices collection of a mesh like so:
mesh = bpy.context.object.data
pointer = mesh.vertices.as_pointer()
I received an error stating that bpy_prop_collection
object has no attribute as_pointer
, which makes sense as bpy_prop_collection
isn't derived from ID
.
The documentation mentions states that the type of vertices
is "MeshVertices bpy_prop_collection of MeshVertex, (readonly)" (doc), and MeshVertices
should be abe to return a pointer, but this isn't the type of neither vertices
nor it elements.
As a workaround, I've been retrieving the vertices data into a numpy array which is then passed onto my C library, as shown in the following example code:
import bpy
import numpy as np
import ctypes
obj = bpy.context.object # Suppose 'obj' is the mesh object
mesh = obj.data
# Allocate an array and copy the data, then set the pointer
# of the struct to the array
vert_array = np.zeros((len(mesh.vertices) * 3), dtype=np.float32)
mesh.vertices.foreach_get("co", vert_array)
vert_ptr = vert_array.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
# Pass the pointer
lib = ctypes.CDLL("bin/libengine.so")
lib.load_vert.argtypes = [ctypes.POINTER(ctypes.float)]
lib.load_vert(vert_ptr)
However, this approach duplicates the vertex data in memory (once in Blender's internal data structures, and once in the numpy array) and require processing which could be avoided.
I've looked into Blender's source code and noticed that the underlying C/C++ API does allow direct memory access. By looking at the BKE_mesh_vert_positions
and CustomData_get_layer_named
functions, we see that the vertices are stored in a contiguous data block:
BLI_INLINE const float (*BKE_mesh_vert_positions(const Mesh *mesh))[3]
{
return (const float(*)[3])CustomData_get_layer_named(&mesh->vdata, CD_PROP_FLOAT3, "position");
}
const void *CustomData_get_layer_named(const CustomData *data,
const eCustomDataType type,
const char *name)
{
int layer_index = CustomData_get_named_layer_index(data, type, name);
if (layer_index == -1) {
return nullptr;
}
return data->layers[layer_index].data;
}
This means that we could have, at least in theory, a pointer to the data.
Is there a method in the Python API to expose these pointers or a way to work with the C/C++ API from Python to get this memory directly, without having to compile a custom version of Blender?
Any guidance on how to directly access these pointers or alternative solutions that avoid memory duplication would be highly appreciated.
答案1
得分: 1
ctypes
模块可以从Python Vertex对象列表创建一个与C兼容的数组。但是你也可以直接获取第一个顶点的指针mesh.vertices[0].as_pointer()
,因为第一个元素的指针也是整个数组的指针。我在Windows 11上测试过,但在Linux上也应该可以工作。我使用Ubuntu应用程序和交叉编译器命令x86_64-w64-mingw32-gcc -shared -o print_pointer.dll print_pointer.c
编译了以下C代码print_pointer.c
。我假设你使用Linux,并使用gcc -shared -fPIC -o print_pointer.so print_pointer.c
编译它。
以下是Python脚本:
import bpy
import ctypes
lib = ctypes.CDLL('/path/to/dll_or_so/print_pointer.dll') # 在你的情况下是 .so 文件
class VertexData(ctypes.Structure):
_fields_ = [("data", ctypes.POINTER(ctypes.c_float)), ("num_vertices", ctypes.c_int)]
obj = bpy.context.object
mesh = obj.data
num_vertices = len(mesh.vertices) * 3
ptr = mesh.vertices[0].as_pointer()
ptr_as_float = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_float))
vertex_data = VertexData(ptr_as_float, num_vertices)
lib.print_pointer(ctypes.byref(vertex_data))
英文:
The ctypes
module can create a C-compatible array from a Python list of Vertex objects. But you can also just take the pointer of the first vertex mesh.vertices[0].as_pointer()
because the pointer to the first element is also the pointer to the entire array. I tested this on Windows 11 but it should also work on Linux. I compiled the following C code print_pointer.c
using the Ubuntu app with cross-compiler command x86_64-w64-mingw32-gcc -shared -o print_pointer.dll print_pointer.c
. I assume you are on Linux and compiled it with gcc -shared -fPIC -o print_pointer.so print_pointer.c
#include <stdio.h>
typedef struct {
float* data;
int num_vertices;
} VertexData;
void print_pointer(VertexData* vertex_data) {
printf("Vertices:\n");
for (int i = 0; i < vertex_data->num_vertices; i += 3) {
float* vertex = vertex_data->data + i;
printf("Vertex %d: (%f, %f, %f)\n", i / 3, vertex[0], vertex[1], vertex[2]);
}
}
Here's the Python script:
import bpy
import ctypes
lib = ctypes.CDLL('/path/to/dll_or_so/print_pointer.dll') # in your case your .so
class VertexData(ctypes.Structure):
_fields_ = [("data", ctypes.POINTER(ctypes.c_float)), ("num_vertices", ctypes.c_int)]
obj = bpy.context.object
mesh = obj.data
num_vertices = len(mesh.vertices) * 3
ptr = mesh.vertices[0].as_pointer()
ptr_as_float = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_float))
vertex_data = VertexData(ptr_as_float, num_vertices)
lib.print_pointer(ctypes.byref(vertex_data))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论