Search code examples
pythonc++cpybind11

about pybind11 Return to the c++ array modification problem question


c++

How to return the xyz array without changing the definition of the Tile structure? You can use the subscript to modify the value

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>

namespace py = pybind11;

struct Tile {
    float xyz[3];
};

struct Vector3d
{
    float x;
    float y;
    float z;

    Vector3d(float x, float y, float z) {
        this->x = x;
        this->y = y;
        this->z = z;
    }

    Tile* pTile = 0;
    /*
    struct *p01 = 0;
    struct *p02 = 0;
    ....
    */
};

PYBIND11_MODULE(bbbb, m)
{
    py::class_<Vector3d>(m, "Vector3d")
        .def(py::init<float, float, float>())
        .def_property("x", [](Vector3d& p)->float {
            return p.x;
        }, [](Vector3d &p, float x) {
            p.x = x;
            if (p.pTile)
                p.pTile->xyz[0] = x;
        });

    py::class_<Tile>(m, "Tile")
        .def(py::init <>())
        .def_property("xyz", [](Tile& p)->py::array {
            auto dtype = pybind11::dtype(pybind11::format_descriptor<float>::format());
            return pybind11::array(dtype, { 3 }, { sizeof(float) }, p.xyz, nullptr);
        }, [](Tile& p) {})
        .def_property("vec_xyz", [](Tile& p)->Vector3d {
            Vector3d vec(p.xyz[0], p.xyz[1], p.xyz[2]);
            vec.pTile = &p;
            return vec;
        }, [](Tile& p) {})
        .def("__repr__", [](const Tile &p) { 
            char buff[100] = { 0 };
            sprintf(buff, "x:%f y:%f z:%f", p.xyz[0], p.xyz[1], p.xyz[2]);
            return std::string(buff, strlen(buff));;
        });
}

python

>>> import bbbb
>>> t = bbbb.Tile()
>>> print(t)
x:0.000000 y:0.000000 z:0.000000
>>> t.xyz[0] = 1.5 #Modifying the value does not work
>>> print(t)
x:0.000000 y:0.000000 z:0.000000
>>> t.vec_xyz.x = 2.5 #Values can be modified
>>> print(t)
x:2.500000 y:0.000000 z:0.000000
>>>

How to make t.xyz[0] = 1.5 work without changing the definition of the Tile structure

If there are multiple similar structures, you need to add structure pointers one by one in Vector3D, which is too troublesome

is there a better way than to return to Vector3D


Solution

  • c++

    #include <pybind11/pybind11.h>
    #include <pybind11/numpy.h>
    
    namespace py = pybind11;
    
    struct Tile1 { //Assume that the imported library cannot modify the definition
        float xyz[3];
    };
    
    struct Tile2 { //Assume that the imported library cannot modify the definition
        short xyz[6];
    };
    
    template<typename TT, typename A, typename B>
    struct List {
    
        TT& m_obj;
        B TT::*pp;
    
        List(TT& obj, B TT::*pm) : m_obj(obj), pp(pm) {
    
        }
    
        void set(int i, A v) {
            if (i >= size()) {
                py::pybind11_fail("IndexError: list index out of range");
            }
            (m_obj.*pp)[i] = v;
        }
    
        A get(int i) {
            if (i >= size()) {
                py::pybind11_fail("IndexError: list index out of range");
            }
            return (m_obj.*pp)[i];
        }
    
        int size() {
            return sizeof((m_obj.*pp)) / sizeof((m_obj.*pp)[0]);
        }
    };
    
    #define LIST_DEFINE(TT,A,B) List<TT, A, A[B]>
    #define LIST_DEFINE_TILE1 LIST_DEFINE(Tile1, float, 3)
    #define LIST_DEFINE_TILE2 LIST_DEFINE(Tile2, short, 6)
    #define LIST_DEFINE_NEW(TT,A,B,M) \
    py::class_<LIST_DEFINE(TT,A,B)>(M, "List_"#A"_"#B) \
    .def("__getitem__", [](LIST_DEFINE(TT,A,B)& p, int i) { return p.get(i); }) \
    .def("__setitem__", [](LIST_DEFINE(TT,A,B)& p, int i, A v) { p.set(i, v); }) \
    .def_property_readonly("length", [](LIST_DEFINE(TT,A,B)& p) { return p.size(); })
    
    void init_Tile1(py::module& m) {
    
        py::class_<Tile1>(m, "Tile1")
            .def(py::init <>())
            .def_property_readonly("xyz", [](Tile1& p) {
            return LIST_DEFINE_TILE1(p, &Tile1::xyz);
        })
            .def("__repr__", [](const Tile1 &p) {
            char buff[100] = { 0 };
            sprintf(buff, "x:%f y:%f z:%f", p.xyz[0], p.xyz[1], p.xyz[2]);
            return std::string(buff, strlen(buff));;
        });
    
        LIST_DEFINE_NEW(Tile1, float, 3, m); //List_flost_3
    
    }
    
    void init_Tile2(py::module& m) {
    
        py::class_<Tile2>(m, "Tile2")
            .def(py::init <>())
            .def_property_readonly("xyz", [](Tile2& p) {
            return LIST_DEFINE_TILE2(p, &Tile2::xyz);
        })
            .def("__repr__", [](const Tile2 &p) {
            char buff[100] = { 0 };
            sprintf(buff, "ax:%d ay:%d az:%d\nbx:%d by:%d bz:%d",
                p.xyz[0], p.xyz[1], p.xyz[2],
                p.xyz[3], p.xyz[4], p.xyz[5]);
            return std::string(buff, strlen(buff));
        });
    
        LIST_DEFINE_NEW(Tile2, short, 6, m); //List_short_6
    }
    
    PYBIND11_MODULE(bbbb, m)
    {
        init_Tile1(m);
        init_Tile2(m);  
    }
    

    python

    >>> import bbbb
    >>> a = bbbb.Tile1()
    >>> a.xyz.length
    3
    >>> a
    x:0.000000 y:0.000000 z:0.000000
    >>> a.xyz[1] = 1.5 #Can modify the value and work
    >>> a
    x:0.000000 y:1.500000 z:0.000000
    >>> b = bbbb.Tile2()
    >>> b.xyz.length
    6
    >>> b
    ax:0 ay:0 az:0
    bx:0 by:0 bz:0
    >>> b.xyz[5] = 100 #Can modify the value and work
    >>> b
    ax:0 ay:0 az:0
    bx:0 by:0 bz:100
    

    A few days after I posted the question and no one replied to me, I came up with a temporary solution during this time, but it is still unavoidable that if there are arrays of different types and lengths, you need to define different types of List_type_n to return, by the way Ask if there is a better solution than this