我目前正在用C和C++为Blender制作一个渲染引擎.我想通过指针从C访问网格的顶点,以减少在Python中花费的时间,并避免不必要的数据重复.

我知道从ID类派生的对象有一个as_pointer()方法可用.但是,当我try 对网格的顶点集合使用as_pointer()时,如下所示:

mesh = bpy.context.object.data
pointer = mesh.vertices.as_pointer()

我收到一个错误,指出bpy_prop_collection对象没有属性as_pointer,这是有道理的,因为bpy_prop_collection不是从ID派生的.

文档中提到,vertices的类型是"MeshVerties bPY_PROP_COLLECTION of MeshVertex,(Readonly)"(doc),MeshVertices应该是返回指针的ABE,但这既不是vertices也不是it元素的类型.

作为一种解决办法,我一直将顶点数据检索到一个NumPy数组中,然后将该数组传递到我的C库中,如下面的示例代码所示:

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)

但是,这种方法会复制内存中的顶点数据(一次在Blender的内部数据 struct 中,一次在NumPy数组中),并且需要进行可以避免的处理.

我研究了Blender的源代码,注意到底层的C/C++API确实允许直接内存访问.通过查看BKE_mesh_vert_positionsCustomData_get_layer_named函数,我们可以看到顶点存储在一个连续的数据块中:

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;
}

这意味着,至少在理论上,我们可以有一个指向数据的指针.

在PythonAPI中有没有公开这些指针的方法,或者有没有一种方法可以从Python中使用C/C++API来直接获得内存,而不必编译Blender的定制版本?

任何关于如何直接访问这些指针或避免内存复制的替代解决方案的指导都将受到高度赞赏.

推荐答案

ctypes模块可以从顶点对象的Python列表创建与C兼容的array.但也可以只取第一个顶点mesh.vertices[0].as_pointer()的指针,因为指向第一个元素的指针也是指向整个数组的指针.我在Windows11上测试了这一点,但它应该也能在Linux上运行.我使用带有交叉编译器命令x86_64-w64-mingw32-gcc -shared -o print_pointer.dll print_pointer.c的Ubuntu应用程序编译了以下C代码print_pointer.c.我假设您使用的是Linux,并使用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]);
    }
}

以下是Python脚本:

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: enter image description here

Python相关问答推荐

如何在WTForm中使用back_plumates参考brand_id?

不同数据类型的Python成员变量不会在具有相同优先级的不同线程中更新

Flask:如何在完整路由代码执行之前返回验证

在Python中使用一行try

在两极中实施频率编码

如何观察cv2.erode()的中间过程?

如何在vercel中指定Python运行时版本?

ambda将时间戳与组内另一列的所有时间戳进行比较

具有症状的分段函数:如何仅针对某些输入值定义函数?

仅从风格中获取 colored颜色 循环

点到面的Y距离

当使用keras.utils.Image_dataset_from_directory仅加载测试数据集时,结果不同

为什么我的Python代码在if-else声明中的行之前执行if-else声明中的行?

对某些列的总数进行民意调查,但不单独列出每列

Gekko:Spring-Mass系统的参数识别

非常奇怪:tzLocal.get_Localzone()基于python3别名的不同输出?

发生异常:TclMessage命令名称无效.!listbox"

如何在python xsModel库中定义一个可选[December]字段,以产生受约束的SON模式

如何将Docker内部运行的mariadb与主机上Docker外部运行的Python脚本连接起来

在pandas/python中计数嵌套类别