Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
.
)
set(INC_SYS
)
set(SRC
bmesh_py_api.cc
bmesh_py_geometry.cc
bmesh_py_ops.cc
bmesh_py_ops_call.cc
bmesh_py_types.cc
bmesh_py_types_customdata.cc
bmesh_py_types_meshdata.cc
bmesh_py_types_select.cc
bmesh_py_utils.cc
bmesh_py_api.hh
bmesh_py_geometry.hh
bmesh_py_ops.hh
bmesh_py_ops_call.hh
bmesh_py_types.hh
bmesh_py_types_customdata.hh
bmesh_py_types_meshdata.hh
bmesh_py_types_select.hh
bmesh_py_utils.hh
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::bmesh
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::intern::guardedalloc
bf_python_mathutils
PRIVATE bf::dependencies::optional::python
)
if(WITH_FREESTYLE)
add_definitions(-DWITH_FREESTYLE)
endif()
if(WITH_GMP)
add_definitions(-DWITH_GMP)
endif()
blender_add_lib(bf_python_bmesh "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@@ -0,0 +1,234 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*
* This file defines the 'bmesh' module.
*/
#include <Python.h>
#include "bmesh.hh"
#include "bmesh_py_types.hh"
#include "bmesh_py_types_customdata.hh"
#include "bmesh_py_types_meshdata.hh"
#include "bmesh_py_types_select.hh"
#include "bmesh_py_geometry.hh"
#include "bmesh_py_ops.hh"
#include "bmesh_py_utils.hh"
#include "BKE_editmesh.hh"
#include "BKE_mesh_types.hh"
#include "DNA_mesh_types.h"
#include "DNA_scene_types.h"
#include "../generic/py_capi_utils.hh"
#include "bmesh_py_api.hh" /* own include */
namespace blender {
PyDoc_STRVAR(
/* Wrap. */
bpy_bm_new_doc,
".. function:: new(*, use_operators=True)\n"
"\n"
" :param use_operators: Support calling operators in :mod:`bmesh.ops` (uses some "
"extra memory per vert/edge/face).\n"
" :type use_operators: bool\n"
" :return: Return a new, empty BMesh.\n"
" :rtype: :class:`bmesh.types.BMesh`\n");
static PyObject *bpy_bm_new(PyObject * /*self*/, PyObject *args, PyObject *kw)
{
static const char *kwlist[] = {"use_operators", nullptr};
BMesh *bm;
bool use_operators = true;
if (!PyArg_ParseTupleAndKeywords(
args, kw, "|$O&:new", const_cast<char **>(kwlist), PyC_ParseBool, &use_operators))
{
return nullptr;
}
BMeshCreateParams params{};
params.use_toolflags = use_operators;
bm = BM_mesh_create(&bm_mesh_allocsize_default, &params);
bm->selectmode = SCE_SELECT_VERTEX;
return BPy_BMesh_CreatePyObject(bm, BPY_BMFLAG_NOP);
}
PyDoc_STRVAR(
/* Wrap. */
bpy_bm_from_edit_mesh_doc,
".. function:: from_edit_mesh(mesh)\n"
"\n"
" Return a BMesh from this mesh, currently the mesh must already be in editmode.\n"
"\n"
" :param mesh: The editmode mesh.\n"
" :type mesh: :class:`bpy.types.Mesh`\n"
" :return: the BMesh associated with this mesh.\n"
" :rtype: :class:`bmesh.types.BMesh`\n");
static PyObject *bpy_bm_from_edit_mesh(PyObject * /*self*/, PyObject *value)
{
BMesh *bm;
Mesh *mesh = static_cast<Mesh *>(PyC_RNA_AsPointer(value, "Mesh"));
if (mesh == nullptr) {
return nullptr;
}
if (mesh->runtime->edit_mesh == nullptr) {
PyErr_SetString(PyExc_ValueError, "The mesh must be in editmode");
return nullptr;
}
bm = mesh->runtime->edit_mesh->bm;
return BPy_BMesh_CreatePyObject(bm, BPY_BMFLAG_IS_WRAPPED);
}
void EDBM_update_extern(Mesh *mesh, const bool do_tessface, const bool is_destructive);
PyDoc_STRVAR(
/* Wrap. */
bpy_bm_update_edit_mesh_doc,
".. function:: update_edit_mesh(mesh, *, loop_triangles=True, destructive=True)\n"
"\n"
" Update the mesh after changes to the BMesh in editmode,\n"
" optionally recalculating n-gon tessellation.\n"
"\n"
" :param mesh: The editmode mesh.\n"
" :type mesh: :class:`bpy.types.Mesh`\n"
" :param loop_triangles: Option to recalculate n-gon tessellation.\n"
" :type loop_triangles: bool\n"
" :param destructive: Use when geometry has been added or removed.\n"
" :type destructive: bool\n");
static PyObject *bpy_bm_update_edit_mesh(PyObject * /*self*/, PyObject *args, PyObject *kw)
{
static const char *kwlist[] = {"mesh", "loop_triangles", "destructive", nullptr};
PyObject *py_me;
Mesh *mesh;
bool do_loop_triangles = true;
bool is_destructive = true;
if (!PyArg_ParseTupleAndKeywords(args,
kw,
"O|$O&O&:update_edit_mesh",
const_cast<char **>(kwlist),
&py_me,
PyC_ParseBool,
&do_loop_triangles,
PyC_ParseBool,
&is_destructive))
{
return nullptr;
}
mesh = static_cast<Mesh *>(PyC_RNA_AsPointer(py_me, "Mesh"));
if (mesh == nullptr) {
return nullptr;
}
if (mesh->runtime->edit_mesh == nullptr) {
PyErr_SetString(PyExc_ValueError, "The mesh must be in editmode");
return nullptr;
}
{
EDBM_update_extern(mesh, do_loop_triangles, is_destructive);
}
Py_RETURN_NONE;
}
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef BPy_BM_methods[] = {
{"new",
reinterpret_cast<PyCFunction>(bpy_bm_new),
METH_VARARGS | METH_KEYWORDS,
bpy_bm_new_doc},
{"from_edit_mesh",
static_cast<PyCFunction>(bpy_bm_from_edit_mesh),
METH_O,
bpy_bm_from_edit_mesh_doc},
{"update_edit_mesh",
reinterpret_cast<PyCFunction>(bpy_bm_update_edit_mesh),
METH_VARARGS | METH_KEYWORDS,
bpy_bm_update_edit_mesh_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
BPy_BM_doc,
"This module provides access to Blender's bmesh data structures.\n"
"\n"
".. include:: include__bmesh.rst\n");
static PyModuleDef BPy_BM_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "bmesh",
/*m_doc*/ BPy_BM_doc,
/*m_size*/ 0,
/*m_methods*/ BPy_BM_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *BPyInit_bmesh()
{
PyObject *mod;
PyObject *submodule;
PyObject *sys_modules = PyImport_GetModuleDict();
BPy_BM_init_types();
BPy_BM_init_types_select();
BPy_BM_init_types_customdata();
BPy_BM_init_types_meshdata();
mod = PyModule_Create(&BPy_BM_module_def);
/* bmesh.types */
PyModule_AddObject(mod, "types", (submodule = BPyInit_bmesh_types()));
PyC_Module_AddToSysModules(sys_modules, submodule);
/* bmesh.ops (not a real module, exposes module like access). */
PyModule_AddObject(mod, "ops", (submodule = BPyInit_bmesh_ops()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "utils", (submodule = BPyInit_bmesh_utils()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "geometry", (submodule = BPyInit_bmesh_geometry()));
PyC_Module_AddToSysModules(sys_modules, submodule);
return mod;
}
} // namespace blender

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*/
#pragma once
#include <Python.h>
namespace blender {
[[nodiscard]] PyObject *BPyInit_bmesh();
} // namespace blender

View File

@@ -0,0 +1,89 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*
* This file defines the 'bmesh.geometry' module.
* Utility functions for operating on 'bmesh.types'
*/
#include <Python.h>
#include "../mathutils/mathutils.hh"
#include "bmesh.hh"
#include "bmesh_py_geometry.hh" /* own include */
#include "bmesh_py_types.hh"
namespace blender {
PyDoc_STRVAR(
/* Wrap. */
bpy_bm_geometry_intersect_face_point_doc,
".. function:: intersect_face_point(face, point)\n"
"\n"
" Tests if the projection of a point is inside a face (using the face's normal).\n"
"\n"
" :param face: The face to test.\n"
" :type face: :class:`bmesh.types.BMFace`\n"
" :param point: The 3D point to test.\n"
" :type point: tuple[float, float, float] | Sequence[float]\n"
" :return: True when the projection of the point is in the face.\n"
" :rtype: bool\n");
static PyObject *bpy_bm_geometry_intersect_face_point(BPy_BMFace * /*self*/, PyObject *args)
{
BPy_BMFace *py_face;
PyObject *py_point;
float point[3];
bool ret;
if (!PyArg_ParseTuple(args, "O!O:intersect_face_point", &BPy_BMFace_Type, &py_face, &py_point)) {
return nullptr;
}
BPY_BM_CHECK_OBJ(py_face);
if (mathutils_array_parse(point, 3, 3, py_point, "intersect_face_point") == -1) {
return nullptr;
}
ret = BM_face_point_inside_test(py_face->f, point);
return PyBool_FromLong(ret);
}
static PyMethodDef BPy_BM_geometry_methods[] = {
{"intersect_face_point",
reinterpret_cast<PyCFunction>(bpy_bm_geometry_intersect_face_point),
METH_VARARGS,
bpy_bm_geometry_intersect_face_point_doc},
{nullptr, nullptr, 0, nullptr},
};
PyDoc_STRVAR(
/* Wrap. */
BPy_BM_utils_doc,
"This module provides access to bmesh geometry evaluation functions.\n");
static PyModuleDef BPy_BM_geometry_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "bmesh.geometry",
/*m_doc*/ BPy_BM_utils_doc,
/*m_size*/ 0,
/*m_methods*/ BPy_BM_geometry_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *BPyInit_bmesh_geometry()
{
PyObject *submodule;
submodule = PyModule_Create(&BPy_BM_geometry_module_def);
return submodule;
}
} // namespace blender

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*/
#pragma once
#include <Python.h>
namespace blender {
[[nodiscard]] PyObject *BPyInit_bmesh_geometry();
} // namespace blender

View File

@@ -0,0 +1,286 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*
* This file defines the 'bmesh.ops' module.
* Operators from 'opdefines' are wrapped.
*/
#include <Python.h>
#include "BLI_dynstr.h"
#include "MEM_guardedalloc.h"
#include "bmesh.hh"
#include "bmesh_py_ops.hh" /* own include */
#include "bmesh_py_ops_call.hh"
namespace blender {
/* bmesh operator 'bmesh.ops.*' callable types
* ******************************************* */
static PyObject *bpy_bmesh_op_repr(BPy_BMeshOpFunc *self)
{
return PyUnicode_FromFormat("<%.200s bmesh.ops.%.200s()>", Py_TYPE(self)->tp_name, self->opname);
}
/* methods
* ======= */
/* __doc__
* ------- */
static char *bmp_slots_as_args(const BMOSlotType slot_types[BMO_OP_MAX_SLOTS], const bool is_out)
{
DynStr *dyn_str = BLI_dynstr_new();
char *ret;
bool quoted;
bool set;
int i = 0;
while (*slot_types[i].name) {
quoted = false;
set = false;
/* Cut off `.out` by using a string size argument. */
const int name_len = is_out ? (strchr(slot_types[i].name, '.') - slot_types[i].name) :
sizeof(slot_types[i].name);
const char *value = "<Unknown>";
switch (slot_types[i].type) {
case BMO_OP_SLOT_BOOL:
value = "False";
break;
case BMO_OP_SLOT_INT:
if (slot_types[i].subtype.intg == BMO_OP_SLOT_SUBTYPE_INT_ENUM) {
value = slot_types[i].enum_flags[0].identifier;
quoted = true;
}
else if (slot_types[i].subtype.intg == BMO_OP_SLOT_SUBTYPE_INT_FLAG) {
value = "";
set = true;
}
else {
value = "0";
}
break;
case BMO_OP_SLOT_FLT:
value = "0.0";
break;
case BMO_OP_SLOT_PTR:
value = "None";
break;
case BMO_OP_SLOT_MAT:
value = "Matrix()";
break;
case BMO_OP_SLOT_VEC:
value = "Vector()";
break;
case BMO_OP_SLOT_ELEMENT_BUF:
value = (slot_types[i].subtype.elem & BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE) ? "None" : "[]";
break;
case BMO_OP_SLOT_MAPPING:
value = "{}";
break;
}
BLI_dynstr_appendf(dyn_str,
i ? ", %.*s=%s%s%s%s%s" : "%.*s=%s%s%s%s%s",
name_len,
slot_types[i].name,
set ? "{" : "",
quoted ? "'" : "",
value,
quoted ? "'" : "",
set ? "}" : "");
i++;
}
ret = BLI_dynstr_get_cstring(dyn_str);
BLI_dynstr_free(dyn_str);
return ret;
}
static PyObject *bpy_bmesh_op_doc_get(BPy_BMeshOpFunc *self, void * /*closure*/)
{
PyObject *ret;
char *slot_in;
char *slot_out;
int i;
i = BMO_opcode_from_opname(self->opname);
slot_in = bmp_slots_as_args(bmo_opdefines[i]->slot_types_in, false);
slot_out = bmp_slots_as_args(bmo_opdefines[i]->slot_types_out, true);
ret = PyUnicode_FromFormat("%.200s bmesh.ops.%.200s(bmesh, %s)\n -> dict(%s)",
Py_TYPE(self)->tp_name,
self->opname,
slot_in,
slot_out);
MEM_delete(slot_in);
MEM_delete(slot_out);
return ret;
}
static PyGetSetDef bpy_bmesh_op_getseters[] = {
{"__doc__",
reinterpret_cast<getter>(bpy_bmesh_op_doc_get),
static_cast<setter>(nullptr),
nullptr,
nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr} /* Sentinel */
};
/* Types
* ===== */
static PyTypeObject bmesh_op_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "BMeshOpFunc",
/*tp_basicsize*/ sizeof(BPy_BMeshOpFunc),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ nullptr,
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ reinterpret_cast<reprfunc>(bpy_bmesh_op_repr),
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ reinterpret_cast<ternaryfunc>(BPy_BMO_call),
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ nullptr,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ nullptr,
/*tp_members*/ nullptr,
/*tp_getset*/ bpy_bmesh_op_getseters,
/*tp_base*/ nullptr,
/*tp_dict*/ nullptr,
/*tp_descr_get*/ nullptr,
/*tp_descr_set*/ nullptr,
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ nullptr,
/*tp_free*/ nullptr,
/*tp_is_gc*/ nullptr,
/*tp_bases*/ nullptr,
/*tp_mro*/ nullptr,
/*tp_cache*/ nullptr,
/*tp_subclasses*/ nullptr,
/*tp_weaklist*/ nullptr,
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
};
/* bmesh module 'bmesh.ops'
* ************************ */
static PyObject *bpy_bmesh_op_CreatePyObject(const char *opname)
{
BPy_BMeshOpFunc *self = PyObject_New(BPy_BMeshOpFunc, &bmesh_op_Type);
self->opname = opname;
return reinterpret_cast<PyObject *>(self);
}
static PyObject *bpy_bmesh_ops_module_getattro(PyObject * /*self*/, PyObject *pyname)
{
const char *opname = PyUnicode_AsUTF8(pyname);
if (BMO_opcode_from_opname(opname) != -1) {
return bpy_bmesh_op_CreatePyObject(opname);
}
PyErr_Format(PyExc_AttributeError, "BMeshOpsModule: operator \"%.200s\" doesn't exist", opname);
return nullptr;
}
static PyObject *bpy_bmesh_ops_module_dir(PyObject * /*self*/)
{
const uint tot = bmo_opdefines_total;
uint i;
PyObject *ret;
ret = PyList_New(bmo_opdefines_total);
for (i = 0; i < tot; i++) {
PyList_SET_ITEM(ret, i, PyUnicode_FromString(bmo_opdefines[i]->opname));
}
return ret;
}
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef BPy_BM_ops_methods[] = {
{"__getattr__", static_cast<PyCFunction>(bpy_bmesh_ops_module_getattro), METH_O, nullptr},
{"__dir__", reinterpret_cast<PyCFunction>(bpy_bmesh_ops_module_dir), METH_NOARGS, nullptr},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
BPy_BM_ops_doc,
"Access to BMesh operators.");
static PyModuleDef BPy_BM_ops_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "bmesh.ops",
/*m_doc*/ BPy_BM_ops_doc,
/*m_size*/ 0,
/*m_methods*/ BPy_BM_ops_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *BPyInit_bmesh_ops()
{
PyObject *submodule = PyModule_Create(&BPy_BM_ops_module_def);
if (PyType_Ready(&bmesh_op_Type) < 0) {
return nullptr;
}
return submodule;
}
} // namespace blender

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*/
#pragma once
#include <Python.h>
namespace blender {
[[nodiscard]] PyObject *BPyInit_bmesh_ops();
} // namespace blender

View File

@@ -0,0 +1,870 @@
/* 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"
namespace blender {
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 = reinterpret_cast<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 = reinterpret_cast<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(reinterpret_cast<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, &(reinterpret_cast<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(reinterpret_cast<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(reinterpret_cast<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(reinterpret_cast<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(
reinterpret_cast<BPy_BMGeneric *>(value),
bm,
bm_iter_itype_htype_map[(reinterpret_cast<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(reinterpret_cast<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(reinterpret_cast<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,
(reinterpret_cast<BPy_BMElem *>(arg_key))->ele,
(reinterpret_cast<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(reinterpret_cast<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, (reinterpret_cast<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(reinterpret_cast<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, (reinterpret_cast<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(reinterpret_cast<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, (reinterpret_cast<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(reinterpret_cast<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, (reinterpret_cast<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(
reinterpret_cast<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(*reinterpret_cast<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(*reinterpret_cast<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(*reinterpret_cast<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 = reinterpret_cast<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;
}
} // namespace blender

View File

@@ -0,0 +1,25 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*/
#pragma once
#include <Python.h>
namespace blender {
struct BPy_BMeshOpFunc {
PyObject_HEAD /* Required Python macro. */
const char *opname;
};
/**
* This is the `__call__` for `bmesh.ops.xxx()`.
*/
[[nodiscard]] PyObject *BPy_BMO_call(BPy_BMeshOpFunc *self, PyObject *args, PyObject *kw);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,283 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*/
#pragma once
#include <Python.h>
#include "bmesh.hh"
namespace blender {
struct BMesh;
struct BMEdge;
struct BMElem;
struct BMFace;
struct BMLoop;
struct BMVert;
extern PyTypeObject BPy_BMesh_Type;
extern PyTypeObject BPy_BMVert_Type;
extern PyTypeObject BPy_BMEdge_Type;
extern PyTypeObject BPy_BMFace_Type;
extern PyTypeObject BPy_BMLoop_Type;
extern PyTypeObject BPy_BMElemSeq_Type;
extern PyTypeObject BPy_BMVertSeq_Type;
extern PyTypeObject BPy_BMEdgeSeq_Type;
extern PyTypeObject BPy_BMFaceSeq_Type;
extern PyTypeObject BPy_BMLoopSeq_Type;
extern PyTypeObject BPy_BMIter_Type;
#define BPy_BMesh_Check(v) (Py_TYPE(v) == &BPy_BMesh_Type)
#define BPy_BMVert_Check(v) (Py_TYPE(v) == &BPy_BMVert_Type)
#define BPy_BMEdge_Check(v) (Py_TYPE(v) == &BPy_BMEdge_Type)
#define BPy_BMFace_Check(v) (Py_TYPE(v) == &BPy_BMFace_Type)
#define BPy_BMLoop_Check(v) (Py_TYPE(v) == &BPy_BMLoop_Type)
#define BPy_BMElemSeq_Check(v) (Py_TYPE(v) == &BPy_BMElemSeq_Type)
#define BPy_BMVertSeq_Check(v) (Py_TYPE(v) == &BPy_BMVertSeq_Type)
#define BPy_BMEdgeSeq_Check(v) (Py_TYPE(v) == &BPy_BMEdgeSeq_Type)
#define BPy_BMFaceSeq_Check(v) (Py_TYPE(v) == &BPy_BMFaceSeq_Type)
#define BPy_BMLoopSeq_Check(v) (Py_TYPE(v) == &BPy_BMLoopSeq_Type)
#define BPy_BMIter_Check(v) (Py_TYPE(v) == &BPy_BMIter_Type)
/* trick since we know they share a hash function */
#define BPy_BMElem_Check(v) (Py_TYPE(v)->tp_hash == BPy_BMVert_Type.tp_hash)
/* cast from _any_ bmesh type - they all have BMesh first */
struct BPy_BMGeneric {
PyObject_HEAD
BMesh *bm; /* keep first */
};
/* BPy_BMVert/BPy_BMEdge/BPy_BMFace/BPy_BMLoop can cast to this */
struct BPy_BMElem {
PyObject_HEAD
BMesh *bm; /* keep first */
BMElem *ele;
};
struct BPy_BMesh {
PyObject_HEAD
BMesh *bm; /* keep first */
int flag;
};
/* element types */
struct BPy_BMVert {
PyObject_HEAD
BMesh *bm; /* keep first */
BMVert *v;
};
struct BPy_BMEdge {
PyObject_HEAD
BMesh *bm; /* keep first */
BMEdge *e;
};
struct BPy_BMFace {
PyObject_HEAD
BMesh *bm; /* keep first */
BMFace *f;
};
struct BPy_BMLoop {
PyObject_HEAD
BMesh *bm; /* keep first */
BMLoop *l;
};
/* iterators */
/* used for ...
* - BPy_BMElemSeq_Type
* - BPy_BMVertSeq_Type
* - BPy_BMEdgeSeq_Type
* - BPy_BMFaceSeq_Type
* - BPy_BMLoopSeq_Type
*/
struct BPy_BMElemSeq {
PyObject_HEAD
BMesh *bm; /* keep first */
/* if this is a sequence on an existing element,
* loops of faces for eg.
* If this variable is set, it will be used */
/* we hold a reference to this.
* check in case the owner becomes invalid on access */
/* TODO: make this a GC'd object!, will function OK without this though. */
BPy_BMElem *py_ele;
/* iterator type */
short itype;
};
struct BPy_BMIter {
PyObject_HEAD
BMesh *bm; /* keep first */
BMIter iter;
};
void BPy_BM_init_types();
[[nodiscard]] PyObject *BPyInit_bmesh_types();
enum {
BPY_BMFLAG_NOP = 0, /* do nothing */
BPY_BMFLAG_IS_WRAPPED = 1, /* the mesh is owned by editmode */
};
[[nodiscard]] PyObject *BPy_BMesh_CreatePyObject(BMesh *bm, int flag);
[[nodiscard]] PyObject *BPy_BMVert_CreatePyObject(BMesh *bm, BMVert *v);
[[nodiscard]] PyObject *BPy_BMEdge_CreatePyObject(BMesh *bm, BMEdge *e);
[[nodiscard]] PyObject *BPy_BMFace_CreatePyObject(BMesh *bm, BMFace *f);
[[nodiscard]] PyObject *BPy_BMLoop_CreatePyObject(BMesh *bm, BMLoop *l);
[[nodiscard]] PyObject *BPy_BMElemSeq_CreatePyObject(BMesh *bm, BPy_BMElem *py_ele, char itype);
[[nodiscard]] PyObject *BPy_BMVertSeq_CreatePyObject(BMesh *bm);
[[nodiscard]] PyObject *BPy_BMEdgeSeq_CreatePyObject(BMesh *bm);
[[nodiscard]] PyObject *BPy_BMFaceSeq_CreatePyObject(BMesh *bm);
[[nodiscard]] PyObject *BPy_BMLoopSeq_CreatePyObject(BMesh *bm);
[[nodiscard]] PyObject *BPy_BMIter_CreatePyObject(BMesh *bm);
/** Just checks type and creates vert/edge/face/loop. */
[[nodiscard]] PyObject *BPy_BMElem_CreatePyObject(BMesh *bm, BMHeader *ele);
/**
* Generic python seq as BMVert/Edge/Face array,
* return value must be freed with PyMem_FREE(...);
*
* The 'bm_r' value is assigned when empty, and used when set.
*/
[[nodiscard]] void *BPy_BMElem_PySeq_As_Array_FAST(BMesh **r_bm,
PyObject *seq_fast,
Py_ssize_t min,
Py_ssize_t max,
Py_ssize_t *r_seq_num,
char htype,
bool do_unique_check,
bool do_bm_check,
const char *error_prefix);
[[nodiscard]] void *BPy_BMElem_PySeq_As_Array(BMesh **r_bm,
PyObject *seq,
Py_ssize_t min,
Py_ssize_t max,
Py_ssize_t *r_seq_num,
char htype,
bool do_unique_check,
bool do_bm_check,
const char *error_prefix);
[[nodiscard]] BMVert **BPy_BMVert_PySeq_As_Array(BMesh **r_bm,
PyObject *seq,
Py_ssize_t min,
Py_ssize_t max,
Py_ssize_t *r_seq_num,
bool do_unique_check,
bool do_bm_check,
const char *error_prefix);
[[nodiscard]] BMEdge **BPy_BMEdge_PySeq_As_Array(BMesh **r_bm,
PyObject *seq,
Py_ssize_t min,
Py_ssize_t max,
Py_ssize_t *r_seq_num,
bool do_unique_check,
bool do_bm_check,
const char *error_prefix);
[[nodiscard]] BMFace **BPy_BMFace_PySeq_As_Array(BMesh **r_bm,
PyObject *seq,
Py_ssize_t min,
Py_ssize_t max,
Py_ssize_t *r_seq_num,
bool do_unique_check,
bool do_bm_check,
const char *error_prefix);
[[nodiscard]] BMLoop **BPy_BMLoop_PySeq_As_Array(BMesh **r_bm,
PyObject *seq,
Py_ssize_t min,
Py_ssize_t max,
Py_ssize_t *r_seq_num,
bool do_unique_check,
bool do_bm_check,
const char *error_prefix);
[[nodiscard]] PyObject *BPy_BMElem_Array_As_Tuple(BMesh *bm, BMHeader **elem, Py_ssize_t elem_num);
[[nodiscard]] PyObject *BPy_BMVert_Array_As_Tuple(BMesh *bm, BMVert **elem, Py_ssize_t elem_num);
[[nodiscard]] PyObject *BPy_BMEdge_Array_As_Tuple(BMesh *bm, BMEdge **elem, Py_ssize_t elem_num);
[[nodiscard]] PyObject *BPy_BMFace_Array_As_Tuple(BMesh *bm, BMFace **elem, Py_ssize_t elem_num);
[[nodiscard]] PyObject *BPy_BMLoop_Array_As_Tuple(BMesh *bm,
BMLoop *const *elem,
Py_ssize_t elem_num);
[[nodiscard]] int BPy_BMElem_CheckHType(PyTypeObject *type, char htype);
/**
* Use for error strings only, not thread safe,
*
* \return a string like '(BMVert/BMEdge/BMFace/BMLoop)'
*/
[[nodiscard]] char *BPy_BMElem_StringFromHType_ex(char htype, char ret[32]);
[[nodiscard]] char *BPy_BMElem_StringFromHType(char htype);
// void bpy_bm_generic_invalidate(BPy_BMGeneric *self);
[[nodiscard]] int bpy_bm_generic_valid_check(BPy_BMGeneric *self);
[[nodiscard]] int bpy_bm_generic_valid_check_source(BMesh *bm_source,
const char *error_prefix,
void **args,
uint args_tot) ATTR_NONNULL(1, 2);
[[nodiscard]] int bpy_bm_check_uv_select_sync_valid(BMesh *bm, const char *error_prefix);
[[nodiscard]] int bpy_bm_uv_layer_offset_or_error(BMesh *bm, const char *error_prefix);
[[nodiscard]] int bpy_bm_check_bm_match_or_error(BMesh *bm_a,
BMesh *bm_b,
const char *error_prefix);
#define BPY_BM_CHECK_OBJ(obj) \
if (UNLIKELY(bpy_bm_generic_valid_check((BPy_BMGeneric *)obj) == -1)) { \
return NULL; \
} \
(void)0
#define BPY_BM_CHECK_INT(obj) \
if (UNLIKELY(bpy_bm_generic_valid_check((BPy_BMGeneric *)obj) == -1)) { \
return -1; \
} \
(void)0
/**
* Macros like `BPY_BM_CHECK_OBJ/BPY_BM_CHECK_INT` that ensure we're from the right #BMesh.
*/
#define BPY_BM_CHECK_SOURCE_OBJ(bm, errmsg, ...) \
{ \
void *_args[] = {__VA_ARGS__}; \
if (UNLIKELY(bpy_bm_generic_valid_check_source(bm, errmsg, _args, ARRAY_SIZE(_args)) == -1)) \
{ \
return NULL; \
} \
} \
(void)0
#define BPY_BM_CHECK_SOURCE_INT(bm, errmsg, ...) \
{ \
void *_args[] = {__VA_ARGS__}; \
if (UNLIKELY(bpy_bm_generic_valid_check_source(bm, errmsg, _args, ARRAY_SIZE(_args)) == -1)) \
{ \
return -1; \
} \
} \
(void)0
#define BPY_BM_IS_VALID(obj) (LIKELY((obj)->bm != NULL))
#define BM_ITER_BPY_BM_SEQ(ele, iter, bpy_bmelemseq) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_new( \
iter, \
(bpy_bmelemseq)->bm, \
(bpy_bmelemseq)->itype, \
(bpy_bmelemseq)->py_ele ? ((BPy_BMElem *)(bpy_bmelemseq)->py_ele)->ele : NULL); \
ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter))
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,73 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*/
#pragma once
#include <Python.h>
#include "bmesh_py_types.hh"
namespace blender {
struct BMesh;
/* All use #BPy_BMLayerAccess struct. */
extern PyTypeObject BPy_BMLayerAccessVert_Type;
extern PyTypeObject BPy_BMLayerAccessEdge_Type;
extern PyTypeObject BPy_BMLayerAccessFace_Type;
extern PyTypeObject BPy_BMLayerAccessLoop_Type;
extern PyTypeObject BPy_BMLayerCollection_Type;
extern PyTypeObject BPy_BMLayerItem_Type;
#define BPy_BMLayerAccess_Check(v) (Py_TYPE(v) == &BPy_BMLayerAccess_Type)
#define BPy_BMLayerCollection_Check(v) (Py_TYPE(v) == &BPy_BMLayerCollection_Type)
#define BPy_BMLayerItem_Check(v) (Py_TYPE(v) == &BPy_BMLayerItem_Type)
/** All layers for vert/edge/face/loop. */
struct BPy_BMLayerAccess {
PyObject_HEAD
BMesh *bm; /* keep first */
char htype;
};
/** Access different layer types deform/uv/vertex-color. */
struct BPy_BMLayerCollection {
PyObject_HEAD
BMesh *bm; /* keep first */
char htype;
int type; /* customdata type - CD_XXX */
};
/** Access a specific layer directly. */
struct BPy_BMLayerItem {
PyObject_HEAD
BMesh *bm; /* keep first */
char htype;
int type; /* customdata type - CD_XXX */
int index; /* index of this layer type */
};
[[nodiscard]] PyObject *BPy_BMLayerAccess_CreatePyObject(BMesh *bm, char htype);
[[nodiscard]] PyObject *BPy_BMLayerCollection_CreatePyObject(BMesh *bm, char htype, int type);
[[nodiscard]] PyObject *BPy_BMLayerItem_CreatePyObject(BMesh *bm, char htype, int type, int index);
void BPy_BM_init_types_customdata();
/**
* \brief `BMElem.__getitem__() / __setitem__()`
*
* Assume all error checks are done, eg: `uv = vert[uv_layer]`
*/
[[nodiscard]] PyObject *BPy_BMLayerItem_GetItem(BPy_BMElem *py_ele, BPy_BMLayerItem *py_layer);
[[nodiscard]] int BPy_BMLayerItem_SetItem(BPy_BMElem *py_ele,
BPy_BMLayerItem *py_layer,
PyObject *value);
} // namespace blender

View File

@@ -0,0 +1,787 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*
* This file defines custom-data types which can't be accessed as primitive
* Python types such as #MDeformVert. It also exposed UV map data in a way
* compatible with the (deprecated) #MLoopUV type.
* MLoopUV used to be a struct containing both the UV information and various
* selection flags. This has since been split up into a float2 attribute
* and three boolean attributes for the selection/pin states.
* For backwards compatibility, the original #MLoopUV is emulated in the
* python API. This comes at a performance penalty however, and the plan is
* to provide direct access to the boolean layers for faster access. Eventually
* (probably in 4.0) #BPy_BMLoopUV should be removed on the Python side as well.
*/
#include <Python.h>
#include "../mathutils/mathutils.hh"
#include "DNA_meshdata_types.h"
#include "BKE_customdata.hh"
#include "BLI_math_base.h"
#include "BLI_math_color.h"
#include "BLI_math_vector.h"
#include "BLI_utildefines.h"
#include "BKE_deform.hh"
#include "bmesh.hh"
#include "bmesh_py_types_meshdata.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_utildefines.hh"
namespace blender {
/* Mesh Loop UV
* ************ */
#define BPy_BMLoopUV_Check(v) (Py_TYPE(v) == &BPy_BMLoopUV_Type)
struct BPy_BMLoopUV {
PyObject_HEAD
float *uv;
/**
* Pin may be null, signifying the layer doesn't exist.
*
* Currently its always created on a #BMesh because adding UV layers to an existing #BMesh is
* slow and invalidates existing Python objects having pointers into the original data-blocks
* (since adding a layer re-generates all blocks).
* But eventually the plan is to lazily allocate the boolean layers "on demand".
* Therefore the code handles cases where the pin layer doesn't exist.
*/
bool *pin;
BMLoop *loop;
};
PyDoc_STRVAR(
/* Wrap. */
bpy_bmloopuv_uv_doc,
"Loop UV (as a 2D Vector).\n"
"\n"
":type: :class:`mathutils.Vector`\n");
static PyObject *bpy_bmloopuv_uv_get(BPy_BMLoopUV *self, void * /*closure*/)
{
return Vector_CreatePyObject_wrap(self->uv, 2, nullptr);
}
static int bpy_bmloopuv_uv_set(BPy_BMLoopUV *self, PyObject *value, void * /*closure*/)
{
float tvec[2];
if (mathutils_array_parse(tvec, 2, 2, value, "BMLoopUV.uv") != -1) {
copy_v2_v2(self->uv, tvec);
return 0;
}
return -1;
}
static bool bpy_bmloopuv_pin_uv_ok_or_error(const BPy_BMLoopUV *self)
{
if (self->pin == nullptr) {
PyErr_SetString(PyExc_RuntimeError,
"active uv layer has no associated pin layer. This is a bug!");
return false;
}
return true;
}
PyDoc_STRVAR(
/* Wrap. */
bpy_bmloopuv_pin_uv_doc,
"UV pin state.\n"
"\n"
":type: bool\n");
static PyObject *bpy_bmloopuv_pin_uv_get(BPy_BMLoopUV *self, void * /*closure*/)
{
/* A non existing pin layer means nothing is currently pinned. */
if (UNLIKELY(!bpy_bmloopuv_pin_uv_ok_or_error(self))) {
return nullptr;
}
return PyBool_FromLong(*self->pin);
}
static int bpy_bmloopuv_pin_uv_set(BPy_BMLoopUV *self, PyObject *value, void * /*closure*/)
{
/* TODO: if we add lazy allocation of the associated uv map bool layers to BMesh we need
* to add a pin layer and update self->pin in the case of self->pin being nullptr.
* This isn't easy to do currently as adding CustomData layers to a BMesh invalidates
* existing python objects. So for now lazy allocation isn't done and self->pin should
* never be nullptr. */
BLI_assert(self->pin);
if (UNLIKELY(!bpy_bmloopuv_pin_uv_ok_or_error(self))) {
return -1;
}
*self->pin = PyC_Long_AsBool(value);
return 0;
}
static PyGetSetDef bpy_bmloopuv_getseters[] = {
/* attributes match rna_def_mloopuv. */
{"uv",
reinterpret_cast<getter>(bpy_bmloopuv_uv_get),
reinterpret_cast<setter>(bpy_bmloopuv_uv_set),
bpy_bmloopuv_uv_doc,
nullptr},
{"pin_uv",
reinterpret_cast<getter>(bpy_bmloopuv_pin_uv_get),
reinterpret_cast<setter>(bpy_bmloopuv_pin_uv_set),
bpy_bmloopuv_pin_uv_doc,
nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr} /* Sentinel */
};
PyTypeObject BPy_BMLoopUV_Type; /* bm.loops.layers.uv.active */
static void bm_init_types_bmloopuv()
{
BPy_BMLoopUV_Type.tp_basicsize = sizeof(BPy_BMLoopUV);
BPy_BMLoopUV_Type.tp_name = "BMLoopUV";
BPy_BMLoopUV_Type.tp_doc = nullptr; /* todo */
BPy_BMLoopUV_Type.tp_getset = bpy_bmloopuv_getseters;
BPy_BMLoopUV_Type.tp_flags = Py_TPFLAGS_DEFAULT;
PyType_Ready(&BPy_BMLoopUV_Type);
}
int BPy_BMLoopUV_AssignPyObject(BMesh *bm, BMLoop *loop, PyObject *value)
{
if (UNLIKELY(!BPy_BMLoopUV_Check(value))) {
PyErr_Format(PyExc_TypeError, "expected BMLoopUV, not a %.200s", Py_TYPE(value)->tp_name);
return -1;
}
BPy_BMLoopUV *src = reinterpret_cast<BPy_BMLoopUV *>(value);
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
float *luv = BM_ELEM_CD_GET_FLOAT_P(loop, offsets.uv);
copy_v2_v2(luv, src->uv);
if (src->pin) {
BM_ELEM_CD_SET_BOOL(loop, offsets.pin, *src->pin);
}
return 0;
}
PyObject *BPy_BMLoopUV_CreatePyObject(BMesh *bm, BMLoop *loop, int layer)
{
BPy_BMLoopUV *self = PyObject_New(BPy_BMLoopUV, &BPy_BMLoopUV_Type);
const BMUVOffsets offsets = BM_uv_map_offsets_from_layer(bm, layer);
self->uv = BM_ELEM_CD_GET_FLOAT_P(loop, offsets.uv);
self->pin = offsets.pin >= 0 ? BM_ELEM_CD_GET_BOOL_P(loop, offsets.pin) : nullptr;
return reinterpret_cast<PyObject *>(self);
}
/* --- End Mesh Loop UV --- */
/* Mesh Vert Skin
* ************ */
#define BPy_BMVertSkin_Check(v) (Py_TYPE(v) == &BPy_BMVertSkin_Type)
struct BPy_BMVertSkin {
PyObject_HEAD
MVertSkin *data;
};
PyDoc_STRVAR(
/* Wrap. */
bpy_bmvertskin_radius_doc,
"Vert skin radii (as a 2D Vector).\n"
"\n"
":type: :class:`mathutils.Vector`\n");
static PyObject *bpy_bmvertskin_radius_get(BPy_BMVertSkin *self, void * /*closure*/)
{
return Vector_CreatePyObject_wrap(self->data->radius, 2, nullptr);
}
static int bpy_bmvertskin_radius_set(BPy_BMVertSkin *self, PyObject *value, void * /*closure*/)
{
float tvec[2];
if (mathutils_array_parse(tvec, 2, 2, value, "BMVertSkin.radius") != -1) {
copy_v2_v2(self->data->radius, tvec);
return 0;
}
return -1;
}
PyDoc_STRVAR(
/* Wrap. */
bpy_bmvertskin_flag__use_root_doc,
"Use as root vertex. Setting this flag does not clear other roots in the same mesh island.\n"
"\n"
":type: bool\n");
PyDoc_STRVAR(
/* Wrap. */
bpy_bmvertskin_flag__use_loose_doc,
"Use loose vertex.\n"
"\n"
":type: bool\n");
static PyObject *bpy_bmvertskin_flag_get(BPy_BMVertSkin *self, void *flag_p)
{
const int flag = POINTER_AS_INT(flag_p);
return PyBool_FromLong(self->data->flag & flag);
}
static int bpy_bmvertskin_flag_set(BPy_BMVertSkin *self, PyObject *value, void *flag_p)
{
const eMVertSkinFlag flag = eMVertSkinFlag(POINTER_AS_INT(flag_p));
switch (PyC_Long_AsBool(value)) {
case true:
self->data->flag |= flag;
return 0;
case false:
self->data->flag &= ~flag;
return 0;
default:
/* error is set */
return -1;
}
}
static PyGetSetDef bpy_bmvertskin_getseters[] = {
/* attributes match rna_mesh_gen. */
{"radius",
reinterpret_cast<getter>(bpy_bmvertskin_radius_get),
reinterpret_cast<setter>(bpy_bmvertskin_radius_set),
bpy_bmvertskin_radius_doc,
nullptr},
{"use_root",
reinterpret_cast<getter>(bpy_bmvertskin_flag_get),
reinterpret_cast<setter>(bpy_bmvertskin_flag_set),
bpy_bmvertskin_flag__use_root_doc,
reinterpret_cast<void *>(MVERT_SKIN_ROOT)},
{"use_loose",
reinterpret_cast<getter>(bpy_bmvertskin_flag_get),
reinterpret_cast<setter>(bpy_bmvertskin_flag_set),
bpy_bmvertskin_flag__use_loose_doc,
reinterpret_cast<void *>(MVERT_SKIN_LOOSE)},
{nullptr, nullptr, nullptr, nullptr, nullptr} /* Sentinel */
};
static PyTypeObject BPy_BMVertSkin_Type; /* bm.loops.layers.skin.active */
static void bm_init_types_bmvertskin()
{
BPy_BMVertSkin_Type.tp_basicsize = sizeof(BPy_BMVertSkin);
BPy_BMVertSkin_Type.tp_name = "BMVertSkin";
BPy_BMVertSkin_Type.tp_doc = "Skin vertex data for the skin modifier.";
BPy_BMVertSkin_Type.tp_getset = bpy_bmvertskin_getseters;
BPy_BMVertSkin_Type.tp_flags = Py_TPFLAGS_DEFAULT;
PyType_Ready(&BPy_BMVertSkin_Type);
}
int BPy_BMVertSkin_AssignPyObject(MVertSkin *mvertskin, PyObject *value)
{
if (UNLIKELY(!BPy_BMVertSkin_Check(value))) {
PyErr_Format(PyExc_TypeError, "expected BMVertSkin, not a %.200s", Py_TYPE(value)->tp_name);
return -1;
}
*(mvertskin) = *((reinterpret_cast<BPy_BMVertSkin *>(value))->data);
return 0;
}
PyObject *BPy_BMVertSkin_CreatePyObject(MVertSkin *mvertskin)
{
BPy_BMVertSkin *self = PyObject_New(BPy_BMVertSkin, &BPy_BMVertSkin_Type);
self->data = mvertskin;
return reinterpret_cast<PyObject *>(self);
}
/* --- End Mesh Vert Skin --- */
/* Mesh Loop Color
* *************** */
/* This simply provides a color wrapper for
* color which uses mathutils callbacks for mathutils.Color
*/
#define MLOOPCOL_FROM_CAPSULE(color_capsule) \
((MLoopCol *)PyCapsule_GetPointer(color_capsule, nullptr))
static void mloopcol_to_float(const MLoopCol *mloopcol, float r_col[4])
{
rgba_uchar_to_float(r_col, static_cast<const uchar *>(&mloopcol->r));
}
static void mloopcol_from_float(MLoopCol *mloopcol, const float col[4])
{
rgba_float_to_uchar(static_cast<uchar *>(&mloopcol->r), col);
}
static uchar mathutils_bmloopcol_cb_index = -1;
static int mathutils_bmloopcol_check(BaseMathObject * /*bmo*/)
{
/* always ok */
return 0;
}
static int mathutils_bmloopcol_get(BaseMathObject *bmo, int /*subtype*/)
{
MLoopCol *mloopcol = MLOOPCOL_FROM_CAPSULE(bmo->cb_user);
mloopcol_to_float(mloopcol, bmo->data);
return 0;
}
static int mathutils_bmloopcol_set(BaseMathObject *bmo, int /*subtype*/)
{
MLoopCol *mloopcol = MLOOPCOL_FROM_CAPSULE(bmo->cb_user);
mloopcol_from_float(mloopcol, bmo->data);
return 0;
}
static int mathutils_bmloopcol_get_index(BaseMathObject *bmo, int subtype, int /*index*/)
{
/* Lazy, avoid repeating the case statement. */
if (mathutils_bmloopcol_get(bmo, subtype) == -1) {
return -1;
}
return 0;
}
static int mathutils_bmloopcol_set_index(BaseMathObject *bmo, int subtype, int index)
{
const float f = bmo->data[index];
/* Lazy, avoid repeating the case statement. */
if (mathutils_bmloopcol_get(bmo, subtype) == -1) {
return -1;
}
bmo->data[index] = f;
return mathutils_bmloopcol_set(bmo, subtype);
}
static Mathutils_Callback mathutils_bmloopcol_cb = {
mathutils_bmloopcol_check,
mathutils_bmloopcol_get,
mathutils_bmloopcol_set,
mathutils_bmloopcol_get_index,
mathutils_bmloopcol_set_index,
};
static void bm_init_types_bmloopcol()
{
/* pass */
mathutils_bmloopcol_cb_index = Mathutils_RegisterCallback(&mathutils_bmloopcol_cb);
}
int BPy_BMLoopColor_AssignPyObject(MLoopCol *mloopcol, PyObject *value)
{
float tvec[4];
if (mathutils_array_parse(tvec, 4, 4, value, "BMLoopCol") != -1) {
mloopcol_from_float(mloopcol, tvec);
return 0;
}
return -1;
}
PyObject *BPy_BMLoopColor_CreatePyObject(MLoopCol *mloopcol)
{
PyObject *color_capsule;
color_capsule = PyCapsule_New(mloopcol, nullptr, nullptr);
return Vector_CreatePyObject_cb(color_capsule, 4, mathutils_bmloopcol_cb_index, 0);
}
#undef MLOOPCOL_FROM_CAPSULE
/* --- End Mesh Loop Color --- */
/* Mesh Deform Vert
* **************** */
/**
* This is python type wraps a deform vert as a python dictionary,
* hiding the #MDeformWeight on access, since the mapping is very close, eg:
*
* \code{.c}
* weight = BKE_defvert_find_weight(dv, group_nr);
* BKE_defvert_remove_group(dv, dw)
* \endcode
*
* \code{.py}
* weight = dv[group_nr]
* del dv[group_nr]
* \endcode
*
* \note There is nothing BMesh specific here,
* its only that BMesh is the only part of blender that uses a hand written API like this.
* This type could eventually be used to access lattice weights.
*
* \note Many of Blender-API's dictionary-like-wrappers act like ordered dictionaries,
* This is intentionally _not_ ordered, the weights can be in any order and it won't matter,
* the order should not be used in the API in any meaningful way (as with a python dict)
* only expose as mapping, not a sequence.
*/
#define BPy_BMDeformVert_Check(v) (Py_TYPE(v) == &BPy_BMDeformVert_Type)
struct BPy_BMDeformVert {
PyObject_HEAD
MDeformVert *data;
};
/* Mapping Protocols
* ================= */
static Py_ssize_t bpy_bmdeformvert_len(BPy_BMDeformVert *self)
{
return self->data->totweight;
}
static PyObject *bpy_bmdeformvert_subscript(BPy_BMDeformVert *self, PyObject *key)
{
if (PyIndex_Check(key)) {
int i;
i = PyNumber_AsSsize_t(key, PyExc_IndexError);
if (i == -1 && PyErr_Occurred()) {
return nullptr;
}
MDeformWeight *dw = BKE_defvert_find_index(self->data, i);
if (dw == nullptr) {
PyErr_SetString(PyExc_KeyError,
"BMDeformVert[key] = x: "
"key not found");
return nullptr;
}
return PyFloat_FromDouble(dw->weight);
}
PyErr_Format(
PyExc_TypeError, "BMDeformVert keys must be integers, not %.200s", Py_TYPE(key)->tp_name);
return nullptr;
}
static int bpy_bmdeformvert_ass_subscript(BPy_BMDeformVert *self, PyObject *key, PyObject *value)
{
if (PyIndex_Check(key)) {
int i;
i = PyNumber_AsSsize_t(key, PyExc_IndexError);
if (i == -1 && PyErr_Occurred()) {
return -1;
}
if (value) {
/* Handle `dvert[group_index] = 0.5`. */
if (i < 0) {
PyErr_SetString(PyExc_KeyError,
"BMDeformVert[key] = x: "
"weight keys cannot be negative");
return -1;
}
MDeformWeight *dw = BKE_defvert_ensure_index(self->data, i);
const float f = PyFloat_AsDouble(value);
if (f == -1 && PyErr_Occurred()) { /* Parsed key not a number. */
PyErr_SetString(PyExc_TypeError,
"BMDeformVert[key] = x: "
"assigned value not a number");
return -1;
}
dw->weight = clamp_f(f, 0.0f, 1.0f);
}
else {
/* Handle `del dvert[group_index]`. */
MDeformWeight *dw = BKE_defvert_find_index(self->data, i);
if (dw == nullptr) {
PyErr_SetString(PyExc_KeyError,
"del BMDeformVert[key]: "
"key not found");
}
BKE_defvert_remove_group(self->data, dw);
}
return 0;
}
PyErr_Format(
PyExc_TypeError, "BMDeformVert keys must be integers, not %.200s", Py_TYPE(key)->tp_name);
return -1;
}
static int bpy_bmdeformvert_contains(BPy_BMDeformVert *self, PyObject *value)
{
const int key = PyLong_AsSsize_t(value);
if (key == -1 && PyErr_Occurred()) {
PyErr_SetString(PyExc_TypeError, "BMDeformVert.__contains__: expected an int");
return -1;
}
return (BKE_defvert_find_index(self->data, key) != nullptr) ? 1 : 0;
}
/* only defined for __contains__ */
static PySequenceMethods bpy_bmdeformvert_as_sequence = {
/*sq_length*/ reinterpret_cast<lenfunc>(bpy_bmdeformvert_len),
/*sq_concat*/ nullptr,
/*sq_repeat*/ nullptr,
/* NOTE: if this is set #PySequence_Check() returns True,
* but in this case we don't want to be treated as a seq. */
/*sq_item*/ nullptr,
/*was_sq_slice*/ nullptr, /* DEPRECATED. */
/*sq_ass_item*/ nullptr,
/*was_sq_ass_slice*/ nullptr, /* DEPRECATED. */
/*sq_contains*/ reinterpret_cast<objobjproc>(bpy_bmdeformvert_contains),
/*sq_inplace_concat*/ nullptr,
/*sq_inplace_repeat*/ nullptr,
};
static PyMappingMethods bpy_bmdeformvert_as_mapping = {
/*mp_length*/ reinterpret_cast<lenfunc>(bpy_bmdeformvert_len),
/*mp_subscript*/ reinterpret_cast<binaryfunc>(bpy_bmdeformvert_subscript),
/*mp_ass_subscript*/ reinterpret_cast<objobjargproc>(bpy_bmdeformvert_ass_subscript),
};
/* Methods
* ======= */
PyDoc_STRVAR(
/* Wrap. */
bpy_bmdeformvert_keys_doc,
".. method:: keys()\n"
"\n"
" Return the group indices used by this vertex\n"
" (matching Python's dict.keys() functionality).\n"
"\n"
" :return: The deform group indices this vertex uses.\n"
" :rtype: list[int]\n");
static PyObject *bpy_bmdeformvert_keys(BPy_BMDeformVert *self)
{
PyObject *ret;
int i;
MDeformWeight *dw = self->data->dw;
ret = PyList_New(self->data->totweight);
for (i = 0; i < self->data->totweight; i++, dw++) {
PyList_SET_ITEM(ret, i, PyLong_FromLong(dw->def_nr));
}
return ret;
}
PyDoc_STRVAR(
/* Wrap. */
bpy_bmdeformvert_values_doc,
".. method:: values()\n"
"\n"
" Return the weights of the deform vertex\n"
" (matching Python's dict.values() functionality).\n"
"\n"
" :return: The weights that influence this vertex\n"
" :rtype: list[float]\n");
static PyObject *bpy_bmdeformvert_values(BPy_BMDeformVert *self)
{
PyObject *ret;
int i;
MDeformWeight *dw = self->data->dw;
ret = PyList_New(self->data->totweight);
for (i = 0; i < self->data->totweight; i++, dw++) {
PyList_SET_ITEM(ret, i, PyFloat_FromDouble(dw->weight));
}
return ret;
}
PyDoc_STRVAR(
/* Wrap. */
bpy_bmdeformvert_items_doc,
".. method:: items()\n"
"\n"
" Return (group, weight) pairs for this vertex\n"
" (matching Python's dict.items() functionality).\n"
"\n"
" :return: (key, value) pairs for each deform weight of this vertex.\n"
" :rtype: list[tuple[int, float]]\n");
static PyObject *bpy_bmdeformvert_items(BPy_BMDeformVert *self)
{
PyObject *ret;
PyObject *item;
int i;
MDeformWeight *dw = self->data->dw;
ret = PyList_New(self->data->totweight);
for (i = 0; i < self->data->totweight; i++, dw++) {
item = PyTuple_New(2);
PyTuple_SET_ITEMS(item, PyLong_FromLong(dw->def_nr), PyFloat_FromDouble(dw->weight));
PyList_SET_ITEM(ret, i, item);
}
return ret;
}
PyDoc_STRVAR(
/* Wrap. */
bpy_bmdeformvert_get_doc,
".. method:: get(key, default=None)\n"
"\n"
" Returns the deform weight matching the key or default\n"
" when not found (matches Python's dictionary function of the same name).\n"
"\n"
" :param key: The vertex group index.\n"
" :type key: int\n"
" :param default: Optional argument for the value to return if\n"
" *key* is not found.\n"
" :type default: Any\n"
" :return: The deform weight or the default when not found.\n"
" :rtype: float | Any\n");
static PyObject *bpy_bmdeformvert_get(BPy_BMDeformVert *self, PyObject *args)
{
int key;
PyObject *def = Py_None;
if (!PyArg_ParseTuple(args, "i|O:get", &key, &def)) {
return nullptr;
}
MDeformWeight *dw = BKE_defvert_find_index(self->data, key);
if (dw) {
return PyFloat_FromDouble(dw->weight);
}
return Py_NewRef(def);
}
PyDoc_STRVAR(
/* Wrap. */
bpy_bmdeformvert_clear_doc,
".. method:: clear()\n"
"\n"
" Clears all weights.\n");
static PyObject *bpy_bmdeformvert_clear(BPy_BMDeformVert *self)
{
BKE_defvert_clear(self->data);
Py_RETURN_NONE;
}
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef bpy_bmdeformvert_methods[] = {
{"keys",
reinterpret_cast<PyCFunction>(bpy_bmdeformvert_keys),
METH_NOARGS,
bpy_bmdeformvert_keys_doc},
{"values",
reinterpret_cast<PyCFunction>(bpy_bmdeformvert_values),
METH_NOARGS,
bpy_bmdeformvert_values_doc},
{"items",
reinterpret_cast<PyCFunction>(bpy_bmdeformvert_items),
METH_NOARGS,
bpy_bmdeformvert_items_doc},
{"get",
reinterpret_cast<PyCFunction>(bpy_bmdeformvert_get),
METH_VARARGS,
bpy_bmdeformvert_get_doc},
/* BMESH_TODO `pop`, `popitem`, `update`. */
{"clear",
reinterpret_cast<PyCFunction>(bpy_bmdeformvert_clear),
METH_NOARGS,
bpy_bmdeformvert_clear_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyTypeObject BPy_BMDeformVert_Type; /* bm.loops.layers.uv.active */
static void bm_init_types_bmdvert()
{
BPy_BMDeformVert_Type.tp_basicsize = sizeof(BPy_BMDeformVert);
BPy_BMDeformVert_Type.tp_name = "BMDeformVert";
BPy_BMDeformVert_Type.tp_doc = nullptr; /* todo */
BPy_BMDeformVert_Type.tp_as_sequence = &bpy_bmdeformvert_as_sequence;
BPy_BMDeformVert_Type.tp_as_mapping = &bpy_bmdeformvert_as_mapping;
BPy_BMDeformVert_Type.tp_methods = bpy_bmdeformvert_methods;
BPy_BMDeformVert_Type.tp_flags = Py_TPFLAGS_DEFAULT;
PyType_Ready(&BPy_BMDeformVert_Type);
}
int BPy_BMDeformVert_AssignPyObject(MDeformVert *dvert, PyObject *value)
{
if (UNLIKELY(!BPy_BMDeformVert_Check(value))) {
PyErr_Format(PyExc_TypeError, "expected BMDeformVert, not a %.200s", Py_TYPE(value)->tp_name);
return -1;
}
MDeformVert *dvert_src = (reinterpret_cast<BPy_BMDeformVert *>(value))->data;
if (LIKELY(dvert != dvert_src)) {
BKE_defvert_copy(dvert, dvert_src);
}
return 0;
}
PyObject *BPy_BMDeformVert_CreatePyObject(MDeformVert *dvert)
{
BPy_BMDeformVert *self = PyObject_New(BPy_BMDeformVert, &BPy_BMDeformVert_Type);
self->data = dvert;
return reinterpret_cast<PyObject *>(self);
}
/* --- End Mesh Deform Vert --- */
void BPy_BM_init_types_meshdata()
{
bm_init_types_bmloopuv();
bm_init_types_bmloopcol();
bm_init_types_bmdvert();
bm_init_types_bmvertskin();
}
} // namespace blender

View File

@@ -0,0 +1,47 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*/
#pragma once
#include <Python.h>
#include "bmesh.hh"
namespace blender {
extern PyTypeObject BPy_BMLoopUV_Type;
extern PyTypeObject BPy_BMDeformVert_Type;
#define BPy_BMLoopUV_Check(v) (Py_TYPE(v) == &BPy_BMLoopUV_Type)
struct BPy_BMGenericMeshData {
PyObject_HEAD
void *data;
};
struct MDeformVert;
struct MLoopCol;
struct MVertSkin;
struct BMesh;
[[nodiscard]] int BPy_BMLoopUV_AssignPyObject(struct BMesh *bm, BMLoop *loop, PyObject *value);
[[nodiscard]] PyObject *BPy_BMLoopUV_CreatePyObject(struct BMesh *bm, BMLoop *loop, int layer);
[[nodiscard]] int BPy_BMVertSkin_AssignPyObject(struct MVertSkin *mvertskin, PyObject *value);
[[nodiscard]] PyObject *BPy_BMVertSkin_CreatePyObject(struct MVertSkin *mvertskin);
[[nodiscard]] int BPy_BMLoopColor_AssignPyObject(struct MLoopCol *mloopcol, PyObject *value);
[[nodiscard]] PyObject *BPy_BMLoopColor_CreatePyObject(struct MLoopCol *mloopcol);
[[nodiscard]] int BPy_BMDeformVert_AssignPyObject(struct MDeformVert *dvert, PyObject *value);
[[nodiscard]] PyObject *BPy_BMDeformVert_CreatePyObject(struct MDeformVert *dvert);
/* call to init all types */
void BPy_BM_init_types_meshdata();
} // namespace blender

View File

@@ -0,0 +1,491 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*
* This file defines the types for 'BMesh.select_history'
* sequence and iterator.
*
* select_history is very loosely based on pythons set() type,
* since items can only exist once. however they do have an order.
*/
#include <Python.h>
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
#include "bmesh.hh"
#include "bmesh_py_types.hh"
#include "bmesh_py_types_select.hh"
#include "../generic/python_utildefines.hh"
namespace blender {
PyDoc_STRVAR(
/* Wrap. */
bpy_bmeditselseq_active_doc,
"The last selected element or None (read-only).\n"
"\n"
":type: :class:`bmesh.types.BMVert` | "
":class:`bmesh.types.BMEdge` | :class:`bmesh.types.BMFace` | None\n");
static PyObject *bpy_bmeditselseq_active_get(BPy_BMEditSelSeq *self, void * /*closure*/)
{
BMEditSelection *ese;
BPY_BM_CHECK_OBJ(self);
if ((ese = static_cast<BMEditSelection *>(self->bm->selected.last))) {
return BPy_BMElem_CreatePyObject(self->bm, &ese->ele->head);
}
Py_RETURN_NONE;
}
static PyGetSetDef bpy_bmeditselseq_getseters[] = {
{"active",
reinterpret_cast<getter>(bpy_bmeditselseq_active_get),
static_cast<setter>(nullptr),
bpy_bmeditselseq_active_doc,
nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr} /* Sentinel */
};
PyDoc_STRVAR(
/* Wrap. */
bpy_bmeditselseq_validate_doc,
".. method:: validate()\n"
"\n"
" Ensures all elements in the selection history are selected.\n");
static PyObject *bpy_bmeditselseq_validate(BPy_BMEditSelSeq *self)
{
BPY_BM_CHECK_OBJ(self);
BM_select_history_validate(self->bm);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
bpy_bmeditselseq_clear_doc,
".. method:: clear()\n"
"\n"
" Empties the selection history.\n");
static PyObject *bpy_bmeditselseq_clear(BPy_BMEditSelSeq *self)
{
BPY_BM_CHECK_OBJ(self);
BM_select_history_clear(self->bm);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
bpy_bmeditselseq_add_doc,
".. method:: add(element)\n"
"\n"
" Add an element to the selection history (no action taken if its already added).\n"
"\n"
" :param element: The element to add.\n"
" :type element: :class:`BMVert` | :class:`BMEdge` | :class:`BMFace`\n");
static PyObject *bpy_bmeditselseq_add(BPy_BMEditSelSeq *self, BPy_BMElem *value)
{
const char *error_prefix = "select_history.add(...)";
BPY_BM_CHECK_OBJ(self);
if ((BPy_BMVert_Check(value) || BPy_BMEdge_Check(value) || BPy_BMFace_Check(value)) == false) {
PyErr_Format(PyExc_TypeError,
"%s: expected a BMVert/BMedge/BMFace not a %.200s",
error_prefix,
Py_TYPE(value)->tp_name);
return nullptr;
}
BPY_BM_CHECK_SOURCE_OBJ(self->bm, error_prefix, value);
BM_select_history_store(self->bm, value->ele);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
bpy_bmeditselseq_remove_doc,
".. method:: remove(element)\n"
"\n"
" Remove an element from the selection history.\n"
"\n"
" :param element: The element to remove.\n"
" :type element: :class:`BMVert` | :class:`BMEdge` | :class:`BMFace`\n");
static PyObject *bpy_bmeditselseq_remove(BPy_BMEditSelSeq *self, BPy_BMElem *value)
{
const char *error_prefix = "select_history.remove(...)";
BPY_BM_CHECK_OBJ(self);
if ((BPy_BMVert_Check(value) || BPy_BMEdge_Check(value) || BPy_BMFace_Check(value)) == false) {
PyErr_Format(PyExc_TypeError,
"%s: expected a BMVert/BMedge/BMFace not a %.200s",
error_prefix,
Py_TYPE(value)->tp_name);
return nullptr;
}
BPY_BM_CHECK_SOURCE_OBJ(self->bm, error_prefix, value);
if (BM_select_history_remove(self->bm, value->ele) == false) {
PyErr_Format(PyExc_ValueError, "%s: element not found in selection history", error_prefix);
return nullptr;
}
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
bpy_bmeditselseq_discard_doc,
".. method:: discard(element)\n"
"\n"
" Discard an element from the selection history.\n"
"\n"
" Like remove but doesn't raise an error when the element is not in the selection list.\n"
"\n"
" :param element: The element to discard.\n"
" :type element: :class:`BMVert` | :class:`BMEdge` | :class:`BMFace`\n");
static PyObject *bpy_bmeditselseq_discard(BPy_BMEditSelSeq *self, BPy_BMElem *value)
{
const char *error_prefix = "select_history.discard()";
BPY_BM_CHECK_OBJ(self);
if ((BPy_BMVert_Check(value) || BPy_BMEdge_Check(value) || BPy_BMFace_Check(value)) == false) {
PyErr_Format(PyExc_TypeError,
"%s: expected a BMVert/BMedge/BMFace not a %.200s",
error_prefix,
Py_TYPE(value)->tp_name);
return nullptr;
}
BPY_BM_CHECK_SOURCE_OBJ(self->bm, error_prefix, value);
BM_select_history_remove(self->bm, value->ele);
Py_RETURN_NONE;
}
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef bpy_bmeditselseq_methods[] = {
{"validate",
reinterpret_cast<PyCFunction>(bpy_bmeditselseq_validate),
METH_NOARGS,
bpy_bmeditselseq_validate_doc},
{"clear",
reinterpret_cast<PyCFunction>(bpy_bmeditselseq_clear),
METH_NOARGS,
bpy_bmeditselseq_clear_doc},
{"add", reinterpret_cast<PyCFunction>(bpy_bmeditselseq_add), METH_O, bpy_bmeditselseq_add_doc},
{"remove",
reinterpret_cast<PyCFunction>(bpy_bmeditselseq_remove),
METH_O,
bpy_bmeditselseq_remove_doc},
{"discard",
reinterpret_cast<PyCFunction>(bpy_bmeditselseq_discard),
METH_O,
bpy_bmeditselseq_discard_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
/* Sequences
* ========= */
static Py_ssize_t bpy_bmeditselseq_length(BPy_BMEditSelSeq *self)
{
BPY_BM_CHECK_INT(self);
return self->bm->selected.count();
}
static PyObject *bpy_bmeditselseq_subscript_int(BPy_BMEditSelSeq *self, Py_ssize_t keynum)
{
BMEditSelection *ese;
BPY_BM_CHECK_OBJ(self);
if (keynum < 0) {
ese = static_cast<BMEditSelection *>(BLI_rfindlink(&self->bm->selected, -1 - keynum));
}
else {
ese = static_cast<BMEditSelection *>(BLI_findlink(&self->bm->selected, keynum));
}
if (ese) {
return BPy_BMElem_CreatePyObject(self->bm, &ese->ele->head);
}
PyErr_Format(PyExc_IndexError, "BMElemSeq[index]: index %d out of range", keynum);
return nullptr;
}
static PyObject *bpy_bmeditselseq_subscript_slice(BPy_BMEditSelSeq *self,
Py_ssize_t start,
Py_ssize_t stop)
{
int count = 0;
PyObject *list;
BMEditSelection *ese;
BPY_BM_CHECK_OBJ(self);
list = PyList_New(0);
/* First loop up-until the start. */
for (ese = static_cast<BMEditSelection *>(self->bm->selected.first); ese; ese = ese->next) {
if (count == start) {
break;
}
count++;
}
/* Add items until stop. */
for (; ese; ese = ese->next) {
PyList_APPEND(list, BPy_BMElem_CreatePyObject(self->bm, &ese->ele->head));
count++;
if (count == stop) {
break;
}
}
return list;
}
static PyObject *bpy_bmeditselseq_subscript(BPy_BMEditSelSeq *self, PyObject *key)
{
/* don't need error check here */
if (PyIndex_Check(key)) {
const Py_ssize_t i = PyNumber_AsSsize_t(key, PyExc_IndexError);
if (i == -1 && PyErr_Occurred()) {
return nullptr;
}
return bpy_bmeditselseq_subscript_int(self, i);
}
if (PySlice_Check(key)) {
PySliceObject *key_slice = reinterpret_cast<PySliceObject *>(key);
Py_ssize_t step = 1;
if (key_slice->step != Py_None && !_PyEval_SliceIndex(key_slice->step, &step)) {
return nullptr;
}
if (step != 1) {
PyErr_SetString(PyExc_TypeError, "BMElemSeq[slice]: slice steps not supported");
return nullptr;
}
if (key_slice->start == Py_None && key_slice->stop == Py_None) {
return bpy_bmeditselseq_subscript_slice(self, 0, PY_SSIZE_T_MAX);
}
Py_ssize_t start = 0, stop = PY_SSIZE_T_MAX;
/* avoid PySlice_GetIndicesEx because it needs to know the length ahead of time. */
if (key_slice->start != Py_None && !_PyEval_SliceIndex(key_slice->start, &start)) {
return nullptr;
}
if (key_slice->stop != Py_None && !_PyEval_SliceIndex(key_slice->stop, &stop)) {
return nullptr;
}
if (start < 0 || stop < 0) {
/* only get the length for negative values */
const Py_ssize_t len = bpy_bmeditselseq_length(self);
if (start < 0) {
start += len;
CLAMP_MIN(start, 0);
}
if (stop < 0) {
stop += len;
CLAMP_MIN(stop, 0);
}
}
if (stop - start <= 0) {
return PyList_New(0);
}
return bpy_bmeditselseq_subscript_slice(self, start, stop);
}
PyErr_SetString(PyExc_AttributeError, "BMElemSeq[key]: invalid key, key must be an int");
return nullptr;
}
static int bpy_bmeditselseq_contains(BPy_BMEditSelSeq *self, PyObject *value)
{
BPy_BMElem *value_bm_ele;
BPY_BM_CHECK_INT(self);
value_bm_ele = reinterpret_cast<BPy_BMElem *>(value);
if (value_bm_ele->bm == self->bm) {
return BM_select_history_check(self->bm, value_bm_ele->ele);
}
return 0;
}
static PySequenceMethods bpy_bmeditselseq_as_sequence = {
/*sq_length*/ reinterpret_cast<lenfunc>(bpy_bmeditselseq_length),
/*sq_concat*/ nullptr,
/*sq_repeat*/ nullptr,
/* Only set this so `PySequence_Check()` returns True. */
/*sq_item*/ reinterpret_cast<ssizeargfunc>(bpy_bmeditselseq_subscript_int),
/*was_sq_slice*/ nullptr,
/*sq_ass_item*/ nullptr,
/*was_sq_ass_slice*/ nullptr,
/*sq_contains*/ reinterpret_cast<objobjproc>(bpy_bmeditselseq_contains),
/*sq_inplace_concat*/ nullptr,
/*sq_inplace_repeat*/ nullptr,
};
static PyMappingMethods bpy_bmeditselseq_as_mapping = {
/*mp_length*/ reinterpret_cast<lenfunc>(bpy_bmeditselseq_length),
/*mp_subscript*/ reinterpret_cast<binaryfunc>(bpy_bmeditselseq_subscript),
/*mp_ass_subscript*/ static_cast<objobjargproc>(nullptr),
};
/* Iterator
* -------- */
static PyObject *bpy_bmeditselseq_iter(BPy_BMEditSelSeq *self)
{
BPy_BMEditSelIter *py_iter;
BPY_BM_CHECK_OBJ(self);
py_iter = reinterpret_cast<BPy_BMEditSelIter *>(BPy_BMEditSelIter_CreatePyObject(self->bm));
py_iter->ese = static_cast<BMEditSelection *>(self->bm->selected.first);
return reinterpret_cast<PyObject *>(py_iter);
}
static PyObject *bpy_bmeditseliter_next(BPy_BMEditSelIter *self)
{
BMEditSelection *ese = self->ese;
if (ese == nullptr) {
PyErr_SetNone(PyExc_StopIteration);
return nullptr;
}
self->ese = ese->next;
return BPy_BMElem_CreatePyObject(self->bm, &ese->ele->head);
}
PyTypeObject BPy_BMEditSelSeq_Type;
PyTypeObject BPy_BMEditSelIter_Type;
PyObject *BPy_BMEditSel_CreatePyObject(BMesh *bm)
{
BPy_BMEditSelSeq *self = PyObject_New(BPy_BMEditSelSeq, &BPy_BMEditSelSeq_Type);
self->bm = bm;
/* caller must initialize 'iter' member */
return reinterpret_cast<PyObject *>(self);
}
PyObject *BPy_BMEditSelIter_CreatePyObject(BMesh *bm)
{
BPy_BMEditSelIter *self = PyObject_New(BPy_BMEditSelIter, &BPy_BMEditSelIter_Type);
self->bm = bm;
/* caller must initialize 'iter' member */
return reinterpret_cast<PyObject *>(self);
}
void BPy_BM_init_types_select()
{
BPy_BMEditSelSeq_Type.tp_basicsize = sizeof(BPy_BMEditSelSeq);
BPy_BMEditSelIter_Type.tp_basicsize = sizeof(BPy_BMEditSelIter);
BPy_BMEditSelSeq_Type.tp_name = "BMEditSelSeq";
BPy_BMEditSelIter_Type.tp_name = "BMEditSelIter";
BPy_BMEditSelSeq_Type.tp_doc = nullptr; /* todo */
BPy_BMEditSelIter_Type.tp_doc = nullptr;
BPy_BMEditSelSeq_Type.tp_repr = static_cast<reprfunc>(nullptr);
BPy_BMEditSelIter_Type.tp_repr = static_cast<reprfunc>(nullptr);
BPy_BMEditSelSeq_Type.tp_getset = bpy_bmeditselseq_getseters;
BPy_BMEditSelIter_Type.tp_getset = nullptr;
BPy_BMEditSelSeq_Type.tp_methods = bpy_bmeditselseq_methods;
BPy_BMEditSelIter_Type.tp_methods = nullptr;
BPy_BMEditSelSeq_Type.tp_as_sequence = &bpy_bmeditselseq_as_sequence;
BPy_BMEditSelSeq_Type.tp_as_mapping = &bpy_bmeditselseq_as_mapping;
BPy_BMEditSelSeq_Type.tp_iter = reinterpret_cast<getiterfunc>(bpy_bmeditselseq_iter);
/* Only 1 iterator so far. */
BPy_BMEditSelIter_Type.tp_iter = PyObject_SelfIter;
BPy_BMEditSelIter_Type.tp_iternext = reinterpret_cast<iternextfunc>(bpy_bmeditseliter_next);
BPy_BMEditSelSeq_Type.tp_dealloc = nullptr; //(destructor)bpy_bmeditselseq_dealloc;
BPy_BMEditSelIter_Type.tp_dealloc = nullptr; //(destructor)bpy_bmvert_dealloc;
BPy_BMEditSelSeq_Type.tp_flags = Py_TPFLAGS_DEFAULT;
BPy_BMEditSelIter_Type.tp_flags = Py_TPFLAGS_DEFAULT;
PyType_Ready(&BPy_BMEditSelSeq_Type);
PyType_Ready(&BPy_BMEditSelIter_Type);
}
/* utility function */
int BPy_BMEditSel_Assign(BPy_BMesh *self, PyObject *value)
{
const char *error_prefix = "BMesh.select_history = value";
BPY_BM_CHECK_INT(self);
BMesh *bm = self->bm;
Py_ssize_t value_num;
BMElem **value_array = static_cast<BMElem **>(
BPy_BMElem_PySeq_As_Array(&bm,
value,
0,
PY_SSIZE_T_MAX,
&value_num,
BM_VERT | BM_EDGE | BM_FACE,
true,
true,
error_prefix));
if (value_array == nullptr) {
return -1;
}
BM_select_history_clear(bm);
for (Py_ssize_t i = 0; i < value_num; i++) {
BM_select_history_store_notest(bm, value_array[i]);
}
PyMem_FREE(value_array);
return 0;
}
} // namespace blender

View File

@@ -0,0 +1,48 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*/
#pragma once
#include <Python.h>
#include "bmesh.hh"
struct BBMesh;
namespace blender {
struct BMEditSelection;
struct BPy_BMesh;
extern PyTypeObject BPy_BMEditSelSeq_Type;
extern PyTypeObject BPy_BMEditSelIter_Type;
#define BPy_BMSelectHistory_Check(v) (Py_TYPE(v) == &BPy_BMEditSelSeq_Type)
#define BPy_BMSelectHistoryIter_Check(v) (Py_TYPE(v) == &BPy_BMEditSelIter_Type)
struct BPy_BMEditSelSeq {
PyObject_HEAD
BMesh *bm; /* keep first */
};
struct BPy_BMEditSelIter {
PyObject_HEAD
BMesh *bm; /* keep first */
BMEditSelection *ese;
};
void BPy_BM_init_types_select();
[[nodiscard]] PyObject *BPy_BMEditSel_CreatePyObject(BMesh *bm);
[[nodiscard]] PyObject *BPy_BMEditSelIter_CreatePyObject(BMesh *bm);
/**
* \note doesn't actually check selection.
*/
[[nodiscard]] int BPy_BMEditSel_Assign(BPy_BMesh *self, PyObject *value);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pybmesh
*/
#pragma once
#include <Python.h>
namespace blender {
[[nodiscard]] PyObject *BPyInit_bmesh_utils();
} // namespace blender