Run external C code with Sverchok SNLite node in Blender
Overview
This entry describes how to run external C/C++ code in Blender, using Sverchok's Script Node Lite (SNLite). You can also use this approach with only Blender python without Sverchok. But if you use the add-on, you may write fewer codes and put the outputs into other nodes.
Blender requires add-ons not to include binary files like dll. So this approach cannot used to make add-ons. But this approach may extend the capability of Blender's scripting. When you do tasks that need large amount of calculation and high performance, such as implementing Machine learning, Fractals and Reaction Diffusion, this technique will be some help.
Required Environments
In this approach, these tools or modules will be needed.
- Blender and Sverchok add-on
- Python's ctypes or numpy/ctypeslib module (Blender's python already has them.)
- Precompiled C/C++ dynamic libraries(If you do not need to modify them.)
- C/C++ Compiler or IDE (If you need to build your custom C/C++ library.)
You can also use Cython to run C code. Both Ctypes and Cython have pros and cons. But if you have pre-compiled library, you do not have to compile C/C++ library. So in this blog, I will use ctypes (and numpy/ctypeslib).
Current Sverchok's approach (July, 2021)
Sverchok developers already recognized the possibility to use external C/C++ code using SNLite.
Interesting external dependencies to play with (discussion) · Issue #2152 · nortikin/sverchok · GitHub
But as mentioned above, Blender's add-on should not include binaries. They probably has been exploring the way to do high performance tasks without using C/C++ libraries and Cython. Currently, using Python's numba library,JIT(Just in time) compiler , is possible candidate. I hope this approach work well.
Can we use Numba?... · Issue #2646 · nortikin/sverchok · GitHub
add numba dependency by zeffii · Pull Request #4209 · nortikin/sverchok · GitHub
A simple example to use a C library
Script and code below is included in my GitHub repository.
GitHub - asahidari/run_c_code_with_snlite_b3d: An example of running C code in Blender Sverchok add-on.
This example simply multiply x/y/z values of all vertices, and generate results for all drawing steps. Using 'setup' function can provide this behavior, because 'setup' function can behaves like Processing 'setup'.
I learned this pattern by reading the issue below in Sverchok project in GitHub.
Reaction Diffusion script. · Issue #1734 · nortikin/sverchok · GitHub
C code:
#include <stdlib.h> // declare callback function type typedef void _callback_func(int step, int dim1, int dim2, double* ppArray, void* pObj); // process method to multiply vertex-coordinates with scale value in each step int process(int num_steps, double scale, int dim1, int dim2, double** verts, _callback_func cb_func, void* pObj) { if (verts == NULL) { return -1; } // Allocate buffer double* pBuff = (double*)malloc(sizeof(double) * dim1 * dim2); // set initial value to the buffer for (int i = 0; i < dim1; i++) { for (int j = 0; j < dim2; j++) { pBuff[i*dim2 + j] = verts[i][j]; } } for (int s = 0; s < num_steps; s++) { // multiply element values in each step for (int i = 0; i < dim1; i++) { for (int j = 0; j < dim2; j++) { pBuff[i*dim2 + j] *= scale; } } // call the callback function to pass the parameters if (cb_func != NULL) { cb_func(s, dim1, dim2, pBuff, pObj); } } // Release buffer free(pBuff); return 0; }
Python code:
""" in steps s d=10 n=2 in verts_in v in scale s d=2.0 n=2 in framenum s d=0 n=2 out verts_out v """ def setup(): import numpy as np import numpy.ctypeslib as npct import os import ctypes as ct def callback_func(step, dim1, dim2, data, selfp): # convert array type from c to numpy verts_arr = npct.as_array(ct.POINTER(ct.c_double).from_address(ct.addressof(data)), shape=(dim1, dim2)) arr_stored = verts_arr.tolist() # call class method using self pointer instance = ct.cast(selfp, ct.py_object).value instance.store_frame(step, arr_stored) # class declaration # Derived from ctype.Structure to pass self pointer to c function class CMultiply(ct.Structure): def __init__(self, steps=10, scale=2.0, verts=None): self.frame_storage = {} if verts == None: return; # declare callback function c type c_arr_type = np.ctypeslib.ndpointer(dtype=ct.c_double, flags='C_CONTIGUOUS') callback_func_type = ct.CFUNCTYPE(None, ct.c_int, ct.c_int, ct.c_int, c_arr_type, ct.c_void_p) # load library libscale_verts = npct.load_library('libscale_verts', os.path.dirname('/Path/to/the/library/directory/')) # declare argtypes and restype libscale_verts.process.argtypes = [ ct.c_int, # step count ct.c_double, # scale value ct.c_int, # array dimension1 ct.c_int, # array dimension2 npct.ndpointer(dtype=np.uintp, ndim=1, flags='C'), # verts array callback_func_type, # callback func ct.py_object # self ] libscale_verts.process.restype = ct.c_int # convert array type verts_arr = np.array(verts) verts_arr_ptr = (verts_arr.__array_interface__['data'][0] + np.arange(verts_arr.shape[0])*verts_arr.strides[0]).astype(np.uintp) # call c function dim1, dim2 = verts_arr.shape[0], verts_arr.shape[1] res = libscale_verts.process(steps, scale, dim1, dim2, verts_arr_ptr, callback_func_type(callback_func), ct.py_object(self)) # get frame def get_frame(self, number): return self.frame_storage.get(number) if number in self.frame_storage else [] # store frame def store_frame(self, framestep, data): self.frame_storage[framestep] = data # instantiate main class cm = CMultiply(steps, scale, (verts_in[0] if verts_in is not None else None)) # set results per frame verts_array = cm.get_frame(framenum) verts_out.append(verts_array)
How to use
To use these scripts,
Compile c code to make a dynamic library.
:: Windows cl.exe /D_USRDLL /D_WINDLL scale_verts.c /MT /link /DLL /OUT:libscale_verts.dll
# macOS gcc -dynamiclib -o ./libscale_verts.dylib ./scale_verts.c
# Linux gcc -c -fPIC scale_verts.c -o scale_verts.o gcc scale_verts.o -shared -o libscale_verts.so
Click '+New' button of Blender's text editor and click '+New' to create a text buffer.
- Copy and paste this Python code into the text buffer.
Write the library path to the "load_library" argument in the python code.
# load library libscale_verts = npct.load_library('libscale_verts', os.path.dirname('/Path/to/the/library/directory/'))
Open Sverchok node editor and create Script Node Lite (SNLite) node.
- Write the text buffer name (default is 'Text') to the SNLite node text box and click the right button in the node.
- Input some mesh vertices to the SNLite node and connect the output to a ViewerDraw node.
- Change 'framenum' value of the SNLite node to draw the results of each step.
I recommend to launch Blender from command line to see debug code in your terminal. Link page below is how to launch Blender from command line.
Launching from the Command Line — Blender Manual
What this project is used for?
This project is intended to be used as a template for projects using other C/C++ codes in Blender. This script includes the ways to load C library, to pass array between Python and C, and to use a python class method as a callback function. When you want to run other external C/C++ library in Blender or other Python environments, this project files may be some help.
I made a Reaction Diffusion simulation codes from this project. Top image of this page (Reaction Diffusion with a cube) is generated using the project below.
GitHub - asahidari/ReactionDiffusion_SNLite_b3d: Reaction Diffusion simulation with Sverchok SNLite node and C codes in Blender.
References
About Ctypes
ctypes — A foreign function library for Python — Python 3.9.6 documentation
C-Types Foreign Function Interface (numpy.ctypeslib) — NumPy v1.21 Manual
Python ctypes: loading DLL from from a relative path - Stack Overflow
How can I unload a DLL using ctypes in Python? - Stack Overflow
Cookbook/Ctypes - SciPy wiki dump
executing C code from python (windows) · Issue #4 · zeffii/BlenderPythonRecipes · GitHub
executing C code from python (passing and receiving arrays) · Issue #5 · zeffii/BlenderPythonRecipes · GitHub
Building dynamic libraries
1.4. Building a Dynamic Library from the Command Line - C++ Cookbook [Book]
c - How to build a DLL from the command line in Windows using MSVC - Stack Overflow
Shared libraries with GCC on Linux - Cprogramming.com
Build dylib on Mac os x