Blender V4.3
mathutils_kdtree.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
12#include <Python.h>
13
14#include "MEM_guardedalloc.h"
15
16#include "BLI_kdtree.h"
17#include "BLI_utildefines.h"
18
21
22#include "mathutils.hh"
23#include "mathutils_kdtree.hh" /* own include */
24
25#include "BLI_strict_flags.h" /* Keep last. */
26
27struct PyKDTree {
28 PyObject_HEAD
29 KDTree_3d *obj;
32 uint count_balance; /* size when we last balanced */
33};
34
35/* -------------------------------------------------------------------- */
36/* Utility helper functions */
37
38static void kdtree_nearest_to_py_tuple(const KDTreeNearest_3d *nearest, PyObject *py_retval)
39{
40 BLI_assert(nearest->index >= 0);
41 BLI_assert(PyTuple_GET_SIZE(py_retval) == 3);
42
43 PyTuple_SET_ITEMS(py_retval,
44 Vector_CreatePyObject(nearest->co, 3, nullptr),
45 PyLong_FromLong(nearest->index),
46 PyFloat_FromDouble(nearest->dist));
47}
48
49static PyObject *kdtree_nearest_to_py(const KDTreeNearest_3d *nearest)
50{
51 PyObject *py_retval;
52
53 py_retval = PyTuple_New(3);
54
55 kdtree_nearest_to_py_tuple(nearest, py_retval);
56
57 return py_retval;
58}
59
60static PyObject *kdtree_nearest_to_py_and_check(const KDTreeNearest_3d *nearest)
61{
62 PyObject *py_retval;
63
64 py_retval = PyTuple_New(3);
65
66 if (nearest->index != -1) {
67 kdtree_nearest_to_py_tuple(nearest, py_retval);
68 }
69 else {
70 PyC_Tuple_Fill(py_retval, Py_None);
71 }
72
73 return py_retval;
74}
75
76/* -------------------------------------------------------------------- */
77/* KDTree */
78
79/* annoying since arg parsing won't check overflow */
80#define UINT_IS_NEG(n) ((n) > INT_MAX)
81
82static int PyKDTree__tp_init(PyKDTree *self, PyObject *args, PyObject *kwargs)
83{
84 uint maxsize;
85 const char *keywords[] = {"size", nullptr};
86
87 if (!PyArg_ParseTupleAndKeywords(args, kwargs, "I:KDTree", (char **)keywords, &maxsize)) {
88 return -1;
89 }
90
91 if (UINT_IS_NEG(maxsize)) {
92 PyErr_SetString(PyExc_ValueError, "negative 'size' given");
93 return -1;
94 }
95
96 self->obj = BLI_kdtree_3d_new(maxsize);
97 self->maxsize = maxsize;
98 self->count = 0;
99 self->count_balance = 0;
100
101 return 0;
102}
103
105{
106 BLI_kdtree_3d_free(self->obj);
107 Py_TYPE(self)->tp_free((PyObject *)self);
108}
109
111 /* Wrap. */
112 py_kdtree_insert_doc,
113 ".. method:: insert(co, index)\n"
114 "\n"
115 " Insert a point into the KDTree.\n"
116 "\n"
117 " :arg co: Point 3d position.\n"
118 " :type co: Sequence[float]\n"
119 " :arg index: The index of the point.\n"
120 " :type index: int\n");
121static PyObject *py_kdtree_insert(PyKDTree *self, PyObject *args, PyObject *kwargs)
122{
123 PyObject *py_co;
124 float co[3];
125 int index;
126 const char *keywords[] = {"co", "index", nullptr};
127
128 if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Oi:insert", (char **)keywords, &py_co, &index)) {
129 return nullptr;
130 }
131
132 if (mathutils_array_parse(co, 3, 3, py_co, "insert: invalid 'co' arg") == -1) {
133 return nullptr;
134 }
135
136 if (index < 0) {
137 PyErr_SetString(PyExc_ValueError, "negative index given");
138 return nullptr;
139 }
140
141 if (self->count >= self->maxsize) {
142 PyErr_SetString(PyExc_RuntimeError, "Trying to insert more items than KDTree has room for");
143 return nullptr;
144 }
145
146 BLI_kdtree_3d_insert(self->obj, index, co);
147 self->count++;
148
149 Py_RETURN_NONE;
150}
151
153 /* Wrap. */
154 py_kdtree_balance_doc,
155 ".. method:: balance()\n"
156 "\n"
157 " Balance the tree.\n"
158 "\n"
159 ".. note::\n"
160 "\n"
161 " This builds the entire tree, avoid calling after each insertion.\n");
163{
164 BLI_kdtree_3d_balance(self->obj);
165 self->count_balance = self->count;
166 Py_RETURN_NONE;
167}
168
170 PyObject *py_filter;
172};
173
174static int py_find_nearest_cb(void *user_data, int index, const float co[3], float dist_sq)
175{
176 UNUSED_VARS(co, dist_sq);
177
178 PyKDTree_NearestData *data = static_cast<PyKDTree_NearestData *>(user_data);
179
180 PyObject *py_args = PyTuple_New(1);
181 PyTuple_SET_ITEM(py_args, 0, PyLong_FromLong(index));
182 PyObject *result = PyObject_CallObject(data->py_filter, py_args);
183 Py_DECREF(py_args);
184
185 if (result) {
186 bool use_node;
187 const int ok = PyC_ParseBool(result, &use_node);
188 Py_DECREF(result);
189 if (ok) {
190 return int(use_node);
191 }
192 }
193
194 data->is_error = true;
195 return -1;
196}
197
199 /* Wrap. */
200 py_kdtree_find_doc,
201 ".. method:: find(co, filter=None)\n"
202 "\n"
203 " Find nearest point to ``co``.\n"
204 "\n"
205 " :arg co: 3D coordinates.\n"
206 " :type co: Sequence[float]\n"
207 " :arg filter: function which takes an index and returns True for indices to "
208 "include in the search.\n"
209 " :type filter: Callable[[int], bool]\n"
210 " :return: Returns (position, index, distance).\n"
211 " :rtype: tuple[:class:`Vector`, int, float]\n");
212static PyObject *py_kdtree_find(PyKDTree *self, PyObject *args, PyObject *kwargs)
213{
214 PyObject *py_co, *py_filter = nullptr;
215 float co[3];
216 KDTreeNearest_3d nearest;
217 const char *keywords[] = {"co", "filter", nullptr};
218
219 if (!PyArg_ParseTupleAndKeywords(
220 args, kwargs, "O|$O:find", (char **)keywords, &py_co, &py_filter))
221 {
222 return nullptr;
223 }
224
225 if (mathutils_array_parse(co, 3, 3, py_co, "find: invalid 'co' arg") == -1) {
226 return nullptr;
227 }
228
229 if (self->count != self->count_balance) {
230 PyErr_SetString(PyExc_RuntimeError, "KDTree must be balanced before calling find()");
231 return nullptr;
232 }
233
234 nearest.index = -1;
235
236 if (py_filter == nullptr) {
237 BLI_kdtree_3d_find_nearest(self->obj, co, &nearest);
238 }
239 else {
240 PyKDTree_NearestData data = {nullptr};
241
242 data.py_filter = py_filter;
243 data.is_error = false;
244
245 BLI_kdtree_3d_find_nearest_cb(self->obj, co, py_find_nearest_cb, &data, &nearest);
246
247 if (data.is_error) {
248 return nullptr;
249 }
250 }
251
252 return kdtree_nearest_to_py_and_check(&nearest);
253}
254
256 /* Wrap. */
257 py_kdtree_find_n_doc,
258 ".. method:: find_n(co, n)\n"
259 "\n"
260 " Find nearest ``n`` points to ``co``.\n"
261 "\n"
262 " :arg co: 3D coordinates.\n"
263 " :type co: Sequence[float]\n"
264 " :arg n: Number of points to find.\n"
265 " :type n: int\n"
266 " :return: Returns a list of tuples (position, index, distance).\n"
267 " :rtype: list[tuple[:class:`Vector`, int, float]]\n");
268static PyObject *py_kdtree_find_n(PyKDTree *self, PyObject *args, PyObject *kwargs)
269{
270 PyObject *py_list;
271 PyObject *py_co;
272 float co[3];
273 KDTreeNearest_3d *nearest;
274 uint n;
275 int i, found;
276 const char *keywords[] = {"co", "n", nullptr};
277
278 if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OI:find_n", (char **)keywords, &py_co, &n)) {
279 return nullptr;
280 }
281
282 if (mathutils_array_parse(co, 3, 3, py_co, "find_n: invalid 'co' arg") == -1) {
283 return nullptr;
284 }
285
286 if (UINT_IS_NEG(n)) {
287 PyErr_SetString(PyExc_RuntimeError, "negative 'n' given");
288 return nullptr;
289 }
290
291 if (self->count != self->count_balance) {
292 PyErr_SetString(PyExc_RuntimeError, "KDTree must be balanced before calling find_n()");
293 return nullptr;
294 }
295
296 nearest = static_cast<KDTreeNearest_3d *>(MEM_mallocN(sizeof(KDTreeNearest_3d) * n, __func__));
297
298 found = BLI_kdtree_3d_find_nearest_n(self->obj, co, nearest, n);
299
300 py_list = PyList_New(found);
301
302 for (i = 0; i < found; i++) {
303 PyList_SET_ITEM(py_list, i, kdtree_nearest_to_py(&nearest[i]));
304 }
305
306 MEM_freeN(nearest);
307
308 return py_list;
309}
310
312 /* Wrap. */
313 py_kdtree_find_range_doc,
314 ".. method:: find_range(co, radius)\n"
315 "\n"
316 " Find all points within ``radius`` of ``co``.\n"
317 "\n"
318 " :arg co: 3D coordinates.\n"
319 " :type co: Sequence[float]\n"
320 " :arg radius: Distance to search for points.\n"
321 " :type radius: float\n"
322 " :return: Returns a list of tuples (position, index, distance).\n"
323 " :rtype: list[tuple[:class:`Vector`, int, float]]\n");
324static PyObject *py_kdtree_find_range(PyKDTree *self, PyObject *args, PyObject *kwargs)
325{
326 PyObject *py_list;
327 PyObject *py_co;
328 float co[3];
329 KDTreeNearest_3d *nearest = nullptr;
330 float radius;
331 int i, found;
332
333 const char *keywords[] = {"co", "radius", nullptr};
334
335 if (!PyArg_ParseTupleAndKeywords(
336 args, kwargs, "Of:find_range", (char **)keywords, &py_co, &radius))
337 {
338 return nullptr;
339 }
340
341 if (mathutils_array_parse(co, 3, 3, py_co, "find_range: invalid 'co' arg") == -1) {
342 return nullptr;
343 }
344
345 if (radius < 0.0f) {
346 PyErr_SetString(PyExc_RuntimeError, "negative radius given");
347 return nullptr;
348 }
349
350 if (self->count != self->count_balance) {
351 PyErr_SetString(PyExc_RuntimeError, "KDTree must be balanced before calling find_range()");
352 return nullptr;
353 }
354
355 found = BLI_kdtree_3d_range_search(self->obj, co, &nearest, radius);
356
357 py_list = PyList_New(found);
358
359 for (i = 0; i < found; i++) {
360 PyList_SET_ITEM(py_list, i, kdtree_nearest_to_py(&nearest[i]));
361 }
362
363 if (nearest) {
364 MEM_freeN(nearest);
365 }
366
367 return py_list;
368}
369
370#if (defined(__GNUC__) && !defined(__clang__))
371# pragma GCC diagnostic push
372# pragma GCC diagnostic ignored "-Wcast-function-type"
373#endif
374
375static PyMethodDef PyKDTree_methods[] = {
376 {"insert", (PyCFunction)py_kdtree_insert, METH_VARARGS | METH_KEYWORDS, py_kdtree_insert_doc},
377 {"balance", (PyCFunction)py_kdtree_balance, METH_NOARGS, py_kdtree_balance_doc},
378 {"find", (PyCFunction)py_kdtree_find, METH_VARARGS | METH_KEYWORDS, py_kdtree_find_doc},
379 {"find_n", (PyCFunction)py_kdtree_find_n, METH_VARARGS | METH_KEYWORDS, py_kdtree_find_n_doc},
380 {"find_range",
381 (PyCFunction)py_kdtree_find_range,
382 METH_VARARGS | METH_KEYWORDS,
383 py_kdtree_find_range_doc},
384 {nullptr, nullptr, 0, nullptr},
385};
386
387#if (defined(__GNUC__) && !defined(__clang__))
388# pragma GCC diagnostic pop
389#endif
390
392 /* Wrap. */
393 py_KDtree_doc,
394 "KdTree(size) -> new kd-tree initialized to hold ``size`` items.\n"
395 "\n"
396 ".. note::\n"
397 "\n"
398 " :class:`KDTree.balance` must have been called before using any of the ``find`` "
399 "methods.\n");
400
401PyTypeObject PyKDTree_Type = {
402 /*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
403 /*tp_name*/ "KDTree",
404 /*tp_basicsize*/ sizeof(PyKDTree),
405 /*tp_itemsize*/ 0,
406 /*tp_dealloc*/ (destructor)PyKDTree__tp_dealloc,
407 /*tp_vectorcall_offset*/ 0,
408 /*tp_getattr*/ nullptr,
409 /*tp_setattr*/ nullptr,
410 /*tp_as_async*/ nullptr,
411 /*tp_repr*/ nullptr,
412 /*tp_as_number*/ nullptr,
413 /*tp_as_sequence*/ nullptr,
414 /*tp_as_mapping*/ nullptr,
415 /*tp_hash*/ nullptr,
416 /*tp_call*/ nullptr,
417 /*tp_str*/ nullptr,
418 /*tp_getattro*/ nullptr,
419 /*tp_setattro*/ nullptr,
420 /*tp_as_buffer*/ nullptr,
421 /*tp_flags*/ Py_TPFLAGS_DEFAULT,
422 /*tp_doc*/ py_KDtree_doc,
423 /*tp_traverse*/ nullptr,
424 /*tp_clear*/ nullptr,
425 /*tp_richcompare*/ nullptr,
426 /*tp_weaklistoffset*/ 0,
427 /*tp_iter*/ nullptr,
428 /*tp_iternext*/ nullptr,
429 /*tp_methods*/ (PyMethodDef *)PyKDTree_methods,
430 /*tp_members*/ nullptr,
431 /*tp_getset*/ nullptr,
432 /*tp_base*/ nullptr,
433 /*tp_dict*/ nullptr,
434 /*tp_descr_get*/ nullptr,
435 /*tp_descr_set*/ nullptr,
436 /*tp_dictoffset*/ 0,
437 /*tp_init*/ (initproc)PyKDTree__tp_init,
438 /*tp_alloc*/ (allocfunc)PyType_GenericAlloc,
439 /*tp_new*/ (newfunc)PyType_GenericNew,
440 /*tp_free*/ (freefunc) nullptr,
441 /*tp_is_gc*/ nullptr,
442 /*tp_bases*/ nullptr,
443 /*tp_mro*/ nullptr,
444 /*tp_cache*/ nullptr,
445 /*tp_subclasses*/ nullptr,
446 /*tp_weaklist*/ nullptr,
447 /*tp_del*/ (destructor) nullptr,
448 /*tp_version_tag*/ 0,
449 /*tp_finalize*/ nullptr,
450 /*tp_vectorcall*/ nullptr,
451};
452
454 /* Wrap. */
455 py_kdtree_doc,
456 "Generic 3-dimensional kd-tree to perform spatial searches.");
457static PyModuleDef kdtree_moduledef = {
458 /*m_base*/ PyModuleDef_HEAD_INIT,
459 /*m_name*/ "mathutils.kdtree",
460 /*m_doc*/ py_kdtree_doc,
461 /*m_size*/ 0,
462 /*m_methods*/ nullptr,
463 /*m_slots*/ nullptr,
464 /*m_traverse*/ nullptr,
465 /*m_clear*/ nullptr,
466 /*m_free*/ nullptr,
467};
468
470{
471 PyObject *m = PyModule_Create(&kdtree_moduledef);
472
473 if (m == nullptr) {
474 return nullptr;
475 }
476
477 /* Register the 'KDTree' class */
478 if (PyType_Ready(&PyKDTree_Type)) {
479 return nullptr;
480 }
481 PyModule_AddType(m, &PyKDTree_Type);
482
483 return m;
484}
#define BLI_assert(a)
Definition BLI_assert.h:50
A KD-tree for nearest neighbor search.
unsigned int uint
#define UNUSED_VARS(...)
Read Guarded memory(de)allocation.
PyObject * self
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
void *(* MEM_mallocN)(size_t len, const char *str)
Definition mallocn.cc:44
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
int mathutils_array_parse(float *array, int array_num_min, int array_num_max, PyObject *value, const char *error_prefix)
Definition mathutils.cc:97
PyObject * Vector_CreatePyObject(const float *vec, const int vec_num, PyTypeObject *base_type)
static int PyKDTree__tp_init(PyKDTree *self, PyObject *args, PyObject *kwargs)
static void kdtree_nearest_to_py_tuple(const KDTreeNearest_3d *nearest, PyObject *py_retval)
#define UINT_IS_NEG(n)
static PyObject * py_kdtree_find(PyKDTree *self, PyObject *args, PyObject *kwargs)
PyTypeObject PyKDTree_Type
static PyObject * py_kdtree_find_n(PyKDTree *self, PyObject *args, PyObject *kwargs)
static PyObject * kdtree_nearest_to_py(const KDTreeNearest_3d *nearest)
static PyObject * py_kdtree_find_range(PyKDTree *self, PyObject *args, PyObject *kwargs)
static void PyKDTree__tp_dealloc(PyKDTree *self)
static PyObject * py_kdtree_insert(PyKDTree *self, PyObject *args, PyObject *kwargs)
static PyMethodDef PyKDTree_methods[]
static int py_find_nearest_cb(void *user_data, int index, const float co[3], float dist_sq)
PyDoc_STRVAR(py_kdtree_insert_doc, ".. method:: insert(co, index)\n" "\n" " Insert a point into the KDTree.\n" "\n" " :arg co: Point 3d position.\n" " :type co: Sequence[float]\n" " :arg index: The index of the point.\n" " :type index: int\n")
static PyObject * py_kdtree_balance(PyKDTree *self)
static PyModuleDef kdtree_moduledef
PyMODINIT_FUNC PyInit_mathutils_kdtree()
static PyObject * kdtree_nearest_to_py_and_check(const KDTreeNearest_3d *nearest)
int PyC_ParseBool(PyObject *o, void *p)
void PyC_Tuple_Fill(PyObject *tuple, PyObject *value)
header-only utilities
#define PyTuple_SET_ITEMS(op_arg,...)
PyObject_HEAD KDTree_3d * obj