Accessing C pointers to vertices in Blender’s Python API.

huangapple go评论78阅读模式
英文:

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 verticesis "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 verticesis "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))

默认立方体的结果:
Accessing C pointers to vertices in Blender’s Python API.

英文:

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))

Result for the Default Cube:
Accessing C pointers to vertices in Blender’s Python API.

huangapple
  • 本文由 发表于 2023年8月5日 02:16:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/76838315.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定