Files
test/source/blender/python/bmesh/bmesh_py_ops_call.cc
Brecht Van Lommel 920e709069 Refactor: Make header files more clangd and clang-tidy friendly
When using clangd or running clang-tidy on headers there are
currently many errors. These are noisy in IDEs, make auto fixes
impossible, and break features like code completion, refactoring
and navigation.

This makes source/blender headers work by themselves, which is
generally the goal anyway. But #includes and forward declarations
were often incomplete.

* Add #includes and forward declarations
* Add IWYU pragma: export in a few places
* Remove some unused #includes (but there are many more)
* Tweak ShaderCreateInfo macros to work better with clangd

Some types of headers still have errors, these could be fixed or
worked around with more investigation. Mostly preprocessor
template headers like NOD_static_types.h.

Note that that disabling WITH_UNITY_BUILD is required for clangd to
work properly, otherwise compile_commands.json does not contain
the information for the relevant source files.

For more details see the developer docs:
https://developer.blender.org/docs/handbook/tooling/clangd/

Pull Request: https://projects.blender.org/blender/blender/pulls/132608
2025-01-07 12:39:13 +01:00

859 lines
29 KiB
C++

/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*
* This file provides __call__ aka BPy_BMO_call for
* the bmesh operator and has been given its own file
* because argument conversion is involved.
*/
#include <Python.h>
#include "BLI_utildefines.h"
#include "../mathutils/mathutils.hh"
#include "bmesh.hh"
#include "bmesh_py_ops_call.hh" /* own include */
#include "bmesh_py_types.hh"
#include "../generic/py_capi_utils.hh"
BLI_STATIC_ASSERT(sizeof(PyC_FlagSet) == sizeof(BMO_FlagSet), "size mismatch");
static int bpy_bm_op_as_py_error(BMesh *bm)
{
if (BMO_error_occurred_at_level(bm, BMO_ERROR_FATAL)) {
/* NOTE: we could have multiple errors. */
const char *errmsg;
if (BMO_error_get(bm, &errmsg, nullptr, nullptr)) {
PyErr_Format(PyExc_RuntimeError, "bmesh operator: %.200s", errmsg);
BMO_error_clear(bm);
return -1;
}
}
return 0;
}
/**
* \brief Utility function to check BMVert/BMEdge/BMFace's
*
* \param value:
* \param bm: Check the \a value against this.
* \param htype: Test \a value matches this type.
* \param descr: Description text.
*/
static int bpy_slot_from_py_elem_check(BPy_BMElem *value,
BMesh *bm,
const char htype,
/* for error messages */
const char *opname,
const char *slot_name,
const char *descr)
{
if (!BPy_BMElem_Check(value) || !(value->ele->head.htype & htype)) {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" %.200s, expected a %.200s not *.200s",
opname,
slot_name,
descr,
BPy_BMElem_StringFromHType(htype),
Py_TYPE(value)->tp_name);
return -1;
}
if (value->bm == nullptr) {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" %.200s invalidated element",
opname,
slot_name,
descr);
return -1;
}
if (value->bm != bm) { /* we may want to make this check optional by setting 'bm' to nullptr */
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" %.200s invalidated element",
opname,
slot_name,
descr);
return -1;
}
return 0;
}
/**
* \brief Utility function to check BMVertSeq/BMEdgeSeq/BMFaceSeq's
*
* \param value: Caller must check its a BMeshSeq
* \param bm: Check the \a value against this.
* \param htype_py: The type(s) of \a value.
* \param htype_bmo: The type(s) supported by the target slot.
* \param descr: Description text.
*/
static int bpy_slot_from_py_elemseq_check(BPy_BMGeneric *value,
BMesh *bm,
const char htype_py,
const char htype_bmo,
/* for error messages */
const char *opname,
const char *slot_name,
const char *descr)
{
if (value->bm == nullptr) {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" %.200s, invalidated sequence",
opname,
slot_name,
descr);
return -1;
}
if (value->bm != bm) { /* we may want to make this check optional by setting 'bm' to nullptr */
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" %.200s, invalidated sequence",
opname,
slot_name,
descr);
return -1;
}
if ((htype_py & htype_bmo) == 0) {
char str_bmo[32];
char str_py[32];
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" %.200s, expected "
"a sequence of %.200s not %.200s",
opname,
slot_name,
descr,
BPy_BMElem_StringFromHType_ex(htype_bmo, str_bmo),
BPy_BMElem_StringFromHType_ex(htype_py, str_py));
return -1;
}
return 0;
}
/**
* Use for giving py args to an operator.
*/
static int bpy_slot_from_py(BMesh *bm,
BMOperator *bmop,
BMOpSlot *slot,
PyObject *value,
/* the are just for exception messages */
const char *opname,
const char *slot_name)
{
switch (slot->slot_type) {
case BMO_OP_SLOT_BOOL: {
const int param = PyC_Long_AsBool(value);
if (param == -1) {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" expected True/False or 0/1, not %.200s",
opname,
slot_name,
Py_TYPE(value)->tp_name);
return -1;
}
BMO_SLOT_AS_BOOL(slot) = param;
break;
}
case BMO_OP_SLOT_INT: {
if (slot->slot_subtype.intg == BMO_OP_SLOT_SUBTYPE_INT_ENUM) {
int enum_val = -1;
PyC_FlagSet *items = (PyC_FlagSet *)slot->data.enum_data.flags;
const char *enum_str = PyUnicode_AsUTF8(value);
if (enum_str == nullptr) {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" expected a string, not %.200s",
opname,
slot_name,
Py_TYPE(value)->tp_name);
return -1;
}
if (PyC_FlagSet_ValueFromID(items, enum_str, &enum_val, slot_name) == -1) {
return -1;
}
BMO_SLOT_AS_INT(slot) = enum_val;
}
else if (slot->slot_subtype.intg == BMO_OP_SLOT_SUBTYPE_INT_FLAG) {
int flag = 0;
PyC_FlagSet *items = (PyC_FlagSet *)slot->data.enum_data.flags;
if (PyC_FlagSet_ToBitfield(items, value, &flag, slot_name) == -1) {
return -1;
}
BMO_SLOT_AS_INT(slot) = flag;
}
else {
const int param = PyC_Long_AsI32(value);
if (param == -1 && PyErr_Occurred()) {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" expected an int, not %.200s",
opname,
slot_name,
Py_TYPE(value)->tp_name);
return -1;
}
BMO_SLOT_AS_INT(slot) = param;
}
break;
}
case BMO_OP_SLOT_FLT: {
const float param = PyFloat_AsDouble(value);
if (param == -1 && PyErr_Occurred()) {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" expected a float, not %.200s",
opname,
slot_name,
Py_TYPE(value)->tp_name);
return -1;
}
BMO_SLOT_AS_FLOAT(slot) = param;
break;
}
case BMO_OP_SLOT_MAT: {
/* XXX: BMesh operator design is crappy here, operator slot should define matrix size,
* not the caller! */
MatrixObject *pymat;
if (!Matrix_ParseAny(value, &pymat)) {
return -1;
}
const ushort size = pymat->col_num;
if ((size != pymat->row_num) || !ELEM(size, 3, 4)) {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" expected a 3x3 or 4x4 matrix",
opname,
slot_name);
return -1;
}
BMO_slot_mat_set(bmop, bmop->slots_in, slot_name, pymat->matrix, size);
break;
}
case BMO_OP_SLOT_VEC: {
/* passing slot name here is a bit non-descriptive */
if (mathutils_array_parse(BMO_SLOT_AS_VECTOR(slot), 3, 3, value, slot_name) == -1) {
return -1;
}
break;
}
case BMO_OP_SLOT_ELEMENT_BUF: {
if (slot->slot_subtype.elem & BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE) {
if (bpy_slot_from_py_elem_check((BPy_BMElem *)value,
bm,
(slot->slot_subtype.elem & BM_ALL_NOLOOP),
opname,
slot_name,
"single element") == -1)
{
return -1; /* error is set in bpy_slot_from_py_elem_check() */
}
BMO_slot_buffer_from_single(bmop, slot, &((BPy_BMElem *)value)->ele->head);
}
else {
/* there are many ways we could interpret arguments, for now...
* - verts/edges/faces from the mesh direct,
* this way the operator takes every item.
* - `TODO` a plain python sequence (list) of elements.
* - `TODO` an iterator. eg.
* face.verts
* - `TODO` (type, flag) pair, eg.
* ('VERT', {'TAG'})
*/
if (BPy_BMVertSeq_Check(value)) {
if (bpy_slot_from_py_elemseq_check((BPy_BMGeneric *)value,
bm,
BM_VERT,
(slot->slot_subtype.elem & BM_ALL_NOLOOP),
opname,
slot_name,
"element buffer") == -1)
{
return -1; /* error is set in bpy_slot_from_py_elem_check() */
}
BMO_slot_buffer_from_all(bm, bmop, bmop->slots_in, slot_name, BM_VERT);
}
else if (BPy_BMEdgeSeq_Check(value)) {
if (bpy_slot_from_py_elemseq_check((BPy_BMGeneric *)value,
bm,
BM_EDGE,
(slot->slot_subtype.elem & BM_ALL_NOLOOP),
opname,
slot_name,
"element buffer") == -1)
{
return -1; /* error is set in bpy_slot_from_py_elem_check() */
}
BMO_slot_buffer_from_all(bm, bmop, bmop->slots_in, slot_name, BM_EDGE);
}
else if (BPy_BMFaceSeq_Check(value)) {
if (bpy_slot_from_py_elemseq_check((BPy_BMGeneric *)value,
bm,
BM_FACE,
(slot->slot_subtype.elem & BM_ALL_NOLOOP),
opname,
slot_name,
"element buffer") == -1)
{
return -1; /* error is set in bpy_slot_from_py_elem_check() */
}
BMO_slot_buffer_from_all(bm, bmop, bmop->slots_in, slot_name, BM_FACE);
}
else if (BPy_BMElemSeq_Check(value)) {
BMIter iter;
BMHeader *ele;
int tot;
uint i;
if (bpy_slot_from_py_elemseq_check(
(BPy_BMGeneric *)value,
bm,
bm_iter_itype_htype_map[((BPy_BMElemSeq *)value)->itype],
(slot->slot_subtype.elem & BM_ALL_NOLOOP),
opname,
slot_name,
"element buffer") == -1)
{
return -1; /* error is set in bpy_slot_from_py_elem_check() */
}
/* this will loop over all elements which is a shame but
* we need to know this before alloc */
/* calls bpy_bmelemseq_length() */
tot = Py_TYPE(value)->tp_as_sequence->sq_length(value);
BMO_slot_buffer_alloc(bmop, bmop->slots_in, slot_name, tot);
i = 0;
BM_ITER_BPY_BM_SEQ (ele, &iter, ((BPy_BMElemSeq *)value)) {
slot->data.buf[i] = ele;
i++;
}
}
/* keep this last */
else if (PySequence_Check(value)) {
BMElem **elem_array = nullptr;
Py_ssize_t elem_array_len;
elem_array = static_cast<BMElem **>(
BPy_BMElem_PySeq_As_Array(&bm,
value,
0,
PY_SSIZE_T_MAX,
&elem_array_len,
(slot->slot_subtype.elem & BM_ALL_NOLOOP),
true,
true,
slot_name));
/* error is set above */
if (elem_array == nullptr) {
return -1;
}
BMO_slot_buffer_alloc(bmop, bmop->slots_in, slot_name, elem_array_len);
memcpy(slot->data.buf, elem_array, sizeof(void *) * elem_array_len);
PyMem_FREE(elem_array);
}
else {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" expected "
"a bmesh sequence, list, (htype, flag) pair, not %.200s",
opname,
slot_name,
Py_TYPE(value)->tp_name);
return -1;
}
}
break;
}
case BMO_OP_SLOT_MAPPING: {
/* first check types */
if (slot->slot_subtype.map != BMO_OP_SLOT_SUBTYPE_MAP_EMPTY) {
if (!PyDict_Check(value)) {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" expected "
"a dict, not %.200s",
opname,
slot_name,
Py_TYPE(value)->tp_name);
return -1;
}
}
else {
if (!PySet_Check(value)) {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" expected "
"a set, not %.200s",
opname,
slot_name,
Py_TYPE(value)->tp_name);
return -1;
}
}
switch (slot->slot_subtype.map) {
case BMO_OP_SLOT_SUBTYPE_MAP_ELEM: {
if (PyDict_Size(value) > 0) {
PyObject *arg_key, *arg_value;
Py_ssize_t arg_pos = 0;
while (PyDict_Next(value, &arg_pos, &arg_key, &arg_value)) {
if (bpy_slot_from_py_elem_check((BPy_BMElem *)arg_key,
bm,
BM_ALL_NOLOOP,
opname,
slot_name,
"invalid key in dict") == -1)
{
return -1; /* error is set in bpy_slot_from_py_elem_check() */
}
if (bpy_slot_from_py_elem_check((BPy_BMElem *)arg_value,
bm,
BM_ALL_NOLOOP,
opname,
slot_name,
"invalid value in dict") == -1)
{
return -1; /* error is set in bpy_slot_from_py_elem_check() */
}
BMO_slot_map_elem_insert(
bmop, slot, ((BPy_BMElem *)arg_key)->ele, ((BPy_BMElem *)arg_value)->ele);
}
}
break;
}
case BMO_OP_SLOT_SUBTYPE_MAP_FLT: {
if (PyDict_Size(value) > 0) {
PyObject *arg_key, *arg_value;
Py_ssize_t arg_pos = 0;
while (PyDict_Next(value, &arg_pos, &arg_key, &arg_value)) {
float value_f;
if (bpy_slot_from_py_elem_check((BPy_BMElem *)arg_key,
bm,
BM_ALL_NOLOOP,
opname,
slot_name,
"invalid key in dict") == -1)
{
return -1; /* error is set in bpy_slot_from_py_elem_check() */
}
value_f = PyFloat_AsDouble(arg_value);
if (value_f == -1.0f && PyErr_Occurred()) {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" expected "
"a dict with float values, not %.200s",
opname,
slot_name,
Py_TYPE(arg_value)->tp_name);
return -1;
}
BMO_slot_map_float_insert(bmop, slot, ((BPy_BMElem *)arg_key)->ele, value_f);
}
}
break;
}
case BMO_OP_SLOT_SUBTYPE_MAP_INT: {
if (PyDict_Size(value) > 0) {
PyObject *arg_key, *arg_value;
Py_ssize_t arg_pos = 0;
while (PyDict_Next(value, &arg_pos, &arg_key, &arg_value)) {
int value_i;
if (bpy_slot_from_py_elem_check((BPy_BMElem *)arg_key,
bm,
BM_ALL_NOLOOP,
opname,
slot_name,
"invalid key in dict") == -1)
{
return -1; /* error is set in bpy_slot_from_py_elem_check() */
}
value_i = PyC_Long_AsI32(arg_value);
if (value_i == -1 && PyErr_Occurred()) {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" expected "
"a dict with int values, not %.200s",
opname,
slot_name,
Py_TYPE(arg_value)->tp_name);
return -1;
}
BMO_slot_map_int_insert(bmop, slot, ((BPy_BMElem *)arg_key)->ele, value_i);
}
}
break;
}
case BMO_OP_SLOT_SUBTYPE_MAP_BOOL: {
if (PyDict_Size(value) > 0) {
PyObject *arg_key, *arg_value;
Py_ssize_t arg_pos = 0;
while (PyDict_Next(value, &arg_pos, &arg_key, &arg_value)) {
int value_i;
if (bpy_slot_from_py_elem_check((BPy_BMElem *)arg_key,
bm,
BM_ALL_NOLOOP,
opname,
slot_name,
"invalid key in dict") == -1)
{
return -1; /* error is set in bpy_slot_from_py_elem_check() */
}
value_i = PyC_Long_AsI32(arg_value);
if (value_i == -1 && PyErr_Occurred()) {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" expected "
"a dict with bool values, not %.200s",
opname,
slot_name,
Py_TYPE(arg_value)->tp_name);
return -1;
}
BMO_slot_map_bool_insert(bmop, slot, ((BPy_BMElem *)arg_key)->ele, value_i != 0);
}
}
break;
}
case BMO_OP_SLOT_SUBTYPE_MAP_EMPTY: {
if (PySet_GET_SIZE(value) > 0) {
PyObject *it = PyObject_GetIter(value);
PyObject *arg_key;
while ((arg_key = PyIter_Next(it))) {
/* Borrow from the set. */
Py_DECREF(arg_key);
if (bpy_slot_from_py_elem_check((BPy_BMElem *)arg_key,
bm,
BM_ALL_NOLOOP,
opname,
slot_name,
"invalid key in set") == -1)
{
/* Error is set in #bpy_slot_from_py_elem_check(). */
break;
}
BMO_slot_map_empty_insert(bmop, slot, ((BPy_BMElem *)arg_key)->ele);
}
Py_DECREF(it);
if (arg_key) {
return -1;
}
}
break;
}
case BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL: {
/* can't convert from these */
PyErr_Format(PyExc_NotImplementedError,
"This arguments mapping subtype %d is not supported",
slot->slot_subtype.map);
return -1;
}
}
break;
}
default:
/* TODO: many others. */
PyErr_Format(PyExc_NotImplementedError,
"%.200s: keyword \"%.200s\" type %d not working yet!",
opname,
slot_name,
slot->slot_type);
return -1;
}
/* all is well */
return 0;
}
/**
* Use for getting return values from an operator that's already executed.
*
* \note Don't throw any exceptions and should always return a valid (PyObject *).
*/
static PyObject *bpy_slot_to_py(BMesh *bm, BMOpSlot *slot)
{
PyObject *item = nullptr;
/* keep switch in same order as above */
switch (slot->slot_type) {
case BMO_OP_SLOT_BOOL:
item = PyBool_FromLong(BMO_SLOT_AS_BOOL(slot));
break;
case BMO_OP_SLOT_INT:
item = PyLong_FromLong(BMO_SLOT_AS_INT(slot));
break;
case BMO_OP_SLOT_FLT:
item = PyFloat_FromDouble(double(BMO_SLOT_AS_FLOAT(slot)));
break;
case BMO_OP_SLOT_MAT:
item = Matrix_CreatePyObject((float *)BMO_SLOT_AS_MATRIX(slot), 4, 4, nullptr);
break;
case BMO_OP_SLOT_VEC:
item = Vector_CreatePyObject(BMO_SLOT_AS_VECTOR(slot), slot->len, nullptr);
break;
case BMO_OP_SLOT_PTR:
BLI_assert(0); /* currently we don't have any pointer return values in use */
item = Py_NewRef(Py_None);
break;
case BMO_OP_SLOT_ELEMENT_BUF: {
if (slot->slot_subtype.elem & BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE) {
BMHeader *ele = static_cast<BMHeader *>(BMO_slot_buffer_get_single(slot));
item = ele ? BPy_BMElem_CreatePyObject(bm, ele) : Py_NewRef(Py_None);
}
else {
const int size = slot->len;
void **buffer = BMO_SLOT_AS_BUFFER(slot);
int j;
item = PyList_New(size);
for (j = 0; j < size; j++) {
BMHeader *ele = static_cast<BMHeader *>(buffer[j]);
PyList_SET_ITEM(item, j, BPy_BMElem_CreatePyObject(bm, ele));
}
}
break;
}
case BMO_OP_SLOT_MAPPING: {
GHash *slot_hash = BMO_SLOT_AS_GHASH(slot);
GHashIterator hash_iter;
switch (slot->slot_subtype.map) {
case BMO_OP_SLOT_SUBTYPE_MAP_ELEM: {
item = _PyDict_NewPresized(slot_hash ? BLI_ghash_len(slot_hash) : 0);
if (slot_hash) {
GHASH_ITER (hash_iter, slot_hash) {
BMHeader *ele_key = static_cast<BMHeader *>(BLI_ghashIterator_getKey(&hash_iter));
void *ele_val = BLI_ghashIterator_getValue(&hash_iter);
PyObject *py_key = BPy_BMElem_CreatePyObject(bm, ele_key);
PyObject *py_val = BPy_BMElem_CreatePyObject(bm, static_cast<BMHeader *>(ele_val));
PyDict_SetItem(item, py_key, py_val);
Py_DECREF(py_key);
Py_DECREF(py_val);
}
}
break;
}
case BMO_OP_SLOT_SUBTYPE_MAP_FLT: {
item = _PyDict_NewPresized(slot_hash ? BLI_ghash_len(slot_hash) : 0);
if (slot_hash) {
GHASH_ITER (hash_iter, slot_hash) {
BMHeader *ele_key = static_cast<BMHeader *>(BLI_ghashIterator_getKey(&hash_iter));
void *ele_val = BLI_ghashIterator_getValue(&hash_iter);
PyObject *py_key = BPy_BMElem_CreatePyObject(bm, ele_key);
PyObject *py_val = PyFloat_FromDouble(*(float *)&ele_val);
PyDict_SetItem(item, py_key, py_val);
Py_DECREF(py_key);
Py_DECREF(py_val);
}
}
break;
}
case BMO_OP_SLOT_SUBTYPE_MAP_INT: {
item = _PyDict_NewPresized(slot_hash ? BLI_ghash_len(slot_hash) : 0);
if (slot_hash) {
GHASH_ITER (hash_iter, slot_hash) {
BMHeader *ele_key = static_cast<BMHeader *>(BLI_ghashIterator_getKey(&hash_iter));
void *ele_val = BLI_ghashIterator_getValue(&hash_iter);
PyObject *py_key = BPy_BMElem_CreatePyObject(bm, ele_key);
PyObject *py_val = PyLong_FromLong(*(int *)&ele_val);
PyDict_SetItem(item, py_key, py_val);
Py_DECREF(py_key);
Py_DECREF(py_val);
}
}
break;
}
case BMO_OP_SLOT_SUBTYPE_MAP_BOOL: {
item = _PyDict_NewPresized(slot_hash ? BLI_ghash_len(slot_hash) : 0);
if (slot_hash) {
GHASH_ITER (hash_iter, slot_hash) {
BMHeader *ele_key = static_cast<BMHeader *>(BLI_ghashIterator_getKey(&hash_iter));
void *ele_val = BLI_ghashIterator_getValue(&hash_iter);
PyObject *py_key = BPy_BMElem_CreatePyObject(bm, ele_key);
PyObject *py_val = PyBool_FromLong(*(bool *)&ele_val);
PyDict_SetItem(item, py_key, py_val);
Py_DECREF(py_key);
Py_DECREF(py_val);
}
}
break;
}
case BMO_OP_SLOT_SUBTYPE_MAP_EMPTY: {
item = PySet_New(nullptr);
if (slot_hash) {
GHASH_ITER (hash_iter, slot_hash) {
BMHeader *ele_key = static_cast<BMHeader *>(BLI_ghashIterator_getKey(&hash_iter));
PyObject *py_key = BPy_BMElem_CreatePyObject(bm, ele_key);
PySet_Add(item, py_key);
Py_DECREF(py_key);
}
}
break;
}
case BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL:
/* can't convert from these */
item = Py_NewRef(Py_None);
break;
}
break;
}
}
BLI_assert(item != nullptr);
return item;
}
PyObject *BPy_BMO_call(BPy_BMeshOpFunc *self, PyObject *args, PyObject *kw)
{
PyObject *ret;
BPy_BMesh *py_bm;
BMesh *bm;
BMOperator bmop;
if ((PyTuple_GET_SIZE(args) == 1) && (py_bm = (BPy_BMesh *)PyTuple_GET_ITEM(args, 0)) &&
BPy_BMesh_Check(py_bm))
{
BPY_BM_CHECK_OBJ(py_bm);
bm = py_bm->bm;
if (bm->use_toolflags == false) {
PyErr_SetString(PyExc_ValueError, "bmesh created with 'use_operators=False'");
return nullptr;
}
/* could complain about entering with exceptions... */
BMO_error_clear(bm);
}
else {
PyErr_SetString(PyExc_TypeError,
"bmesh operators expect a single BMesh positional argument, all other args "
"must be keywords");
return nullptr;
}
/* TODO: error check this!, though we do the error check on attribute access. */
/* TODO: make flags optional. */
BMO_op_init(bm, &bmop, BMO_FLAG_DEFAULTS, self->opname);
if (kw && PyDict_Size(kw) > 0) {
/* Setup properties, see `bpy_rna.cc`: #pyrna_py_to_prop()
* which shares this logic for parsing properties. */
PyObject *key, *value;
Py_ssize_t pos = 0;
while (PyDict_Next(kw, &pos, &key, &value)) {
const char *slot_name = PyUnicode_AsUTF8(key);
BMOpSlot *slot;
if (!BMO_slot_exists(bmop.slots_in, slot_name)) {
PyErr_Format(PyExc_TypeError,
"%.200s: keyword \"%.200s\" is invalid for this operator",
self->opname,
slot_name);
BMO_op_finish(bm, &bmop);
return nullptr;
}
slot = BMO_slot_get(bmop.slots_in, slot_name);
/* now assign the value */
if (bpy_slot_from_py(bm, &bmop, slot, value, self->opname, slot_name) == -1) {
BMO_op_finish(bm, &bmop);
return nullptr;
}
}
}
BMO_op_exec(bm, &bmop);
/* from here until the end of the function, no returns, just set 'ret' */
if (UNLIKELY(bpy_bm_op_as_py_error(bm) == -1)) {
ret = nullptr; /* exception raised above */
}
else if (bmop.slots_out[0].slot_name == nullptr) {
ret = Py_NewRef(Py_None);
}
else {
/* build return value */
int i;
ret = PyDict_New();
for (i = 0; bmop.slots_out[i].slot_name; i++) {
// BMOpDefine *op_def = opdefines[bmop.type];
// BMOSlotType *slot_type = op_def->slot_types_out[i];
BMOpSlot *slot = &bmop.slots_out[i];
PyObject *item;
/* this function doesn't throw exceptions */
item = bpy_slot_to_py(bm, slot);
if (item == nullptr) {
item = Py_NewRef(Py_None);
}
#if 1
/* Temporary code, strip off `.out` while we keep this convention. */
{
char slot_name_strip[MAX_SLOTNAME];
const char *ch = strchr(slot->slot_name, '.'); /* can't fail! */
const int tot = ch - slot->slot_name;
BLI_assert(ch != nullptr);
memcpy(slot_name_strip, slot->slot_name, tot);
slot_name_strip[tot] = '\0';
PyDict_SetItemString(ret, slot_name_strip, item);
}
#else
PyDict_SetItemString(ret, slot->slot_name, item);
#endif
Py_DECREF(item);
}
}
BMO_op_finish(bm, &bmop);
return ret;
}