Developer Forum

Getting structure from bpy.types.Spline.as_pointer() with ctypes

Hi. Im trying to get evaluate function for curve spline [Spline](https://docs.blender.org/api/blender2.8/bpy.types.Spline.html) using ctypes. I'm new to ctypes, here is what ive got so far:

import bpy, ctypes
from ctypes import Structure, POINTER


class BezTriple(Structure):
    """
    typedef struct BezTriple {
      float vec[3][3];
      float tilt;
      float weight;
      float radius;
      char ipo;
      char h1, h2;
      char f1, f2, f3;
      char hide;
      char easing;
      float back;
      float amplitude, period;
      char f5;
      char _pad[3];
    } BezTriple;
    """

    _fields_ = [
        ("vec", POINTER(POINTER(ctypes.c_float))),

        ("tilt", ctypes.c_float),
        ("weight", ctypes.c_float),
        ("radius", ctypes.c_short),

        ("ipo", ctypes.c_char),

        ("h1", ctypes.c_char),
        ("h2", ctypes.c_char),

        ("f1", ctypes.c_char),
        ("f2", ctypes.c_char),
        ("f3", ctypes.c_char),

        ("hide", ctypes.c_char),

        ("easing", ctypes.c_char),

        ("back", ctypes.c_float),
        ("amplitude", ctypes.c_float),
        ("period", ctypes.c_float),

        ("f5", ctypes.c_char),
        ("_pad", POINTER(ctypes.c_char)),
    ]


class BPoint(Structure):
    """
    typedef struct BPoint {
      float vec[4];
      float tilt;
      float weight;
      short f1, hide;
      float radius;
      char _pad[4];
    } BPoint;
    """
    _fields_ = [
        ("vec", POINTER(ctypes.c_float)),

        ("tilt", ctypes.c_float),
        ("weight", ctypes.c_float),

        ("f1", ctypes.c_short),
        ("hide", ctypes.c_short),

        ("radius", ctypes.c_float),
        ("_pad", ctypes.c_char_p),
    ]


class Nurb(Structure):
    """
    \source\blender\makesdna\DNA_curve_types.h

    typedef struct Nurb {
      struct Nurb *next, *prev;
      short type;
      short mat_nr;
      short hide, flag;
      int pntsu, pntsv;
      char _pad[4];
      short resolu, resolv;
      short orderu, orderv;
      short flagu, flagv;

      float *knotsu, *knotsv;
      BPoint *bp;
      BezTriple *bezt;

      short tilt_interp;
      short radius_interp;

      int charidx;
    } Nurb;"""

    _fields_ = [
        # ("next", POINTER(Nurb)),
        ("next", ctypes.c_void_p),
        ("prev", ctypes.c_void_p),

        ("type", ctypes.c_short),

        ("mat_nr", ctypes.c_short),
        ("hide", ctypes.c_short),
        ("flag", ctypes.c_short),
        ("pntsu", ctypes.c_int),
        ("pntsv", ctypes.c_int),
        ("_pad", ctypes.c_char_p),

        ("resolu", ctypes.c_short),
        ("resolv", ctypes.c_short),
        ("orderu", ctypes.c_short),
        ("orderv", ctypes.c_short),
        ("flagu", ctypes.c_short),
        ("flagv", ctypes.c_short),
        ("knotsu", POINTER(ctypes.c_float)),
        ("knotsv", POINTER(ctypes.c_float)),

        ("bp", POINTER(BPoint)),
        ("bezt", POINTER(BezTriple)),

        ("tilt_interp", ctypes.c_short),
        ("radius_interp", ctypes.c_short),

        ("charidx", ctypes.c_short),
    ]

spline_ptr = bpy.context.object.data.splines[0].as_pointer()

p = ctypes.POINTER(Nurb)
p.from_address(spline_ptr)

print(p)
print(p.resolu)

The python mirror struct needs to be aligned with the actual struct members. When you use POINTER(something), this only produces a static offset of 8 since all pointer types generally have a sizeof of 8. If a field has a bad offset, everything after the field will be wrong.

Some issues in your script:

Arrays should be defined with multiplication

float value[3];

is equivalent to:

("value", ctypes.c_float * 3)

Multi-dimensional arrays are defined by multiplying its dimensions

float matrix[4][4];

is equivalent to:

("matrix", ctypes.c_float * 4 * 4)

Char arrays

char _pad[3];

should be:

("_pad", ctypes.c_char * 3)

POINTER(ctypes.c_char) implies a size of 8 (pointers are size 8). Char padding is used for structure alignments which is a requirement for compiling. In blender, the char datatype has a size of 1, so _pad[3] means a padding of size 3.

When referencing the same struct, define _fields_ outside and use a POINTER.

class Nurb(Structure):
    pass

Nurb._fields_ = (
    ("next", POINTER(Nurb)),
    ("prev", POINTER(Nurb)),
    (...)
)

Lastly, go through each member one by one and verify the data types match exactly.

As an example:
- BezTriple radius set to c_short, when it should be c_float.
- Nurb charidx set to c_short when it should be c_int

The syntax for accessing directly using from_address is:

POINTER(Nurb)(Nurb.from_address(spline_ptr))

But it’s more practical to use ctypes.cast():

ret = ctypes.cast(spline_ptr, POINTER(Nurb))
4 Likes

Thanks a lot, its very clear and helpful response!!!
Here is working example:
https://pastebin.com/mL7Sertb

Next question is how can i access one of the methods from:
blender\blenkernel\intern\curve.c

1 Like

Thank you very much for this information
@yursiv do you still have the working example somewhere?
there is not a lot of working example of as_pointer() usage unfortunately, and yours got lost sadly

@BD3D Sorry for delay.
Just in case you didn`t find answer yet:

Ctypes stucture

Python wrapper classes(for point and nurbs spline):

2 Likes

Thank you very much ! :slight_smile: