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,163 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup python
*/
#pragma once
#include <optional>
#include <string>
#include "BLI_sys_types.h"
#ifdef WITH_INTERNATIONAL
# include "BLI_string_ref.hh"
#endif
namespace blender {
struct ARegionType;
struct AnimationEvalContext;
struct ChannelDriver; /* DNA_anim_types.h */
struct ID; /* DNA_ID.h */
struct ListBase; /* DNA_listBase.h */
struct Object; /* DNA_object_types.h */
struct PathResolvedRNA;
struct Text; /* defined in DNA_text_types.h */
struct bConstraint; /* DNA_constraint_types.h */
struct bConstraintOb; /* DNA_constraint_types.h */
struct bConstraintTarget; /* DNA_constraint_types.h */
struct bContext;
struct bContextDataResult;
struct StructRNA;
struct wmWindowManager;
/* global interpreter lock */
using BPy_ThreadStatePtr = void *;
/**
* Analogue of #PyEval_SaveThread()
*/
[[nodiscard]] BPy_ThreadStatePtr BPY_thread_save();
/**
* Analogue of #PyEval_RestoreThread()
*/
void BPY_thread_restore(BPy_ThreadStatePtr tstate);
/** Our own wrappers to #Py_BEGIN_ALLOW_THREADS / #Py_END_ALLOW_THREADS */
#define BPy_BEGIN_ALLOW_THREADS \
{ \
BPy_ThreadStatePtr _bpy_saved_tstate = BPY_thread_save(); \
(void)0
#define BPy_END_ALLOW_THREADS \
BPY_thread_restore(_bpy_saved_tstate); \
} \
(void)0
/**
* Print the Python backtrace of the current thread state.
*
* Should be safe to call from anywhere at any point, may not output anything if there is no valid
* python thread state available.
*/
void BPY_thread_backtrace_print();
void BPY_text_free_code(Text *text);
/**
* Needed so the #Main pointer in `bpy.data` doesn't become out of date.
*/
void BPY_modules_update();
void BPY_modules_load_user(bContext *C);
void BPY_app_handlers_reset(bool do_all);
/**
* Run on exit to free any cached data.
*/
void BPY_driver_exit();
/**
* Update function, it gets rid of python-drivers global dictionary: `bpy.app.driver_namespace`,
* forcing #BPY_driver_exec to recreate it. Use this when loading a new `.blend` file
* so any variables setup by the previous blend file are cleared.
*/
void BPY_driver_reset();
/**
* This evaluates Python driver expressions, `driver_orig->expression`
* is a Python expression that should evaluate to a float number, which is returned.
*/
[[nodiscard]] float BPY_driver_exec(PathResolvedRNA *anim_rna,
ChannelDriver *driver,
ChannelDriver *driver_orig,
const AnimationEvalContext *anim_eval_context);
/**
* Acquire the global-interpreter-lock (GIL) and wrap `Py_DECREF`.
* as there are some cases when this needs to be called outside the Python API code.
*/
void BPY_DECREF(void *pyob_ptr);
void BPY_DECREF_RNA_INVALIDATE(void *pyob_ptr);
[[nodiscard]] bool BPY_context_member_get(bContext *C,
const char *member,
bContextDataResult *result);
/**
* Set the global `bpy.context` for Python.
* \param C: The context to set. In rare cases C can be null.
*/
void BPY_context_set(bContext *C);
/**
* Use for updating while a python script runs - in case of file load.
* \param C: The context to activate as `bpy.context`. In rare cases C can be null.
*/
void BPY_context_update(bContext *C);
/**
* Use for `CTX_*_set(..)` functions need to set values which are later read back as expected.
* In this case we don't want the Python context to override the values as it causes problems
* see #66256.
*
* \param dict_p: A pointer to #bContext.data.py_context so we can assign a new value.
* \param dict_orig: The value of #bContext.data.py_context_orig to check if we need to copy.
*/
void BPY_context_dict_clear_members_array(void **dict_p,
void *dict_orig,
const char *context_members[],
uint context_members_len);
void BPY_id_release(ID *id);
/**
* Free (actually dereference) the Python type object representing the given #StrucRNA type,
* if it is defined.
*/
void BPY_free_srna_pytype(StructRNA *srna);
/**
* Avoids duplicating keyword list.
*/
[[nodiscard]] bool BPY_string_is_keyword(const char *str);
/**
* Get current Python stack location.
* Returns a string like `filename.py:123` if available, #std::nullopt otherwise.
*/
[[nodiscard]] std::optional<std::string> BPY_python_current_file_and_line();
/* `bpy_rna_callback.cc` */
void BPY_callback_screen_free(ARegionType *art);
void BPY_callback_wm_free(wmWindowManager *wm);
/* I18n for addons */
#ifdef WITH_INTERNATIONAL
[[nodiscard]] std::optional<StringRefNull> BPY_app_translations_py_pgettext(StringRef msgctxt,
StringRef msgid);
#endif
} // namespace blender

View File

@@ -0,0 +1,21 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup python
*
* Logging defines.
*/
#pragma once
struct CLG_LogRef;
namespace blender {
/* bpy_interface.cc */
extern CLG_LogRef *BPY_LOG_RNA;
} // namespace blender

View File

@@ -0,0 +1,36 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup python
*
* Functionality relating to Python setup & tear down.
*/
#pragma once
/* For 'FILE'. */
#include <cstdio>
namespace blender {
struct bContext;
/* `bpy_interface.cc` */
/** Call #BPY_context_set first. */
void BPY_python_start(bContext *C, int argc, const char **argv);
void BPY_python_end(bool do_python_exit);
void BPY_python_reset(bContext *C);
void BPY_python_use_system_env();
void BPY_python_use_user_env();
[[nodiscard]] bool BPY_python_use_system_env_get();
void BPY_python_backtrace(FILE *fp);
/* `bpy_app.cc` */
/** Access `main_args_help_as_string` needed to resolve bad level call. */
extern char *(*BPY_python_app_help_text_fn)(bool all);
} // namespace blender

View File

@@ -0,0 +1,254 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup python
*
* \subsection common_args Common Arguments
*
* - `C` the #bContext (never NULL).
*
* - `imports`: This is simply supported for convenience since imports can make constructing
* strings more cumbersome as otherwise small expressions become multi-line code-blocks.
* Optional (ignored when NULL), otherwise this is a NULL terminated array of module names.
*
* Failure to import any modules prevents any further execution.
*
* - `err_info` #BPy_RunErrInfo is passed to some functions so errors can be forwarded to the UI.
* Option (when NULL errors are printed to the `stdout` and cleared).
* However this should be used in any case the error would be useful to show to the user.
*/
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_string_ref.hh"
#include "BLI_sys_types.h"
namespace blender {
struct ReportList;
struct Text;
struct bContext;
struct IDProperty;
/* `bpy_interface_run.cc` */
/* -------------------------------------------------------------------- */
/** \name Run File/Text as a Script
*
* \note #BPY_run_filepath and #BPY_run_filepath have almost identical behavior
* one operates on a file-path, the other on a blender text-block.
* \{ */
/**
* Execute `filepath` as a Python script.
*
* Wrapper for `PyRun_File` (similar to calling python with a script argument).
* Used for the `--python` command line argument.
*
* \param C: The context (never NULL).
* \param filepath: The file path to execute.
* \param reports: Failure to execute the script will report the exception here (may be NULL).
* \return true on success, otherwise false with an error reported to `reports`.
*
* \note Python scripts could consider `bpy.utils.execfile`, which has the advantage of returning
* the object as a module for data access & caching `pyc` file for faster re-execution.
*/
bool BPY_run_filepath(bContext *C, const char *filepath, ReportList *reports) ATTR_NONNULL(1, 2);
/**
* Execute a Blender `text` block as a Python script.
*
* Wrapper for `Py_CompileStringObject` & `PyEval_EvalCode`.
* Used for the `--python-text` command line argument.
*
* \param C: The context (never NULL).
* \param text: The text-block to execute.
* \param reports: Failure to execute the script will report the exception here (may be NULL).
* \param do_jump: When true, any error moves the cursor to the location of that error.
* Useful for executing scripts interactively from the text editor.
* \return true on success, otherwise false with an error reported to `reports`.
*
* \note The `__file__` is constructed by joining the blend file-path to the name of the text.
* This is done so error messages give useful output however there are rare cases causes problems
* with introspection tools which attempt to load `__file__`.
*/
bool BPY_run_text(bContext *C, Text *text, ReportList *reports, bool do_jump) ATTR_NONNULL(1, 2);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Run a String as a Script
*
* - Use 'eval' for simple single-line expressions.
* - Use 'exec' for full multi-line scripts.
* \{ */
/**
* Compile the python expression and \return true if no errors occurred during compilation,
* otherwise \return false.
*
* \param C: Optional context (may be null).
*/
bool BPY_string_compile_check(const char *expr);
/**
* Run an entire script, matches: `exec(compile(..., "exec"))`
*
* \param C: Optional context (may be null),
* used for `bpy.context` and reporting errors to `CTX_wm_reports(C)`.
*/
bool BPY_run_string_exec(bContext *C, const char *imports[], const char *expr);
/**
* Run an expression, matches: `exec(compile(..., "eval"))`.
*
* \param C: Optional context (may be null),
* used for `bpy.context` and reporting errors to `CTX_wm_reports(C)`.
*/
bool BPY_run_string_eval(bContext *C, const char *imports[], const char *expr);
/**
* Run a script, with the given local variables.
*
* \param C: Optional context (may be null),
* used for `bpy.context` and reporting errors to `CTX_wm_reports(C)`.
*
* \param script: The Python script to run, can be multiple lines.
*
* \param locals: group property with string keys, defining the script's local variables.
*/
bool BPY_run_string_exec_with_locals(bContext *C, StringRefNull script, IDProperty &locals);
/**
* Run a script, with the given local variables, and return a result.
*
* The script should assign a value to a script-local variable. Its value will be returned as
* IDProperty. This is limited to simple values (None, bool, int, float, string), where integers
* are limited to signed 32-bit values.
*
* \param C: Optional context (may be null), used for `bpy.context` and reporting errors to
* `CTX_wm_reports(C)`.
*
* \param script: The Python script to run, can be multiple lines.
*
* \param locals: group property with string keys, defining the script's local variables.
*
* \param result_var_name: the name of the local variable that the script assigns its result to.
* This MUST be a valid Python identifier.
*
* \returns the value of the result variable (see above) after the script finished. If the result
* value was not set, `std::noopt` is returned. A value of `None` will return in a `nullptr` value
* of the `std::optional`.
*/
std::optional<IDProperty *> BPY_run_string_exec_with_locals_return_idprop(
bContext *C, StringRefNull script, IDProperty &locals, StringRefNull result_var_name);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Run a String as a Script & Return the Result
*
* Convenience functions for executing a script and returning the result as an expected type.
* \{ */
/**
* \note When this struct is passed in as NULL,
* print errors to the `stdout` and clear.
*/
struct BPy_RunErrInfo {
/** Brief text, single line (can show this in status bar for example). */
bool use_single_line_error;
/** Report with optional prefix (when non-NULL). */
ReportList *reports;
const char *report_prefix;
/** Allocated exception text (assign when non-NULL). */
char **r_string;
};
/**
* Evaluate `expr` as a number (double).
*
* \param C: See \ref common_args.
* \param imports: See \ref common_args.
* \param expr: The expression to evaluate.
* \param err_info: See \ref common_args.
* \param r_value: The resulting value.
* \return Success.
*/
[[nodiscard]] bool BPY_run_string_as_number(bContext *C,
const char *imports[],
const char *expr,
BPy_RunErrInfo *err_info,
double *r_value) ATTR_NONNULL(1, 3, 5);
/**
* Evaluate `expr` as an integer or pointer.
*
* \note Support both int and pointers.
*
* \param C: See \ref common_args.
* \param imports: See \ref common_args.
* \param expr: The expression to evaluate.
* \param err_info: See \ref common_args.
* \param r_value: The resulting value.
* \return Success.
*/
[[nodiscard]] bool BPY_run_string_as_intptr(bContext *C,
const char *imports[],
const char *expr,
BPy_RunErrInfo *err_info,
intptr_t *r_value) ATTR_NONNULL(1, 3, 5);
/**
* Evaluate `expr` as a string.
*
* \param C: See \ref common_args.
* \param imports: See \ref common_args.
* \param expr: The expression to evaluate.
* \param err_info: See \ref common_args.
* \param r_value: The resulting value.
* \return Success.
*/
[[nodiscard]] bool BPY_run_string_as_string_and_len(bContext *C,
const char *imports[],
const char *expr,
BPy_RunErrInfo *err_info,
char **r_value,
size_t *r_value_len) ATTR_NONNULL(1, 3, 5, 6);
/** See #BPY_run_string_as_string_and_len */
[[nodiscard]] bool BPY_run_string_as_string(bContext *C,
const char *imports[],
const char *expr,
BPy_RunErrInfo *err_info,
char **r_value) ATTR_NONNULL(1, 3, 5);
/**
* Evaluate `expr` as a string or None.
* Where a success return value with `*r_value == nullptr` indicates a value of None.
*
* \param C: See \ref common_args.
* \param imports: See \ref common_args.
* \param expr: The expression to evaluate.
* \param err_info: See \ref common_args.
* \param r_value: The resulting value.
* \return Success.
*/
[[nodiscard]] bool BPY_run_string_as_string_and_len_or_none(bContext *C,
const char *imports[],
const char *expr,
BPy_RunErrInfo *err_info,
char **r_value,
size_t *r_value_len)
ATTR_NONNULL(1, 3, 5, 6);
/** See #BPY_run_string_as_string_and_len */
[[nodiscard]] bool BPY_run_string_as_string_or_none(bContext *C,
const char *imports[],
const char *expr,
BPy_RunErrInfo *err_info,
char **r_value) ATTR_NONNULL(1, 3, 5);
/** \} */
} // namespace blender

View File

@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
if(WITH_PYTHON_MODULE)
add_definitions(-DPy_ENABLE_SHARED)
endif()
add_subdirectory(intern)
add_subdirectory(generic)
add_subdirectory(gpu)
add_subdirectory(mathutils)
add_subdirectory(bmesh)

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

View File

@@ -0,0 +1,52 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
.
../../makesrna
)
set(INC_SYS
)
set(SRC
bl_math_py_api.cc
blf_py_api.cc
bpy_threads.cc
idprop_py_api.cc
idprop_py_ui_api.cc
imbuf_py_api.cc
py_capi_rna.cc
py_capi_utils.cc
python_compat.cc
bl_math_py_api.hh
blf_py_api.hh
idprop_py_api.hh
idprop_py_ui_api.hh
imbuf_py_api.hh
py_capi_rna.hh
py_capi_utils.hh
python_compat.hh
# header-only
python_utildefines.hh
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::dna
PRIVATE bf::gpu
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
PRIVATE bf::dependencies::optional::python
PRIVATE bf::dependencies::epoxy
)
if(WITH_PYTHON_MODULE)
add_definitions(-DWITH_PYTHON_MODULE)
endif()
blender_add_lib(bf_python_ext "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@@ -0,0 +1,156 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* \file
* \ingroup pygen
*
* This file defines the 'bl_math' module, a module for math utilities.
*/
#include <Python.h>
#include "BLI_utildefines.h"
#include "bl_math_py_api.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Python Functions
* \{ */
PyDoc_STRVAR(
/* Wrap. */
py_bl_math_clamp_doc,
".. function:: clamp(value, min=0, max=1)\n"
"\n"
" Clamps the float value between minimum and maximum. To avoid\n"
" confusion, any call must use either one or all three arguments.\n"
"\n"
" :param value: The value to clamp.\n"
" :type value: float\n"
" :param min: The minimum value, defaults to 0.\n"
" :type min: float\n"
" :param max: The maximum value, defaults to 1.\n"
" :type max: float\n"
" :return: The clamped value.\n"
" :rtype: float\n");
static PyObject *py_bl_math_clamp(PyObject * /*self*/, PyObject *args)
{
double x, minv = 0.0, maxv = 1.0;
if (PyTuple_Size(args) <= 1) {
if (!PyArg_ParseTuple(args, "d:clamp", &x)) {
return nullptr;
}
}
else {
if (!PyArg_ParseTuple(args, "ddd:clamp", &x, &minv, &maxv)) {
return nullptr;
}
}
CLAMP(x, minv, maxv);
return PyFloat_FromDouble(x);
}
PyDoc_STRVAR(
/* Wrap. */
py_bl_math_lerp_doc,
".. function:: lerp(from_value, to_value, factor)\n"
"\n"
" Linearly interpolate between two float values based on factor.\n"
"\n"
" :param from_value: The value to return when factor is 0.\n"
" :type from_value: float\n"
" :param to_value: The value to return when factor is 1.\n"
" :type to_value: float\n"
" :param factor: The interpolation value, normally in [0.0, 1.0].\n"
" :type factor: float\n"
" :return: The interpolated value.\n"
" :rtype: float\n");
static PyObject *py_bl_math_lerp(PyObject * /*self*/, PyObject *args)
{
double a, b, x;
if (!PyArg_ParseTuple(args, "ddd:lerp", &a, &b, &x)) {
return nullptr;
}
return PyFloat_FromDouble(a * (1.0 - x) + b * x);
}
PyDoc_STRVAR(
/* Wrap. */
py_bl_math_smoothstep_doc,
".. function:: smoothstep(from_value, to_value, value)\n"
"\n"
" Performs smooth interpolation between 0 and 1 as value changes between from and "
"to values.\n"
" Outside the range the function returns the same value as the nearest edge.\n"
"\n"
" :param from_value: The edge value where the result is 0.\n"
" :type from_value: float\n"
" :param to_value: The edge value where the result is 1.\n"
" :type to_value: float\n"
" :param value: The interpolation value.\n"
" :type value: float\n"
" :return: The interpolated value in [0.0, 1.0].\n"
" :rtype: float\n");
static PyObject *py_bl_math_smoothstep(PyObject * /*self*/, PyObject *args)
{
double a, b, x;
if (!PyArg_ParseTuple(args, "ddd:smoothstep", &a, &b, &x)) {
return nullptr;
}
double t = (x - a) / (b - a);
CLAMP(t, 0.0, 1.0);
return PyFloat_FromDouble(t * t * (3.0 - 2.0 * t));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Module Definition
* \{ */
static PyMethodDef M_bl_math_methods[] = {
{"clamp", static_cast<PyCFunction>(py_bl_math_clamp), METH_VARARGS, py_bl_math_clamp_doc},
{"lerp", static_cast<PyCFunction>(py_bl_math_lerp), METH_VARARGS, py_bl_math_lerp_doc},
{"smoothstep",
static_cast<PyCFunction>(py_bl_math_smoothstep),
METH_VARARGS,
py_bl_math_smoothstep_doc},
{nullptr, nullptr, 0, nullptr},
};
PyDoc_STRVAR(
/* Wrap. */
M_bl_math_doc,
"Miscellaneous math utilities module.");
static PyModuleDef M_bl_math_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "bl_math",
/*m_doc*/ M_bl_math_doc,
/*m_size*/ 0,
/*m_methods*/ M_bl_math_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyMODINIT_FUNC BPyInit_bl_math()
{
PyObject *submodule = PyModule_Create(&M_bl_math_module_def);
return submodule;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* \file
* \ingroup pygen
*/
#pragma once
#include <Python.h>
namespace blender {
PyMODINIT_FUNC BPyInit_bl_math();
} // namespace blender

View File

@@ -0,0 +1,852 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pygen
*
* This file defines the `blf` module, used for drawing text to the GPU or image buffers.
*/
/* Future-proof, See https://docs.python.org/3/c-api/arg.html#strings-and-buffers */
#define PY_SSIZE_T_CLEAN
#include "blf_py_api.hh"
#include "py_capi_utils.hh"
#include <Python.h>
#include "../../blenfont/BLF_api.hh"
#include "BLI_utildefines.h"
#include "../../imbuf/IMB_colormanagement.hh"
#include "../../imbuf/IMB_imbuf.hh"
#include "../../imbuf/IMB_imbuf_types.hh"
#include "python_compat.hh" /* IWYU pragma: keep. */
#include "python_utildefines.hh"
#include "imbuf_py_api.hh"
namespace blender {
struct BPyBLFImBufContext {
PyObject_HEAD /* Required Python macro. */
PyObject *py_imbuf;
int fontid;
BLFBufferState *buffer_state;
};
PyDoc_STRVAR(
/* Wrap. */
py_blf_position_doc,
".. function:: position(fontid, x, y, z)\n"
"\n"
" Set the position for drawing text.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param x: X axis position to draw the text.\n"
" :type x: float\n"
" :param y: Y axis position to draw the text.\n"
" :type y: float\n"
" :param z: Z axis position to draw the text (typically 0).\n"
" :type z: float\n");
static PyObject *py_blf_position(PyObject * /*self*/, PyObject *args)
{
int fontid;
float x, y, z;
if (!PyArg_ParseTuple(args, "ifff:blf.position", &fontid, &x, &y, &z)) {
return nullptr;
}
BLF_position(fontid, x, y, z);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_size_doc,
".. function:: size(fontid, size)\n"
"\n"
" Set the size for drawing text.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param size: Point size of the font.\n"
" :type size: float\n");
static PyObject *py_blf_size(PyObject * /*self*/, PyObject *args)
{
int fontid;
float size;
if (!PyArg_ParseTuple(args, "if:blf.size", &fontid, &size)) {
return nullptr;
}
BLF_size(fontid, size);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_aspect_doc,
".. function:: aspect(fontid, aspect)\n"
"\n"
" Set the aspect for drawing text.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param aspect: The aspect ratio for non-uniform scaling of text.\n"
" :type aspect: float\n");
static PyObject *py_blf_aspect(PyObject * /*self*/, PyObject *args)
{
float aspect;
int fontid;
if (!PyArg_ParseTuple(args, "if:blf.aspect", &fontid, &aspect)) {
return nullptr;
}
BLF_aspect(fontid, aspect, aspect, 1.0);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_color_doc,
".. function:: color(fontid, r, g, b, a)\n"
"\n"
" Set the color for drawing text.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param r: Red channel 0.0 - 1.0.\n"
" :type r: float\n"
" :param g: Green channel 0.0 - 1.0.\n"
" :type g: float\n"
" :param b: Blue channel 0.0 - 1.0.\n"
" :type b: float\n"
" :param a: Alpha channel 0.0 - 1.0.\n"
" :type a: float\n");
static PyObject *py_blf_color(PyObject * /*self*/, PyObject *args)
{
int fontid;
float rgba[4];
if (!PyArg_ParseTuple(args, "iffff:blf.color", &fontid, &rgba[0], &rgba[1], &rgba[2], &rgba[3]))
{
return nullptr;
}
BLF_color4fv(fontid, rgba);
/* NOTE(@ideasman42): that storing these colors separately looks like something that could
* be refactored away if the font's internal color format was changed from `uint8` to `float`. */
BLF_buffer_col(fontid, rgba);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_draw_doc,
".. function:: draw(fontid, text)\n"
"\n"
" Draw text in the current context.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param text: The text to draw.\n"
" :type text: str\n");
static PyObject *py_blf_draw(PyObject * /*self*/, PyObject *args)
{
const char *text;
Py_ssize_t text_length;
int fontid;
if (!PyArg_ParseTuple(args, "is#:blf.draw", &fontid, &text, &text_length)) {
return nullptr;
}
BLF_draw(fontid, text, uint(text_length));
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_draw_buffer_doc,
".. function:: draw_buffer(fontid, text)\n"
"\n"
" Draw text into the image buffer bound via :func:`blf.bind_imbuf`.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param text: The text to draw into the bound image buffer.\n"
" :type text: str\n");
static PyObject *py_blf_draw_buffer(PyObject * /*self*/, PyObject *args)
{
const char *text;
Py_ssize_t text_length;
int fontid;
if (!PyArg_ParseTuple(args, "is#:blf.draw_buffer", &fontid, &text, &text_length)) {
return nullptr;
}
BLF_draw_buffer(fontid, text, uint(text_length));
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_dimensions_doc,
".. function:: dimensions(fontid, text)\n"
"\n"
" Return the width and height of the text.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param text: The text to measure.\n"
" :type text: str\n"
" :return: The width and height of the text.\n"
" :rtype: tuple[float, float]\n");
static PyObject *py_blf_dimensions(PyObject * /*self*/, PyObject *args)
{
const char *text;
float width, height;
PyObject *ret;
int fontid;
if (!PyArg_ParseTuple(args, "is:blf.dimensions", &fontid, &text)) {
return nullptr;
}
BLF_width_and_height(fontid, text, INT_MAX, &width, &height);
ret = PyTuple_New(2);
PyTuple_SET_ITEMS(ret, PyFloat_FromDouble(width), PyFloat_FromDouble(height));
return ret;
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_clipping_doc,
".. function:: clipping(fontid, xmin, ymin, xmax, ymax)\n"
"\n"
" Set the clipping, enable/disable using :data:`CLIPPING`.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param xmin: Left edge of the clipping rectangle.\n"
" :type xmin: float\n"
" :param ymin: Bottom edge of the clipping rectangle.\n"
" :type ymin: float\n"
" :param xmax: Right edge of the clipping rectangle.\n"
" :type xmax: float\n"
" :param ymax: Top edge of the clipping rectangle.\n"
" :type ymax: float\n");
static PyObject *py_blf_clipping(PyObject * /*self*/, PyObject *args)
{
float xmin, ymin, xmax, ymax;
int fontid;
if (!PyArg_ParseTuple(args, "iffff:blf.clipping", &fontid, &xmin, &ymin, &xmax, &ymax)) {
return nullptr;
}
BLF_clipping(fontid, xmin, ymin, xmax, ymax);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_word_wrap_doc,
".. function:: word_wrap(fontid, wrap_width)\n"
"\n"
" Set the wrap width, enable/disable using :data:`WORD_WRAP`.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param wrap_width: The width (in pixels) to wrap words at.\n"
" :type wrap_width: int\n");
static PyObject *py_blf_word_wrap(PyObject * /*self*/, PyObject *args)
{
int wrap_width;
int fontid;
if (!PyArg_ParseTuple(args, "ii:blf.word_wrap", &fontid, &wrap_width)) {
return nullptr;
}
BLF_wordwrap(fontid, wrap_width);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_disable_doc,
".. function:: disable(fontid, option)\n"
"\n"
" Disable a font drawing option.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param option: One of :data:`ROTATION`, :data:`CLIPPING`, "
":data:`SHADOW`, :data:`MONOCHROME` or :data:`WORD_WRAP`.\n"
" :type option: int\n");
static PyObject *py_blf_disable(PyObject * /*self*/, PyObject *args)
{
int option, fontid;
if (!PyArg_ParseTuple(args, "ii:blf.disable", &fontid, &option)) {
return nullptr;
}
BLF_disable(fontid, FontFlags(option));
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_enable_doc,
".. function:: enable(fontid, option)\n"
"\n"
" Enable a font drawing option.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param option: One of :data:`ROTATION`, :data:`CLIPPING`, "
":data:`SHADOW`, :data:`MONOCHROME` or :data:`WORD_WRAP`.\n"
" :type option: int\n");
static PyObject *py_blf_enable(PyObject * /*self*/, PyObject *args)
{
int option, fontid;
if (!PyArg_ParseTuple(args, "ii:blf.enable", &fontid, &option)) {
return nullptr;
}
BLF_enable(fontid, FontFlags(option));
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_rotation_doc,
".. function:: rotation(fontid, angle)\n"
"\n"
" Set the text rotation angle, enable/disable using :data:`ROTATION`.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param angle: The angle for text drawing to use (in radians).\n"
" :type angle: float\n");
static PyObject *py_blf_rotation(PyObject * /*self*/, PyObject *args)
{
float angle;
int fontid;
if (!PyArg_ParseTuple(args, "if:blf.rotation", &fontid, &angle)) {
return nullptr;
}
BLF_rotation(fontid, angle);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_shadow_doc,
".. function:: shadow(fontid, level, r, g, b, a)\n"
"\n"
" Shadow options, enable/disable using :data:`SHADOW`.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param level: The shadow type: 0 for none, 3 for 3x3 blur, 5 for 5x5 blur "
"or 6 for outline. Other values raise a :exc:`TypeError`.\n"
" :type level: int\n"
" :param r: Shadow color (red channel 0.0 - 1.0).\n"
" :type r: float\n"
" :param g: Shadow color (green channel 0.0 - 1.0).\n"
" :type g: float\n"
" :param b: Shadow color (blue channel 0.0 - 1.0).\n"
" :type b: float\n"
" :param a: Shadow color (alpha channel 0.0 - 1.0).\n"
" :type a: float\n");
static PyObject *py_blf_shadow(PyObject * /*self*/, PyObject *args)
{
int level, fontid;
float rgba[4];
if (!PyArg_ParseTuple(
args, "iiffff:blf.shadow", &fontid, &level, &rgba[0], &rgba[1], &rgba[2], &rgba[3]))
{
return nullptr;
}
if (!ELEM(level, 0, 3, 5, 6)) {
PyErr_SetString(PyExc_TypeError, "blf.shadow expected arg to be in (0, 3, 5, 6)");
return nullptr;
}
BLF_shadow(fontid, FontShadowType(level), rgba);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_shadow_offset_doc,
".. function:: shadow_offset(fontid, x, y)\n"
"\n"
" Set the offset for shadow text, enable/disable using :data:`SHADOW`.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param x: Horizontal shadow offset value in pixels.\n"
" :type x: int\n"
" :param y: Vertical shadow offset value in pixels.\n"
" :type y: int\n");
static PyObject *py_blf_shadow_offset(PyObject * /*self*/, PyObject *args)
{
int x, y, fontid;
if (!PyArg_ParseTuple(args, "iii:blf.shadow_offset", &fontid, &x, &y)) {
return nullptr;
}
BLF_shadow_offset(fontid, x, y);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_load_doc,
".. function:: load(filepath)\n"
"\n"
" Load a new font.\n"
"\n"
" :param filepath: The filepath of the font.\n"
" :type filepath: str | bytes\n"
" :return: The new font's fontid or -1 if there was an error.\n"
" :rtype: int\n");
static PyObject *py_blf_load(PyObject * /*self*/, PyObject *args)
{
PyC_UnicodeAsBytesAndSize_Data filepath_data = {nullptr};
if (!PyArg_ParseTuple(args,
"O&" /* `filepath` */
":blf.load",
PyC_ParseUnicodeAsBytesAndSize,
&filepath_data))
{
return nullptr;
}
const int font_id = BLF_load(filepath_data.value);
Py_XDECREF(filepath_data.value_coerce);
return PyLong_FromLong(font_id);
}
PyDoc_STRVAR(
/* Wrap. */
py_blf_unload_doc,
".. function:: unload(filepath)\n"
"\n"
" Unload an existing font.\n"
"\n"
" :param filepath: The filepath of the font.\n"
" :type filepath: str | bytes\n");
static PyObject *py_blf_unload(PyObject * /*self*/, PyObject *args)
{
PyC_UnicodeAsBytesAndSize_Data filepath_data = {nullptr};
if (!PyArg_ParseTuple(args,
"O&" /* `filepath` */
":blf.unload",
PyC_ParseUnicodeAsBytesAndSize,
&filepath_data))
{
return nullptr;
}
BLF_unload(filepath_data.value);
Py_XDECREF(filepath_data.value_coerce);
Py_RETURN_NONE;
}
/* -------------------------------------------------------------------- */
/** \name Image Buffer Access
*
* Context manager for #ImBuf.
* \{ */
static PyObject *py_blf_bind_imbuf_enter(BPyBLFImBufContext *self)
{
if (UNLIKELY(self->buffer_state)) {
PyErr_SetString(PyExc_ValueError,
"BLFImBufContext.__enter__: unable to enter the same context more than once");
return nullptr;
}
ImBuf *ibuf = BPy_ImBuf_FromPyObject(self->py_imbuf);
if (ibuf == nullptr) {
/* The error will have been set. */
return nullptr;
}
BLFBufferState *buffer_state = BLF_buffer_state_push(self->fontid);
if (buffer_state == nullptr) {
PyErr_Format(PyExc_ValueError, "bind_imbuf: unknown fontid %d", self->fontid);
return nullptr;
}
BLF_buffer(self->fontid,
ibuf->float_data_for_write(),
ibuf->byte_data_for_write(),
ibuf->x,
ibuf->y,
4,
ibuf->byte_buffer.colorspace);
self->buffer_state = buffer_state;
Py_RETURN_NONE;
}
static PyObject *py_blf_bind_imbuf_exit(BPyBLFImBufContext *self, PyObject * /*args*/)
{
BLF_buffer_state_pop(self->buffer_state);
self->buffer_state = nullptr;
Py_RETURN_NONE;
}
static void py_blf_bind_imbuf_dealloc(BPyBLFImBufContext *self)
{
if (self->buffer_state) {
/* This should practically never happen since it implies
* `__enter__` is called without a matching `__exit__`.
* Do this mainly for correctness:
* if the process somehow exits before exiting the context manager. */
BLF_buffer_state_free(self->buffer_state);
}
PyObject_GC_UnTrack(self);
Py_CLEAR(self->py_imbuf);
PyObject_GC_Del(self);
}
static int py_blf_bind_imbuf_traverse(BPyBLFImBufContext *self, visitproc visit, void *arg)
{
Py_VISIT(self->py_imbuf);
return 0;
}
static int py_blf_bind_imbuf_clear(BPyBLFImBufContext *self)
{
Py_CLEAR(self->py_imbuf);
return 0;
}
#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 py_blf_bind_imbuf_methods[] = {
{"__enter__", reinterpret_cast<PyCFunction>(py_blf_bind_imbuf_enter), METH_NOARGS},
{"__exit__", reinterpret_cast<PyCFunction>(py_blf_bind_imbuf_exit), METH_VARARGS},
{nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
BPyBLFImBufContext_Type_doc,
"Context manager returned by :func:`blf.bind_imbuf` that binds an image buffer\n"
"as the destination for text drawing.");
static PyTypeObject BPyBLFImBufContext_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "BLFImBufContext",
/*tp_basicsize*/ sizeof(BPyBLFImBufContext),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(py_blf_bind_imbuf_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
/*tp_doc*/ BPyBLFImBufContext_Type_doc,
/*tp_traverse*/ reinterpret_cast<traverseproc>(py_blf_bind_imbuf_traverse),
/*tp_clear*/ reinterpret_cast<inquiry>(py_blf_bind_imbuf_clear),
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ py_blf_bind_imbuf_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ nullptr,
/*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,
};
PyDoc_STRVAR(
/* Wrap. */
py_blf_bind_imbuf_doc,
".. function:: bind_imbuf(fontid, imbuf, *, display_name=None)\n"
"\n"
" Context manager to draw text into an image buffer instead of the GPU's context.\n"
"\n"
" :param fontid: The id of the typeface as returned by :func:`blf.load`, for default "
"font use 0.\n"
" :type fontid: int\n"
" :param imbuf: The image to draw into.\n"
" :type imbuf: :class:`imbuf.types.ImBuf`\n"
" :param display_name: Ignored (formerly a color-space transform name), "
"kept for backwards compatibility.\n"
" :type display_name: str | None\n"
" :return: The BLF ImBuf context manager.\n"
" :rtype: :class:`blf.types.BLFImBufContext`\n");
static PyObject *py_blf_bind_imbuf(PyObject * /*self*/, PyObject *args, PyObject *kwds)
{
int fontid;
PyObject *py_imbuf = nullptr;
const char *display_name = nullptr;
static const char *_keywords[] = {
"",
"",
"display_name",
nullptr,
};
static _PyArg_Parser _parser = {
"i" /* `fontid` */
"O!" /* `image` */
"|" /* Optional arguments. */
"z" /* `display_name` */
":bind_imbuf",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kwds, &_parser, &fontid, &Py_ImBuf_Type, &py_imbuf, &display_name))
{
return nullptr;
}
/* Display name is ignored, it is only kept for backwards compatibility. This should
* always have been the image buffer byte colorspace rather than a display. */
BPyBLFImBufContext *ret = PyObject_GC_New(BPyBLFImBufContext, &BPyBLFImBufContext_Type);
ret->py_imbuf = Py_NewRef(py_imbuf);
ret->fontid = fontid;
ret->buffer_state = nullptr;
PyObject_GC_Track(ret);
return reinterpret_cast<PyObject *>(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
/*----------------------------MODULE INIT-------------------------*/
static PyMethodDef BLF_methods[] = {
{"aspect", static_cast<PyCFunction>(py_blf_aspect), METH_VARARGS, py_blf_aspect_doc},
{"clipping", static_cast<PyCFunction>(py_blf_clipping), METH_VARARGS, py_blf_clipping_doc},
{"word_wrap", static_cast<PyCFunction>(py_blf_word_wrap), METH_VARARGS, py_blf_word_wrap_doc},
{"disable", static_cast<PyCFunction>(py_blf_disable), METH_VARARGS, py_blf_disable_doc},
{"dimensions",
static_cast<PyCFunction>(py_blf_dimensions),
METH_VARARGS,
py_blf_dimensions_doc},
{"draw", static_cast<PyCFunction>(py_blf_draw), METH_VARARGS, py_blf_draw_doc},
{"draw_buffer",
static_cast<PyCFunction>(py_blf_draw_buffer),
METH_VARARGS,
py_blf_draw_buffer_doc},
{"enable", static_cast<PyCFunction>(py_blf_enable), METH_VARARGS, py_blf_enable_doc},
{"position", static_cast<PyCFunction>(py_blf_position), METH_VARARGS, py_blf_position_doc},
{"rotation", static_cast<PyCFunction>(py_blf_rotation), METH_VARARGS, py_blf_rotation_doc},
{"shadow", static_cast<PyCFunction>(py_blf_shadow), METH_VARARGS, py_blf_shadow_doc},
{"shadow_offset",
static_cast<PyCFunction>(py_blf_shadow_offset),
METH_VARARGS,
py_blf_shadow_offset_doc},
{"size", static_cast<PyCFunction>(py_blf_size), METH_VARARGS, py_blf_size_doc},
{"color", static_cast<PyCFunction>(py_blf_color), METH_VARARGS, py_blf_color_doc},
{"load", static_cast<PyCFunction>(py_blf_load), METH_VARARGS, py_blf_load_doc},
{"unload", static_cast<PyCFunction>(py_blf_unload), METH_VARARGS, py_blf_unload_doc},
{"bind_imbuf",
reinterpret_cast<PyCFunction>(py_blf_bind_imbuf),
METH_VARARGS | METH_KEYWORDS,
py_blf_bind_imbuf_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
BLF_doc,
"This module provides access to Blender's text drawing functions.");
static PyModuleDef BLF_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "blf",
/*m_doc*/ BLF_doc,
/*m_size*/ 0,
/*m_methods*/ BLF_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
/* -------------------------------------------------------------------- */
/** \name Module Definition (`blf.types`)
* \{ */
PyDoc_STRVAR(
/* Wrap. */
BLF_types_doc,
"This module provides access to font drawing types.");
static PyModuleDef BLF_types_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "blf.types",
/*m_doc*/ BLF_types_doc,
/*m_size*/ 0,
/*m_methods*/ nullptr,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
static PyObject *BPyInit_blf_types()
{
PyObject *submodule = PyModule_Create(&BLF_types_module_def);
if (PyType_Ready(&BPyBLFImBufContext_Type) < 0) {
return nullptr;
}
PyModule_AddType(submodule, &BPyBLFImBufContext_Type);
return submodule;
}
/** \} */
PyObject *BPyInit_blf()
{
PyObject *mod;
PyObject *submodule;
PyObject *sys_modules = PyImport_GetModuleDict();
mod = PyModule_Create(&BLF_module_def);
PyModule_AddIntConstant(mod, "ROTATION", BLF_ROTATION);
PyModule_AddIntConstant(mod, "CLIPPING", BLF_CLIPPING);
PyModule_AddIntConstant(mod, "SHADOW", BLF_SHADOW);
PyModule_AddIntConstant(mod, "WORD_WRAP", BLF_WORD_WRAP);
PyModule_AddIntConstant(mod, "MONOCHROME", BLF_MONOCHROME);
PyModule_AddIntConstant(mod, "NO_FALLBACK", BLF_NO_FALLBACK);
/* `blf.types` */
PyModule_AddObject(mod, "types", (submodule = BPyInit_blf_types()));
PyC_Module_AddToSysModules(sys_modules, submodule);
return mod;
}
} // namespace blender

View File

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

View File

@@ -0,0 +1,67 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pygen
*
* This file contains wrapper functions related to global interpreter lock.
* these functions are slightly different from the original Python API,
* don't throw SIGABRT even if the thread state is nullptr. */
#include <Python.h>
#include "python_compat.hh" /* IWYU pragma: keep. */
#include "../BPY_extern.hh"
namespace blender {
BPy_ThreadStatePtr BPY_thread_save()
{
/* Use `_PyThreadState_UncheckedGet()` instead of `PyThreadState_Get()`, to avoid a fatal error
* issued when a thread state is nullptr (the thread state can be nullptr when quitting Blender).
*
* `PyEval_SaveThread()` will release the GIL, so this thread has to have the GIL to begin with
* or badness will ensue. */
if (PyThreadState_GetUnchecked() && PyGILState_Check()) {
return static_cast<BPy_ThreadStatePtr>(PyEval_SaveThread());
}
return nullptr;
}
void BPY_thread_restore(BPy_ThreadStatePtr tstate)
{
if (tstate) {
PyEval_RestoreThread(static_cast<PyThreadState *>(tstate));
}
}
void BPY_thread_backtrace_print()
{
PyThreadState *tstate = PyGILState_GetThisThreadState();
if (tstate) {
PyFrameObject *frame = PyThreadState_GetFrame(tstate);
printf(frame ? "Python stack trace:\n" : "No Python stack trace available.\n");
while (frame) {
PyCodeObject *frame_co = PyFrame_GetCode(frame);
int line = PyFrame_GetLineNumber(frame);
const char *filename = PyUnicode_AsUTF8(frame_co->co_filename);
const char *funcname = PyUnicode_AsUTF8(frame_co->co_name);
printf(" %s:%d %s\n", filename, line, funcname);
Py_DECREF(frame_co);
PyFrameObject *frame_back = PyFrame_GetBack(frame);
Py_DECREF(frame);
frame = frame_back;
}
printf("\n");
}
else {
printf("No Python thread state available.\n");
}
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,125 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pygen
*/
#pragma once
#include <Python.h>
namespace blender {
struct BPy_IDGroup_Iter;
struct ID;
struct IDProperty;
extern PyTypeObject BPy_IDArray_Type;
extern PyTypeObject BPy_IDGroup_Type;
extern PyTypeObject BPy_IDGroup_ViewKeys_Type;
extern PyTypeObject BPy_IDGroup_ViewValues_Type;
extern PyTypeObject BPy_IDGroup_ViewItems_Type;
extern PyTypeObject BPy_IDGroup_IterKeys_Type;
extern PyTypeObject BPy_IDGroup_IterValues_Type;
extern PyTypeObject BPy_IDGroup_IterItems_Type;
#define BPy_IDArray_Check(v) (PyObject_TypeCheck(v, &BPy_IDArray_Type))
#define BPy_IDArray_CheckExact(v) (Py_TYPE(v) == &BPy_IDArray_Type)
#define BPy_IDGroup_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_Type))
#define BPy_IDGroup_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_Type)
#define BPy_IDGroup_ViewKeys_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_ViewKeys_Type))
#define BPy_IDGroup_ViewKeys_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_ViewKeys_Type)
#define BPy_IDGroup_ViewValues_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_ViewValues_Type))
#define BPy_IDGroup_ViewValues_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_ViewValues_Type)
#define BPy_IDGroup_ViewItems_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_ViewItems_Type))
#define BPy_IDGroup_ViewItems_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_ViewItems_Type)
#define BPy_IDGroup_IterKeys_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_IterKeys_Type))
#define BPy_IDGroup_IterKeys_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_IterKeys_Type)
#define BPy_IDGroup_IterValues_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_IterValues_Type))
#define BPy_IDGroup_IterValues_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_IterValues_Type)
#define BPy_IDGroup_IterItems_Check(v) (PyObject_TypeCheck(v, &BPy_IDGroup_IterItems_Type))
#define BPy_IDGroup_IterItems_CheckExact(v) (Py_TYPE(v) == &BPy_IDGroup_IterItems_Type)
struct BPy_IDProperty {
PyObject_HEAD
struct ID *owner_id; /* can be NULL */
struct IDProperty *prop; /* must be second member */
struct IDProperty *parent;
};
struct BPy_IDArray {
PyObject_HEAD
struct ID *owner_id; /* can be NULL */
struct IDProperty *prop; /* must be second member */
};
struct BPy_IDGroup_Iter {
PyObject_HEAD
BPy_IDProperty *group;
struct IDProperty *cur;
/** Use for detecting manipulation during iteration (which is not allowed). */
int len_init;
/** Iterate in the reverse direction. */
bool reversed;
};
/** Use to implement `IDPropertyGroup.keys/values/items` */
struct BPy_IDGroup_View {
PyObject_HEAD
/** This will be NULL when accessing keys on data that has no ID properties. */
BPy_IDProperty *group;
bool reversed;
};
[[nodiscard]] PyObject *BPy_Wrap_GetKeys(IDProperty *prop);
[[nodiscard]] PyObject *BPy_Wrap_GetValues(ID *id, IDProperty *prop);
[[nodiscard]] PyObject *BPy_Wrap_GetItems(ID *id, IDProperty *prop);
[[nodiscard]] PyObject *BPy_Wrap_GetKeys_View_WithID(ID *id, IDProperty *prop);
[[nodiscard]] PyObject *BPy_Wrap_GetValues_View_WithID(ID *id, IDProperty *prop);
[[nodiscard]] PyObject *BPy_Wrap_GetItems_View_WithID(ID *id, IDProperty *prop);
[[nodiscard]] int BPy_Wrap_SetMapItem(IDProperty *prop, PyObject *key, PyObject *val);
/**
* For simple, non nested types this is the same as #BPy_IDGroup_WrapData.
*/
[[nodiscard]] PyObject *BPy_IDGroup_MapDataToPy(IDProperty *prop);
[[nodiscard]] PyObject *BPy_IDGroup_WrapData(ID *id, IDProperty *prop, IDProperty *parent);
/**
* \note group can be a pointer array or a group.
* assume we already checked key is a string.
*
* \return success.
*/
[[nodiscard]] bool BPy_IDProperty_Map_ValidateAndCreate(PyObject *key,
IDProperty *group,
PyObject *ob);
void IDProp_Init_Types();
[[nodiscard]] PyObject *BPyInit_idprop();
/**
* Create an IDProperty from a Python object.
*
* \param prop_exist: pre-existing IDProperty to populate with the value. Can be `nullptr` to
* allocate a new IDProperty.
* \param name: the name of the IDProperty. Only used when creating a new IDProperty.
* \param ob: the Python object to convert.
* \param do_conversion: when there is a pre-existing IDProperty, whether the Python object's value
* should be converted to its type (if not the same type already).
* \param can_create: whether the function is allowed to create a new property. If this is `false`
* and `prop_exists` is `nullptr`, this function is a no-op.
*
* \return the existing/created IDProperty if the value was set on it, and `nullptr` otherwise.
*/
IDProperty *BPy_IDProperty_FromPyObject(
IDProperty *prop_exist, const char *name, PyObject *ob, bool do_conversion, bool can_create);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pygen
*/
#pragma once
#include <Python.h>
namespace blender {
struct IDProperty;
extern PyTypeObject BPy_IDPropertyUIManager_Type;
struct BPy_IDPropertyUIManager {
PyObject_HEAD
IDProperty *property;
};
void IDPropertyUIData_Init_Types();
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup pygen
*/
#include <Python.h>
namespace blender {
struct ImBuf;
[[nodiscard]] PyObject *BPyInit_imbuf();
extern PyTypeObject Py_ImBuf_Type;
/** Return the #ImBuf or null with an error set. */
[[nodiscard]] ImBuf *BPy_ImBuf_FromPyObject(PyObject *py_imbuf);
} // namespace blender

View File

@@ -0,0 +1,250 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pygen
*
* Python/RNA utilities.
*
* RNA functions that aren't part of the `bpy_rna.cc` API.
*/
/* Future-proof, See https://docs.python.org/3/c-api/arg.html#strings-and-buffers */
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "py_capi_rna.hh"
#include "BLI_bitmap.h"
#include "BLI_dynstr.h"
#include "RNA_access.hh"
#include "MEM_guardedalloc.h"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Enum Utilities
* \{ */
char *pyrna_enum_repr(const EnumPropertyItem *item)
{
DynStr *dynstr = BLI_dynstr_new();
/* We can't compare with the first element in the array
* since it may be a category (without an identifier). */
for (bool is_first = true; item->identifier; item++) {
if (item->identifier[0]) {
BLI_dynstr_appendf(dynstr, is_first ? "'%s'" : ", '%s'", item->identifier);
is_first = false;
}
}
char *cstring = BLI_dynstr_get_cstring(dynstr);
BLI_dynstr_free(dynstr);
return cstring;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Enum Conversion Utilities
* \{ */
int pyrna_enum_value_from_id(const EnumPropertyItem *item,
const char *identifier,
int *r_value,
const char *error_prefix)
{
if (RNA_enum_value_from_id(item, identifier, r_value) == 0) {
const char *enum_str = pyrna_enum_repr(item);
PyErr_Format(
PyExc_ValueError, "%s: '%.200s' not found in (%s)", error_prefix, identifier, enum_str);
MEM_delete(enum_str);
return -1;
}
return 0;
}
BLI_bitmap *pyrna_enum_bitmap_from_set(const EnumPropertyItem *items,
PyObject *value,
int type_size,
bool type_convert_sign,
int bitmap_size,
const char *error_prefix)
{
BLI_assert(PySet_Check(value));
BLI_bitmap *bitmap = BLI_BITMAP_NEW(bitmap_size, __func__);
if (PySet_GET_SIZE(value) > 0) {
/* Set looping. */
PyObject *it = PyObject_GetIter(value);
PyObject *key;
while ((key = PyIter_Next(it))) {
/* Borrow from the set. */
Py_DECREF(key);
const char *param = PyUnicode_AsUTF8(key);
if (param == nullptr) {
PyErr_Format(PyExc_TypeError,
"%.200s expected a string, not %.200s",
error_prefix,
Py_TYPE(key)->tp_name);
break;
}
int ret;
if (pyrna_enum_value_from_id(items, param, &ret, error_prefix) == -1) {
break;
}
int index = ret;
if (type_convert_sign) {
if (type_size == 2) {
union {
signed short as_signed;
ushort as_unsigned;
} ret_convert;
ret_convert.as_signed = short(ret);
index = int(ret_convert.as_unsigned);
}
else if (type_size == 1) {
union {
signed char as_signed;
uchar as_unsigned;
} ret_convert;
ret_convert.as_signed = static_cast<signed char>(ret);
index = int(ret_convert.as_unsigned);
}
else {
BLI_assert_unreachable();
}
}
BLI_assert(index < bitmap_size);
BLI_BITMAP_ENABLE(bitmap, index);
}
Py_DECREF(it);
if (key) {
MEM_delete(bitmap);
bitmap = nullptr;
}
}
return bitmap;
}
int pyrna_enum_bitfield_from_set(const EnumPropertyItem *items,
PyObject *value,
int *r_value,
const char *error_prefix)
{
BLI_assert(PySet_Check(value));
/* Set of enum items, concatenate all values with OR. */
int flag = 0;
*r_value = 0;
PyObject *key = nullptr;
if (PySet_GET_SIZE(value) > 0) {
/* Set looping. */
PyObject *it = PyObject_GetIter(value);
while ((key = PyIter_Next(it))) {
/* Borrow from the set. */
Py_DECREF(key);
const char *param = PyUnicode_AsUTF8(key);
if (param == nullptr) {
PyErr_Format(PyExc_TypeError,
"%.200s expected a string, not %.200s",
error_prefix,
Py_TYPE(key)->tp_name);
break;
}
int ret;
if (pyrna_enum_value_from_id(items, param, &ret, error_prefix) == -1) {
break;
}
flag |= ret;
}
Py_DECREF(it);
if (key) {
return -1;
}
}
*r_value = flag;
return 0;
}
PyObject *pyrna_enum_bitfield_as_set(const EnumPropertyItem *items, int value)
{
PyObject *ret = PySet_New(nullptr);
const char *identifier[RNA_ENUM_BITFLAG_SIZE + 1];
if (RNA_enum_bitflag_identifiers(items, value, identifier)) {
PyObject *item;
int index;
for (index = 0; identifier[index]; index++) {
item = PyUnicode_FromString(identifier[index]);
PySet_Add(ret, item);
Py_DECREF(item);
}
}
return ret;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Argument Parsing Helpers
* \{ */
int pyrna_enum_value_parse_string(PyObject *o, void *p)
{
const char *identifier = PyUnicode_AsUTF8(o);
if (identifier == nullptr) {
PyErr_Format(PyExc_TypeError, "expected a string enum, not %.200s", Py_TYPE(o)->tp_name);
return 0;
}
BPy_EnumProperty_Parse *parse_data = static_cast<BPy_EnumProperty_Parse *>(p);
if (pyrna_enum_value_from_id(
parse_data->items, identifier, &parse_data->value, "enum identifier") == -1)
{
return 0;
}
parse_data->value_orig = o;
parse_data->is_set = true;
return 1;
}
int pyrna_enum_bitfield_parse_set(PyObject *o, void *p)
{
if (!PySet_Check(o)) {
PyErr_Format(PyExc_TypeError, "expected a set, not %.200s", Py_TYPE(o)->tp_name);
return 0;
}
BPy_EnumProperty_Parse *parse_data = static_cast<BPy_EnumProperty_Parse *>(p);
if (pyrna_enum_bitfield_from_set(
parse_data->items, o, &parse_data->value, "enum identifier set") == -1)
{
return 0;
}
parse_data->value_orig = o;
parse_data->is_set = true;
return 1;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,82 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* \file
* \ingroup pygen
*/
#pragma once
#include <Python.h>
namespace blender {
struct EnumPropertyItem;
/**
* Convert all items into a single comma separated string.
* Use for creating useful error messages.
*/
[[nodiscard]] char *pyrna_enum_repr(const EnumPropertyItem *item);
/**
* Same as #RNA_enum_value_from_id, but raises an exception.
*/
[[nodiscard]] int pyrna_enum_value_from_id(const EnumPropertyItem *item,
const char *identifier,
int *r_value,
const char *error_prefix);
/**
* Takes a set of strings and map it to and array of booleans.
*
* Useful when the values aren't flags.
*
* \param type_convert_sign: Maps signed to unsigned range,
* needed when we want to use the full range of a signed short/char.
*/
[[nodiscard]] unsigned int *pyrna_enum_bitmap_from_set(const EnumPropertyItem *items,
PyObject *value,
int type_size,
bool type_convert_sign,
int bitmap_size,
const char *error_prefix);
/**
* 'value' _must_ be a set type, error check before calling.
*/
[[nodiscard]] int pyrna_enum_bitfield_from_set(const EnumPropertyItem *items,
PyObject *value,
int *r_value,
const char *error_prefix);
[[nodiscard]] PyObject *pyrna_enum_bitfield_as_set(const EnumPropertyItem *items, int value);
/**
* Data for #pyrna_enum_value_parse_string & #pyrna_enum_bitfield_parse_set parsing utilities.
* Use with #PyArg_ParseTuple's `O&` formatting.
*/
struct BPy_EnumProperty_Parse {
const EnumPropertyItem *items;
/**
* Set when the value was successfully parsed.
* Useful if the input ever needs to be included in an error message.
* (if the value is not supported under certain conditions).
*/
PyObject *value_orig;
int value;
bool is_set;
};
/**
* Use with #PyArg_ParseTuple's `O&` formatting.
*/
[[nodiscard]] int pyrna_enum_value_parse_string(PyObject *o, void *p);
/**
* Use with #PyArg_ParseTuple's `O&` formatting.
*/
[[nodiscard]] int pyrna_enum_bitfield_parse_set(PyObject *o, void *p);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,545 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pygen
*/
#pragma once
#include <Python.h>
#include <optional>
#include <string>
#include <type_traits>
#include "BLI_compiler_attrs.h"
#include "BLI_span.hh"
#include "BLI_sys_types.h"
#include "DNA_vec_types.h"
namespace blender {
/** Useful to print Python objects while debugging. */
void PyC_ObSpit(const char *name, PyObject *var);
/**
* A version of #PyC_ObSpit that writes into a string (and doesn't take a name argument).
* Use for logging.
*/
void PyC_ObSpitStr(char *result, size_t result_maxncpy, PyObject *var);
void PyC_LineSpit();
void PyC_StackSpit();
/**
* Return a string containing the full stack trace.
*
* - Only call when `PyErr_Occurred() != 0` .
* - The exception is left in place without being manipulated,
* although they will be normalized in order to display them (`PyErr_Print` also does this).
* - `SystemExit` exceptions will exit (so `sys.exit(..)` works, matching `PyErr_Print` behavior).
* - The always returns a Python string (unless exiting where the function doesn't return).
*/
[[nodiscard]] PyObject *PyC_ExceptionBuffer() ATTR_RETURNS_NONNULL;
/**
* A version of #PyC_ExceptionBuffer that returns the last exception only.
*
* Useful for error messages from evaluating numeric expressions for example
* where a full multi-line stack-trace isn't needed and doesn't format well in the status-bar.
*/
[[nodiscard]] PyObject *PyC_ExceptionBuffer_Simple() ATTR_RETURNS_NONNULL;
/**
* Get exit code `sys.exit(..)` was called with.
*/
[[nodiscard]] std::optional<int> PyC_ExceptionSystemExitCode();
/**
* Capture exit code from current python exception.
*
* If the current exception is `SystemExit`, capture the exit code and return true.
* Otherwise return false;
*/
bool PyC_Err_CaptureSystemExitCode();
[[nodiscard]] PyObject *PyC_Object_GetAttrStringArgs(PyObject *o, Py_ssize_t n, ...);
[[nodiscard]] PyObject *PyC_FrozenSetFromStrings(const char **strings);
/**
* Similar to #PyErr_Format(),
*
* Implementation - we can't actually prepend the existing exception,
* because it could have _any_ arguments given to it, so instead we get its
* `__str__` output and raise our own exception including it.
*/
PyObject *PyC_Err_Format_Prefix(PyObject *exception_type_prefix, const char *format, ...);
PyObject *PyC_Err_SetString_Prefix(PyObject *exception_type_prefix, const char *str);
/**
* Use for Python callbacks run directly from C,
* when we can't use normal methods of raising exceptions.
*/
void PyC_Err_PrintWithFunc(PyObject *py_func);
void PyC_FileAndNum(const char **r_filename, int *r_lineno);
/**
* The "safe" version checks Python is running first.
* Typically the caller should know this but there are times it's impractical.
*/
void PyC_FileAndNum_Safe(const char **r_filename, int *r_lineno);
[[nodiscard]] int PyC_AsArray_FAST(void *array,
size_t array_item_size,
PyObject *value_fast,
Py_ssize_t length,
const PyTypeObject *type,
const char *error_prefix);
[[nodiscard]] int PyC_AsArray(void *array,
size_t array_item_size,
PyObject *value,
Py_ssize_t length,
const PyTypeObject *type,
const char *error_prefix);
[[nodiscard]] int PyC_AsArray_Multi_FAST(void *array,
size_t array_item_size,
PyObject *value_fast,
const int *dims,
int dims_len,
const PyTypeObject *type,
const char *error_prefix);
[[nodiscard]] int PyC_AsArray_Multi(void *array,
size_t array_item_size,
PyObject *value,
const int *dims,
int dims_len,
const PyTypeObject *type,
const char *error_prefix);
[[nodiscard]] PyObject *PyC_Tuple_PackArray_F32(const float *array, uint len);
[[nodiscard]] PyObject *PyC_Tuple_PackArray_F64(const double *array, uint len);
[[nodiscard]] PyObject *PyC_Tuple_PackArray_I32(const int *array, uint len);
[[nodiscard]] PyObject *PyC_Tuple_PackArray_I32FromBool(const int *array, uint len);
[[nodiscard]] PyObject *PyC_Tuple_PackArray_Bool(const bool *array, uint len);
/**
* \note Any errors converting strings will return null with the error left as-is.
*/
[[nodiscard]] PyObject *PyC_Tuple_PackArray_String(const char **array, uint len);
[[nodiscard]] PyObject *PyC_Tuple_PackArray_Multi_F32(const float *array,
const int dims[],
int dims_len);
[[nodiscard]] PyObject *PyC_Tuple_PackArray_Multi_F64(const double *array,
const int dims[],
int dims_len);
[[nodiscard]] PyObject *PyC_Tuple_PackArray_Multi_I32(const int *array,
const int dims[],
int dims_len);
[[nodiscard]] PyObject *PyC_Tuple_PackArray_Multi_Bool(const bool *array,
const int dims[],
int dims_len);
/**
* Caller needs to ensure tuple is uninitialized.
* Handy for filling a tuple with None for eg.
*/
void PyC_Tuple_Fill(PyObject *tuple, PyObject *value);
void PyC_List_Fill(PyObject *list, PyObject *value);
/**
* Create a `str` from bytes in a way which is compatible with non UTF8 encoded file-system paths,
* see: #111033.
* Follow http://www.python.org/dev/peps/pep-0383/
*/
[[nodiscard]] PyObject *PyC_UnicodeFromBytes(const char *str);
/**
* \param size: The length of the string: `strlen(str)`.
*/
[[nodiscard]] PyObject *PyC_UnicodeFromBytesAndSize(const char *str, Py_ssize_t size);
[[nodiscard]] const char *PyC_UnicodeAsBytes(PyObject *py_str,
PyObject **r_coerce); /* coerce must be NULL */
/**
* String conversion, escape non-unicode chars
* \param r_size: The string length (not including the null terminator).
* \note By convention Blender API's use len/length however Python API's use the term size,
* as this is an alternative to Python's #PyUnicode_AsUTF8AndSize, follow it's naming.
* \param r_coerce: must reference a pointer set to NULL.
*/
[[nodiscard]] const char *PyC_UnicodeAsBytesAndSize(PyObject *py_str,
Py_ssize_t *r_size,
PyObject **r_coerce);
/**
* Notes on using this structure:
* - Always initialize to `{nullptr}`.
* - Always `Py_XDECREF(value_coerce)` before returning,
* after this `value` must not be accessed.
*/
struct PyC_UnicodeAsBytesAndSize_Data {
PyObject *value_coerce;
const char *value;
Py_ssize_t value_len;
};
/**
* Use with PyArg_ParseTuple's "O&" formatting.
*
* Expose #PyC_UnicodeAsBytes in a way which is useful to the argument parser.
* \param o: An argument parsed to #PyC_UnicodeAsBytes.
* \param p: Pointer to #PyC_UnicodeAsBytes_Data.
*
* \note The Python API docs reference `PyUnicode_FSConverter` however this does not support
* paths which non UTF8 encoding, see: #111033.
*/
int PyC_ParseUnicodeAsBytesAndSize(PyObject *o, void *p);
/** A version of #PyC_ParseUnicodeAsBytesAndSize that accepts None. */
int PyC_ParseUnicodeAsBytesAndSize_OrNone(PyObject *o, void *p);
/**
* Description: This function creates a new Python dictionary object.
* NOTE: dict is owned by sys.modules["__main__"] module, reference is borrowed
* NOTE: important we use the dict from __main__, this is what python expects
* for 'pickle' to work as well as strings like this...
* >> foo = 10
* >> print(__import__("__main__").foo)
*
* NOTE: this overwrites __main__ which gives problems with nested calls.
* be sure to run #PyC_MainModule_Backup & #PyC_MainModule_Restore if there is
* any chance that python is in the call stack.
*/
[[nodiscard]] PyObject *PyC_DefaultNameSpace(const char *filename) ATTR_NONNULL(1);
void PyC_RunQuicky(const char *filepath, int n, ...) ATTR_NONNULL(1);
/**
* Import `imports` into `py_dict`.
*
* \param py_dict: A Python dictionary, typically used as a name-space for script execution.
* \param imports: A NULL terminated array of strings.
* \return true when all modules import without errors, otherwise return false.
* The caller is expected to handle the exception.
*/
[[nodiscard]] bool PyC_NameSpace_ImportArray(PyObject *py_dict, const char *imports[]);
/**
* #PyC_MainModule_Restore MUST be called after #PyC_MainModule_Backup.
*/
[[nodiscard]] PyObject *PyC_MainModule_Backup();
void PyC_MainModule_Restore(PyObject *main_mod);
/**
* Add a module to `sys.modules` using the module's `__name__` as the key.
*
* Equivalent to: `sys.modules[module.__name__] = module`.
*
* \param sys_modules: The result of #PyImport_GetModuleDict().
* \param module: The module to add.
* \return 0 on success, -1 on error.
*/
int PyC_Module_AddToSysModules(PyObject *sys_modules, PyObject *module);
[[nodiscard]] bool PyC_IsInterpreterActive();
/**
* Generic function to avoid depending on RNA.
*/
[[nodiscard]] void *PyC_RNA_AsPointer(PyObject *value, const char *type_name);
/* flag / set --- interchange */
struct PyC_FlagSet {
int value;
const char *identifier;
};
[[nodiscard]] PyObject *PyC_FlagSet_AsString(const PyC_FlagSet *item);
[[nodiscard]] int PyC_FlagSet_ValueFromID_int(const PyC_FlagSet *item,
const char *identifier,
int *r_value);
[[nodiscard]] int PyC_FlagSet_ValueFromID(const PyC_FlagSet *item,
const char *identifier,
int *r_value,
const char *error_prefix);
[[nodiscard]] int PyC_FlagSet_ToBitfield(const PyC_FlagSet *items,
PyObject *value,
int *r_value,
const char *error_prefix);
[[nodiscard]] PyObject *PyC_FlagSet_FromBitfield(PyC_FlagSet *items, int flag);
/**
* \return success
*
* \note it is caller's responsibility to acquire & release GIL!
*/
[[nodiscard]] bool PyC_RunString_AsNumber(const char **imports,
const char *expr,
const char *filename,
double *r_value) ATTR_NONNULL(2, 3, 4);
[[nodiscard]] bool PyC_RunString_AsIntPtr(const char **imports,
const char *expr,
const char *filename,
intptr_t *r_value) ATTR_NONNULL(2, 3, 4);
/**
* \param r_value_size: The length of the string assigned: `strlen(*r_value)`.
*/
[[nodiscard]] bool PyC_RunString_AsStringAndSize(const char **imports,
const char *expr,
const char *filename,
char **r_value,
size_t *r_value_size) ATTR_NONNULL(2, 3, 4, 5);
[[nodiscard]] bool PyC_RunString_AsString(const char **imports,
const char *expr,
const char *filename,
char **r_value) ATTR_NONNULL(2, 3, 4);
/**
* \param r_value_size: The length of the string assigned: `strlen(*r_value)`.
*/
[[nodiscard]] bool PyC_RunString_AsStringAndSizeOrNone(const char **imports,
const char *expr,
const char *filename,
char **r_value,
size_t *r_value_size) ATTR_NONNULL(2, 3, 4);
[[nodiscard]] bool PyC_RunString_AsStringOrNone(const char **imports,
const char *expr,
const char *filename,
char **r_value) ATTR_NONNULL(2, 3, 4);
/**
* Flush Python's `sys.stdout` and `sys.stderr`. Errors are ignored.
*/
void PyC_StdFilesFlush();
/**
* Use with PyArg_ParseTuple's "O&" formatting.
*
* \see #PyC_Long_AsBool for a similar function to use outside of argument parsing.
*/
[[nodiscard]] int PyC_ParseBool(PyObject *o, void *p);
/**
* Use with PyArg_ParseTuple's "O&" formatting.
*
* A version of `O!` that also accepts None (setting the pointer to nullptr).
*/
struct PyC_TypeOrNone {
PyTypeObject *type;
PyObject **value_p;
};
[[nodiscard]] int PyC_ParseTypeOrNone(PyObject *o, void *p);
/**
* Use with PyArg_ParseTuple's "O&" formatting.
*
* A version of `i` that also accepts None (leaving the `std::optional<int>` empty).
*/
[[nodiscard]] int PyC_ParseOptionalInt(PyObject *o, void *p);
/**
* Use with PyArg_ParseTuple's "O&" formatting.
*
* A version of `d` that also accepts None (leaving the `std::optional<double>` empty).
*/
[[nodiscard]] int PyC_ParseOptionalDouble(PyObject *o, void *p);
/**
* Use with PyArg_ParseTuple's "O&" formatting.
*
* A version of `f` that also accepts None (leaving the `std::optional<float>` empty).
*/
[[nodiscard]] int PyC_ParseOptionalFloat(PyObject *o, void *p);
/**
* Use with PyArg_ParseTuple's "O&" formatting.
*
* A version of `I` that also accepts None (leaving the `std::optional<uint>` empty).
*/
[[nodiscard]] int PyC_ParseOptionalUInt(PyObject *o, void *p);
/**
* Use with PyArg_ParseTuple's "O&" formatting.
*
* A version of #PyC_ParseBool that also accepts None
* (leaving the `std::optional<bool>` empty).
*/
[[nodiscard]] int PyC_ParseOptionalBool(PyObject *o, void *p);
/**
* Use with PyArg_ParseTuple's "O&" formatting.
*
* Parse `((x1, y1), (x2, y2))` into an `rcti`.
*/
[[nodiscard]] int PyC_ParseRectI(PyObject *o, void *p);
/**
* A version of #PyC_ParseRectI that accepts None
* (leaving the `std::optional<rcti>` empty).
*/
[[nodiscard]] int PyC_ParseOptionalRectI(PyObject *o, void *p);
/**
* Cast a pointer of a PyObject-derived type to `PyObject *`.
*
* A type-safe alternative to the C/Python API's `_PyObject_CAST` which is
* a plain C-style cast without any validation. This verifies at compile time
* that `T::ob_base` is `PyObject` or `PyVarObject` (from `PyObject_HEAD`
* or `PyObject_VAR_HEAD`).
*/
template<typename T> inline PyObject *PyC_Object_CAST(T *value)
{
static_assert(std::is_same_v<decltype(T::ob_base), PyObject> ||
std::is_same_v<decltype(T::ob_base), PyVarObject>,
"Type must use PyObject_HEAD or PyObject_VAR_HEAD");
return reinterpret_cast<PyObject *>(value);
}
/** A version of #PyC_Object_CAST that casts `T**` to `PyObject **`. */
template<typename T> inline PyObject **PyC_Object_ptr_CAST(T **value_p)
{
static_assert(std::is_same_v<decltype(T::ob_base), PyObject> ||
std::is_same_v<decltype(T::ob_base), PyVarObject>,
"Type must use PyObject_HEAD or PyObject_VAR_HEAD");
return reinterpret_cast<PyObject **>(value_p);
}
/** Initializer for #PyC_TypeOrNone, validates PyObject compatibility at compile time. */
#define PyC_TYPE_OR_NONE_INIT(py_type, value_p) {(py_type), PyC_Object_ptr_CAST(value_p)}
struct PyC_StringEnumItems {
int value;
const char *id;
};
struct PyC_StringEnum {
const struct PyC_StringEnumItems *items;
int value_found;
};
/**
* Use with PyArg_ParseTuple's "O&" formatting.
*/
[[nodiscard]] int PyC_ParseStringEnum(PyObject *o, void *p);
[[nodiscard]] const char *PyC_StringEnum_FindIDFromValue(const struct PyC_StringEnumItems *items,
int value);
/**
* Silly function, we don't use arg. just check its compatible with `__deepcopy__`.
*/
[[nodiscard]] int PyC_CheckArgs_DeepCopy(PyObject *args);
/* Integer parsing (with overflow checks), -1 on error. */
/**
* Comparison with #PyObject_IsTrue
* ================================
*
* Even though Python provides a way to retrieve the boolean value for an object,
* in many cases it's far too relaxed, with the following examples coercing values.
*
* \code{.py}
* data.value = "Text" # True.
* data.value = "" # False.
* data.value = {1, 2} # True
* data.value = {} # False.
* data.value = None # False.
* \endcode
*
* In practice this is often a mistake by the script author that doesn't behave as they expect.
* So it's better to be more strict for attribute assignment and function arguments,
* only accepting True/False 0/1.
*
* If coercing a value is desired, it can be done explicitly: `data.value = bool(value)`
*
* \see #PyC_ParseBool for use with #PyArg_ParseTuple and related functions.
*
* \note Don't use `bool` return type, so -1 can be used as an error value.
*/
[[nodiscard]] int PyC_Long_AsBool(PyObject *value);
[[nodiscard]] int8_t PyC_Long_AsI8(PyObject *value);
[[nodiscard]] int16_t PyC_Long_AsI16(PyObject *value);
#if 0 /* inline */
[[nodiscard]] int32_t PyC_Long_AsI32(PyObject *value);
[[nodiscard]] int64_t PyC_Long_AsI64(PyObject *value);
#endif
/**
* Unlike Python's #PyLong_AsUnsignedLong and #PyLong_AsUnsignedLongLong, these unsigned integer
* parsing functions fall back to calling #PyNumber_Index when their argument is not a
* `PyLongObject`. This matches Python's signed integer parsing functions which also fall back to
* calling #PyNumber_Index.
*/
[[nodiscard]] uint8_t PyC_Long_AsU8(PyObject *value);
[[nodiscard]] uint16_t PyC_Long_AsU16(PyObject *value);
[[nodiscard]] uint32_t PyC_Long_AsU32(PyObject *value);
/**
* #PyLong_AsUnsignedLongLong, unlike #PyLong_AsLongLong, does not fall back to calling
* #PyNumber_Index when its argument is not a `PyLongObject` instance. To match parsing signed
* integer types with #PyLong_AsLongLong, this function performs the #PyNumber_Index fallback, if
* necessary, before calling #PyLong_AsUnsignedLongLong.
*/
[[nodiscard]] uint64_t PyC_Long_AsU64(PyObject *value);
/** Inline so type signatures match as expected. */
[[nodiscard]] Py_LOCAL_INLINE(int32_t) PyC_Long_AsI32(PyObject *value)
{
return int32_t(PyLong_AsInt(value));
}
[[nodiscard]] Py_LOCAL_INLINE(int64_t) PyC_Long_AsI64(PyObject *value)
{
return int64_t(PyLong_AsLongLong(value));
}
/* Utils for format string in `struct` module style syntax. */
[[nodiscard]] char PyC_StructFmt_type_from_str(const char *typestr);
[[nodiscard]] bool PyC_StructFmt_type_is_float_any(char format);
[[nodiscard]] bool PyC_StructFmt_type_is_int_any(char format);
[[nodiscard]] bool PyC_StructFmt_type_is_byte(char format);
[[nodiscard]] bool PyC_StructFmt_type_is_bool(char format);
/**
* Create a `str` from `std::string`, wraps #PyC_UnicodeFromBytesAndSize.
*/
[[nodiscard]] PyObject *PyC_UnicodeFromStdStr(const std::string &str);
[[nodiscard]] inline PyObject *PyC_Tuple_Pack_F32(const Span<float> values)
{
return PyC_Tuple_PackArray_F32(values.data(), values.size());
}
[[nodiscard]] inline PyObject *PyC_Tuple_Pack_F64(const Span<double> values)
{
return PyC_Tuple_PackArray_F64(values.data(), values.size());
}
[[nodiscard]] inline PyObject *PyC_Tuple_Pack_I32(const Span<int> values)
{
return PyC_Tuple_PackArray_I32(values.data(), values.size());
}
[[nodiscard]] inline PyObject *PyC_Tuple_Pack_I32FromBool(const Span<int> values)
{
return PyC_Tuple_PackArray_I32FromBool(values.data(), values.size());
}
[[nodiscard]] inline PyObject *PyC_Tuple_Pack_Bool(const Span<bool> values)
{
return PyC_Tuple_PackArray_Bool(values.data(), values.size());
}
/**
* Check that all keys in `dict` are Python strings.
*
* Use this to validate keyword arguments from `tp_call` which,
* unlike regular Python function calls, does not enforce string keys.
*/
[[nodiscard]] bool PyC_Dict_CheckKeysAreStrings(PyObject *dict);
/**
* Create a `memoryview` from the contents of `info`,
* similar to #PyMemoryView_FromBuffer.
*
* Unlike #PyMemoryView_FromBuffer the returned `memoryview` takes ownership of `info->buf`:
* when the last reference to the `memoryview`
* (or any `memoryview` derived from it via `cast()` / slicing) is released,
* the buffer is freed with #MEM_delete_void.
*
* \return A new `memoryview` reference, or null with an exception set on failure.
* `info->buf` is freed even when the function returns null.
*/
[[nodiscard]] PyObject *PyC_MemoryView_FromBufferOwned(const Py_buffer *info);
} // namespace blender

View File

@@ -0,0 +1,70 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pygen
*
* Functions relating to compatibility across Python versions.
*/
#include <Python.h> /* IWYU pragma: keep. */
#include "BLI_utildefines.h" /* IWYU pragma: keep. */
#include "python_compat.hh" /* IWYU pragma: keep. */
int _PyArg_CheckPositional(const char *name, Py_ssize_t nargs, Py_ssize_t min, Py_ssize_t max)
{
BLI_assert(min >= 0);
BLI_assert(min <= max);
if (nargs < min) {
if (name != nullptr) {
PyErr_Format(PyExc_TypeError,
"%.200s expected %s%zd argument%s, got %zd",
name,
(min == max ? "" : "at least "),
min,
min == 1 ? "" : "s",
nargs);
}
else {
PyErr_Format(PyExc_TypeError,
"unpacked tuple should have %s%zd element%s,"
" but has %zd",
(min == max ? "" : "at least "),
min,
min == 1 ? "" : "s",
nargs);
}
return 0;
}
if (nargs == 0) {
return 1;
}
if (nargs > max) {
if (name != nullptr) {
PyErr_Format(PyExc_TypeError,
"%.200s expected %s%zd argument%s, got %zd",
name,
(min == max ? "" : "at most "),
max,
max == 1 ? "" : "s",
nargs);
}
else {
PyErr_Format(PyExc_TypeError,
"unpacked tuple should have %s%zd element%s,"
" but has %zd",
(min == max ? "" : "at most "),
max,
max == 1 ? "" : "s",
nargs);
}
return 0;
}
return 1;
}

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pygen
* \brief header-only compatibility defines.
*
* \note this header should not be removed/cleaned where Python is used.
* Because its required for Blender to build against different versions of Python.
*/
#pragma once
#include <Python.h>
/* This code is not placed in the blender namespace, as it is meant to replace Python functions
* in the global namespace. */
/* Python 3.14 made some changes, use the "new" names. */
#if PY_VERSION_HEX < 0x030e0000
# define Py_HashPointer _Py_HashPointer
# define PyThreadState_GetUnchecked _PyThreadState_UncheckedGet
/* TODO: Support: `PyDict_Pop`, it has different arguments. */
#endif
/** Removed in Python 3.13. */
int _PyArg_CheckPositional(const char *name, Py_ssize_t nargs, Py_ssize_t min, Py_ssize_t max);

View File

@@ -0,0 +1,38 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pygen
* \brief header-only utilities
* \note light addition to Python.h, use py_capi_utils.hh for larger features.
*/
#pragma once
#include <Python.h>
namespace blender {
#define PyTuple_SET_ITEMS(op_arg, ...) \
{ \
PyTupleObject *op = (PyTupleObject *)op_arg; \
PyObject **ob_items = op->ob_item; \
CHECK_TYPE_ANY(op_arg, PyObject *, PyTupleObject *); \
BLI_assert(VA_NARGS_COUNT(__VA_ARGS__) == PyTuple_GET_SIZE(op)); \
ARRAY_SET_ITEMS(ob_items, __VA_ARGS__); \
} \
(void)0
/**
* Append & transfer ownership to the list,
* avoids inline #Py_DECREF all over (which is quite a large macro).
*/
Py_LOCAL_INLINE(int) PyList_APPEND(PyObject *op, PyObject *v)
{
int ret = PyList_Append(op, v);
Py_DecRef(v);
return ret;
}
} // namespace blender

View File

@@ -0,0 +1,74 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
.
../../editors/include
../../makesrna
)
set(INC_SYS
)
set(SRC
gpu_py.cc
gpu_py_api.cc
gpu_py_batch.cc
gpu_py_buffer.cc
gpu_py_capabilities.cc
gpu_py_compute.cc
gpu_py_element.cc
gpu_py_framebuffer.cc
gpu_py_matrix.cc
gpu_py_offscreen.cc
gpu_py_platform.cc
gpu_py_select.cc
gpu_py_shader.cc
gpu_py_shader_create_info.cc
gpu_py_state.cc
gpu_py_texture.cc
gpu_py_types.cc
gpu_py_uniformbuffer.cc
gpu_py_vertex_buffer.cc
gpu_py_vertex_format.cc
gpu_py.hh
gpu_py_api.hh
gpu_py_batch.hh
gpu_py_buffer.hh
gpu_py_capabilities.hh
gpu_py_compute.hh
gpu_py_element.hh
gpu_py_framebuffer.hh
gpu_py_matrix.hh
gpu_py_offscreen.hh
gpu_py_platform.hh
gpu_py_select.hh
gpu_py_shader.hh
gpu_py_state.hh
gpu_py_texture.hh
gpu_py_types.hh
gpu_py_uniformbuffer.hh
gpu_py_vertex_buffer.hh
gpu_py_vertex_format.hh
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::dna
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::intern::guardedalloc
PRIVATE bf::windowmanager
PRIVATE bf::dependencies::optional::python
)
if(WITH_OPENGL_BACKEND)
list(APPEND LIB
PRIVATE bf::dependencies::epoxy
)
endif()
blender_add_lib(bf_python_gpu "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@@ -0,0 +1,72 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_init_exit.hh"
#include "GPU_primitive.hh"
#include "GPU_texture.hh"
#include "../generic/py_capi_utils.hh"
#include "gpu_py.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name GPU Enums
* \{ */
PyC_StringEnumItems bpygpu_primtype_items[] = {
{GPU_PRIM_POINTS, "POINTS"},
{GPU_PRIM_LINES, "LINES"},
{GPU_PRIM_TRIS, "TRIS"},
{GPU_PRIM_LINE_STRIP, "LINE_STRIP"},
{GPU_PRIM_LINE_LOOP, "LINE_LOOP"},
{GPU_PRIM_TRI_STRIP, "TRI_STRIP"},
{GPU_PRIM_TRI_FAN, "TRI_FAN"},
{GPU_PRIM_LINES_ADJ, "LINES_ADJ"},
{GPU_PRIM_TRIS_ADJ, "TRIS_ADJ"},
{GPU_PRIM_LINE_STRIP_ADJ, "LINE_STRIP_ADJ"},
{0, nullptr},
};
PyC_StringEnumItems bpygpu_dataformat_items[] = {
{GPU_DATA_FLOAT, "FLOAT"},
{GPU_DATA_INT, "INT"},
{GPU_DATA_UINT, "UINT"},
{GPU_DATA_UBYTE, "UBYTE"},
{GPU_DATA_UINT_24_8_DEPRECATED, "UINT_24_8"},
{GPU_DATA_10_11_11_REV, "10_11_11_REV"},
{0, nullptr},
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Utilities
* \{ */
bool bpygpu_is_init_or_error()
{
if (!GPU_is_init()) {
PyErr_SetString(
PyExc_SystemError,
"GPU functions for drawing requires the gpu module to be initialized. See gpu.init.");
return false;
}
return true;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,56 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "../generic/py_capi_utils.hh"
namespace blender {
extern struct PyC_StringEnumItems bpygpu_primtype_items[];
extern struct PyC_StringEnumItems bpygpu_dataformat_items[];
/* Docstring Literal types for shared enums. */
#define PYDOC_PRIMTYPE_LITERAL \
"Literal[" \
"'POINTS', " \
"'LINES', " \
"'TRIS', " \
"'LINE_STRIP', " \
"'LINE_LOOP', " \
"'TRI_STRIP', " \
"'TRI_FAN', " \
"'LINES_ADJ', " \
"'TRIS_ADJ', " \
"'LINE_STRIP_ADJ']"
#define PYDOC_DATAFORMAT_LITERAL \
"Literal[" \
"'FLOAT', " \
"'INT', " \
"'UINT', " \
"'UBYTE', " \
"'UINT_24_8', " \
"'10_11_11_REV']"
[[nodiscard]] bool bpygpu_is_init_or_error();
#define BPYGPU_IS_INIT_OR_ERROR_OBJ \
if (UNLIKELY(!bpygpu_is_init_or_error())) { \
return NULL; \
} \
((void)0)
#define BPYGPU_IS_INIT_OR_ERROR_INT \
if (UNLIKELY(!bpygpu_is_init_or_error())) { \
return -1; \
} \
((void)0)
} // namespace blender

View File

@@ -0,0 +1,151 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* Experimental Python API, not considered public yet (called '_gpu'),
* we may re-expose as public later.
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "../generic/py_capi_utils.hh"
#include "gpu_py_capabilities.hh"
#include "gpu_py_compute.hh"
#include "gpu_py_matrix.hh"
#include "gpu_py_platform.hh"
#include "gpu_py_select.hh"
#include "gpu_py_state.hh"
#include "gpu_py_types.hh"
#include "BKE_global.hh"
#include "GPU_context.hh"
#include "GPU_init_exit.hh"
#include "WM_api.hh"
#include "gpu_py_api.hh" /* Own include. */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name GPU Module
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_init_doc,
".. function:: init()\n"
"\n"
" Initializes the GPU module for background use.\n"
" If the initialization fails, a SystemError will be raised.\n");
static PyObject *pygpu_init(PyObject * /*self*/)
{
if (!G.background || GPU_is_init()) {
/* GPU is already initialized. */
Py_RETURN_NONE;
}
if (!GPU_backend_supported()) {
PyErr_SetString(PyExc_SystemError, "Failed to initialize GPU. GPU backend not supported");
return nullptr;
}
/* Cannot use GPU_init() as it requires a GPU context to have been created.
* See WM_init_gpu implementation. */
WM_init_gpu();
if (!GPU_is_init()) {
PyErr_SetString(PyExc_SystemError, "Failed to initialize GPU. Unexpected Error");
return nullptr;
}
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 pygpu_tp_methods[] = {
{"init", reinterpret_cast<PyCFunction>(pygpu_init), METH_NOARGS, pygpu_init_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_doc,
"This module provides Python wrappers for the GPU implementation in Blender.\n"
"Some higher level functions can be found in the :mod:`gpu_extras` module.\n");
static PyModuleDef pygpu_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu",
/*m_doc*/ pygpu_doc,
/*m_size*/ 0,
/*m_methods*/ pygpu_tp_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *BPyInit_gpu()
{
PyObject *sys_modules = PyImport_GetModuleDict();
PyObject *submodule;
PyObject *mod;
mod = PyModule_Create(&pygpu_module_def);
PyModule_AddObject(mod, "types", (submodule = bpygpu_types_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "capabilities", (submodule = bpygpu_capabilities_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "matrix", (submodule = bpygpu_matrix_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "platform", (submodule = bpygpu_platform_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "select", (submodule = bpygpu_select_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "shader", (submodule = bpygpu_shader_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "state", (submodule = bpygpu_state_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "texture", (submodule = bpygpu_texture_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
PyModule_AddObject(mod, "compute", (submodule = bpygpu_compute_init()));
PyC_Module_AddToSysModules(sys_modules, submodule);
return mod;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,21 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
namespace blender {
/* Each type object could have a method for free GPU resources.
* However, it is currently of little use. */
// #define BPYGPU_USE_GPUOBJ_FREE_METHOD
[[nodiscard]] PyObject *BPyInit_gpu();
} // namespace blender

View File

@@ -0,0 +1,708 @@
/* SPDX-FileCopyrightText: 2015 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* This file defines the off-screen functionalities of the 'gpu' module
* used for off-screen OpenGL rendering.
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "BLI_utildefines.h"
#include "GPU_batch.hh"
#include "GPU_state.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "gpu_py.hh"
#include "gpu_py_element.hh"
#include "gpu_py_shader.hh"
#include "gpu_py_vertex_buffer.hh"
#include "gpu_py_batch.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Utility Functions
* \{ */
static bool pygpu_batch_is_program_or_error(BPyGPUBatch *self)
{
if (!self->batch->shader) {
PyErr_SetString(PyExc_RuntimeError, "batch does not have any program assigned to it");
return false;
}
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPUBatch Type
* \{ */
static PyObject *pygpu_batch__tp_new(PyTypeObject * /*type*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
const char *exc_str_missing_arg = "GPUBatch.__new__() missing required argument '%s' (pos %d)";
PyC_StringEnum prim_type = {bpygpu_primtype_items, GPU_PRIM_NONE};
BPyGPUVertBuf *py_vertbuf = nullptr;
BPyGPUIndexBuf *py_indexbuf = nullptr;
PyC_TypeOrNone py_indexbuf_or_none = PyC_TYPE_OR_NONE_INIT(&BPyGPUIndexBuf_Type, &py_indexbuf);
static const char *_keywords[] = {"type", "buf", "elem", nullptr};
static _PyArg_Parser _parser = {
"|$" /* Optional keyword only arguments. */
"O&" /* `type` */
"O!" /* `buf` */
"O&" /* `elem` */
":GPUBatch.__new__",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kwds,
&_parser,
PyC_ParseStringEnum,
&prim_type,
&BPyGPUVertBuf_Type,
&py_vertbuf,
PyC_ParseTypeOrNone,
&py_indexbuf_or_none))
{
return nullptr;
}
BLI_assert(prim_type.value_found != GPU_PRIM_NONE);
if (prim_type.value_found == GPU_PRIM_LINE_LOOP) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"'LINE_LOOP' is deprecated. Please use 'LINE_STRIP' and close the segment.",
1);
}
else if (prim_type.value_found == GPU_PRIM_TRI_FAN) {
PyErr_WarnEx(
PyExc_DeprecationWarning,
"'TRI_FAN' is deprecated. Please use 'TRI_STRIP' or 'TRIS' and try modifying your "
"vertices or indices to match the topology.",
1);
}
if (py_vertbuf == nullptr) {
PyErr_Format(PyExc_TypeError, exc_str_missing_arg, _keywords[1], 2);
return nullptr;
}
gpu::Batch *batch = GPU_batch_create(GPUPrimType(prim_type.value_found),
py_vertbuf->buf,
py_indexbuf ? py_indexbuf->elem : nullptr);
BPyGPUBatch *ret = reinterpret_cast<BPyGPUBatch *>(BPyGPUBatch_CreatePyObject(batch));
#ifdef USE_GPU_PY_REFERENCES
ret->references = PyList_New(py_indexbuf ? 2 : 1);
PyList_SET_ITEM(ret->references, 0, (PyObject *)py_vertbuf);
Py_INCREF(py_vertbuf);
if (py_indexbuf != nullptr) {
PyList_SET_ITEM(ret->references, 1, (PyObject *)py_indexbuf);
Py_INCREF(py_indexbuf);
}
BLI_assert(!PyObject_GC_IsTracked((PyObject *)ret));
PyObject_GC_Track(ret);
#endif
return reinterpret_cast<PyObject *>(ret);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_batch_vertbuf_add_doc, ".. method:: vertbuf_add(buf)\n"
"\n"
" Add another vertex buffer to the Batch.\n"
" It is not possible to add more vertices to the batch using this method.\n"
" Instead it can be used to add more attributes to the existing vertices.\n"
" A good use case would be when you have a separate\n"
" vertex buffer for vertex positions and vertex normals.\n"
" Current a batch can have at most " STRINGIFY(GPU_BATCH_VBO_MAX_LEN) " vertex buffers.\n"
"\n"
" :param buf: The vertex buffer that will be added to the batch.\n"
" :type buf: :class:`gpu.types.GPUVertBuf`\n"
);
static PyObject *pygpu_batch_vertbuf_add(BPyGPUBatch *self, BPyGPUVertBuf *py_buf)
{
if (!BPyGPUVertBuf_Check(py_buf)) {
PyErr_Format(PyExc_TypeError, "Expected a GPUVertBuf, got %s", Py_TYPE(py_buf)->tp_name);
return nullptr;
}
if (GPU_vertbuf_get_vertex_len(self->batch->verts[0]) != GPU_vertbuf_get_vertex_len(py_buf->buf))
{
PyErr_Format(PyExc_TypeError,
"Expected %d length, got %d",
GPU_vertbuf_get_vertex_len(self->batch->verts[0]),
GPU_vertbuf_get_vertex_len(py_buf->buf));
return nullptr;
}
if (self->batch->verts[GPU_BATCH_VBO_MAX_LEN - 1] != nullptr) {
PyErr_SetString(
PyExc_RuntimeError,
"Maximum number of vertex buffers exceeded: " STRINGIFY(GPU_BATCH_VBO_MAX_LEN));
return nullptr;
}
#ifdef USE_GPU_PY_REFERENCES
/* Hold user */
PyList_Append(self->references, reinterpret_cast<PyObject *>(py_buf));
#endif
GPU_batch_vertbuf_add(self->batch, py_buf->buf, false);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_batch_program_set_doc,
".. method:: program_set(program)\n"
"\n"
" Assign a shader to this batch that will be used for drawing when not overwritten later.\n"
" Note: This method has to be called in the draw context that the batch will be drawn in.\n"
" This function does not need to be called when you always\n"
" set the shader when calling :meth:`gpu.types.GPUBatch.draw`.\n"
"\n"
" :param program: The program/shader the batch will use in future draw calls.\n"
" :type program: :class:`gpu.types.GPUShader`\n");
static PyObject *pygpu_batch_program_set(BPyGPUBatch *self, BPyGPUShader *py_shader)
{
static bool deprecation_warning_issued = false;
/* Deprecation warning raised when calling `gpu.types.GPUBatch.program_set`. */
if (!deprecation_warning_issued) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calls to GPUBatch.program_set are deprecated."
"Please set the shader via the 'program' parameter when calling "
"GPUBatch.draw/draw_instanced/draw_range.",
1);
deprecation_warning_issued = true;
}
if (!BPyGPUShader_Check(py_shader)) {
PyErr_Format(PyExc_TypeError, "Expected a GPUShader, got %s", Py_TYPE(py_shader)->tp_name);
return nullptr;
}
gpu::Shader *shader = py_shader->shader;
GPU_batch_set_shader(self->batch, shader);
#ifdef USE_GPU_PY_REFERENCES
/* Remove existing user (if any), hold new user. */
int i = PyList_GET_SIZE(self->references);
while (--i != -1) {
PyObject *py_shader_test = PyList_GET_ITEM(self->references, i);
if (BPyGPUShader_Check(py_shader_test)) {
PyList_SET_ITEM(self->references, i, (PyObject *)py_shader);
Py_INCREF(py_shader);
Py_DECREF(py_shader_test);
/* Only ever reference one shader. */
break;
}
}
if (i == -1) {
/* No references set in the loop, so add it here. */
PyList_Append(self->references, reinterpret_cast<PyObject *>(py_shader));
}
#endif
Py_RETURN_NONE;
}
/**
* Verify if the Shader is compatible with the batch and can be used for rendering.
* Derived from `polyline_draw_workaround` in `gpu_immediate.cc`.
*/
static const char *pygpu_shader_check_compatibility(gpu::Batch *batch)
{
if (!batch->shader) {
return nullptr;
}
/* Currently only POLYLINE shaders are checked. */
if (!bpygpu_shader_is_polyline(batch->shader)) {
return nullptr;
}
/* Check batch compatibility with shader. */
for (auto *vert : Span(batch->verts, ARRAY_SIZE(batch->verts))) {
if (!vert) {
continue;
}
GPUVertFormat &format = vert->format;
if ((format.stride % 4) != 0) {
return "For POLYLINE shaders, only 4-byte aligned formats are supported";
}
int pos_attr_id = -1;
int col_attr_id = -1;
for (uint a_idx = 0; a_idx < format.attr_len; a_idx++) {
const GPUVertAttr *a = &format.attrs[a_idx];
if ((a->offset % 4) != 0) {
return "For POLYLINE shaders, only 4-byte aligned attributes are supported";
}
const StringRefNull name = GPU_vertformat_attr_name_get(&format, a, 0);
if (pos_attr_id == -1 && name == "pos") {
if (!ELEM(a->type.comp_type(), GPU_COMP_F32)) {
return "For POLYLINE shaders, the 'pos' attribute needs to be 'F32'";
}
if (!ELEM(a->type.fetch_mode(), GPU_FETCH_FLOAT)) {
return "For POLYLINE shaders, the 'pos' attribute must use the 'FLOAT' fetch type";
}
pos_attr_id = a_idx;
}
else if (col_attr_id == -1 && name == "color") {
if (!ELEM(a->type.comp_type(), GPU_COMP_F32, GPU_COMP_U8)) {
return "For POLYLINE shaders, the 'color' attribute needs to be 'F32' or 'U8'";
}
col_attr_id = a_idx;
}
if (pos_attr_id != -1 && col_attr_id != -1) {
break;
}
}
}
return nullptr;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_batch_draw_doc,
".. method:: draw(shader=None)\n"
"\n"
" Run the drawing shader with the parameters assigned to the batch.\n"
"\n"
" :param shader: Shader that performs the drawing operations.\n"
" If ``None`` is passed, the last shader set to this batch will run.\n"
" :type shader: :class:`gpu.types.GPUShader` | None\n");
static PyObject *pygpu_batch_draw(BPyGPUBatch *self, PyObject *args, PyObject *kw)
{
static bool deprecation_warning_issued = false;
BPyGPUShader *py_shader = nullptr;
PyC_TypeOrNone py_shader_or_none = PyC_TYPE_OR_NONE_INIT(&BPyGPUShader_Type, &py_shader);
static const char *_keywords[] = {"shader", nullptr};
static _PyArg_Parser _parser = {
"|" /* Optional arguments. */
"O&" /* `shader` */
":GPUBatch.draw",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kw, &_parser, PyC_ParseTypeOrNone, &py_shader_or_none))
{
return nullptr;
}
if (py_shader == nullptr) {
if (!deprecation_warning_issued) {
/* Deprecation warning raised when calling gpu.types.GPUBatch.draw without a valid GPUShader.
*/
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calling GPUBatch.draw without specifying a shader is deprecated. "
"Please provide a valid GPUShader as the 'shader' parameter.",
1);
deprecation_warning_issued = true;
}
if (!pygpu_batch_is_program_or_error(self)) {
return nullptr;
}
}
else if (self->batch->shader != py_shader->shader) {
GPU_batch_set_shader(self->batch, py_shader->shader);
}
/* Emit a warning when trying to draw wide lines as it is too late to automatically switch to a
* polyline shader. */
if (py_shader && py_shader->is_builtin &&
ELEM(self->batch->prim_type, GPU_PRIM_LINES, GPU_PRIM_LINE_STRIP, GPU_PRIM_LINE_LOOP))
{
gpu::Shader *shader = py_shader->shader;
const float line_width = GPU_line_width_get();
const bool use_linesmooth = GPU_line_smooth_get();
if (line_width > 1.0f || use_linesmooth) {
if (shader == GPU_shader_get_builtin_shader(GPU_SHADER_3D_FLAT_COLOR)) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calling GPUBatch.draw to draw wide or smooth lines with "
"GPU_SHADER_3D_FLAT_COLOR is deprecated. "
"Use GPU_SHADER_3D_POLYLINE_FLAT_COLOR instead.",
1);
}
else if (shader == GPU_shader_get_builtin_shader(GPU_SHADER_3D_SMOOTH_COLOR)) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calling GPUBatch.draw to draw wide or smooth lines with "
"GPU_SHADER_3D_SMOOTH_COLOR is deprecated. "
"Use GPU_SHADER_3D_POLYLINE_SMOOTH_COLOR instead.",
1);
}
else if (shader == GPU_shader_get_builtin_shader(GPU_SHADER_3D_UNIFORM_COLOR)) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calling GPUBatch.draw to draw wide or smooth lines with "
"GPU_SHADER_3D_UNIFORM_COLOR is deprecated. "
"Use GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR instead.",
1);
}
}
}
/* Emit a warning when trying to draw points with a regular shader as it is too late to
* automatically switch to a point shader. */
if (py_shader && py_shader->is_builtin && self->batch->prim_type == GPU_PRIM_POINTS) {
gpu::Shader *shader = py_shader->shader;
if (shader == GPU_shader_get_builtin_shader(GPU_SHADER_3D_FLAT_COLOR)) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calling GPUBatch.draw to draw points with "
"GPU_SHADER_3D_FLAT_COLOR is deprecated. "
"Use GPU_SHADER_3D_POINT_FLAT_COLOR instead.",
1);
}
else if (shader == GPU_shader_get_builtin_shader(GPU_SHADER_3D_SMOOTH_COLOR)) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calling GPUBatch.draw to draw points with "
"GPU_SHADER_3D_SMOOTH_COLOR is deprecated. "
"Use GPU_SHADER_3D_POINT_FLAT_COLOR instead.",
1);
}
else if (shader == GPU_shader_get_builtin_shader(GPU_SHADER_3D_UNIFORM_COLOR)) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Calling GPUBatch.draw to draw points with "
"GPU_SHADER_3D_UNIFORM_COLOR is deprecated. "
"Use GPU_SHADER_3D_POINT_SMOOTH_COLOR instead.",
1);
}
}
if (const char *error = pygpu_shader_check_compatibility(self->batch)) {
PyErr_SetString(PyExc_RuntimeError, error);
return nullptr;
}
GPU_batch_draw(self->batch);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_batch_draw_instanced_doc,
".. method:: draw_instanced(program, *, instance_start=0, instance_count=0)\n"
"\n"
" Draw multiple instances of the drawing program with the parameters assigned\n"
" to the batch. In the vertex shader, ``gl_InstanceID`` will contain the instance\n"
" number being drawn.\n"
"\n"
" :param program: Program that performs the drawing operations.\n"
" :type program: :class:`gpu.types.GPUShader`\n"
" :param instance_start: Number of the first instance to draw.\n"
" :type instance_start: int\n"
" :param instance_count: Number of instances to draw. When not provided or set to 0\n"
" the number of instances will be determined by the number of rows in the first\n"
" vertex buffer.\n"
" :type instance_count: int\n");
static PyObject *pygpu_batch_draw_instanced(BPyGPUBatch *self, PyObject *args, PyObject *kw)
{
BPyGPUShader *py_program = nullptr;
int instance_start = 0;
int instance_count = 0;
static const char *_keywords[] = {"program", "instance_start", "instance_count", nullptr};
static _PyArg_Parser _parser = {
"O!" /* `program` */
"|$" /* Optional keyword only arguments. */
"i" /* `instance_start` */
"i" /* `instance_count` */
":GPUBatch.draw_instanced",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kw, &_parser, &BPyGPUShader_Type, &py_program, &instance_start, &instance_count))
{
return nullptr;
}
GPU_batch_set_shader(self->batch, py_program->shader);
GPU_batch_draw_instance_range(self->batch, instance_start, instance_count);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_batch_draw_range_doc,
".. method:: draw_range(program, *, elem_start=0, elem_count=0)\n"
"\n"
" Run the drawing program with the parameters assigned to the batch. "
"Only draw the ``elem_count`` elements of the index buffer starting at ``elem_start``.\n"
"\n"
" :param program: Program that performs the drawing operations.\n"
" :type program: :class:`gpu.types.GPUShader`\n"
" :param elem_start: First index to draw. When not provided or set to 0 drawing\n"
" will start from the first element of the index buffer.\n"
" :type elem_start: int\n"
" :param elem_count: Number of elements of the index buffer to draw. When not\n"
" provided or set to 0 all elements from ``elem_start`` to the end of the\n"
" index buffer will be drawn.\n"
" :type elem_count: int\n");
static PyObject *pygpu_batch_draw_range(BPyGPUBatch *self, PyObject *args, PyObject *kw)
{
BPyGPUShader *py_program = nullptr;
int elem_start = 0;
int elem_count = 0;
static const char *_keywords[] = {"program", "elem_start", "elem_count", nullptr};
static _PyArg_Parser _parser = {
"O!" /* `program` */
"|$" /* Optional keyword only arguments. */
"i" /* `elem_start` */
"i" /* `elem_count` */
":GPUBatch.draw_range",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kw, &_parser, &BPyGPUShader_Type, &py_program, &elem_start, &elem_count))
{
return nullptr;
}
GPU_batch_set_shader(self->batch, py_program->shader);
GPU_batch_draw_range(self->batch, elem_start, elem_count);
Py_RETURN_NONE;
}
static PyObject *pygpu_batch_program_use_begin(BPyGPUBatch *self)
{
if (!pygpu_batch_is_program_or_error(self)) {
return nullptr;
}
GPU_shader_bind(self->batch->shader);
Py_RETURN_NONE;
}
static PyObject *pygpu_batch_program_use_end(BPyGPUBatch *self)
{
if (!pygpu_batch_is_program_or_error(self)) {
return nullptr;
}
GPU_shader_unbind();
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 pygpu_batch__tp_methods[] = {
{"vertbuf_add",
reinterpret_cast<PyCFunction>(pygpu_batch_vertbuf_add),
METH_O,
pygpu_batch_vertbuf_add_doc},
{"program_set",
reinterpret_cast<PyCFunction>(pygpu_batch_program_set),
METH_O,
pygpu_batch_program_set_doc},
{"draw",
reinterpret_cast<PyCFunction>(pygpu_batch_draw),
METH_VARARGS | METH_KEYWORDS,
pygpu_batch_draw_doc},
{"draw_instanced",
reinterpret_cast<PyCFunction>(pygpu_batch_draw_instanced),
METH_VARARGS | METH_KEYWORDS,
pygpu_batch_draw_instanced_doc},
{"draw_range",
reinterpret_cast<PyCFunction>(pygpu_batch_draw_range),
METH_VARARGS | METH_KEYWORDS,
pygpu_batch_draw_range_doc},
{"_program_use_begin",
reinterpret_cast<PyCFunction>(pygpu_batch_program_use_begin),
METH_NOARGS,
""},
{"_program_use_end",
reinterpret_cast<PyCFunction>(pygpu_batch_program_use_end),
METH_NOARGS,
""},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
#ifdef USE_GPU_PY_REFERENCES
static int pygpu_batch__tp_traverse(BPyGPUBatch *self, visitproc visit, void *arg)
{
Py_VISIT(self->references);
return 0;
}
static int pygpu_batch__tp_clear(BPyGPUBatch *self)
{
Py_CLEAR(self->references);
return 0;
}
static int pygpu_batch__tp_is_gc(BPyGPUBatch *self)
{
return self->references != nullptr;
}
#endif
static void pygpu_batch__tp_dealloc(BPyGPUBatch *self)
{
GPU_batch_discard(self->batch);
#ifdef USE_GPU_PY_REFERENCES
PyObject_GC_UnTrack(self);
if (self->references) {
pygpu_batch__tp_clear(self);
Py_XDECREF(self->references);
}
#endif
Py_TYPE(self)->tp_free(self);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_batch__tp_doc,
".. class:: GPUBatch\n"
"\n"
" Reusable container for drawable geometry.\n"
"\n"
" .. method:: __init__(type, buf, elem=None)\n"
"\n"
" :param type: The primitive type of geometry to be drawn.\n"
" :type type: " PYDOC_PRIMTYPE_LITERAL
"\n"
" :param buf: Vertex buffer containing all or some of the attributes required for "
"drawing.\n"
" :type buf: :class:`gpu.types.GPUVertBuf`\n"
" :param elem: An optional index buffer.\n"
" :type elem: :class:`gpu.types.GPUIndexBuf` | None\n");
PyTypeObject BPyGPUBatch_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUBatch",
/*tp_basicsize*/ sizeof(BPyGPUBatch),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(pygpu_batch__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
#ifdef USE_GPU_PY_REFERENCES
/*tp_flags*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
#else
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
#endif
/*tp_doc*/ pygpu_batch__tp_doc,
#ifdef USE_GPU_PY_REFERENCES
/*tp_traverse*/ reinterpret_cast<traverseproc>(pygpu_batch__tp_traverse),
#else
/*tp_traverse*/ nullptr,
#endif
#ifdef USE_GPU_PY_REFERENCES
/*tp_clear*/ reinterpret_cast<inquiry>(pygpu_batch__tp_clear),
#else
/*tp_clear*/ nullptr,
#endif
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_batch__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ nullptr,
/*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*/ pygpu_batch__tp_new,
/*tp_free*/ nullptr,
#ifdef USE_GPU_PY_REFERENCES
/*tp_is_gc*/ reinterpret_cast<inquiry>(pygpu_batch__tp_is_gc),
#else
/*tp_is_gc*/ nullptr,
#endif
/*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,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
PyObject *BPyGPUBatch_CreatePyObject(gpu::Batch *batch)
{
BPyGPUBatch *self;
#ifdef USE_GPU_PY_REFERENCES
self = reinterpret_cast<BPyGPUBatch *>(_PyObject_GC_New(&BPyGPUBatch_Type));
self->references = nullptr;
#else
self = PyObject_New(BPyGPUBatch, &BPyGPUBatch_Type);
#endif
self->batch = batch;
return reinterpret_cast<PyObject *>(self);
}
/** \} */
#undef BPY_GPU_BATCH_CHECK_OBJ
} // namespace blender

View File

@@ -0,0 +1,39 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "BLI_compiler_attrs.h"
namespace blender {
namespace gpu {
class Batch;
}
#define USE_GPU_PY_REFERENCES
extern PyTypeObject BPyGPUBatch_Type;
#define BPyGPUBatch_Check(v) (Py_TYPE(v) == &BPyGPUBatch_Type)
struct BPyGPUBatch {
PyObject_HEAD
/* The batch is owned, we may support thin wrapped batches later. */
gpu::Batch *batch;
#ifdef USE_GPU_PY_REFERENCES
/* Just to keep a user to prevent freeing buffers we're using. */
PyObject *references;
#endif
};
[[nodiscard]] PyObject *BPyGPUBatch_CreatePyObject(gpu::Batch *batch) ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,796 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* This file defines the gpu.types.Buffer API.
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include <algorithm>
#include "BLI_utildefines.h"
#include "MEM_guardedalloc.h"
#include "GPU_texture.hh"
#include "../generic/py_capi_utils.hh"
#include "gpu_py.hh"
#include "gpu_py_buffer.hh"
namespace blender {
#define PYGPU_BUFFER_PROTOCOL
#define MAX_DIMENSIONS 64
/* -------------------------------------------------------------------- */
/** \name Utility Functions
* \{ */
static Py_ssize_t pygpu_buffer_dimensions_tot_elem(const Py_ssize_t *shape, Py_ssize_t shape_len)
{
Py_ssize_t tot = shape[0];
for (int i = 1; i < shape_len; i++) {
tot *= shape[i];
}
return tot;
}
static bool pygpu_buffer_dimensions_tot_len_compare(const Py_ssize_t *shape_a,
const Py_ssize_t shape_a_len,
const Py_ssize_t *shape_b,
const Py_ssize_t shape_b_len)
{
if (pygpu_buffer_dimensions_tot_elem(shape_a, shape_a_len) !=
pygpu_buffer_dimensions_tot_elem(shape_b, shape_b_len))
{
PyErr_SetString(PyExc_BufferError, "array size does not match");
return false;
}
return true;
}
static bool pygpu_buffer_pyobj_as_shape(PyObject *shape_obj,
Py_ssize_t r_shape[MAX_DIMENSIONS],
Py_ssize_t *r_shape_len)
{
Py_ssize_t shape_len = 0;
if (PyLong_Check(shape_obj)) {
shape_len = 1;
if ((r_shape[0] = PyLong_AsSsize_t(shape_obj)) < 1) {
PyErr_SetString(PyExc_AttributeError, "dimension must be greater than or equal to 1");
return false;
}
}
else if (PySequence_Check(shape_obj)) {
shape_len = PySequence_Size(shape_obj);
if (shape_len > MAX_DIMENSIONS) {
PyErr_SetString(PyExc_AttributeError,
"too many dimensions, max is " STRINGIFY(MAX_DIMENSIONS));
return false;
}
if (shape_len < 1) {
PyErr_SetString(PyExc_AttributeError, "sequence must have at least one dimension");
return false;
}
for (int i = 0; i < shape_len; i++) {
PyObject *ob = PySequence_GetItem(shape_obj, i);
if (!PyLong_Check(ob)) {
PyErr_Format(PyExc_TypeError,
"invalid dimension %i, expected an int, not a %.200s",
i,
Py_TYPE(ob)->tp_name);
Py_DECREF(ob);
return false;
}
r_shape[i] = PyLong_AsSsize_t(ob);
Py_DECREF(ob);
if (r_shape[i] < 1) {
PyErr_SetString(PyExc_AttributeError, "dimension must be greater than or equal to 1");
return false;
}
}
}
else {
PyErr_Format(PyExc_TypeError,
"invalid second argument expected a sequence "
"or an int, not a %.200s",
Py_TYPE(shape_obj)->tp_name);
}
*r_shape_len = shape_len;
return true;
}
static const char *pygpu_buffer_formatstr(eGPUDataFormat data_format)
{
switch (data_format) {
case GPU_DATA_FLOAT:
return "f";
case GPU_DATA_INT:
return "i";
case GPU_DATA_UINT:
return "I";
case GPU_DATA_UBYTE:
return "B";
case GPU_DATA_UINT_24_8_DEPRECATED:
case GPU_DATA_10_11_11_REV:
return "I";
default:
break;
}
return nullptr;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name BPyGPUBuffer API
* \{ */
static BPyGPUBuffer *pygpu_buffer_make_from_data(PyObject *parent,
const eGPUDataFormat format,
const int shape_len,
const Py_ssize_t *shape,
void *buf)
{
BPyGPUBuffer *buffer = reinterpret_cast<BPyGPUBuffer *>(_PyObject_GC_New(&BPyGPU_BufferType));
buffer->parent = nullptr;
buffer->format = format;
buffer->shape_len = shape_len;
buffer->shape = MEM_new_array_uninitialized<Py_ssize_t>(size_t(shape_len), "BPyGPUBuffer shape");
memcpy(buffer->shape, shape, sizeof(*buffer->shape) * size_t(shape_len));
buffer->buf.as_void = buf;
if (parent) {
Py_INCREF(parent);
buffer->parent = parent;
BLI_assert(!PyObject_GC_IsTracked((PyObject *)buffer));
PyObject_GC_Track(buffer);
}
return buffer;
}
static PyObject *pygpu_buffer__sq_item(BPyGPUBuffer *self, Py_ssize_t i)
{
if (i >= self->shape[0] || i < 0) {
PyErr_SetString(PyExc_IndexError, "array index out of range");
return nullptr;
}
const char *formatstr = pygpu_buffer_formatstr(eGPUDataFormat(self->format));
if (self->shape_len == 1) {
switch (self->format) {
case GPU_DATA_FLOAT:
return Py_BuildValue(formatstr, self->buf.as_float[i]);
case GPU_DATA_INT:
return Py_BuildValue(formatstr, self->buf.as_int[i]);
case GPU_DATA_UBYTE:
return Py_BuildValue(formatstr, self->buf.as_byte[i]);
case GPU_DATA_UINT:
case GPU_DATA_UINT_24_8_DEPRECATED:
case GPU_DATA_10_11_11_REV:
return Py_BuildValue(formatstr, self->buf.as_uint[i]);
}
}
else {
int offset = i * GPU_texture_dataformat_size(eGPUDataFormat(self->format));
for (int j = 1; j < self->shape_len; j++) {
offset *= self->shape[j];
}
return reinterpret_cast<PyObject *>(
pygpu_buffer_make_from_data(reinterpret_cast<PyObject *>(self),
eGPUDataFormat(self->format),
self->shape_len - 1,
self->shape + 1,
self->buf.as_byte + offset));
}
return nullptr;
}
static PyObject *pygpu_buffer_to_list(BPyGPUBuffer *self)
{
const Py_ssize_t len = self->shape[0];
PyObject *list = PyList_New(len);
for (Py_ssize_t i = 0; i < len; i++) {
PyList_SET_ITEM(list, i, pygpu_buffer__sq_item(self, i));
}
return list;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_buffer_to_list_doc,
".. method:: to_list()\n"
"\n"
" Return the buffer as a list.\n"
"\n"
" :return: The buffer as a list.\n"
" :rtype: list\n");
static PyObject *pygpu_buffer_to_list_recursive(BPyGPUBuffer *self)
{
PyObject *list;
if (self->shape_len > 1) {
int i, len = self->shape[0];
list = PyList_New(len);
for (i = 0; i < len; i++) {
/* "BPyGPUBuffer *sub_tmp" is a temporary object created just to be read for nested lists.
* That is why it is decremented/freed soon after.
* TODO: For efficiency, avoid creating #BPyGPUBuffer when creating nested lists. */
BPyGPUBuffer *sub_tmp = reinterpret_cast<BPyGPUBuffer *>(pygpu_buffer__sq_item(self, i));
PyList_SET_ITEM(list, i, pygpu_buffer_to_list_recursive(sub_tmp));
Py_DECREF(sub_tmp);
}
}
else {
list = pygpu_buffer_to_list(self);
}
return list;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_buffer_dimensions_doc,
"The size of the buffer for each dimension.\n"
"\n"
"Setting the dimensions is supported when the total number of elements is unchanged.\n"
"\n"
":type: list[int]\n");
static PyObject *pygpu_buffer_dimensions_get(BPyGPUBuffer *self, void * /*arg*/)
{
PyObject *list = PyList_New(self->shape_len);
int i;
for (i = 0; i < self->shape_len; i++) {
PyList_SET_ITEM(list, i, PyLong_FromLong(self->shape[i]));
}
return list;
}
static int pygpu_buffer_dimensions_set(BPyGPUBuffer *self, PyObject *value, void * /*type*/)
{
Py_ssize_t shape[MAX_DIMENSIONS];
Py_ssize_t shape_len = 0;
if (!pygpu_buffer_pyobj_as_shape(value, shape, &shape_len)) {
return -1;
}
if (!pygpu_buffer_dimensions_tot_len_compare(shape, shape_len, self->shape, self->shape_len)) {
return -1;
}
if (shape_len != self->shape_len) {
MEM_delete(self->shape);
self->shape = MEM_new_array_uninitialized<Py_ssize_t>(size_t(shape_len), __func__);
}
self->shape_len = shape_len;
memcpy(self->shape, shape, sizeof(*self->shape) * size_t(shape_len));
return 0;
}
static int pygpu_buffer__tp_traverse(BPyGPUBuffer *self, visitproc visit, void *arg)
{
Py_VISIT(self->parent);
return 0;
}
static int pygpu_buffer__tp_clear(BPyGPUBuffer *self)
{
if (self->parent) {
Py_CLEAR(self->parent);
self->buf.as_void = nullptr;
}
return 0;
}
static void pygpu_buffer__tp_dealloc(BPyGPUBuffer *self)
{
if (self->parent) {
PyObject_GC_UnTrack(self);
Py_CLEAR(self->parent);
}
else if (self->buf.as_void) {
MEM_delete_void(self->buf.as_void);
}
MEM_delete(self->shape);
PyObject_GC_Del(self);
}
static PyObject *pygpu_buffer__tp_repr(BPyGPUBuffer *self)
{
PyObject *repr;
PyObject *list = pygpu_buffer_to_list_recursive(self);
const char *typestr = PyC_StringEnum_FindIDFromValue(bpygpu_dataformat_items, self->format);
repr = PyUnicode_FromFormat("Buffer(%s, %R)", typestr, list);
Py_DECREF(list);
return repr;
}
static int pygpu_buffer__sq_ass_item(BPyGPUBuffer *self, Py_ssize_t i, PyObject *v);
static int pygpu_buffer_ass_slice(BPyGPUBuffer *self,
Py_ssize_t begin,
Py_ssize_t end,
PyObject *seq)
{
PyObject *item;
int count, err = 0;
begin = std::max<Py_ssize_t>(begin, 0);
end = std::min(end, self->shape[0]);
begin = std::min(begin, end);
if (!PySequence_Check(seq)) {
PyErr_Format(PyExc_TypeError,
"buffer[:] = value, invalid assignment. "
"Expected a sequence, not an %.200s type",
Py_TYPE(seq)->tp_name);
return -1;
}
/* re-use count var */
if ((count = PySequence_Size(seq)) != (end - begin)) {
PyErr_Format(PyExc_TypeError,
"buffer[:] = value, size mismatch in assignment. "
"Expected: %d (given: %d)",
count,
end - begin);
return -1;
}
for (count = begin; count < end; count++) {
item = PySequence_GetItem(seq, count - begin);
if (item) {
err = pygpu_buffer__sq_ass_item(self, count, item);
Py_DECREF(item);
}
else {
err = -1;
}
if (err) {
break;
}
}
return err;
}
static PyObject *pygpu_buffer__tp_new(PyTypeObject * /*type*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
PyObject *length_ob, *init = nullptr;
BPyGPUBuffer *buffer = nullptr;
Py_ssize_t shape[MAX_DIMENSIONS];
Py_ssize_t shape_len = 0;
if (kwds && PyDict_Size(kwds)) {
PyErr_SetString(PyExc_TypeError, "Buffer(): takes no keyword args");
return nullptr;
}
PyC_StringEnum pygpu_dataformat = {bpygpu_dataformat_items, GPU_DATA_FLOAT};
if (!PyArg_ParseTuple(
args, "O&O|O: Buffer", PyC_ParseStringEnum, &pygpu_dataformat, &length_ob, &init))
{
return nullptr;
}
if (pygpu_dataformat.value_found == GPU_DATA_UINT_24_8_DEPRECATED) {
PyErr_WarnEx(PyExc_DeprecationWarning, "`UINT_24_8` is deprecated, use `FLOAT` instead", 1);
}
if (!pygpu_buffer_pyobj_as_shape(length_ob, shape, &shape_len)) {
return nullptr;
}
if (init && PyObject_CheckBuffer(init)) {
Py_buffer pybuffer;
if (PyObject_GetBuffer(init, &pybuffer, PyBUF_ND | PyBUF_FORMAT) == -1) {
/* PyObject_GetBuffer raise a PyExc_BufferError */
return nullptr;
}
Py_ssize_t *pybuffer_shape = pybuffer.shape;
Py_ssize_t pybuffer_ndim = pybuffer.ndim;
if (!pybuffer_shape) {
pybuffer_shape = &pybuffer.len;
pybuffer_ndim = 1;
}
if (pygpu_buffer_dimensions_tot_len_compare(shape, shape_len, pybuffer_shape, pybuffer_ndim)) {
buffer = pygpu_buffer_make_from_data(
init, eGPUDataFormat(pygpu_dataformat.value_found), shape_len, shape, pybuffer.buf);
}
PyBuffer_Release(&pybuffer);
}
else {
buffer = BPyGPU_Buffer_CreatePyObject(pygpu_dataformat.value_found, shape, shape_len, nullptr);
if (init && pygpu_buffer_ass_slice(buffer, 0, shape[0], init)) {
Py_DECREF(buffer);
return nullptr;
}
}
return reinterpret_cast<PyObject *>(buffer);
}
static int pygpu_buffer__tp_is_gc(BPyGPUBuffer *self)
{
return self->parent != nullptr;
}
/* BPyGPUBuffer sequence methods */
static Py_ssize_t pygpu_buffer__sq_length(BPyGPUBuffer *self)
{
return self->shape[0];
}
static PyObject *pygpu_buffer_slice(BPyGPUBuffer *self, Py_ssize_t begin, Py_ssize_t end)
{
PyObject *list;
Py_ssize_t count;
begin = std::max<Py_ssize_t>(begin, 0);
end = std::min(end, self->shape[0]);
begin = std::min(begin, end);
list = PyList_New(end - begin);
for (count = begin; count < end; count++) {
PyList_SET_ITEM(list, count - begin, pygpu_buffer__sq_item(self, count));
}
return list;
}
static int pygpu_buffer__sq_ass_item(BPyGPUBuffer *self, Py_ssize_t i, PyObject *v)
{
if (i >= self->shape[0] || i < 0) {
PyErr_SetString(PyExc_IndexError, "array assignment index out of range");
return -1;
}
if (self->shape_len != 1) {
BPyGPUBuffer *row = reinterpret_cast<BPyGPUBuffer *>(pygpu_buffer__sq_item(self, i));
if (row) {
const int ret = pygpu_buffer_ass_slice(row, 0, self->shape[1], v);
Py_DECREF(row);
return ret;
}
return -1;
}
switch (self->format) {
case GPU_DATA_FLOAT:
return PyArg_Parse(v, "f:Expected floats", &self->buf.as_float[i]) ? 0 : -1;
case GPU_DATA_INT:
return PyArg_Parse(v, "i:Expected ints", &self->buf.as_int[i]) ? 0 : -1;
case GPU_DATA_UBYTE:
return PyArg_Parse(v, "b:Expected ints", &self->buf.as_byte[i]) ? 0 : -1;
case GPU_DATA_UINT:
case GPU_DATA_UINT_24_8_DEPRECATED:
case GPU_DATA_10_11_11_REV:
return PyArg_Parse(v, "I:Expected unsigned ints", &self->buf.as_uint[i]) ? 0 : -1;
default:
return 0; /* should never happen */
}
}
static PyObject *pygpu_buffer__mp_subscript(BPyGPUBuffer *self, PyObject *item)
{
if (PyIndex_Check(item)) {
Py_ssize_t i;
i = PyNumber_AsSsize_t(item, PyExc_IndexError);
if (i == -1 && PyErr_Occurred()) {
return nullptr;
}
if (i < 0) {
i += self->shape[0];
}
return pygpu_buffer__sq_item(self, i);
}
if (PySlice_Check(item)) {
Py_ssize_t start, stop, step, slicelength;
if (PySlice_GetIndicesEx(item, self->shape[0], &start, &stop, &step, &slicelength) < 0) {
return nullptr;
}
if (slicelength <= 0) {
return PyTuple_New(0);
}
if (step == 1) {
return pygpu_buffer_slice(self, start, stop);
}
PyErr_SetString(PyExc_IndexError, "slice steps not supported with vectors");
return nullptr;
}
PyErr_Format(
PyExc_TypeError, "buffer indices must be integers, not %.200s", Py_TYPE(item)->tp_name);
return nullptr;
}
static int pygpu_buffer__mp_ass_subscript(BPyGPUBuffer *self, PyObject *item, PyObject *value)
{
if (PyIndex_Check(item)) {
Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError);
if (i == -1 && PyErr_Occurred()) {
return -1;
}
if (i < 0) {
i += self->shape[0];
}
return pygpu_buffer__sq_ass_item(self, i, value);
}
if (PySlice_Check(item)) {
Py_ssize_t start, stop, step, slicelength;
if (PySlice_GetIndicesEx(item, self->shape[0], &start, &stop, &step, &slicelength) < 0) {
return -1;
}
if (step == 1) {
return pygpu_buffer_ass_slice(self, start, stop, value);
}
PyErr_SetString(PyExc_IndexError, "slice steps not supported with vectors");
return -1;
}
PyErr_Format(
PyExc_TypeError, "buffer indices must be integers, not %.200s", Py_TYPE(item)->tp_name);
return -1;
}
#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 pygpu_buffer__tp_methods[] = {
{"to_list",
reinterpret_cast<PyCFunction>(pygpu_buffer_to_list_recursive),
METH_NOARGS,
pygpu_buffer_to_list_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
static PyGetSetDef pygpu_buffer_getseters[] = {
{"dimensions",
reinterpret_cast<getter>(pygpu_buffer_dimensions_get),
reinterpret_cast<setter>(pygpu_buffer_dimensions_set),
pygpu_buffer_dimensions_doc,
nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr},
};
static PySequenceMethods pygpu_buffer__tp_as_sequence = {
/*sq_length*/ reinterpret_cast<lenfunc>(pygpu_buffer__sq_length),
/*sq_concat*/ nullptr,
/*sq_repeat*/ nullptr,
/*sq_item*/ reinterpret_cast<ssizeargfunc>(pygpu_buffer__sq_item),
/*was_sq_slice*/ nullptr, /* DEPRECATED. Handled by #pygpu_buffer__sq_item. */
/*sq_ass_item*/ reinterpret_cast<ssizeobjargproc>(pygpu_buffer__sq_ass_item),
/*was_sq_ass_slice*/ nullptr, /* DEPRECATED. Handled by #pygpu_buffer__sq_ass_item. */
/*sq_contains*/ nullptr,
/*sq_inplace_concat*/ nullptr,
/*sq_inplace_repeat*/ nullptr,
};
static PyMappingMethods pygpu_buffer__tp_as_mapping = {
/*mp_length*/ reinterpret_cast<lenfunc>(pygpu_buffer__sq_length),
/*mp_subscript*/ reinterpret_cast<binaryfunc>(pygpu_buffer__mp_subscript),
/*mp_ass_subscript*/ reinterpret_cast<objobjargproc>(pygpu_buffer__mp_ass_subscript),
};
#ifdef PYGPU_BUFFER_PROTOCOL
static void pygpu_buffer_strides_calc(const eGPUDataFormat format,
const int shape_len,
const Py_ssize_t *shape,
Py_ssize_t *r_strides)
{
Py_ssize_t stride = GPU_texture_dataformat_size(format);
for (int i = shape_len; i-- > 0;) {
r_strides[i] = stride;
stride *= shape[i];
}
}
/* Here is the buffer interface function */
static int pygpu_buffer__bf_getbuffer(BPyGPUBuffer *self, Py_buffer *view, int flags)
{
if (UNLIKELY(view == nullptr)) {
PyErr_SetString(PyExc_ValueError, "null view in get-buffer is obsolete");
return -1;
}
memset(view, 0, sizeof(*view));
view->obj = reinterpret_cast<PyObject *>(self);
view->buf = self->buf.as_void;
view->len = bpygpu_Buffer_size(self);
view->readonly = 0;
view->itemsize = GPU_texture_dataformat_size(eGPUDataFormat(self->format));
if (flags & PyBUF_FORMAT) {
view->format = const_cast<char *>(pygpu_buffer_formatstr(eGPUDataFormat(self->format)));
}
if (flags & PyBUF_ND) {
view->ndim = self->shape_len;
view->shape = self->shape;
}
if (flags & PyBUF_STRIDES) {
view->strides = MEM_new_array_uninitialized<Py_ssize_t>(size_t(view->ndim),
"BPyGPUBuffer strides");
pygpu_buffer_strides_calc(
eGPUDataFormat(self->format), view->ndim, view->shape, view->strides);
}
view->suboffsets = nullptr;
view->internal = nullptr;
Py_INCREF(self);
return 0;
}
static void pygpu_buffer__bf_releasebuffer(PyObject * /*exporter*/, Py_buffer *view)
{
MEM_SAFE_DELETE(view->strides);
}
static PyBufferProcs pygpu_buffer__tp_as_buffer = {
/*bf_getbuffer*/ reinterpret_cast<getbufferproc>(pygpu_buffer__bf_getbuffer),
/*bf_releasebuffer*/ static_cast<releasebufferproc>(pygpu_buffer__bf_releasebuffer),
};
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_buffer__tp_doc,
".. class:: Buffer\n"
"\n"
" For Python access to GPU functions requiring a pointer.\n"
"\n"
" .. method:: __init__(format, dimensions, data)\n"
"\n"
" :param format: Format type to interpret the buffer.\n"
" ``UINT_24_8`` is deprecated, use ``FLOAT`` instead.\n"
" :type format: " PYDOC_DATAFORMAT_LITERAL
"\n"
" :param dimensions: Array describing the dimensions.\n"
" :type dimensions: int | Sequence[int]\n"
" :param data: Optional data array.\n"
" :type data: Buffer | Sequence[float] | Sequence[int]\n");
PyTypeObject BPyGPU_BufferType = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "Buffer",
/*tp_basicsize*/ sizeof(BPyGPUBuffer),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(pygpu_buffer__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_compare*/ nullptr,
/*tp_repr*/ reinterpret_cast<reprfunc>(pygpu_buffer__tp_repr),
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ &pygpu_buffer__tp_as_sequence,
/*tp_as_mapping*/ &pygpu_buffer__tp_as_mapping,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
#ifdef PYGPU_BUFFER_PROTOCOL
/*tp_as_buffer*/ &pygpu_buffer__tp_as_buffer,
#else
/*tp_as_buffer*/ nullptr,
#endif
/*tp_flags*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
/*tp_doc*/ pygpu_buffer__tp_doc,
/*tp_traverse*/ reinterpret_cast<traverseproc>(pygpu_buffer__tp_traverse),
/*tp_clear*/ reinterpret_cast<inquiry>(pygpu_buffer__tp_clear),
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_buffer__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ pygpu_buffer_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*/ pygpu_buffer__tp_new,
/*tp_free*/ nullptr,
/*tp_is_gc*/ reinterpret_cast<inquiry>(pygpu_buffer__tp_is_gc),
/*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,
};
static size_t pygpu_buffer_calc_size(const int format,
const int shape_len,
const Py_ssize_t *shape)
{
return pygpu_buffer_dimensions_tot_elem(shape, shape_len) *
GPU_texture_dataformat_size(eGPUDataFormat(format));
}
size_t bpygpu_Buffer_size(BPyGPUBuffer *buffer)
{
return pygpu_buffer_calc_size(buffer->format, buffer->shape_len, buffer->shape);
}
BPyGPUBuffer *BPyGPU_Buffer_CreatePyObject(const int format,
const Py_ssize_t *shape,
const int shape_len,
void *buffer)
{
if (buffer == nullptr) {
size_t size = pygpu_buffer_calc_size(format, shape_len, shape);
buffer = MEM_new_zeroed(size, "BPyGPUBuffer buffer");
}
return pygpu_buffer_make_from_data(nullptr, eGPUDataFormat(format), shape_len, shape, buffer);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "BLI_sys_types.h"
namespace blender {
extern PyTypeObject BPyGPU_BufferType;
#define BPyGPU_Buffer_Check(v) (Py_TYPE(v) == &BPyGPU_BufferType)
/**
* Buffer Object
*
* For Python access to GPU functions requiring a pointer.
*/
struct BPyGPUBuffer {
PyObject_HEAD
PyObject *parent;
int format;
int shape_len;
Py_ssize_t *shape;
union {
char *as_byte;
int *as_int;
uint *as_uint;
float *as_float;
void *as_void;
} buf;
};
[[nodiscard]] size_t bpygpu_Buffer_size(BPyGPUBuffer *buffer);
/**
* Create a buffer object
*
* \param shape: An array of `shape_len` integers representing the size of each dimension.
* \param buffer: When not NULL holds a contiguous buffer
* with the correct format from which the buffer will be initialized
*/
[[nodiscard]] BPyGPUBuffer *BPyGPU_Buffer_CreatePyObject(int format,
const Py_ssize_t *shape,
int shape_len,
void *buffer);
} // namespace blender

View File

@@ -0,0 +1,518 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_capabilities.hh"
#include "gpu_py.hh"
#include "gpu_py_capabilities.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Functions
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_texture_size_get_doc,
".. function:: max_texture_size_get()\n"
"\n"
" Get estimated maximum texture size to be able to handle.\n"
"\n"
" :return: Texture size.\n"
" :rtype: int\n");
static PyObject *pygpu_max_texture_size_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_texture_size());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_texture_layers_get_doc,
".. function:: max_texture_layers_get()\n"
"\n"
" Get maximum number of layers in texture.\n"
"\n"
" :return: Number of layers.\n"
" :rtype: int\n");
static PyObject *pygpu_max_texture_layers_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_texture_layers());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_textures_get_doc,
".. function:: max_textures_get()\n"
"\n"
" Get maximum supported texture image units used for\n"
" accessing texture maps from the vertex shader and the\n"
" fragment processor.\n"
"\n"
" :return: Texture image units.\n"
" :rtype: int\n");
static PyObject *pygpu_max_textures_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_textures());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_textures_vert_get_doc,
".. function:: max_textures_vert_get()\n"
"\n"
" Get maximum supported texture image units used for\n"
" accessing texture maps from the vertex shader.\n"
"\n"
" :return: Texture image units.\n"
" :rtype: int\n");
static PyObject *pygpu_max_textures_vert_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_textures());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_textures_geom_get_doc,
".. function:: max_textures_geom_get()\n"
"\n"
" Get maximum supported texture image units used for\n"
" accessing texture maps from the geometry shader.\n"
"\n"
" :return: Texture image units.\n"
" :rtype: int\n");
static PyObject *pygpu_max_textures_geom_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_textures());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_textures_frag_get_doc,
".. function:: max_textures_frag_get()\n"
"\n"
" Get maximum supported texture image units used for\n"
" accessing texture maps from the fragment shader.\n"
"\n"
" :return: Texture image units.\n"
" :rtype: int\n");
static PyObject *pygpu_max_textures_frag_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_textures());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_images_get_doc,
".. function:: max_images_get()\n"
"\n"
" Get maximum supported number of image units.\n"
"\n"
" :return: Number of image units.\n"
" :rtype: int\n");
static PyObject *pygpu_max_images_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_images());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_uniforms_vert_get_doc,
".. function:: max_uniforms_vert_get()\n"
"\n"
" Get maximum number of values held in uniform variable\n"
" storage for a vertex shader.\n"
"\n"
" :return: Number of values.\n"
" :rtype: int\n");
static PyObject *pygpu_max_uniforms_vert_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_uniforms_vert());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_uniforms_frag_get_doc,
".. function:: max_uniforms_frag_get()\n"
"\n"
" Get maximum number of values held in uniform variable\n"
" storage for a fragment shader.\n"
"\n"
" :return: Number of values.\n"
" :rtype: int\n");
static PyObject *pygpu_max_uniforms_frag_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_uniforms_frag());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_batch_indices_get_doc,
".. function:: max_batch_indices_get()\n"
"\n"
" Get maximum number of vertex array indices.\n"
"\n"
" :return: Number of indices.\n"
" :rtype: int\n");
static PyObject *pygpu_max_batch_indices_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_batch_indices());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_batch_vertices_get_doc,
".. function:: max_batch_vertices_get()\n"
"\n"
" Get maximum number of vertex array vertices.\n"
"\n"
" :return: Number of vertices.\n"
" :rtype: int\n");
static PyObject *pygpu_max_batch_vertices_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_batch_vertices());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_vertex_attribs_get_doc,
".. function:: max_vertex_attribs_get()\n"
"\n"
" Get maximum number of vertex attributes accessible to\n"
" a vertex shader.\n"
"\n"
" :return: Number of attributes.\n"
" :rtype: int\n");
static PyObject *pygpu_max_vertex_attribs_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_vertex_attribs());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_varying_floats_get_doc,
".. function:: max_varying_floats_get()\n"
"\n"
" Get maximum number of varying variables used by\n"
" vertex and fragment shaders.\n"
"\n"
" :return: Number of variables.\n"
" :rtype: int\n");
static PyObject *pygpu_max_varying_floats_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyLong_FromLong(GPU_max_varying_floats());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_extensions_get_doc,
".. function:: extensions_get()\n"
"\n"
" Get supported extensions in the current context.\n"
"\n"
" :return: Extensions.\n"
" :rtype: tuple[str, ...]\n");
static PyObject *pygpu_extensions_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int extensions_len = GPU_extensions_len();
PyObject *ret = PyTuple_New(extensions_len);
PyObject **ob_items = (reinterpret_cast<PyTupleObject *>(ret))->ob_item;
for (int i = 0; i < extensions_len; i++) {
ob_items[i] = PyUnicode_FromString(GPU_extension_get(i));
}
return ret;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_compute_shader_support_get_doc,
".. function:: compute_shader_support_get()\n"
"\n"
" Are compute shaders supported.\n"
"\n"
" :return: True when supported, False when not supported.\n"
" :rtype: bool\n");
static PyObject *pygpu_compute_shader_support_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
static bool deprecation_warning_issued = false;
if (!deprecation_warning_issued) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"compute_shader_support_get is deprecated. All platforms have support for "
"compute shaders.",
1);
deprecation_warning_issued = true;
}
return PyBool_FromLong(true);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_shader_image_load_store_support_get_doc,
".. function:: shader_image_load_store_support_get()\n"
"\n"
" Is image load/store supported.\n"
"\n"
" :return: True when supported, False when not supported.\n"
" :rtype: bool\n");
static PyObject *pygpu_shader_image_load_store_support_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
static bool deprecation_warning_issued = false;
if (!deprecation_warning_issued) {
PyErr_WarnEx(
PyExc_DeprecationWarning,
"shader_image_load_store_support_get is deprecated. All platforms have support for "
"image load store.",
1);
deprecation_warning_issued = true;
}
/* Now required to start Blender. */
return PyBool_FromLong(true);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_hdr_support_get_doc,
".. function:: hdr_support_get()\n"
"\n"
" Return whether GPU backend supports High Dynamic range for viewport.\n"
"\n"
" :return: HDR support available.\n"
" :rtype: bool\n");
static PyObject *pygpu_hdr_support_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyBool_FromLong(GPU_hdr_support());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_work_group_count_get_doc,
".. function:: max_work_group_count_get(index)\n"
"\n"
" Get maximum number of work groups that may be dispatched to a compute shader.\n"
"\n"
" :param index: Index of the dimension.\n"
" :type index: int\n"
" :return: Maximum number of work groups for the queried dimension.\n"
" :rtype: int\n");
static PyObject *pygpu_max_work_group_count_get(PyObject * /*self*/, PyObject *args)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int index;
if (!PyArg_ParseTuple(args, "i", &index)) {
return nullptr;
}
const int max_work_group_count = GPU_max_work_group_count(index);
return PyLong_FromLong(max_work_group_count);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_max_work_group_size_get_doc,
".. function:: max_work_group_size_get(index)\n"
"\n"
" Get maximum size of a work group that may be dispatched to a compute shader.\n"
"\n"
" :param index: Index of the dimension.\n"
" :type index: int\n"
" :return: Maximum size of a work group for the queried dimension.\n"
" :rtype: int\n");
static PyObject *pygpu_max_work_group_size_get(PyObject * /*self*/, PyObject *args)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int index;
if (!PyArg_ParseTuple(args, "i", &index)) {
return nullptr;
}
const int max_work_group_size = GPU_max_work_group_size(index);
return PyLong_FromLong(max_work_group_size);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Module
* \{ */
#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 pygpu_capabilities__tp_methods[] = {
{"max_texture_size_get",
reinterpret_cast<PyCFunction>(pygpu_max_texture_size_get),
METH_NOARGS,
pygpu_max_texture_size_get_doc},
{"max_texture_layers_get",
reinterpret_cast<PyCFunction>(pygpu_max_texture_layers_get),
METH_NOARGS,
pygpu_max_texture_layers_get_doc},
{"max_textures_get",
reinterpret_cast<PyCFunction>(pygpu_max_textures_get),
METH_NOARGS,
pygpu_max_textures_get_doc},
{"max_textures_vert_get",
reinterpret_cast<PyCFunction>(pygpu_max_textures_vert_get),
METH_NOARGS,
pygpu_max_textures_vert_get_doc},
{"max_textures_geom_get",
reinterpret_cast<PyCFunction>(pygpu_max_textures_geom_get),
METH_NOARGS,
pygpu_max_textures_geom_get_doc},
{"max_textures_frag_get",
reinterpret_cast<PyCFunction>(pygpu_max_textures_frag_get),
METH_NOARGS,
pygpu_max_textures_frag_get_doc},
{"max_images_get",
reinterpret_cast<PyCFunction>(pygpu_max_images_get),
METH_NOARGS,
pygpu_max_images_get_doc},
{"max_uniforms_vert_get",
reinterpret_cast<PyCFunction>(pygpu_max_uniforms_vert_get),
METH_NOARGS,
pygpu_max_uniforms_vert_get_doc},
{"max_uniforms_frag_get",
reinterpret_cast<PyCFunction>(pygpu_max_uniforms_frag_get),
METH_NOARGS,
pygpu_max_uniforms_frag_get_doc},
{"max_batch_indices_get",
reinterpret_cast<PyCFunction>(pygpu_max_batch_indices_get),
METH_NOARGS,
pygpu_max_batch_indices_get_doc},
{"max_batch_vertices_get",
reinterpret_cast<PyCFunction>(pygpu_max_batch_vertices_get),
METH_NOARGS,
pygpu_max_batch_vertices_get_doc},
{"max_vertex_attribs_get",
reinterpret_cast<PyCFunction>(pygpu_max_vertex_attribs_get),
METH_NOARGS,
pygpu_max_vertex_attribs_get_doc},
{"max_varying_floats_get",
reinterpret_cast<PyCFunction>(pygpu_max_varying_floats_get),
METH_NOARGS,
pygpu_max_varying_floats_get_doc},
{"extensions_get",
reinterpret_cast<PyCFunction>(pygpu_extensions_get),
METH_NOARGS,
pygpu_extensions_get_doc},
{"compute_shader_support_get",
reinterpret_cast<PyCFunction>(pygpu_compute_shader_support_get),
METH_NOARGS,
pygpu_compute_shader_support_get_doc},
{"shader_image_load_store_support_get",
reinterpret_cast<PyCFunction>(pygpu_shader_image_load_store_support_get),
METH_NOARGS,
pygpu_shader_image_load_store_support_get_doc},
{"hdr_support_get",
reinterpret_cast<PyCFunction>(pygpu_hdr_support_get),
METH_NOARGS,
pygpu_hdr_support_get_doc},
{
"max_work_group_count_get",
static_cast<PyCFunction>(pygpu_max_work_group_count_get),
METH_VARARGS,
pygpu_max_work_group_count_get_doc,
},
{
"max_work_group_size_get",
static_cast<PyCFunction>(pygpu_max_work_group_size_get),
METH_VARARGS,
pygpu_max_work_group_size_get_doc,
},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_capabilities__tp_doc,
"This module provides access to the GPU capabilities.");
static PyModuleDef pygpu_capabilities_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu.capabilities",
/*m_doc*/ pygpu_capabilities__tp_doc,
/*m_size*/ 0,
/*m_methods*/ pygpu_capabilities__tp_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *bpygpu_capabilities_init()
{
PyObject *submodule;
submodule = PyModule_Create(&pygpu_capabilities_module_def);
return submodule;
}
/** \} */
} // namespace blender

View File

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

View File

@@ -0,0 +1,161 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_capabilities.hh"
#include "GPU_compute.hh"
#include "GPU_state.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "gpu_py.hh"
#include "gpu_py_compute.hh" /* own include */
#include "gpu_py_shader.hh"
namespace blender {
PyDoc_STRVAR(
/* Wrap. */
pygpu_compute_dispatch_doc,
".. function:: dispatch(shader, groups_x_len, groups_y_len, groups_z_len)\n"
"\n"
" Dispatches GPU compute.\n"
"\n"
" :param shader: The shader that you want to dispatch.\n"
" :type shader: :class:`gpu.types.GPUShader`\n"
" :param groups_x_len: Int for group x length:\n"
" :type groups_x_len: int\n"
" :param groups_y_len: Int for group y length:\n"
" :type groups_y_len: int\n"
" :param groups_z_len: Int for group z length:\n"
" :type groups_z_len: int\n");
static PyObject *pygpu_compute_dispatch(PyObject * /*self*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
BPyGPUShader *py_shader;
int groups_x_len;
int groups_y_len;
int groups_z_len;
static const char *_keywords[] = {
"shader", "groups_x_len", "groups_y_len", "groups_z_len", nullptr};
static _PyArg_Parser _parser = {
"O" /* `shader` */
"i" /* `groups_x_len` */
"i" /* `groups_y_len` */
"i" /* `groups_z_len` */
":dispatch",
_keywords,
nullptr,
};
if (_PyArg_ParseTupleAndKeywordsFast(
args, kwds, &_parser, &py_shader, &groups_x_len, &groups_y_len, &groups_z_len))
{
if (!BPyGPUShader_Check(py_shader)) {
PyErr_Format(PyExc_TypeError, "Expected a GPUShader, got %s", Py_TYPE(py_shader)->tp_name);
return nullptr;
}
/* Check that groups do not exceed #GPU_max_work_group_count(). */
const int max_work_group_count_x = GPU_max_work_group_count(0);
const int max_work_group_count_y = GPU_max_work_group_count(1);
const int max_work_group_count_z = GPU_max_work_group_count(2);
/* Report back to the user both the requested and the maximum supported value. */
if (groups_x_len > GPU_max_work_group_count(0)) {
PyErr_Format(PyExc_ValueError,
"groups_x_len (%d) exceeds maximum supported value (max work group count: %d)",
groups_x_len,
max_work_group_count_x);
return nullptr;
}
if (groups_y_len > GPU_max_work_group_count(1)) {
PyErr_Format(PyExc_ValueError,
"groups_y_len (%d) exceeds maximum supported value (max work group count: %d)",
groups_y_len,
max_work_group_count_y);
return nullptr;
}
if (groups_z_len > GPU_max_work_group_count(2)) {
PyErr_Format(PyExc_ValueError,
"groups_z_len (%d) exceeds maximum supported value (max work group count: %d)",
groups_z_len,
max_work_group_count_z);
return nullptr;
}
gpu::Shader *shader = py_shader->shader;
GPU_compute_dispatch(shader, groups_x_len, groups_y_len, groups_z_len);
GPU_memory_barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS);
}
Py_RETURN_NONE;
}
/* -------------------------------------------------------------------- */
/** \name Module
* \{ */
#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 pygpu_compute__tp_methods[] = {
{"dispatch",
reinterpret_cast<PyCFunction>(pygpu_compute_dispatch),
METH_VARARGS | METH_KEYWORDS,
pygpu_compute_dispatch_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_compute__tp_doc,
"This module provides access to the global GPU compute functions.");
static PyModuleDef pygpu_compute_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu.compute",
/*m_doc*/ pygpu_compute__tp_doc,
/*m_size*/ 0,
/*m_methods*/ pygpu_compute__tp_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *bpygpu_compute_init()
{
PyObject *submodule;
submodule = PyModule_Create(&pygpu_compute_module_def);
return submodule;
}
/** \} */
} // namespace blender

View File

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

View File

@@ -0,0 +1,266 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_index_buffer.hh"
#include "MEM_guardedalloc.h"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "gpu_py.hh"
#include "gpu_py_element.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name IndexBuf Type
* \{ */
static PyObject *pygpu_IndexBuf__tp_new(PyTypeObject * /*type*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
const char *error_prefix = "IndexBuf.__new__";
bool ok = true;
PyC_StringEnum prim_type = {bpygpu_primtype_items, GPU_PRIM_NONE};
PyObject *seq;
uint verts_per_prim;
uint index_len;
GPUIndexBufBuilder builder;
static const char *_keywords[] = {"type", "seq", nullptr};
static _PyArg_Parser _parser = {
"$" /* Keyword only arguments. */
"O&" /* `type` */
"O" /* `seq` */
":IndexBuf.__new__",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kwds, &_parser, PyC_ParseStringEnum, &prim_type, &seq))
{
return nullptr;
}
verts_per_prim = GPU_indexbuf_primitive_len(GPUPrimType(prim_type.value_found));
if (verts_per_prim == -1) {
PyErr_SetString(PyExc_ValueError,
"The argument 'type' must be "
"'POINTS', 'LINES', 'TRIS', 'LINES_ADJ' or 'TRIS_ADJ'");
return nullptr;
}
if (PyObject_CheckBuffer(seq)) {
Py_buffer pybuffer;
if (PyObject_GetBuffer(seq, &pybuffer, PyBUF_FORMAT | PyBUF_ND) == -1) {
/* PyObject_GetBuffer already handles error messages. */
return nullptr;
}
if (pybuffer.ndim != 1 && pybuffer.shape[1] != verts_per_prim) {
PyErr_Format(PyExc_ValueError, "Each primitive must exactly %d indices", verts_per_prim);
PyBuffer_Release(&pybuffer);
return nullptr;
}
if (pybuffer.itemsize != 4 ||
PyC_StructFmt_type_is_float_any(PyC_StructFmt_type_from_str(pybuffer.format)))
{
PyErr_SetString(PyExc_ValueError, "Each index must be an 4-bytes integer value");
PyBuffer_Release(&pybuffer);
return nullptr;
}
index_len = pybuffer.shape[0];
if (pybuffer.ndim != 1) {
index_len *= pybuffer.shape[1];
}
/* The `vertex_len` parameter is only used for asserts in the Debug build. */
/* Not very useful in python since scripts are often tested in Release build. */
/* Use `INT_MAX` instead of the actual number of vertices. */
GPU_indexbuf_init(&builder, GPUPrimType(prim_type.value_found), index_len, INT_MAX);
uint *buf = static_cast<uint *>(pybuffer.buf);
for (uint i = index_len; i--; buf++) {
GPU_indexbuf_add_generic_vert(&builder, *buf);
}
PyBuffer_Release(&pybuffer);
}
else {
PyObject *seq_fast = PySequence_Fast(seq, error_prefix);
if (seq_fast == nullptr) {
return nullptr;
}
const uint seq_len = PySequence_Fast_GET_SIZE(seq_fast);
PyObject **seq_items = PySequence_Fast_ITEMS(seq_fast);
index_len = seq_len * verts_per_prim;
/* The `vertex_len` parameter is only used for asserts in the Debug build. */
/* Not very useful in python since scripts are often tested in Release build. */
/* Use `INT_MAX` instead of the actual number of vertices. */
GPU_indexbuf_init(&builder, GPUPrimType(prim_type.value_found), index_len, INT_MAX);
if (verts_per_prim == 1) {
for (uint i = 0; i < seq_len; i++) {
GPU_indexbuf_add_generic_vert(&builder, PyC_Long_AsU32(seq_items[i]));
}
}
else {
int values[4];
for (uint i = 0; i < seq_len; i++) {
PyObject *seq_fast_item = PySequence_Fast(seq_items[i], error_prefix);
if (seq_fast_item == nullptr) {
PyErr_Format(PyExc_TypeError,
"%s: expected a sequence, got %s",
error_prefix,
Py_TYPE(seq_items[i])->tp_name);
ok = false;
goto finally;
}
ok = PyC_AsArray_FAST(values,
sizeof(*values),
seq_fast_item,
verts_per_prim,
&PyLong_Type,
error_prefix) == 0;
if (ok) {
for (uint j = 0; j < verts_per_prim; j++) {
GPU_indexbuf_add_generic_vert(&builder, values[j]);
}
}
Py_DECREF(seq_fast_item);
}
}
if (PyErr_Occurred()) {
ok = false;
}
finally:
Py_DECREF(seq_fast);
}
if (ok == false) {
MEM_delete(builder.data);
return nullptr;
}
return BPyGPUIndexBuf_CreatePyObject(GPU_indexbuf_build(&builder));
}
static void pygpu_IndexBuf__tp_dealloc(BPyGPUIndexBuf *self)
{
GPU_indexbuf_discard(self->elem);
Py_TYPE(self)->tp_free(self);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_IndexBuf__tp_doc,
".. class:: GPUIndexBuf\n"
"\n"
" Contains an index buffer.\n"
"\n"
" .. method:: __init__(type, seq)\n"
"\n"
" :param type: The primitive type this index buffer is composed of.\n"
" :type type: Literal['POINTS', 'LINES', 'TRIS', 'LINES_ADJ', 'TRIS_ADJ']\n"
" :param seq: Indices this index buffer will contain.\n"
" Whether a 1D or 2D sequence is required depends on the type.\n"
" Optionally the sequence can support the buffer protocol.\n"
" :type seq: Buffer | Sequence[int] | Sequence[Sequence[int]]\n");
PyTypeObject BPyGPUIndexBuf_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUIndexBuf",
/*tp_basicsize*/ sizeof(BPyGPUIndexBuf),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(pygpu_IndexBuf__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_IndexBuf__tp_doc,
/*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*/ nullptr,
/*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*/ pygpu_IndexBuf__tp_new,
/*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,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
PyObject *BPyGPUIndexBuf_CreatePyObject(gpu::IndexBuf *elem)
{
BPyGPUIndexBuf *self;
self = PyObject_New(BPyGPUIndexBuf, &BPyGPUIndexBuf_Type);
self->elem = elem;
return reinterpret_cast<PyObject *>(self);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,30 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
namespace blender {
namespace gpu {
class IndexBuf;
}
extern PyTypeObject BPyGPUIndexBuf_Type;
#define BPyGPUIndexBuf_Check(v) (Py_TYPE(v) == &BPyGPUIndexBuf_Type)
struct BPyGPUIndexBuf {
PyObject_HEAD
gpu::IndexBuf *elem;
};
[[nodiscard]] PyObject *BPyGPUIndexBuf_CreatePyObject(gpu::IndexBuf *elem);
} // namespace blender

View File

@@ -0,0 +1,958 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* This file defines the framebuffer functionalities of the 'gpu' module
* used for off-screen OpenGL rendering.
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_context.hh"
#include "GPU_framebuffer.hh"
#include "GPU_init_exit.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "../generic/python_utildefines.hh"
#include "../mathutils/mathutils.hh"
#include "gpu_py.hh"
#include "gpu_py_buffer.hh"
#include "gpu_py_framebuffer.hh" /* own include */
#include "gpu_py_texture.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name gpu::FrameBuffer Common Utilities
* \{ */
static int pygpu_framebuffer_valid_check(BPyGPUFrameBuffer *bpygpu_fb)
{
if (UNLIKELY(bpygpu_fb->fb == nullptr)) {
PyErr_SetString(PyExc_ReferenceError, "GPU framebuffer was freed, no further access is valid");
return -1;
}
return 0;
}
#define PYGPU_FRAMEBUFFER_CHECK_OBJ(bpygpu) \
{ \
if (UNLIKELY(pygpu_framebuffer_valid_check(bpygpu) == -1)) { \
return nullptr; \
} \
} \
((void)0)
static void pygpu_framebuffer_free_if_possible(gpu::FrameBuffer *fb)
{
if (GPU_is_init()) {
GPU_framebuffer_free(fb);
}
else {
printf("PyFramebuffer freed after the context has been destroyed.\n");
}
}
static void pygpu_framebuffer_free_safe(BPyGPUFrameBuffer *self)
{
if (self->fb) {
#ifndef GPU_NO_USE_PY_REFERENCES
GPU_framebuffer_py_reference_set(self->fb, nullptr);
if (!self->shared_reference)
#endif
{
pygpu_framebuffer_free_if_possible(self->fb);
}
self->fb = nullptr;
}
}
/* Keep less than or equal to #FRAMEBUFFER_STACK_DEPTH */
#define GPU_PY_FRAMEBUFFER_STACK_LEN 16
static bool pygpu_framebuffer_stack_push_and_bind_or_error(gpu::FrameBuffer *fb)
{
if (GPU_framebuffer_stack_level_get() >= GPU_PY_FRAMEBUFFER_STACK_LEN) {
PyErr_SetString(
PyExc_RuntimeError,
"Maximum framebuffer stack depth " STRINGIFY(GPU_PY_FRAMEBUFFER_STACK_LEN) " reached");
return false;
}
GPU_framebuffer_push(GPU_framebuffer_active_get());
GPU_framebuffer_bind(fb);
return true;
}
static bool pygpu_framebuffer_stack_pop_and_restore_or_error(gpu::FrameBuffer *fb)
{
if (GPU_framebuffer_stack_level_get() == 0) {
PyErr_SetString(PyExc_RuntimeError, "Minimum framebuffer stack depth reached");
return false;
}
if (fb && !GPU_framebuffer_bound(fb)) {
PyErr_SetString(PyExc_RuntimeError, "Framebuffer is not bound");
return false;
}
gpu::FrameBuffer *fb_prev = GPU_framebuffer_pop();
GPU_framebuffer_bind(fb_prev);
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Stack (Context Manager)
*
* Safer alternative to ensure balanced push/pop calls.
*
* \{ */
struct PyFrameBufferStackContext {
PyObject_HEAD /* Required Python macro. */
BPyGPUFrameBuffer *py_fb;
int level;
};
static void pygpu_framebuffer_stack_context__tp_dealloc(PyFrameBufferStackContext *self)
{
Py_DECREF(self->py_fb);
PyObject_DEL(self);
}
static PyObject *pygpu_framebuffer_stack_context_enter(PyFrameBufferStackContext *self)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self->py_fb);
/* sanity - should never happen */
if (self->level != -1) {
PyErr_SetString(PyExc_RuntimeError, "Already in use");
return nullptr;
}
if (!pygpu_framebuffer_stack_push_and_bind_or_error(self->py_fb->fb)) {
return nullptr;
}
self->level = GPU_framebuffer_stack_level_get();
Py_RETURN_NONE;
}
static PyObject *pygpu_framebuffer_stack_context_exit(PyFrameBufferStackContext *self,
PyObject * /*args*/)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self->py_fb);
/* sanity - should never happen */
if (self->level == -1) {
fprintf(stderr, "Not yet in use\n");
return nullptr;
}
const int level = GPU_framebuffer_stack_level_get();
if (level != self->level) {
fprintf(stderr, "Level of bind mismatch, expected %d, got %d\n", self->level, level);
}
if (!pygpu_framebuffer_stack_pop_and_restore_or_error(self->py_fb->fb)) {
return nullptr;
}
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 pygpu_framebuffer_stack_context__tp_methods[] = {
{"__enter__",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_stack_context_enter),
METH_NOARGS},
{"__exit__",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_stack_context_exit),
METH_VARARGS},
{nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
static PyTypeObject FramebufferStackContext_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUFrameBufferStackContext",
/*tp_basicsize*/ sizeof(PyFrameBufferStackContext),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(pygpu_framebuffer_stack_context__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*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*/ pygpu_framebuffer_stack_context__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ nullptr,
/*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,
};
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_bind_doc,
".. method:: bind()\n"
"\n"
" Context manager to ensure balanced bind calls, even in the case of an error.\n");
static PyObject *pygpu_framebuffer_bind(BPyGPUFrameBuffer *self)
{
PyFrameBufferStackContext *ret = PyObject_New(PyFrameBufferStackContext,
&FramebufferStackContext_Type);
ret->py_fb = self;
ret->level = -1;
Py_INCREF(self);
return reinterpret_cast<PyObject *>(ret);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPUFramebuffer Type
* \{ */
/* Fill in the GPUAttachment according to the PyObject parameter.
* PyObject *o can be nullptr, Py_None, BPyGPUTexture or a dictionary containing the keyword
* "texture" and the optional keywords "layer" and "mip". Returns false on error. In this case, a
* python message will be raised and GPUAttachment will not be touched. */
static bool pygpu_framebuffer_new_parse_arg(PyObject *o, GPUAttachment *r_attach)
{
GPUAttachment tmp_attach = GPU_ATTACHMENT_NONE;
if (!o || o == Py_None) {
/* Pass. */;
}
else if (BPyGPUTexture_Check(o)) {
if (!bpygpu_ParseTexture(o, &tmp_attach.tex)) {
return false;
}
}
else {
const char *c_texture = "texture";
const char *c_layer = "layer";
const char *c_mip = "mip";
PyObject *key, *value;
Py_ssize_t pos = 0;
while (PyDict_Next(o, &pos, &key, &value)) {
if (!PyUnicode_Check(key)) {
PyErr_SetString(PyExc_TypeError, "keywords must be strings");
return false;
}
if (c_texture && PyUnicode_CompareWithASCIIString(key, c_texture) == 0) {
/* Compare only once. */
c_texture = nullptr;
if (!bpygpu_ParseTexture(value, &tmp_attach.tex)) {
return false;
}
}
else if (c_layer && PyUnicode_CompareWithASCIIString(key, c_layer) == 0) {
/* Compare only once. */
c_layer = nullptr;
tmp_attach.layer = PyLong_AsLong(value);
if (tmp_attach.layer == -1 && PyErr_Occurred()) {
return false;
}
}
else if (c_mip && PyUnicode_CompareWithASCIIString(key, c_mip) == 0) {
/* Compare only once. */
c_mip = nullptr;
tmp_attach.mip = PyLong_AsLong(value);
if (tmp_attach.mip == -1 && PyErr_Occurred()) {
return false;
}
}
else {
PyErr_Format(
PyExc_TypeError, "'%U' is an invalid keyword argument for this attribute", key);
return false;
}
}
}
*r_attach = tmp_attach;
return true;
}
static PyObject *pygpu_framebuffer__tp_new(PyTypeObject * /*self*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
if (!GPU_context_active_get()) {
PyErr_SetString(PyExc_RuntimeError, "No active GPU context found");
return nullptr;
}
PyObject *depth_attachment = nullptr;
PyObject *color_attachements = nullptr;
static const char *_keywords[] = {"depth_slot", "color_slots", nullptr};
static _PyArg_Parser _parser = {
"|$" /* Optional keyword only arguments. */
"O" /* `depth_slot` */
"O" /* `color_slots` */
":GPUFrameBuffer.__new__",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kwds, &_parser, &depth_attachment, &color_attachements))
{
return nullptr;
}
/* Keep in sync with #GPU_FB_MAX_COLOR_ATTACHMENT.
* TODO: share the define. */
#define BPYGPU_FB_MAX_COLOR_ATTACHMENT 6
GPUAttachment config[BPYGPU_FB_MAX_COLOR_ATTACHMENT + 1];
if (!pygpu_framebuffer_new_parse_arg(depth_attachment, &config[0])) {
return nullptr;
}
if (config[0].tex && !GPU_texture_has_depth_format(config[0].tex)) {
PyErr_SetString(PyExc_ValueError, "Depth texture with incompatible format");
return nullptr;
}
int color_attachements_len = 0;
if (color_attachements && color_attachements != Py_None) {
if (PySequence_Check(color_attachements)) {
color_attachements_len = PySequence_Size(color_attachements);
if (color_attachements_len > BPYGPU_FB_MAX_COLOR_ATTACHMENT) {
PyErr_SetString(PyExc_AttributeError,
"too many attachments, max is " STRINGIFY(BPYGPU_FB_MAX_COLOR_ATTACHMENT));
return nullptr;
}
for (int i = 0; i < color_attachements_len; i++) {
PyObject *o = PySequence_GetItem(color_attachements, i);
bool ok = pygpu_framebuffer_new_parse_arg(o, &config[i + 1]);
Py_DECREF(o);
if (!ok) {
return nullptr;
}
}
}
else {
if (!pygpu_framebuffer_new_parse_arg(color_attachements, &config[1])) {
return nullptr;
}
color_attachements_len = 1;
}
}
gpu::FrameBuffer *fb_python = GPU_framebuffer_create("fb_python");
GPU_framebuffer_config_array(fb_python, config, color_attachements_len + 1);
return BPyGPUFrameBuffer_CreatePyObject(fb_python, false);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_is_bound_doc,
"Checks if this is the active frame-buffer in the context.\n"
"\n"
":type: bool\n");
static PyObject *pygpu_framebuffer_is_bound(BPyGPUFrameBuffer *self, void * /*type*/)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self);
return PyBool_FromLong(GPU_framebuffer_bound(self->fb));
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_clear_doc,
".. method:: clear(*, color=None, depth=None, stencil=None)\n"
"\n"
" Fill color, depth and stencil textures with specific value.\n"
" Common values: color=(0.0, 0.0, 0.0, 1.0), depth=1.0, stencil=0.\n"
"\n"
" :param color: Sequence of 3 or 4 floats representing ``(r, g, b, a)``.\n"
" :type color: Sequence[float] | None\n"
" :param depth: depth value.\n"
" :type depth: float | None\n"
" :param stencil: stencil value.\n"
" :type stencil: int | None\n");
static PyObject *pygpu_framebuffer_clear(BPyGPUFrameBuffer *self, PyObject *args, PyObject *kwds)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self);
if (!GPU_framebuffer_bound(self->fb)) {
return nullptr;
}
PyObject *py_col = nullptr;
PyObject *py_depth = nullptr;
PyObject *py_stencil = nullptr;
static const char *_keywords[] = {"color", "depth", "stencil", nullptr};
static _PyArg_Parser _parser = {
"|$" /* Optional keyword only arguments. */
"O" /* `color` */
"O" /* `depth` */
"O" /* `stencil` */
":clear",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, &py_col, &py_depth, &py_stencil)) {
return nullptr;
}
GPUFrameBufferBits buffers = GPUFrameBufferBits(0);
float col[4] = {0.0f, 0.0f, 0.0f, 1.0f};
float depth = 1.0f;
uint stencil = 0;
if (py_col && py_col != Py_None) {
if (mathutils_array_parse(
col, 3, 4, py_col, "gpu::FrameBuffer.clear(), invalid 'color' arg") == -1)
{
return nullptr;
}
buffers |= GPU_COLOR_BIT;
}
if (py_depth && py_depth != Py_None) {
depth = PyFloat_AsDouble(py_depth);
if (PyErr_Occurred()) {
return nullptr;
}
buffers |= GPU_DEPTH_BIT;
}
if (py_stencil && py_stencil != Py_None) {
if ((stencil = PyC_Long_AsU32(py_stencil)) == uint(-1)) {
return nullptr;
}
buffers |= GPU_STENCIL_BIT;
}
GPU_framebuffer_clear(self->fb, buffers, double4(UNPACK4(col)), depth, stencil);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_viewport_set_doc,
".. method:: viewport_set(x, y, xsize, ysize)\n"
"\n"
" Set the viewport for this framebuffer object.\n"
" Note: The viewport state is not saved upon framebuffer rebind.\n"
"\n"
" :param x: Lower left corner x of the viewport rectangle, in pixels.\n"
" :type x: int\n"
" :param y: Lower left corner y of the viewport rectangle, in pixels.\n"
" :type y: int\n"
" :param xsize: Width of the viewport.\n"
" :type xsize: int\n"
" :param ysize: Height of the viewport.\n"
" :type ysize: int\n");
static PyObject *pygpu_framebuffer_viewport_set(BPyGPUFrameBuffer *self, PyObject *args)
{
int x, y, xsize, ysize;
if (!PyArg_ParseTuple(args, "iiii:viewport_set", &x, &y, &xsize, &ysize)) {
return nullptr;
}
GPU_framebuffer_viewport_set(self->fb, x, y, xsize, ysize);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_viewport_get_doc,
".. method:: viewport_get()\n"
"\n"
" Returns position and dimension to current viewport.\n"
"\n"
" :return: The viewport as ``(x, y, width, height)``.\n"
" :rtype: tuple[int, int, int, int]\n");
static PyObject *pygpu_framebuffer_viewport_get(BPyGPUFrameBuffer *self)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self);
int viewport[4];
GPU_framebuffer_viewport_get(self->fb, viewport);
PyObject *ret = PyTuple_New(4);
PyTuple_SET_ITEMS(ret,
PyLong_FromLong(viewport[0]),
PyLong_FromLong(viewport[1]),
PyLong_FromLong(viewport[2]),
PyLong_FromLong(viewport[3]));
return ret;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_read_color_doc,
".. method:: read_color(x, y, xsize, ysize, channels, slot, format, *, data=None)\n"
"\n"
" Read a block of pixels from the frame buffer.\n"
"\n"
" :param x: Lower left corner x of a rectangular block of pixels.\n"
" :type x: int\n"
" :param y: Lower left corner y of a rectangular block of pixels.\n"
" :type y: int\n"
" :param xsize: Width of the pixel rectangle.\n"
" :type xsize: int\n"
" :param ysize: Height of the pixel rectangle.\n"
" :type ysize: int\n"
" :param channels: Number of components to read.\n"
" :type channels: int\n"
" :param slot: The framebuffer slot to read data from.\n"
" :type slot: int\n"
" :param format: The format that describes the content of a single channel.\n"
" ``UINT_24_8`` is deprecated, use ``FLOAT`` instead.\n"
" :type format: " PYDOC_DATAFORMAT_LITERAL
"\n"
" :param data: Optional Buffer object to fill with the pixels values.\n"
" :type data: :class:`gpu.types.Buffer` | None\n"
" :return: The Buffer with the read pixels.\n"
" :rtype: :class:`gpu.types.Buffer`\n");
static PyObject *pygpu_framebuffer_read_color(BPyGPUFrameBuffer *self,
PyObject *args,
PyObject *kwds)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self);
int x, y, w, h, channels;
uint slot;
PyC_StringEnum pygpu_dataformat = {bpygpu_dataformat_items,
int(gpu::TextureFormat::UNORM_8_8_8_8)};
BPyGPUBuffer *py_buffer = nullptr;
PyC_TypeOrNone py_buffer_or_none = PyC_TYPE_OR_NONE_INIT(&BPyGPU_BufferType, &py_buffer);
static const char *_keywords[] = {
"x", "y", "xsize", "ysize", "channels", "slot", "format", "data", nullptr};
static _PyArg_Parser _parser = {
"i" /* `x` */
"i" /* `y` */
"i" /* `xsize` */
"i" /* `ysize` */
"i" /* `channels` */
"I" /* `slot` */
"O&" /* `format` */
"|$" /* Optional keyword only arguments. */
"O&" /* `data` */
":read_color",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kwds,
&_parser,
&x,
&y,
&w,
&h,
&channels,
&slot,
PyC_ParseStringEnum,
&pygpu_dataformat,
PyC_ParseTypeOrNone,
&py_buffer_or_none))
{
return nullptr;
}
if (pygpu_dataformat.value_found == GPU_DATA_UINT_24_8_DEPRECATED) {
PyErr_WarnEx(PyExc_DeprecationWarning, "`UINT_24_8` is deprecated, use `FLOAT` instead", 1);
}
if (!IN_RANGE_INCL(channels, 1, 4)) {
PyErr_SetString(PyExc_AttributeError, "Color channels must be 1, 2, 3 or 4");
return nullptr;
}
if (slot >= BPYGPU_FB_MAX_COLOR_ATTACHMENT) {
PyErr_SetString(PyExc_ValueError, "slot overflow");
return nullptr;
}
int2 extent = GPU_framebuffer_extent_get(self->fb);
if (x < 0 || w < 0 || x + w > extent.x || y < 0 || h < 0 || y + h > extent.y) {
PyErr_SetString(PyExc_ValueError,
"Trying to read color outside the extent of the framebuffer");
return nullptr;
}
if (py_buffer) {
if (pygpu_dataformat.value_found != py_buffer->format) {
PyErr_SetString(PyExc_AttributeError,
"the format of the buffer is different from that specified");
return nullptr;
}
size_t size_curr = bpygpu_Buffer_size(py_buffer);
size_t size_expected = w * h * channels *
GPU_texture_dataformat_size(
eGPUDataFormat(pygpu_dataformat.value_found));
if (size_curr < size_expected) {
PyErr_SetString(PyExc_BufferError, "the buffer size is smaller than expected");
return nullptr;
}
Py_INCREF(py_buffer);
}
else {
const Py_ssize_t shape[3] = {h, w, channels};
py_buffer = BPyGPU_Buffer_CreatePyObject(pygpu_dataformat.value_found, shape, 3, nullptr);
BLI_assert(bpygpu_Buffer_size(py_buffer) ==
size_t(w) * size_t(h) * size_t(channels) *
GPU_texture_dataformat_size(eGPUDataFormat(pygpu_dataformat.value_found)));
}
GPU_framebuffer_read_color(self->fb,
x,
y,
w,
h,
channels,
int(slot),
eGPUDataFormat(pygpu_dataformat.value_found),
py_buffer->buf.as_void);
return reinterpret_cast<PyObject *>(py_buffer);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_read_depth_doc,
".. method:: read_depth(x, y, xsize, ysize, *, data=None)\n"
"\n"
" Read a pixel depth block from the frame buffer.\n"
"\n"
" :param x: Lower left corner x of a rectangular block of pixels.\n"
" :type x: int\n"
" :param y: Lower left corner y of a rectangular block of pixels.\n"
" :type y: int\n"
" :param xsize: Width of the pixel rectangle.\n"
" :type xsize: int\n"
" :param ysize: Height of the pixel rectangle.\n"
" :type ysize: int\n"
" :param data: Optional Buffer object to fill with the pixels values.\n"
" :type data: :class:`gpu.types.Buffer` | None\n"
" :return: The Buffer with the read pixels.\n"
" :rtype: :class:`gpu.types.Buffer`\n");
static PyObject *pygpu_framebuffer_read_depth(BPyGPUFrameBuffer *self,
PyObject *args,
PyObject *kwds)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self);
int x, y, w, h;
BPyGPUBuffer *py_buffer = nullptr;
PyC_TypeOrNone py_buffer_or_none = PyC_TYPE_OR_NONE_INIT(&BPyGPU_BufferType, &py_buffer);
static const char *_keywords[] = {"x", "y", "xsize", "ysize", "data", nullptr};
static _PyArg_Parser _parser = {
"i" /* `x` */
"i" /* `y` */
"i" /* `xsize` */
"i" /* `ysize` */
"|$" /* Optional keyword only arguments. */
"O&" /* `data` */
":read_depth",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kwds, &_parser, &x, &y, &w, &h, PyC_ParseTypeOrNone, &py_buffer_or_none))
{
return nullptr;
}
int2 extent = GPU_framebuffer_extent_get(self->fb);
if (x < 0 || w < 0 || x + w > extent.x || y < 0 || h < 0 || y + h > extent.y) {
PyErr_SetString(PyExc_ValueError,
"Trying to read depth outside the extent of the framebuffer");
return nullptr;
}
if (py_buffer) {
if (py_buffer->format != GPU_DATA_FLOAT) {
PyErr_SetString(PyExc_AttributeError, "the format of the buffer must be 'GPU_DATA_FLOAT'");
return nullptr;
}
size_t size_curr = bpygpu_Buffer_size(py_buffer);
size_t size_expected = w * h * GPU_texture_dataformat_size(GPU_DATA_FLOAT);
if (size_curr < size_expected) {
PyErr_SetString(PyExc_BufferError, "the buffer size is smaller than expected");
return nullptr;
}
Py_INCREF(py_buffer);
}
else {
const Py_ssize_t shape[2] = {h, w};
py_buffer = BPyGPU_Buffer_CreatePyObject(GPU_DATA_FLOAT, shape, 2, nullptr);
BLI_assert(bpygpu_Buffer_size(py_buffer) ==
w * h * GPU_texture_dataformat_size(GPU_DATA_FLOAT));
}
GPU_framebuffer_read_depth(self->fb, x, y, w, h, GPU_DATA_FLOAT, py_buffer->buf.as_void);
return reinterpret_cast<PyObject *>(py_buffer);
}
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer_free_doc,
".. method:: free()\n"
"\n"
" Free the framebuffer object.\n"
" The framebuffer will no longer be accessible.\n");
static PyObject *pygpu_framebuffer_free(BPyGPUFrameBuffer *self)
{
PYGPU_FRAMEBUFFER_CHECK_OBJ(self);
pygpu_framebuffer_free_safe(self);
Py_RETURN_NONE;
}
#endif
static void BPyGPUFrameBuffer__tp_dealloc(BPyGPUFrameBuffer *self)
{
pygpu_framebuffer_free_safe(self);
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject *>(self));
}
static PyGetSetDef pygpu_framebuffer__tp_getseters[] = {
{"is_bound",
reinterpret_cast<getter>(pygpu_framebuffer_is_bound),
static_cast<setter>(nullptr),
pygpu_framebuffer_is_bound_doc,
nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr} /* Sentinel */
};
#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 pygpu_framebuffer__tp_methods[] = {
{"bind",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_bind),
METH_NOARGS,
pygpu_framebuffer_bind_doc},
{"clear",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_clear),
METH_VARARGS | METH_KEYWORDS,
pygpu_framebuffer_clear_doc},
{"viewport_set",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_viewport_set),
METH_VARARGS,
pygpu_framebuffer_viewport_set_doc},
{"viewport_get",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_viewport_get),
METH_NOARGS,
pygpu_framebuffer_viewport_get_doc},
{"read_color",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_read_color),
METH_VARARGS | METH_KEYWORDS,
pygpu_framebuffer_read_color_doc},
{"read_depth",
reinterpret_cast<PyCFunction>(pygpu_framebuffer_read_depth),
METH_VARARGS | METH_KEYWORDS,
pygpu_framebuffer_read_depth_doc},
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
{"free", (PyCFunction)pygpu_framebuffer_free, METH_NOARGS, pygpu_framebuffer_free_doc},
#endif
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
/* Ideally type aliases would de-duplicate:
* `GPUTexture | dict[str, int | GPUTexture]` in this docstring. */
PyDoc_STRVAR(
/* Wrap. */
pygpu_framebuffer__tp_doc,
".. class:: GPUFrameBuffer\n"
"\n"
" This object gives access to framebuffer functionalities.\n"
" When a 'layer' is specified in a argument, a single layer of a 3D or array "
"texture is attached to the frame-buffer.\n"
" For cube map textures, layer is translated into a cube map face.\n"
"\n"
" .. method:: __init__(*, depth_slot=None, color_slots=None)\n"
"\n"
" :param depth_slot: GPUTexture to attach or a ``dict`` containing keywords: "
"'texture', 'layer' and 'mip'.\n"
" :type depth_slot: :class:`gpu.types.GPUTexture` | dict[str, int | "
":class:`gpu.types.GPUTexture`] | None\n"
" :param color_slots: Tuple where each item can be a GPUTexture or a ``dict`` "
"containing keywords: 'texture', 'layer' and 'mip'.\n"
" :type color_slots: :class:`gpu.types.GPUTexture` | "
"dict[str, int | :class:`gpu.types.GPUTexture`] | "
"Sequence[:class:`gpu.types.GPUTexture` | dict[str, int | "
":class:`gpu.types.GPUTexture`]] | "
"None\n");
PyTypeObject BPyGPUFrameBuffer_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUFrameBuffer",
/*tp_basicsize*/ sizeof(BPyGPUFrameBuffer),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(BPyGPUFrameBuffer__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_framebuffer__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_framebuffer__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ pygpu_framebuffer__tp_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*/ pygpu_framebuffer__tp_new,
/*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,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
PyObject *BPyGPUFrameBuffer_CreatePyObject(gpu::FrameBuffer *fb, bool shared_reference)
{
BPyGPUFrameBuffer *self;
#ifndef GPU_NO_USE_PY_REFERENCES
if (shared_reference) {
void **ref = GPU_framebuffer_py_reference_get(fb);
if (ref) {
/* Retrieve BPyGPUFrameBuffer reference. */
self = reinterpret_cast<BPyGPUFrameBuffer *> POINTER_OFFSET(
ref, -offsetof(BPyGPUFrameBuffer, fb));
BLI_assert(self->fb == fb);
Py_INCREF(self);
return reinterpret_cast<PyObject *>(self);
}
}
#else
UNUSED_VARS(shared_reference);
#endif
self = PyObject_New(BPyGPUFrameBuffer, &BPyGPUFrameBuffer_Type);
self->fb = fb;
#ifndef GPU_NO_USE_PY_REFERENCES
self->shared_reference = shared_reference;
BLI_assert(GPU_framebuffer_py_reference_get(fb) == nullptr);
GPU_framebuffer_py_reference_set(fb, reinterpret_cast<void **>(&self->fb));
#endif
return reinterpret_cast<PyObject *>(self);
}
/** \} */
#undef PYGPU_FRAMEBUFFER_CHECK_OBJ
} // namespace blender

View File

@@ -0,0 +1,37 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "BLI_compiler_attrs.h"
namespace blender {
namespace gpu {
class FrameBuffer;
} // namespace gpu
extern PyTypeObject BPyGPUFrameBuffer_Type;
#define BPyGPUFrameBuffer_Check(v) (Py_TYPE(v) == &BPyGPUFrameBuffer_Type)
struct BPyGPUFrameBuffer {
PyObject_HEAD
gpu::FrameBuffer *fb;
#ifndef GPU_NO_USE_PY_REFERENCES
bool shared_reference;
#endif
};
[[nodiscard]] PyObject *BPyGPUFrameBuffer_CreatePyObject(gpu::FrameBuffer *fb,
bool shared_reference) ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,732 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* This file defines the gpu.matrix stack API.
*
* \warning While these functions attempt to ensure correct stack usage.
* Mixing Python and C functions may still crash on invalid use.
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "BLI_utildefines.h"
#include "../mathutils/mathutils.hh"
#define USE_GPU_PY_MATRIX_API
#include "GPU_matrix.hh"
#undef USE_GPU_PY_MATRIX_API
#include "gpu_py.hh"
#include "gpu_py_matrix.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Helper Functions
* \{ */
static bool pygpu_stack_is_push_model_view_ok_or_error()
{
if (GPU_matrix_stack_level_get_model_view() >= GPU_PY_MATRIX_STACK_LEN) {
PyErr_SetString(
PyExc_RuntimeError,
"Maximum model-view stack depth " STRINGIFY(GPU_PY_MATRIX_STACK_DEPTH) " reached");
return false;
}
return true;
}
static bool pygpu_stack_is_push_projection_ok_or_error()
{
if (GPU_matrix_stack_level_get_projection() >= GPU_PY_MATRIX_STACK_LEN) {
PyErr_SetString(
PyExc_RuntimeError,
"Maximum projection stack depth " STRINGIFY(GPU_PY_MATRIX_STACK_DEPTH) " reached");
return false;
}
return true;
}
static bool pygpu_stack_is_pop_model_view_ok_or_error()
{
if (GPU_matrix_stack_level_get_model_view() == 0) {
PyErr_SetString(PyExc_RuntimeError, "Minimum model-view stack depth reached");
return false;
}
return true;
}
static bool pygpu_stack_is_pop_projection_ok_or_error()
{
if (GPU_matrix_stack_level_get_projection() == 0) {
PyErr_SetString(PyExc_RuntimeError, "Minimum projection stack depth reached");
return false;
}
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Manage Stack
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_push_doc,
".. function:: push()\n"
"\n"
" Add to the model-view matrix stack.\n");
static PyObject *pygpu_matrix_push(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
if (!pygpu_stack_is_push_model_view_ok_or_error()) {
return nullptr;
}
GPU_matrix_push();
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_pop_doc,
".. function:: pop()\n"
"\n"
" Remove the last model-view matrix from the stack.\n");
static PyObject *pygpu_matrix_pop(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
if (!pygpu_stack_is_pop_model_view_ok_or_error()) {
return nullptr;
}
GPU_matrix_pop();
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_push_projection_doc,
".. function:: push_projection()\n"
"\n"
" Add to the projection matrix stack.\n");
static PyObject *pygpu_matrix_push_projection(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
if (!pygpu_stack_is_push_projection_ok_or_error()) {
return nullptr;
}
GPU_matrix_push_projection();
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_pop_projection_doc,
".. function:: pop_projection()\n"
"\n"
" Remove the last projection matrix from the stack.\n");
static PyObject *pygpu_matrix_pop_projection(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
if (!pygpu_stack_is_pop_projection_ok_or_error()) {
return nullptr;
}
GPU_matrix_pop_projection();
Py_RETURN_NONE;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Stack (Context Manager)
*
* Safer alternative to ensure balanced push/pop calls.
*
* \{ */
struct BPyGPU_MatrixStackContext {
PyObject_HEAD /* Required Python macro. */
int type;
int level;
};
enum {
PYGPU_MATRIX_TYPE_MODEL_VIEW = 1,
PYGPU_MATRIX_TYPE_PROJECTION = 2,
};
static PyObject *pygpu_matrix_stack_context_enter(BPyGPU_MatrixStackContext *self);
static PyObject *pygpu_matrix_stack_context_exit(BPyGPU_MatrixStackContext *self, PyObject *args);
#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 pygpu_matrix_stack_context__tp_methods[] = {
{"__enter__", reinterpret_cast<PyCFunction>(pygpu_matrix_stack_context_enter), METH_NOARGS},
{"__exit__", reinterpret_cast<PyCFunction>(pygpu_matrix_stack_context_exit), METH_VARARGS},
{nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_stack_context__tp_doc,
"Context manager for matrix stack push/pop.");
PyTypeObject PyGPUMatrixStackContext_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "MatrixStackContext",
/*tp_basicsize*/ sizeof(BPyGPU_MatrixStackContext),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ nullptr,
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_matrix_stack_context__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_matrix_stack_context__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ nullptr,
/*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,
};
static PyObject *pygpu_matrix_stack_context_enter(BPyGPU_MatrixStackContext *self)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
/* sanity - should never happen */
if (self->level != -1) {
PyErr_SetString(PyExc_RuntimeError, "Already in use");
return nullptr;
}
if (self->type == PYGPU_MATRIX_TYPE_MODEL_VIEW) {
if (!pygpu_stack_is_push_model_view_ok_or_error()) {
return nullptr;
}
GPU_matrix_push();
self->level = GPU_matrix_stack_level_get_model_view();
}
else if (self->type == PYGPU_MATRIX_TYPE_PROJECTION) {
if (!pygpu_stack_is_push_projection_ok_or_error()) {
return nullptr;
}
GPU_matrix_push_projection();
self->level = GPU_matrix_stack_level_get_projection();
}
else {
BLI_assert_unreachable();
}
Py_RETURN_NONE;
}
static PyObject *pygpu_matrix_stack_context_exit(BPyGPU_MatrixStackContext *self,
PyObject * /*args*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
/* sanity - should never happen */
if (self->level == -1) {
fprintf(stderr, "Not yet in use\n");
goto finally;
}
if (self->type == PYGPU_MATRIX_TYPE_MODEL_VIEW) {
const int level = GPU_matrix_stack_level_get_model_view();
if (level != self->level) {
fprintf(stderr, "Level push/pop mismatch, expected %d, got %d\n", self->level, level);
}
if (level != 0) {
GPU_matrix_pop();
}
}
else if (self->type == PYGPU_MATRIX_TYPE_PROJECTION) {
const int level = GPU_matrix_stack_level_get_projection();
if (level != self->level) {
fprintf(stderr, "Level push/pop mismatch, expected %d, got %d", self->level, level);
}
if (level != 0) {
GPU_matrix_pop_projection();
}
}
else {
BLI_assert_unreachable();
}
finally:
Py_RETURN_NONE;
}
static PyObject *pygpu_matrix_push_pop_impl(int type)
{
BPyGPU_MatrixStackContext *ret = PyObject_New(BPyGPU_MatrixStackContext,
&PyGPUMatrixStackContext_Type);
ret->type = type;
ret->level = -1;
return reinterpret_cast<PyObject *>(ret);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_push_pop_doc,
".. function:: push_pop()\n"
"\n"
" Context manager to ensure balanced push/pop calls, even in the case of an error.\n"
"\n"
" :return: The context manager.\n"
" :rtype: :class:`gpu.types.MatrixStackContext`\n");
static PyObject *pygpu_matrix_push_pop(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return pygpu_matrix_push_pop_impl(PYGPU_MATRIX_TYPE_MODEL_VIEW);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_push_pop_projection_doc,
".. function:: push_pop_projection()\n"
"\n"
" Context manager to ensure balanced push/pop calls, even in the case of an error.\n"
"\n"
" :return: The context manager.\n"
" :rtype: :class:`gpu.types.MatrixStackContext`\n");
static PyObject *pygpu_matrix_push_pop_projection(PyObject * /*self*/)
{
return pygpu_matrix_push_pop_impl(PYGPU_MATRIX_TYPE_PROJECTION);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Manipulate State
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_multiply_matrix_doc,
".. function:: multiply_matrix(matrix)\n"
"\n"
" Multiply the current stack matrix.\n"
"\n"
" :param matrix: A 4x4 matrix.\n"
" :type matrix: :class:`mathutils.Matrix`\n");
static PyObject *pygpu_matrix_multiply_matrix(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
MatrixObject *pymat;
if (!Matrix_Parse4x4(value, &pymat)) {
return nullptr;
}
GPU_matrix_mul(pymat->matrix);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_scale_doc,
".. function:: scale(scale)\n"
"\n"
" Scale the current stack matrix.\n"
"\n"
" :param scale: Scale the current stack matrix with 2 or 3 floats.\n"
" :type scale: Sequence[float]\n");
static PyObject *pygpu_matrix_scale(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float scale[3];
int len;
if ((len = mathutils_array_parse(
scale, 2, 3, value, "gpu.matrix.scale(): invalid vector arg")) == -1)
{
return nullptr;
}
if (len == 2) {
GPU_matrix_scale_2fv(scale);
}
else {
GPU_matrix_scale_3fv(scale);
}
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_scale_uniform_doc,
".. function:: scale_uniform(scale)\n"
"\n"
" Scale the current stack matrix uniformly.\n"
"\n"
" :param scale: Uniform scale factor.\n"
" :type scale: float\n");
static PyObject *pygpu_matrix_scale_uniform(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float scalar;
if ((scalar = PyFloat_AsDouble(value)) == -1.0f && PyErr_Occurred()) {
PyErr_Format(PyExc_TypeError, "expected a number, not %.200s", Py_TYPE(value)->tp_name);
return nullptr;
}
GPU_matrix_scale_1f(scalar);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_translate_doc,
".. function:: translate(offset)\n"
"\n"
" Translate the current stack matrix.\n"
"\n"
" :param offset: Translate the current stack matrix with 2 or 3 floats.\n"
" :type offset: Sequence[float]\n");
static PyObject *pygpu_matrix_translate(PyObject * /*self*/, PyObject *value)
{
float offset[3];
int len;
if ((len = mathutils_array_parse(
offset, 2, 3, value, "gpu.matrix.translate(): invalid vector arg")) == -1)
{
return nullptr;
}
if (len == 2) {
GPU_matrix_translate_2fv(offset);
}
else {
GPU_matrix_translate_3fv(offset);
}
Py_RETURN_NONE;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Write State
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_reset_doc,
".. function:: reset()\n"
"\n"
" Empty stack and set to identity.\n");
static PyObject *pygpu_matrix_reset(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
GPU_matrix_reset();
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_load_identity_doc,
".. function:: load_identity()\n"
"\n"
" Load an identity matrix into the stack.\n");
static PyObject *pygpu_matrix_load_identity(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
GPU_matrix_identity_set();
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_load_matrix_doc,
".. function:: load_matrix(matrix)\n"
"\n"
" Load a matrix into the stack.\n"
"\n"
" :param matrix: A 4x4 matrix.\n"
" :type matrix: :class:`mathutils.Matrix`\n");
static PyObject *pygpu_matrix_load_matrix(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
MatrixObject *pymat;
if (!Matrix_Parse4x4(value, &pymat)) {
return nullptr;
}
GPU_matrix_set(pymat->matrix);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_load_projection_matrix_doc,
".. function:: load_projection_matrix(matrix)\n"
"\n"
" Load a projection matrix into the stack.\n"
"\n"
" :param matrix: A 4x4 matrix.\n"
" :type matrix: :class:`mathutils.Matrix`\n");
static PyObject *pygpu_matrix_load_projection_matrix(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
MatrixObject *pymat;
if (!Matrix_Parse4x4(value, &pymat)) {
return nullptr;
}
GPU_matrix_projection_set(pymat->matrix);
Py_RETURN_NONE;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Read State
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_get_projection_matrix_doc,
".. function:: get_projection_matrix()\n"
"\n"
" Return a copy of the projection matrix.\n"
"\n"
" :return: A 4x4 projection matrix.\n"
" :rtype: :class:`mathutils.Matrix`\n");
static PyObject *pygpu_matrix_get_projection_matrix(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float matrix[4][4];
GPU_matrix_projection_get(matrix);
return Matrix_CreatePyObject(&matrix[0][0], 4, 4, nullptr);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_get_model_view_matrix_doc,
".. function:: get_model_view_matrix()\n"
"\n"
" Return a copy of the model-view matrix.\n"
"\n"
" :return: A 4x4 view matrix.\n"
" :rtype: :class:`mathutils.Matrix`\n");
static PyObject *pygpu_matrix_get_model_view_matrix(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float matrix[4][4];
GPU_matrix_model_view_get(matrix);
return Matrix_CreatePyObject(&matrix[0][0], 4, 4, nullptr);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix_get_normal_matrix_doc,
".. function:: get_normal_matrix()\n"
"\n"
" Return a copy of the normal matrix.\n"
"\n"
" :return: A 3x3 normal matrix.\n"
" :rtype: :class:`mathutils.Matrix`\n");
static PyObject *pygpu_matrix_get_normal_matrix(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float matrix[3][3];
GPU_matrix_normal_get(matrix);
return Matrix_CreatePyObject(&matrix[0][0], 3, 3, nullptr);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Module
* \{ */
#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 pygpu_matrix__tp_methods[] = {
/* Manage Stack */
{"push", reinterpret_cast<PyCFunction>(pygpu_matrix_push), METH_NOARGS, pygpu_matrix_push_doc},
{"pop", reinterpret_cast<PyCFunction>(pygpu_matrix_pop), METH_NOARGS, pygpu_matrix_pop_doc},
{"push_projection",
reinterpret_cast<PyCFunction>(pygpu_matrix_push_projection),
METH_NOARGS,
pygpu_matrix_push_projection_doc},
{"pop_projection",
reinterpret_cast<PyCFunction>(pygpu_matrix_pop_projection),
METH_NOARGS,
pygpu_matrix_pop_projection_doc},
/* Stack (Context Manager) */
{"push_pop",
reinterpret_cast<PyCFunction>(pygpu_matrix_push_pop),
METH_NOARGS,
pygpu_matrix_push_pop_doc},
{"push_pop_projection",
reinterpret_cast<PyCFunction>(pygpu_matrix_push_pop_projection),
METH_NOARGS,
pygpu_matrix_push_pop_projection_doc},
/* Manipulate State */
{"multiply_matrix",
static_cast<PyCFunction>(pygpu_matrix_multiply_matrix),
METH_O,
pygpu_matrix_multiply_matrix_doc},
{"scale", static_cast<PyCFunction>(pygpu_matrix_scale), METH_O, pygpu_matrix_scale_doc},
{"scale_uniform",
static_cast<PyCFunction>(pygpu_matrix_scale_uniform),
METH_O,
pygpu_matrix_scale_uniform_doc},
{"translate",
static_cast<PyCFunction>(pygpu_matrix_translate),
METH_O,
pygpu_matrix_translate_doc},
/* TODO */
#if 0
{"rotate", (PyCFunction)pygpu_matrix_rotate, METH_O, pygpu_matrix_rotate_doc},
{"rotate_axis", (PyCFunction)pygpu_matrix_rotate_axis, METH_O, pygpu_matrix_rotate_axis_doc},
{"look_at", (PyCFunction)pygpu_matrix_look_at, METH_O, pygpu_matrix_look_at_doc},
#endif
/* Write State */
{"reset",
reinterpret_cast<PyCFunction>(pygpu_matrix_reset),
METH_NOARGS,
pygpu_matrix_reset_doc},
{"load_identity",
reinterpret_cast<PyCFunction>(pygpu_matrix_load_identity),
METH_NOARGS,
pygpu_matrix_load_identity_doc},
{"load_matrix",
static_cast<PyCFunction>(pygpu_matrix_load_matrix),
METH_O,
pygpu_matrix_load_matrix_doc},
{"load_projection_matrix",
static_cast<PyCFunction>(pygpu_matrix_load_projection_matrix),
METH_O,
pygpu_matrix_load_projection_matrix_doc},
/* Read State */
{"get_projection_matrix",
reinterpret_cast<PyCFunction>(pygpu_matrix_get_projection_matrix),
METH_NOARGS,
pygpu_matrix_get_projection_matrix_doc},
{"get_model_view_matrix",
reinterpret_cast<PyCFunction>(pygpu_matrix_get_model_view_matrix),
METH_NOARGS,
pygpu_matrix_get_model_view_matrix_doc},
{"get_normal_matrix",
reinterpret_cast<PyCFunction>(pygpu_matrix_get_normal_matrix),
METH_NOARGS,
pygpu_matrix_get_normal_matrix_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_matrix__tp_doc,
"This module provides access to the matrix stack.");
static PyModuleDef pygpu_matrix_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu.matrix",
/*m_doc*/ pygpu_matrix__tp_doc,
/*m_size*/ 0,
/*m_methods*/ pygpu_matrix__tp_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *bpygpu_matrix_init()
{
PyObject *submodule;
submodule = PyModule_Create(&pygpu_matrix_module_def);
return submodule;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,19 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
namespace blender {
extern PyTypeObject PyGPUMatrixStackContext_Type;
[[nodiscard]] PyObject *bpygpu_matrix_init();
} // namespace blender

View File

@@ -0,0 +1,689 @@
/* SPDX-FileCopyrightText: 2015 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* This file defines the offscreen functionalities of the 'gpu' module
* used for off-screen OpenGL rendering.
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "BLI_string_utf8.h"
#include "BKE_global.hh"
#include "BKE_lib_id.hh" /* For #BKE_id_is_in_global_main. */
#include "BKE_scene.hh"
#include "DNA_view3d_types.h"
#include "GPU_context.hh"
#include "GPU_framebuffer.hh"
#include "GPU_state.hh"
#include "GPU_texture.hh"
#include "GPU_viewport.hh"
#include "ED_view3d_offscreen.hh"
#include "../mathutils/mathutils.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "gpu_py.hh"
#include "gpu_py_texture.hh"
#include "gpu_py_offscreen.hh" /* own include */
namespace blender {
/* Define the free method to avoid breakage. */
#define BPYGPU_USE_GPUOBJ_FREE_METHOD
/* -------------------------------------------------------------------- */
/** \name GPUOffScreen Common Utilities
* \{ */
static const PyC_StringEnumItems pygpu_framebuffer_color_texture_formats[] = {
{int(gpu::TextureFormat::UNORM_8_8_8_8), "RGBA8"},
{int(gpu::TextureFormat::UNORM_16_16_16_16), "RGBA16"},
{int(gpu::TextureFormat::SFLOAT_16_16_16_16), "RGBA16F"},
{int(gpu::TextureFormat::SFLOAT_32_32_32_32), "RGBA32F"},
{0, nullptr},
};
static int pygpu_offscreen_valid_check(BPyGPUOffScreen *py_ofs)
{
if (UNLIKELY(py_ofs->ofs == nullptr)) {
PyErr_SetString(PyExc_ReferenceError,
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
"GPU offscreen was freed, no further access is valid"
#else
"GPU offscreen: internal error"
#endif
);
return -1;
}
return 0;
}
#define BPY_GPU_OFFSCREEN_CHECK_OBJ(bpygpu) \
{ \
if (UNLIKELY(pygpu_offscreen_valid_check(bpygpu) == -1)) { \
return nullptr; \
} \
} \
((void)0)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Stack (Context Manager)
*
* Safer alternative to ensure balanced push/pop calls.
*
* \{ */
struct OffScreenStackContext {
PyObject_HEAD /* Required Python macro. */
BPyGPUOffScreen *py_offscreen;
int level;
bool is_explicitly_bound; /* Bound by "bind" method. */
};
static void pygpu_offscreen_stack_context__tp_dealloc(OffScreenStackContext *self)
{
Py_DECREF(self->py_offscreen);
PyObject_DEL(self);
}
static PyObject *pygpu_offscreen_stack_context_enter(OffScreenStackContext *self)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
BPY_GPU_OFFSCREEN_CHECK_OBJ(self->py_offscreen);
if (!self->is_explicitly_bound) {
if (self->level != -1) {
PyErr_SetString(PyExc_RuntimeError, "Already in use");
return nullptr;
}
GPU_offscreen_bind(self->py_offscreen->ofs, true);
self->level = GPU_framebuffer_stack_level_get();
}
Py_RETURN_NONE;
}
static PyObject *pygpu_offscreen_stack_context_exit(OffScreenStackContext *self,
PyObject * /*args*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
BPY_GPU_OFFSCREEN_CHECK_OBJ(self->py_offscreen);
if (self->level == -1) {
PyErr_SetString(PyExc_RuntimeError, "Not yet in use\n");
return nullptr;
}
const int level = GPU_framebuffer_stack_level_get();
if (level != self->level) {
PyErr_Format(
PyExc_RuntimeError, "Level of bind mismatch, expected %d, got %d\n", self->level, level);
}
GPU_offscreen_unbind(self->py_offscreen->ofs, true);
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 pygpu_offscreen_stack_context__tp_methods[] = {
{"__enter__", reinterpret_cast<PyCFunction>(pygpu_offscreen_stack_context_enter), METH_NOARGS},
{"__exit__", reinterpret_cast<PyCFunction>(pygpu_offscreen_stack_context_exit), METH_VARARGS},
{nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_stack_context__tp_doc,
"Context manager for off-screen framebuffer binding.");
PyTypeObject PyGPUOffscreenStackContext_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "OffScreenStackContext",
/*tp_basicsize*/ sizeof(OffScreenStackContext),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(pygpu_offscreen_stack_context__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_offscreen_stack_context__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_offscreen_stack_context__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ nullptr,
/*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,
};
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_bind_doc,
".. method:: bind()\n"
"\n"
" Context manager to ensure balanced bind calls, even in the case of an error.\n"
"\n"
" :return: A context manager for the off-screen binding.\n"
" :rtype: :class:`gpu.types.OffScreenStackContext`\n");
static PyObject *pygpu_offscreen_bind(BPyGPUOffScreen *self)
{
OffScreenStackContext *ret = PyObject_New(OffScreenStackContext,
&PyGPUOffscreenStackContext_Type);
ret->py_offscreen = self;
ret->level = -1;
ret->is_explicitly_bound = false;
Py_INCREF(self);
pygpu_offscreen_stack_context_enter(ret);
ret->is_explicitly_bound = true;
return reinterpret_cast<PyObject *>(ret);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_unbind_doc,
".. method:: unbind(*, restore=True)\n"
"\n"
" Unbind the offscreen object.\n"
"\n"
" :param restore: Restore the OpenGL state, can only be used when the state has been "
"saved before.\n"
" :type restore: bool\n");
static PyObject *pygpu_offscreen_unbind(BPyGPUOffScreen *self, PyObject *args, PyObject *kwds)
{
bool restore = true;
BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
static const char *_keywords[] = {"restore", nullptr};
static _PyArg_Parser _parser = {
"|$" /* Optional keyword only arguments. */
"O&" /* `restore` */
":unbind",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, PyC_ParseBool, &restore)) {
return nullptr;
}
GPU_offscreen_unbind(self->ofs, restore);
GPU_apply_state();
Py_RETURN_NONE;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPUOffscreen Type
* \{ */
static PyObject *pygpu_offscreen__tp_new(PyTypeObject * /*self*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
GPUOffScreen *ofs = nullptr;
int width, height;
PyC_StringEnum pygpu_textureformat = {pygpu_framebuffer_color_texture_formats,
int(gpu::TextureFormat::UNORM_8_8_8_8)};
char err_out[256];
static const char *_keywords[] = {"width", "height", "format", nullptr};
static _PyArg_Parser _parser = {
"i" /* `width` */
"i" /* `height` */
"|$" /* Optional keyword only arguments. */
"O&" /* `format` */
":GPUOffScreen.__new__",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kwds, &_parser, &width, &height, PyC_ParseStringEnum, &pygpu_textureformat))
{
return nullptr;
}
if (GPU_context_active_get()) {
ofs = GPU_offscreen_create(width,
height,
true,
gpu::TextureFormat(pygpu_textureformat.value_found),
GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_HOST_READ,
false,
err_out);
}
else {
STRNCPY_UTF8(err_out, "No active GPU context found");
}
if (ofs == nullptr) {
PyErr_Format(PyExc_RuntimeError,
"gpu.offscreen.new(...) failed with '%s'",
err_out[0] ? err_out : "unknown error");
return nullptr;
}
return BPyGPUOffScreen_CreatePyObject(ofs);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_width_doc,
"Width of the texture.\n"
"\n"
":type: int\n");
static PyObject *pygpu_offscreen_width_get(BPyGPUOffScreen *self, void * /*type*/)
{
BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
return PyLong_FromLong(GPU_offscreen_width(self->ofs));
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_height_doc,
"Height of the texture.\n"
"\n"
":type: int\n");
static PyObject *pygpu_offscreen_height_get(BPyGPUOffScreen *self, void * /*type*/)
{
BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
return PyLong_FromLong(GPU_offscreen_height(self->ofs));
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_texture_color_doc,
"The color texture attached.\n"
"\n"
":type: :class:`gpu.types.GPUTexture`\n");
static PyObject *pygpu_offscreen_texture_color_get(BPyGPUOffScreen *self, void * /*type*/)
{
BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
gpu::Texture *texture = GPU_offscreen_color_texture(self->ofs);
return BPyGPUTexture_CreatePyObject(texture, true);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_draw_view3d_doc,
".. method:: draw_view3d(scene, view_layer, view3d, region, view_matrix, projection_matrix, "
"*, do_color_management=False, draw_background=True)\n"
"\n"
" Draw the 3d viewport in the offscreen object.\n"
"\n"
" :param scene: Scene to draw.\n"
" :type scene: :class:`bpy.types.Scene`\n"
" :param view_layer: View layer to draw.\n"
" :type view_layer: :class:`bpy.types.ViewLayer`\n"
" :param view3d: 3D View to get the drawing settings from.\n"
" :type view3d: :class:`bpy.types.SpaceView3D`\n"
" :param region: Region of the 3D View (required as temporary draw target).\n"
" :type region: :class:`bpy.types.Region`\n"
" :param view_matrix: View Matrix (e.g. ``camera.matrix_world.inverted()``).\n"
" :type view_matrix: :class:`mathutils.Matrix`\n"
" :param projection_matrix: Projection Matrix (e.g. ``camera.calc_matrix_camera(...)``).\n"
" :type projection_matrix: :class:`mathutils.Matrix`\n"
" :param do_color_management: Color manage the output.\n"
" :type do_color_management: bool\n"
" :param draw_background: Draw background.\n"
" :type draw_background: bool\n");
static PyObject *pygpu_offscreen_draw_view3d(BPyGPUOffScreen *self, PyObject *args, PyObject *kwds)
{
MatrixObject *py_mat_view, *py_mat_projection;
PyObject *py_scene, *py_view_layer, *py_region, *py_view3d;
Depsgraph *depsgraph;
Scene *scene;
ViewLayer *view_layer;
View3D *v3d;
ARegion *region;
bool do_color_management = false;
bool draw_background = true;
BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
static const char *_keywords[] = {
"scene",
"view_layer",
"view3d",
"region",
"view_matrix",
"projection_matrix",
"do_color_management",
"draw_background",
nullptr,
};
static _PyArg_Parser _parser = {
"O" /* `scene` */
"O" /* `view_layer` */
"O" /* `view3d` */
"O" /* `region` */
"O&" /* `view_matrix` */
"O&" /* `projection_matrix` */
"|$" /* Optional keyword only arguments. */
"O&" /* `do_color_management` */
"O&" /* `draw_background` */
":draw_view3d",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kwds,
&_parser,
&py_scene,
&py_view_layer,
&py_view3d,
&py_region,
Matrix_Parse4x4,
&py_mat_view,
Matrix_Parse4x4,
&py_mat_projection,
PyC_ParseBool,
&do_color_management,
PyC_ParseBool,
&draw_background) ||
(!(scene = static_cast<Scene *>(PyC_RNA_AsPointer(py_scene, "Scene"))) ||
!(view_layer = static_cast<ViewLayer *>(PyC_RNA_AsPointer(py_view_layer, "ViewLayer"))) ||
!(v3d = static_cast<View3D *>(PyC_RNA_AsPointer(py_view3d, "SpaceView3D"))) ||
!(region = static_cast<ARegion *>(PyC_RNA_AsPointer(py_region, "Region")))))
{
return nullptr;
}
if (ED_view3d_draw_offscreen_check_nested()) {
/* NOTE(@ideasman42): Nested draw calls could be supported.
* Adding support for this looks to be possible but non-trivial. */
PyErr_SetString(PyExc_RuntimeError, "Nested off-screen drawing not supported");
return nullptr;
}
BLI_assert(BKE_id_is_in_global_main(&scene->id));
depsgraph = BKE_scene_ensure_depsgraph(G_MAIN, scene, view_layer);
GPU_offscreen_bind(self->ofs, true);
/* Cache the #GPUViewport so the frame-buffers and associated textures are
* not reallocated each time, see: #89204 */
if (!self->viewport) {
self->viewport = GPU_viewport_create();
}
else {
GPU_viewport_tag_update(self->viewport);
}
ED_view3d_draw_offscreen(depsgraph,
scene,
eDrawType(v3d->shading.type),
v3d,
region,
nullptr,
GPU_offscreen_width(self->ofs),
GPU_offscreen_height(self->ofs),
reinterpret_cast<const float (*)[4]>(py_mat_view->matrix),
reinterpret_cast<const float (*)[4]>(py_mat_projection->matrix),
true,
draw_background,
"",
do_color_management,
true,
self->ofs,
self->viewport);
GPU_offscreen_unbind(self->ofs, true);
Py_RETURN_NONE;
}
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen_free_doc,
".. method:: free()\n"
"\n"
" Free the offscreen object.\n"
" The framebuffer, texture and render objects will no longer be accessible.\n");
static PyObject *pygpu_offscreen_free(BPyGPUOffScreen *self)
{
BPY_GPU_OFFSCREEN_CHECK_OBJ(self);
if (self->viewport) {
GPU_viewport_free(self->viewport);
self->viewport = nullptr;
}
GPU_offscreen_free(self->ofs);
self->ofs = nullptr;
Py_RETURN_NONE;
}
#endif
static void BPyGPUOffScreen__tp_dealloc(BPyGPUOffScreen *self)
{
if (self->viewport) {
GPU_viewport_free(self->viewport);
}
if (self->ofs) {
GPU_offscreen_free(self->ofs);
}
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject *>(self));
}
static PyGetSetDef pygpu_offscreen__tp_getseters[] = {
{"texture_color",
reinterpret_cast<getter>(pygpu_offscreen_texture_color_get),
static_cast<setter>(nullptr),
pygpu_offscreen_texture_color_doc,
nullptr},
{"width",
reinterpret_cast<getter>(pygpu_offscreen_width_get),
static_cast<setter>(nullptr),
pygpu_offscreen_width_doc,
nullptr},
{"height",
reinterpret_cast<getter>(pygpu_offscreen_height_get),
static_cast<setter>(nullptr),
pygpu_offscreen_height_doc,
nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr} /* Sentinel */
};
#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 pygpu_offscreen__tp_methods[] = {
{"bind",
reinterpret_cast<PyCFunction>(pygpu_offscreen_bind),
METH_NOARGS,
pygpu_offscreen_bind_doc},
{"unbind",
reinterpret_cast<PyCFunction>(pygpu_offscreen_unbind),
METH_VARARGS | METH_KEYWORDS,
pygpu_offscreen_unbind_doc},
{"draw_view3d",
reinterpret_cast<PyCFunction>(pygpu_offscreen_draw_view3d),
METH_VARARGS | METH_KEYWORDS,
pygpu_offscreen_draw_view3d_doc},
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
{"free",
reinterpret_cast<PyCFunction>(pygpu_offscreen_free),
METH_NOARGS,
pygpu_offscreen_free_doc},
#endif
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_offscreen__tp_doc,
".. class:: GPUOffScreen\n"
"\n"
" This object gives access to off screen buffers.\n"
"\n"
" .. method:: __init__(width, height, *, format='RGBA8')\n"
"\n"
" :param width: Horizontal dimension of the buffer.\n"
" :type width: int\n"
" :param height: Vertical dimension of the buffer.\n"
" :type height: int\n"
" :param format: Internal data format inside GPU memory for color attachment texture.\n"
" :type format: Literal['RGBA8', 'RGBA16', 'RGBA16F', 'RGBA32F']\n");
PyTypeObject BPyGPUOffScreen_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUOffScreen",
/*tp_basicsize*/ sizeof(BPyGPUOffScreen),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(BPyGPUOffScreen__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_compare*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_offscreen__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_offscreen__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ pygpu_offscreen__tp_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*/ pygpu_offscreen__tp_new,
/*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,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
PyObject *BPyGPUOffScreen_CreatePyObject(GPUOffScreen *ofs)
{
BPyGPUOffScreen *self;
self = PyObject_New(BPyGPUOffScreen, &BPyGPUOffScreen_Type);
self->ofs = ofs;
self->viewport = nullptr;
return reinterpret_cast<PyObject *>(self);
}
/** \} */
#undef BPY_GPU_OFFSCREEN_CHECK_OBJ
} // namespace blender

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "BLI_compiler_attrs.h"
namespace blender {
struct GPUOffScreen;
struct GPUViewport;
extern PyTypeObject BPyGPUOffScreen_Type;
extern PyTypeObject PyGPUOffscreenStackContext_Type;
#define BPyGPUOffScreen_Check(v) (Py_TYPE(v) == &BPyGPUOffScreen_Type)
struct BPyGPUOffScreen {
PyObject_HEAD
GPUOffScreen *ofs;
GPUViewport *viewport;
};
[[nodiscard]] PyObject *BPyGPUOffScreen_CreatePyObject(GPUOffScreen *ofs) ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,419 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_context.hh"
#include "GPU_platform.hh"
#include "gpu_py.hh"
#include "gpu_py_platform.hh" /* Own include. */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name GPU Device Type
* \{ */
PyDoc_STRVAR(pygpu_device_index_doc,
"Device index.\n"
"\n"
":type: int\n");
static PyObject *pygpu_device_index_get(BPyGPUDevice *self, void * /*closure*/)
{
return PyLong_FromLong(self->index);
}
PyDoc_STRVAR(pygpu_device_identifier_doc,
"Device identifier.\n"
"\n"
":type: str\n");
static PyObject *pygpu_device_identifier_get(BPyGPUDevice *self, void * /*closure*/)
{
return PyUnicode_FromString(self->identifier);
}
PyDoc_STRVAR(pygpu_device_name_doc,
"Device name.\n"
"\n"
":type: str\n");
static PyObject *pygpu_device_name_get(BPyGPUDevice *self, void * /*closure*/)
{
return PyUnicode_FromString(self->name);
}
/* Property descriptors */
static PyGetSetDef pygpu_device_getseters[] = {
{"index",
reinterpret_cast<getter>(pygpu_device_index_get),
nullptr,
pygpu_device_index_doc,
nullptr},
{"identifier",
reinterpret_cast<getter>(pygpu_device_identifier_get),
nullptr,
pygpu_device_identifier_doc,
nullptr},
{"name",
reinterpret_cast<getter>(pygpu_device_name_get),
nullptr,
pygpu_device_name_doc,
nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr},
};
/* Representation */
static PyObject *pygpu_device__tp_repr(BPyGPUDevice *self)
{
return PyUnicode_FromFormat("<GPUDevice index=%d identifier=\"%s\" name=\"%s\">",
self->index,
self->identifier,
self->name);
}
/** Rich comparison for GPUDevice types, compares by index. */
static PyObject *pygpu_device__tp_richcmp(BPyGPUDevice *self, PyObject *other, int op)
{
if (!Py_IS_TYPE(other, &BPyGPU_DeviceType)) {
Py_RETURN_NOTIMPLEMENTED;
}
BPyGPUDevice *other_device = reinterpret_cast<BPyGPUDevice *>(other);
switch (op) {
case Py_LT:
return PyBool_FromLong(self->index < other_device->index);
case Py_LE:
return PyBool_FromLong(self->index <= other_device->index);
case Py_EQ:
return PyBool_FromLong(self->index == other_device->index);
case Py_NE:
return PyBool_FromLong(self->index != other_device->index);
case Py_GT:
return PyBool_FromLong(self->index > other_device->index);
case Py_GE:
return PyBool_FromLong(self->index >= other_device->index);
}
Py_RETURN_NOTIMPLEMENTED;
}
/* Type definition */
PyDoc_STRVAR(pygpu_device__tp_doc,
".. class:: GPUDevice\n"
"\n"
" Represents a GPU device.\n"
"\n"
" :ivar index: Device index.\n"
" :type index: int\n"
" :ivar identifier: Device identifier.\n"
" :type identifier: str\n"
" :ivar name: Device name.\n"
" :type name: str\n");
PyTypeObject BPyGPU_DeviceType = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "gpu.platform.GPUDevice",
/*tp_basicsize*/ sizeof(BPyGPUDevice),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ nullptr,
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_compare*/ nullptr,
/*tp_repr*/ reinterpret_cast<reprfunc>(pygpu_device__tp_repr),
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_device__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ reinterpret_cast<richcmpfunc>(pygpu_device__tp_richcmp),
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ nullptr,
/*tp_members*/ nullptr,
/*tp_getset*/ pygpu_device_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,
};
static BPyGPUDevice *pygpu_device_new(int index, const char *identifier, const char *name)
{
BPyGPUDevice *self = reinterpret_cast<BPyGPUDevice *>(
BPyGPU_DeviceType.tp_alloc(&BPyGPU_DeviceType, 0));
if (self != nullptr) {
self->index = index;
self->identifier = identifier;
self->name = name;
}
return self;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Functions
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_platform_vendor_get_doc,
".. function:: vendor_get()\n"
"\n"
" Get GPU vendor.\n"
"\n"
" :return: Vendor name.\n"
" :rtype: str\n");
static PyObject *pygpu_platform_vendor_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyUnicode_FromString(GPU_platform_vendor());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_platform_renderer_get_doc,
".. function:: renderer_get()\n"
"\n"
" Get GPU to be used for rendering.\n"
"\n"
" :return: GPU name.\n"
" :rtype: str\n");
static PyObject *pygpu_platform_renderer_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyUnicode_FromString(GPU_platform_renderer());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_platform_version_get_doc,
".. function:: version_get()\n"
"\n"
" Get GPU driver version.\n"
"\n"
" :return: Driver version.\n"
" :rtype: str\n");
static PyObject *pygpu_platform_version_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyUnicode_FromString(GPU_platform_version());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_platform_device_type_get_doc,
".. function:: device_type_get()\n"
"\n"
" Get GPU device type.\n"
"\n"
" :return: Device type ('APPLE', 'NVIDIA', 'AMD', 'INTEL', 'SOFTWARE', 'QUALCOMM', "
"'UNKNOWN').\n"
" :rtype: str\n");
static PyObject *pygpu_platform_device_type_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
const char *device;
if (GPU_type_matches(GPU_DEVICE_APPLE, GPU_OS_ANY, GPU_DRIVER_ANY)) {
device = "APPLE";
}
else if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_ANY)) {
device = "NVIDIA";
}
else if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_ANY)) {
device = "AMD";
}
else if (GPU_type_matches(GPU_DEVICE_INTEL | GPU_DEVICE_INTEL_UHD, GPU_OS_ANY, GPU_DRIVER_ANY)) {
device = "INTEL";
}
else if (GPU_type_matches(GPU_DEVICE_SOFTWARE, GPU_OS_ANY, GPU_DRIVER_ANY)) {
device = "SOFTWARE";
}
/* Right now we can only detect Qualcomm GPUs on Windows, not other OSes */
else if (GPU_type_matches(GPU_DEVICE_QUALCOMM, GPU_OS_WIN, GPU_DRIVER_ANY)) {
device = "QUALCOMM";
}
else {
device = "UNKNOWN";
}
return PyUnicode_FromString(device);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_platform_backend_type_get_doc,
".. function:: backend_type_get()\n"
"\n"
" Get active GPU backend.\n"
"\n"
" :return: Backend type ('OPENGL', 'VULKAN', 'METAL', 'NONE', 'UNKNOWN').\n"
" :rtype: str\n");
static PyObject *pygpu_platform_backend_type_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
const char *backend = "UNKNOWN";
switch (GPU_backend_get_type()) {
case GPU_BACKEND_VULKAN: {
backend = "VULKAN";
break;
}
case GPU_BACKEND_METAL: {
backend = "METAL";
break;
}
case GPU_BACKEND_NONE: {
backend = "NONE";
break;
}
case GPU_BACKEND_OPENGL: {
backend = "OPENGL";
break;
}
case GPU_BACKEND_ANY:
break;
}
return PyUnicode_FromString(backend);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_platform_devices_get_doc,
".. function:: devices_get()\n"
"\n"
" Get all available GPU devices.\n"
"\n"
" :return: List of :class:`GPUDevice` objects for each device.\n"
" :rtype: list\n");
static PyObject *pygpu_platform_devices_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
Span<GPUDevice> devices = GPU_platform_devices_list();
PyObject *list = PyList_New(devices.size());
for (int i = 0; i < devices.size(); i++) {
const GPUDevice &dev = devices[i];
PyObject *item = reinterpret_cast<PyObject *>(
pygpu_device_new(dev.index, dev.identifier.c_str(), dev.name.c_str()));
PyList_SET_ITEM(list, i, item);
}
/* Sort by index (first attribute) for deterministic ordering. */
PyList_Sort(list);
return list;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Module
* \{ */
#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 pygpu_platform__tp_methods[] = {
{"vendor_get",
reinterpret_cast<PyCFunction>(pygpu_platform_vendor_get),
METH_NOARGS,
pygpu_platform_vendor_get_doc},
{"renderer_get",
reinterpret_cast<PyCFunction>(pygpu_platform_renderer_get),
METH_NOARGS,
pygpu_platform_renderer_get_doc},
{"version_get",
reinterpret_cast<PyCFunction>(pygpu_platform_version_get),
METH_NOARGS,
pygpu_platform_version_get_doc},
{"device_type_get",
reinterpret_cast<PyCFunction>(pygpu_platform_device_type_get),
METH_NOARGS,
pygpu_platform_device_type_get_doc},
{"backend_type_get",
reinterpret_cast<PyCFunction>(pygpu_platform_backend_type_get),
METH_NOARGS,
pygpu_platform_backend_type_get_doc},
{"devices_get",
reinterpret_cast<PyCFunction>(pygpu_platform_devices_get),
METH_NOARGS,
pygpu_platform_devices_get_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_platform__tp_doc,
"This module provides access to GPU Platform definitions.");
static PyModuleDef pygpu_platform_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu.platform",
/*m_doc*/ pygpu_platform__tp_doc,
/*m_size*/ 0,
/*m_methods*/ pygpu_platform__tp_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *bpygpu_platform_init()
{
PyObject *submodule;
submodule = PyModule_Create(&pygpu_platform_module_def);
return submodule;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
namespace blender {
/* GPU Device Python object structure */
struct BPyGPUDevice {
PyObject_HEAD
int index;
const char *identifier;
const char *name;
};
extern PyTypeObject BPyGPU_DeviceType;
[[nodiscard]] PyObject *bpygpu_platform_init();
} // namespace blender

View File

@@ -0,0 +1,92 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* This file defines the gpu.select API.
*
* \note Currently only used for gizmo selection,
* will need to add begin/end and a way to access the hits.
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "../generic/py_capi_utils.hh"
#include "GPU_select.hh"
#include "gpu_py.hh"
#include "gpu_py_select.hh" /* Own include. */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Methods
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_select_load_id_doc,
".. function:: load_id(id)\n"
"\n"
" Set the selection ID.\n"
"\n"
" :param id: Number (32-bit uint).\n"
" :type id: int\n");
static PyObject *pygpu_select_load_id(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
uint id;
if ((id = PyC_Long_AsU32(value)) == uint(-1)) {
return nullptr;
}
GPU_select_load_id(id);
Py_RETURN_NONE;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Module
* \{ */
static PyMethodDef pygpu_select__tp_methods[] = {
/* Manage Stack */
{"load_id", static_cast<PyCFunction>(pygpu_select_load_id), METH_O, pygpu_select_load_id_doc},
{nullptr, nullptr, 0, nullptr},
};
PyDoc_STRVAR(
/* Wrap. */
pygpu_select__tp_doc,
"This module provides access to selection.");
static PyModuleDef pygpu_select_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu.select",
/*m_doc*/ pygpu_select__tp_doc,
/*m_size*/ 0,
/*m_methods*/ pygpu_select__tp_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *bpygpu_select_init()
{
PyObject *submodule;
submodule = PyModule_Create(&pygpu_select_module_def);
return submodule;
}
/** \} */
} // namespace blender

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
struct GPUShaderCreateInfo;
struct GPUStageInterfaceInfo;
namespace blender {
#ifndef __cplusplus
# include "../generic/py_capi_utils.hh"
#endif
namespace gpu {
class Shader;
} // namespace gpu
/* Make sure that there is always a reference count for PyObjects of type String as the strings are
* passed by reference in the #GPUStageInterfaceInfo and #GPUShaderCreateInfo APIs. */
#define USE_GPU_PY_REFERENCES
/* `gpu_py_shader.cc` */
extern PyTypeObject BPyGPUShader_Type;
#define BPyGPUShader_Check(v) (Py_TYPE(v) == &BPyGPUShader_Type)
struct BPyGPUShader {
PyObject_HEAD
gpu::Shader *shader;
bool is_builtin;
};
[[nodiscard]] PyObject *BPyGPUShader_CreatePyObject(gpu::Shader *shader, bool is_builtin);
[[nodiscard]] PyObject *bpygpu_shader_init();
/* gpu_py_shader_create_info.cc */
extern const struct PyC_StringEnumItems pygpu_attrtype_items[];
extern PyTypeObject BPyGPUShaderCreateInfo_Type;
extern PyTypeObject BPyGPUStageInterfaceInfo_Type;
#define BPyGPUShaderCreateInfo_Check(v) (Py_TYPE(v) == &BPyGPUShaderCreateInfo_Type)
#define BPyGPUStageInterfaceInfo_Check(v) (Py_TYPE(v) == &BPyGPUStageInterfaceInfo_Type)
struct BPyGPUStageInterfaceInfo {
PyObject_HEAD
GPUStageInterfaceInfo *interface;
#ifdef USE_GPU_PY_REFERENCES
/* Just to keep a user to prevent freeing buffers we're using. */
PyObject *references;
#endif
};
struct BPyGPUShaderCreateInfo {
PyObject_HEAD
GPUShaderCreateInfo *info;
#ifdef USE_GPU_PY_REFERENCES
/* Just to keep a user to prevent freeing buffers we're using. */
PyObject *vertex_source;
PyObject *fragment_source;
PyObject *compute_source;
PyObject *typedef_source;
PyObject *references;
#endif
size_t constants_total_size;
};
[[nodiscard]] PyObject *BPyGPUStageInterfaceInfo_CreatePyObject(GPUStageInterfaceInfo *interface);
[[nodiscard]] PyObject *BPyGPUShaderCreateInfo_CreatePyObject(GPUShaderCreateInfo *info);
[[nodiscard]] bool bpygpu_shader_is_polyline(gpu::Shader *shader);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,688 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* This file defines the gpu.state API.
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_framebuffer.hh"
#include "GPU_state.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_utildefines.hh"
#include "gpu_py.hh"
#include "gpu_py_framebuffer.hh"
#include "gpu_py_state.hh" /* own include */
/* Docstring Literal types. */
#define PYDOC_BLEND_LITERAL \
"Literal['NONE', 'ALPHA', 'ALPHA_PREMULT', 'ADDITIVE', " \
"'ADDITIVE_PREMULT', 'MULTIPLY', 'SUBTRACT', 'INVERT']"
#define PYDOC_DEPTHTEST_LITERAL \
"Literal['NONE', 'ALWAYS', 'LESS', 'LESS_EQUAL', 'EQUAL', 'GREATER', 'GREATER_EQUAL']"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Helper Functions
* \{ */
static const PyC_StringEnumItems pygpu_state_blend_items[] = {
{GPU_BLEND_NONE, "NONE"},
{GPU_BLEND_ALPHA, "ALPHA"},
{GPU_BLEND_ALPHA_PREMULT, "ALPHA_PREMULT"},
{GPU_BLEND_ADDITIVE, "ADDITIVE"},
{GPU_BLEND_ADDITIVE_PREMULT, "ADDITIVE_PREMULT"},
{GPU_BLEND_MULTIPLY, "MULTIPLY"},
{GPU_BLEND_SUBTRACT, "SUBTRACT"},
{GPU_BLEND_INVERT, "INVERT"},
/**
* These are quite special cases used inside the draw manager.
* {GPU_BLEND_OIT, "OIT"},
* {GPU_BLEND_BACKGROUND, "BACKGROUND"},
* {GPU_BLEND_CUSTOM, "CUSTOM"},
*/
{0, nullptr},
};
static const PyC_StringEnumItems pygpu_state_depthtest_items[] = {
{GPU_DEPTH_NONE, "NONE"},
{GPU_DEPTH_ALWAYS, "ALWAYS"},
{GPU_DEPTH_LESS, "LESS"},
{GPU_DEPTH_LESS_EQUAL, "LESS_EQUAL"},
{GPU_DEPTH_EQUAL, "EQUAL"},
{GPU_DEPTH_GREATER, "GREATER"},
{GPU_DEPTH_GREATER_EQUAL, "GREATER_EQUAL"},
{0, nullptr},
};
static const PyC_StringEnumItems pygpu_state_faceculling_items[] = {
{GPU_CULL_NONE, "NONE"},
{GPU_CULL_FRONT, "FRONT"},
{GPU_CULL_BACK, "BACK"},
{0, nullptr},
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Manage Stack
* \{ */
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_blend_set_doc,
".. function:: blend_set(mode)\n"
"\n"
" Defines the fixed pipeline blending equation.\n"
"\n"
" :param mode: The type of blend mode.\n"
"\n"
" * ``NONE`` No blending.\n"
" * ``ALPHA`` The original color channels are interpolated according to the alpha "
"value.\n"
" * ``ALPHA_PREMULT`` The original color channels are interpolated according to the "
"alpha value with the new colors pre-multiplied by this value.\n"
" * ``ADDITIVE`` The original color channels are added by the corresponding ones.\n"
" * ``ADDITIVE_PREMULT`` The original color channels are added by the corresponding ones "
"that are pre-multiplied by the alpha value.\n"
" * ``MULTIPLY`` The original color channels are multiplied by the corresponding ones.\n"
" * ``SUBTRACT`` The original color channels are subtracted by the corresponding ones.\n"
" * ``INVERT`` The original color channels are replaced by its complementary color.\n"
//" * ``OIT``.\n"
//" * ``BACKGROUND`` .\n"
//" * ``CUSTOM`` .\n"
" :type mode: " PYDOC_BLEND_LITERAL "\n");
static PyObject *pygpu_state_blend_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
PyC_StringEnum pygpu_blend = {pygpu_state_blend_items};
if (!PyC_ParseStringEnum(value, &pygpu_blend)) {
return nullptr;
}
GPU_blend(GPUBlend(pygpu_blend.value_found));
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_blend_get_doc,
".. function:: blend_get()\n"
"\n"
" Current blending equation.\n"
"\n"
" :return: The current blend mode.\n"
" :rtype: str\n");
static PyObject *pygpu_state_blend_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
GPUBlend blend = GPU_blend_get();
return PyUnicode_FromString(PyC_StringEnum_FindIDFromValue(pygpu_state_blend_items, blend));
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_clip_distances_set_doc,
".. function:: clip_distances_set(distances_enabled)\n"
"\n"
" Sets the number of ``gl_ClipDistance`` planes used for clip geometry.\n"
"\n"
" :param distances_enabled: Number of clip distances enabled.\n"
" :type distances_enabled: int\n");
static PyObject *pygpu_state_clip_distances_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int distances_enabled = int(PyLong_AsUnsignedLong(value));
if (distances_enabled == -1) {
return nullptr;
}
if (distances_enabled > 6) {
PyErr_SetString(PyExc_ValueError, "too many distances enabled, max is 6");
}
GPU_clip_distances(distances_enabled);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_depth_test_set_doc,
".. function:: depth_test_set(mode)\n"
"\n"
" Defines the depth_test equation.\n"
"\n"
" :param mode: The depth test equation name.\n"
" :type mode: " PYDOC_DEPTHTEST_LITERAL "\n");
static PyObject *pygpu_state_depth_test_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
PyC_StringEnum pygpu_depth_test = {pygpu_state_depthtest_items};
if (!PyC_ParseStringEnum(value, &pygpu_depth_test)) {
return nullptr;
}
GPU_depth_test(GPUDepthTest(pygpu_depth_test.value_found));
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_depth_test_get_doc,
".. function:: depth_test_get()\n"
"\n"
" Current depth_test equation.\n"
"\n"
" :return: The current depth test mode.\n"
" :rtype: str\n");
static PyObject *pygpu_state_depth_test_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
GPUDepthTest test = GPU_depth_test_get();
return PyUnicode_FromString(PyC_StringEnum_FindIDFromValue(pygpu_state_depthtest_items, test));
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_depth_mask_set_doc,
".. function:: depth_mask_set(value)\n"
"\n"
" Write to depth component.\n"
"\n"
" :param value: True for writing to the depth component.\n"
" :type value: bool\n");
static PyObject *pygpu_state_depth_mask_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
bool write_to_depth;
if (!PyC_ParseBool(value, &write_to_depth)) {
return nullptr;
}
GPU_depth_mask(write_to_depth);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_depth_mask_get_doc,
".. function:: depth_mask_get()\n"
"\n"
" Writing status in the depth component.\n"
"\n"
" :return: True if writing to the depth component is enabled.\n"
" :rtype: bool\n");
static PyObject *pygpu_state_depth_mask_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
return PyBool_FromLong(GPU_depth_mask_get());
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_viewport_set_doc,
".. function:: viewport_set(x, y, xsize, ysize)\n"
"\n"
" Specifies the viewport of the active framebuffer.\n"
" Note: The viewport state is not saved upon framebuffer rebind.\n"
"\n"
" :param x: Lower left corner x coordinate, in pixels.\n"
" :type x: int\n"
" :param y: Lower left corner y coordinate, in pixels.\n"
" :type y: int\n"
" :param xsize: Width of the viewport.\n"
" :type xsize: int\n"
" :param ysize: Height of the viewport.\n"
" :type ysize: int\n");
static PyObject *pygpu_state_viewport_set(PyObject * /*self*/, PyObject *args)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int x, y, xsize, ysize;
if (!PyArg_ParseTuple(args, "iiii:viewport_set", &x, &y, &xsize, &ysize)) {
return nullptr;
}
GPU_viewport(x, y, xsize, ysize);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_viewport_get_doc,
".. function:: viewport_get()\n"
"\n"
" Viewport of the active framebuffer.\n"
"\n"
" :return: The viewport as a tuple (x, y, xsize, ysize).\n"
" :rtype: tuple[int, int, int, int]\n");
static PyObject *pygpu_state_viewport_get(PyObject * /*self*/, PyObject * /*args*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int viewport[4];
GPU_viewport_size_get_i(viewport);
PyObject *ret = PyTuple_New(4);
PyTuple_SET_ITEMS(ret,
PyLong_FromLong(viewport[0]),
PyLong_FromLong(viewport[1]),
PyLong_FromLong(viewport[2]),
PyLong_FromLong(viewport[3]));
return ret;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_scissor_set_doc,
".. function:: scissor_set(x, y, xsize, ysize)\n"
"\n"
" Specifies the scissor area of the active framebuffer.\n"
" Note: The scissor state is not saved upon framebuffer rebind.\n"
"\n"
" :param x: Lower left corner x coordinate, in pixels.\n"
" :type x: int\n"
" :param y: Lower left corner y coordinate, in pixels.\n"
" :type y: int\n"
" :param xsize: Width of the scissor rectangle.\n"
" :type xsize: int\n"
" :param ysize: Height of the scissor rectangle.\n"
" :type ysize: int\n");
static PyObject *pygpu_state_scissor_set(PyObject * /*self*/, PyObject *args)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int x, y, xsize, ysize;
if (!PyArg_ParseTuple(args, "iiii:scissor_set", &x, &y, &xsize, &ysize)) {
return nullptr;
}
GPU_scissor(x, y, xsize, ysize);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_scissor_get_doc,
".. function:: scissor_get()\n"
"\n"
" Retrieve the scissors of the active framebuffer.\n"
" Note: Only valid between 'scissor_set' and a framebuffer rebind.\n"
"\n"
" :return: The scissor of the active framebuffer as a tuple\n"
" (x, y, xsize, ysize).\n"
" x, y: lower left corner of the scissor rectangle, in pixels.\n"
" xsize, ysize: width and height of the scissor rectangle.\n"
" :rtype: tuple[int, int, int, int]\n");
static PyObject *pygpu_state_scissor_get(PyObject * /*self*/, PyObject * /*args*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int scissor[4];
GPU_scissor_get(scissor);
PyObject *ret = PyTuple_New(4);
PyTuple_SET_ITEMS(ret,
PyLong_FromLong(scissor[0]),
PyLong_FromLong(scissor[1]),
PyLong_FromLong(scissor[2]),
PyLong_FromLong(scissor[3]));
return ret;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_scissor_test_set_doc,
".. function:: scissor_test_set(enable)\n"
"\n"
" Enable/disable scissor testing on the active framebuffer.\n"
"\n"
" :param enable:\n"
" True - enable scissor testing.\n"
" False - disable scissor testing.\n"
" :type enable: bool\n");
static PyObject *pygpu_state_scissor_test_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
bool enabled;
if (!PyC_ParseBool(value, &enabled)) {
return nullptr;
}
GPU_scissor_test(enabled);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_line_width_set_doc,
".. function:: line_width_set(width)\n"
"\n"
" Specify the width of rasterized lines.\n"
"\n"
" :param width: New width.\n"
" :type width: float\n");
static PyObject *pygpu_state_line_width_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float width = float(PyFloat_AsDouble(value));
if (PyErr_Occurred()) {
return nullptr;
}
GPU_line_width(width);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_line_width_get_doc,
".. function:: line_width_get()\n"
"\n"
" Current width of rasterized lines.\n"
"\n"
" :return: The current line width.\n"
" :rtype: float\n");
static PyObject *pygpu_state_line_width_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float width = GPU_line_width_get();
return PyFloat_FromDouble(double(width));
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_point_size_set_doc,
".. function:: point_size_set(size)\n"
"\n"
" Specify the diameter of rasterized points.\n"
"\n"
" When ``program_point_size_set(False)`` is set, this sets the ``size``\n"
" uniform of point shaders. Any value set by ``uniform_float(\"size\", ...)``\n"
" on a point shader will be overwritten by this function. Use\n"
" ``program_point_size_set(True)`` to disable this override and control\n"
" point size via ``gl_PointSize`` in the vertex shader.\n"
"\n"
" :param size: New diameter.\n"
" :type size: float\n");
static PyObject *pygpu_state_point_size_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
float size = float(PyFloat_AsDouble(value));
if (PyErr_Occurred()) {
return nullptr;
}
GPU_point_size(size);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_color_mask_set_doc,
".. function:: color_mask_set(r, g, b, a)\n"
"\n"
" Enable or disable writing of frame buffer color components.\n"
"\n"
" :param r: Red component.\n"
" :type r: bool\n"
" :param g: Green component.\n"
" :type g: bool\n"
" :param b: Blue component.\n"
" :type b: bool\n"
" :param a: Alpha component.\n"
" :type a: bool\n");
static PyObject *pygpu_state_color_mask_set(PyObject * /*self*/, PyObject *args)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
int r, g, b, a;
if (!PyArg_ParseTuple(args, "pppp:color_mask_set", &r, &g, &b, &a)) {
return nullptr;
}
GPU_color_mask(bool(r), bool(g), bool(b), bool(a));
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_face_culling_set_doc,
".. function:: face_culling_set(culling)\n"
"\n"
" Specify whether none, front-facing or back-facing facets can be culled.\n"
"\n"
" :param culling: The face culling mode.\n"
" :type culling: Literal['NONE', 'FRONT', 'BACK']\n");
static PyObject *pygpu_state_face_culling_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
PyC_StringEnum pygpu_faceculling = {pygpu_state_faceculling_items};
if (!PyC_ParseStringEnum(value, &pygpu_faceculling)) {
return nullptr;
}
GPU_face_culling(GPUFaceCullTest(pygpu_faceculling.value_found));
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_front_facing_set_doc,
".. function:: front_facing_set(invert)\n"
"\n"
" Specifies the orientation of front-facing polygons.\n"
"\n"
" :param invert: True for clockwise polygons as front-facing.\n"
" :type invert: bool\n");
static PyObject *pygpu_state_front_facing_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
bool invert;
if (!PyC_ParseBool(value, &invert)) {
return nullptr;
}
GPU_front_facing(invert);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_program_point_size_set_doc,
".. function:: program_point_size_set(enable)\n"
"\n"
" When enabled (True), point size is taken from the shader builtin\n"
" ``gl_PointSize``. When disabled (False), the global state value can\n"
" be applied to the shader ``size`` uniform, overwriting any user-set\n"
" value.\n"
"\n"
" :param enable: True to use shader ``gl_PointSize``, False for global state.\n"
" :type enable: bool\n");
static PyObject *pygpu_state_program_point_size_set(PyObject * /*self*/, PyObject *value)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
bool enable;
if (!PyC_ParseBool(value, &enable)) {
return nullptr;
}
GPU_program_point_size(enable);
Py_RETURN_NONE;
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_state_active_framebuffer_get_doc,
".. function:: active_framebuffer_get()\n"
"\n"
" Return the active frame-buffer in context.\n"
"\n"
" :return: The active framebuffer.\n"
" :rtype: :class:`gpu.types.GPUFrameBuffer`\n");
static PyObject *pygpu_state_active_framebuffer_get(PyObject * /*self*/)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
gpu::FrameBuffer *fb = GPU_framebuffer_active_get();
return BPyGPUFrameBuffer_CreatePyObject(fb, true);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Module
* \{ */
#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 pygpu_state__tp_methods[] = {
/* Manage Stack */
{"blend_set",
static_cast<PyCFunction>(pygpu_state_blend_set),
METH_O,
pygpu_state_blend_set_doc},
{"blend_get",
reinterpret_cast<PyCFunction>(pygpu_state_blend_get),
METH_NOARGS,
pygpu_state_blend_get_doc},
{"clip_distances_set",
static_cast<PyCFunction>(pygpu_state_clip_distances_set),
METH_O,
pygpu_state_clip_distances_set_doc},
{"depth_test_set",
static_cast<PyCFunction>(pygpu_state_depth_test_set),
METH_O,
pygpu_state_depth_test_set_doc},
{"depth_test_get",
reinterpret_cast<PyCFunction>(pygpu_state_depth_test_get),
METH_NOARGS,
pygpu_state_depth_test_get_doc},
{"depth_mask_set",
static_cast<PyCFunction>(pygpu_state_depth_mask_set),
METH_O,
pygpu_state_depth_mask_set_doc},
{"depth_mask_get",
reinterpret_cast<PyCFunction>(pygpu_state_depth_mask_get),
METH_NOARGS,
pygpu_state_depth_mask_get_doc},
{"viewport_set",
static_cast<PyCFunction>(pygpu_state_viewport_set),
METH_VARARGS,
pygpu_state_viewport_set_doc},
{"viewport_get",
static_cast<PyCFunction>(pygpu_state_viewport_get),
METH_NOARGS,
pygpu_state_viewport_get_doc},
{"scissor_set",
static_cast<PyCFunction>(pygpu_state_scissor_set),
METH_VARARGS,
pygpu_state_scissor_set_doc},
{"scissor_get",
static_cast<PyCFunction>(pygpu_state_scissor_get),
METH_NOARGS,
pygpu_state_scissor_get_doc},
{"scissor_test_set",
static_cast<PyCFunction>(pygpu_state_scissor_test_set),
METH_O,
pygpu_state_scissor_test_set_doc},
{"line_width_set",
static_cast<PyCFunction>(pygpu_state_line_width_set),
METH_O,
pygpu_state_line_width_set_doc},
{"line_width_get",
reinterpret_cast<PyCFunction>(pygpu_state_line_width_get),
METH_NOARGS,
pygpu_state_line_width_get_doc},
{"point_size_set",
static_cast<PyCFunction>(pygpu_state_point_size_set),
METH_O,
pygpu_state_point_size_set_doc},
{"color_mask_set",
static_cast<PyCFunction>(pygpu_state_color_mask_set),
METH_VARARGS,
pygpu_state_color_mask_set_doc},
{"face_culling_set",
static_cast<PyCFunction>(pygpu_state_face_culling_set),
METH_O,
pygpu_state_face_culling_set_doc},
{"front_facing_set",
static_cast<PyCFunction>(pygpu_state_front_facing_set),
METH_O,
pygpu_state_front_facing_set_doc},
{"program_point_size_set",
static_cast<PyCFunction>(pygpu_state_program_point_size_set),
METH_O,
pygpu_state_program_point_size_set_doc},
{"active_framebuffer_get",
reinterpret_cast<PyCFunction>(pygpu_state_active_framebuffer_get),
METH_NOARGS,
pygpu_state_active_framebuffer_get_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
pygpu_state__tp_doc,
"This module provides access to the gpu state.");
static PyModuleDef pygpu_state_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu.state",
/*m_doc*/ pygpu_state__tp_doc,
/*m_size*/ 0,
/*m_methods*/ pygpu_state__tp_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *bpygpu_state_init()
{
PyObject *submodule;
submodule = PyModule_Create(&pygpu_state_module_def);
return submodule;
}
/** \} */
} // namespace blender

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "BLI_compiler_attrs.h"
namespace blender {
namespace gpu {
class Texture;
}
extern PyTypeObject BPyGPUTexture_Type;
/**
* GPU_DEPTH24_STENCIL8 and GPU_DEPTH_COMPONENT24 are deprecated in Blender 5.0. These formats are
* automatically converted to their 32F variant.
*/
constexpr int GPU_DEPTH24_STENCIL8_DEPRECATED = -1;
constexpr int GPU_DEPTH_COMPONENT24_DEPRECATED = -2;
extern const struct PyC_StringEnumItems pygpu_textureformat_items[];
#define BPyGPUTexture_Check(v) (Py_TYPE(v) == &BPyGPUTexture_Type)
struct BPyGPUTexture {
PyObject_HEAD
gpu::Texture *tex;
};
[[nodiscard]] int bpygpu_ParseTexture(PyObject *o, void *p);
[[nodiscard]] PyObject *bpygpu_texture_init();
[[nodiscard]] PyObject *BPyGPUTexture_CreatePyObject(gpu::Texture *tex, bool shared_reference)
ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,109 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "gpu_py_matrix.hh"
#include "gpu_py_offscreen.hh"
#include "gpu_py_types.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name GPU Types Module
* \{ */
static PyModuleDef pygpu_types_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "gpu.types",
/*m_doc*/ nullptr,
/*m_size*/ 0,
/*m_methods*/ nullptr,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *bpygpu_types_init()
{
PyObject *submodule;
submodule = PyModule_Create(&pygpu_types_module_def);
if (PyType_Ready(&BPyGPU_BufferType) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUVertFormat_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUVertBuf_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUIndexBuf_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUBatch_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUOffScreen_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUShader_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUTexture_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUFrameBuffer_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUUniformBuf_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUShaderCreateInfo_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPUStageInterfaceInfo_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&PyGPUMatrixStackContext_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&PyGPUOffscreenStackContext_Type) < 0) {
return nullptr;
}
if (PyType_Ready(&BPyGPU_DeviceType) < 0) {
return nullptr;
}
PyModule_AddType(submodule, &BPyGPU_BufferType);
PyModule_AddType(submodule, &BPyGPUVertFormat_Type);
PyModule_AddType(submodule, &BPyGPUVertBuf_Type);
PyModule_AddType(submodule, &BPyGPUIndexBuf_Type);
PyModule_AddType(submodule, &BPyGPUBatch_Type);
PyModule_AddType(submodule, &BPyGPUOffScreen_Type);
PyModule_AddType(submodule, &BPyGPUShader_Type);
PyModule_AddType(submodule, &BPyGPUTexture_Type);
PyModule_AddType(submodule, &BPyGPUFrameBuffer_Type);
PyModule_AddType(submodule, &BPyGPUUniformBuf_Type);
PyModule_AddType(submodule, &BPyGPUShaderCreateInfo_Type);
PyModule_AddType(submodule, &BPyGPUStageInterfaceInfo_Type);
PyModule_AddType(submodule, &PyGPUMatrixStackContext_Type);
PyModule_AddType(submodule, &PyGPUOffscreenStackContext_Type);
PyModule_AddType(submodule, &BPyGPU_DeviceType);
return submodule;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include "gpu_py_buffer.hh" // IWYU pragma: export
#include "gpu_py_batch.hh" // IWYU pragma: export
#include "gpu_py_compute.hh" // IWYU pragma: export
#include "gpu_py_element.hh" // IWYU pragma: export
#include "gpu_py_framebuffer.hh" // IWYU pragma: export
#include "gpu_py_offscreen.hh" // IWYU pragma: export
#include "gpu_py_platform.hh" // IWYU pragma: export
#include "gpu_py_shader.hh" // IWYU pragma: export
#include "gpu_py_texture.hh" // IWYU pragma: export
#include "gpu_py_uniformbuffer.hh" // IWYU pragma: export
#include "gpu_py_vertex_buffer.hh" // IWYU pragma: export
#include "gpu_py_vertex_format.hh" // IWYU pragma: export
namespace blender {
[[nodiscard]] PyObject *bpygpu_types_init();
} // namespace blender

View File

@@ -0,0 +1,257 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* This file defines the uniform buffer functionalities of the 'gpu' module
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "BLI_string_utf8.h"
#include "GPU_context.hh"
#include "GPU_uniform_buffer.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "gpu_py.hh"
#include "gpu_py_uniformbuffer.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name gpu::UniformBuf Common Utilities
* \{ */
static int pygpu_uniformbuffer_valid_check(BPyGPUUniformBuf *bpygpu_ub)
{
if (UNLIKELY(bpygpu_ub->ubo == nullptr)) {
PyErr_SetString(PyExc_ReferenceError,
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
"GPU uniform buffer was freed, no further access is valid");
#else
"GPU uniform buffer: internal error");
#endif
return -1;
}
return 0;
}
#define BPYGPU_UNIFORMBUF_CHECK_OBJ(bpygpu) \
{ \
if (UNLIKELY(pygpu_uniformbuffer_valid_check(bpygpu) == -1)) { \
return nullptr; \
} \
} \
((void)0)
/** \} */
/* -------------------------------------------------------------------- */
/** \name gpu::UniformBuf Type
* \{ */
static PyObject *pygpu_uniformbuffer__tp_new(PyTypeObject * /*self*/,
PyObject *args,
PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
gpu::UniformBuf *ubo = nullptr;
PyObject *pybuffer_obj;
char err_out[256] = "unknown error. See console";
static const char *_keywords[] = {"data", nullptr};
static _PyArg_Parser _parser = {
"O" /* `data` */
":GPUUniformBuf.__new__",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, &pybuffer_obj)) {
return nullptr;
}
if (!GPU_context_active_get()) {
STRNCPY_UTF8(err_out, "No active GPU context found");
}
else {
Py_buffer pybuffer;
if (PyObject_GetBuffer(pybuffer_obj, &pybuffer, PyBUF_SIMPLE) == -1) {
/* PyObject_GetBuffer raise a PyExc_BufferError */
return nullptr;
}
if ((pybuffer.len % 16) != 0) {
STRNCPY_UTF8(err_out, "UBO is not padded to size of vec4");
}
else {
ubo = GPU_uniformbuf_create_ex(pybuffer.len, pybuffer.buf, "python_uniformbuffer");
}
PyBuffer_Release(&pybuffer);
}
if (ubo == nullptr) {
PyErr_Format(PyExc_RuntimeError, "GPUUniformBuf.__new__(...) failed with '%s'", err_out);
return nullptr;
}
return BPyGPUUniformBuf_CreatePyObject(ubo);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_uniformbuffer_update_doc,
".. method:: update(data)\n"
"\n"
" Update the data of the uniform buffer object.\n"
"\n"
" :param data: Data to fill the buffer.\n"
" :type data: Buffer\n");
static PyObject *pygpu_uniformbuffer_update(BPyGPUUniformBuf *self, PyObject *obj)
{
BPYGPU_UNIFORMBUF_CHECK_OBJ(self);
Py_buffer pybuffer;
if (PyObject_GetBuffer(obj, &pybuffer, PyBUF_SIMPLE) == -1) {
/* PyObject_GetBuffer raise a PyExc_BufferError */
return nullptr;
}
GPU_uniformbuf_update(self->ubo, pybuffer.buf);
PyBuffer_Release(&pybuffer);
Py_RETURN_NONE;
}
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
PyDoc_STRVAR(
/* Wrap. */
pygpu_uniformbuffer_free_doc,
".. method:: free()\n"
"\n"
" Free the uniform buffer object.\n"
" The uniform buffer object will no longer be accessible.\n");
static PyObject *pygpu_uniformbuffer_free(BPyGPUUniformBuf *self)
{
BPYGPU_UNIFORMBUF_CHECK_OBJ(self);
GPU_uniformbuf_free(self->ubo);
self->ubo = nullptr;
Py_RETURN_NONE;
}
#endif
static void BPyGPUUniformBuf__tp_dealloc(BPyGPUUniformBuf *self)
{
if (self->ubo) {
GPU_uniformbuf_free(self->ubo);
}
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject *>(self));
}
static PyGetSetDef pygpu_uniformbuffer__tp_getseters[] = {
{nullptr, nullptr, nullptr, nullptr, nullptr} /* Sentinel */
};
static PyMethodDef pygpu_uniformbuffer__tp_methods[] = {
{"update",
reinterpret_cast<PyCFunction>(pygpu_uniformbuffer_update),
METH_O,
pygpu_uniformbuffer_update_doc},
#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD
{"free", (PyCFunction)pygpu_uniformbuffer_free, METH_NOARGS, pygpu_uniformbuffer_free_doc},
#endif
{nullptr, nullptr, 0, nullptr},
};
PyDoc_STRVAR(
/* Wrap. */
pygpu_uniformbuffer__tp_doc,
".. class:: GPUUniformBuf\n"
"\n"
" This object gives access to uniform buffers.\n"
"\n"
" .. method:: __init__(data)\n"
"\n"
" :param data: Data to fill the buffer.\n"
" :type data: Buffer\n");
PyTypeObject BPyGPUUniformBuf_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUUniformBuf",
/*tp_basicsize*/ sizeof(BPyGPUUniformBuf),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(BPyGPUUniformBuf__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_uniformbuffer__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_uniformbuffer__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ pygpu_uniformbuffer__tp_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*/ pygpu_uniformbuffer__tp_new,
/*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,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
PyObject *BPyGPUUniformBuf_CreatePyObject(gpu::UniformBuf *ubo)
{
BPyGPUUniformBuf *self;
self = PyObject_New(BPyGPUUniformBuf, &BPyGPUUniformBuf_Type);
self->ubo = ubo;
return reinterpret_cast<PyObject *>(self);
}
/** \} */
#undef BPYGPU_UNIFORMBUF_CHECK_OBJ
} // namespace blender

View File

@@ -0,0 +1,32 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "BLI_compiler_attrs.h"
namespace blender {
namespace gpu {
class UniformBuf;
} // namespace gpu
extern PyTypeObject BPyGPUUniformBuf_Type;
#define BPyGPUUniformBuf_Check(v) (Py_TYPE(v) == &BPyGPUUniformBuf_Type)
struct BPyGPUUniformBuf {
PyObject_HEAD
gpu::UniformBuf *ubo;
};
[[nodiscard]] PyObject *BPyGPUUniformBuf_CreatePyObject(gpu::UniformBuf *ubo) ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,434 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "GPU_vertex_buffer.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "gpu_py.hh"
#include "gpu_py_vertex_buffer.hh" /* own include */
#include "gpu_py_vertex_format.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Utility Functions
* \{ */
#define PYGPU_AS_NATIVE_SWITCH(attr) \
switch (attr->type.comp_type()) { \
case GPU_COMP_I8: { \
PY_AS_NATIVE(int8_t, PyC_Long_AsI8); \
break; \
} \
case GPU_COMP_U8: { \
PY_AS_NATIVE(uint8_t, PyC_Long_AsU8); \
break; \
} \
case GPU_COMP_I16: { \
PY_AS_NATIVE(int16_t, PyC_Long_AsI16); \
break; \
} \
case GPU_COMP_U16: { \
PY_AS_NATIVE(uint16_t, PyC_Long_AsU16); \
break; \
} \
case GPU_COMP_I32: { \
PY_AS_NATIVE(int32_t, PyC_Long_AsI32); \
break; \
} \
case GPU_COMP_U32: { \
PY_AS_NATIVE(uint32_t, PyC_Long_AsU32); \
break; \
} \
case GPU_COMP_F32: { \
PY_AS_NATIVE(float, PyFloat_AsDouble); \
break; \
} \
default: \
BLI_assert_unreachable(); \
break; \
} \
((void)0)
/* No error checking, callers must run PyErr_Occurred */
static void pygpu_fill_format_elem(void *data_dst_void, PyObject *py_src, const GPUVertAttr *attr)
{
#define PY_AS_NATIVE(ty_dst, py_as_native) \
{ \
ty_dst *data_dst = static_cast<ty_dst *>(data_dst_void); \
*data_dst = py_as_native(py_src); \
} \
((void)0)
PYGPU_AS_NATIVE_SWITCH(attr);
#undef PY_AS_NATIVE
}
/* No error checking, callers must run PyErr_Occurred */
static void pygpu_fill_format_sequence(void *data_dst_void,
PyObject *py_seq_fast,
const GPUVertAttr *attr)
{
const uint len = attr->type.comp_len();
PyObject **value_fast_items = PySequence_Fast_ITEMS(py_seq_fast);
/**
* Args are constants, so range checks will be optimized out if they're no-op's.
*/
#define PY_AS_NATIVE(ty_dst, py_as_native) \
ty_dst *data_dst = static_cast<ty_dst *>(data_dst_void); \
for (uint i = 0; i < len; i++) { \
data_dst[i] = py_as_native(value_fast_items[i]); \
} \
((void)0)
PYGPU_AS_NATIVE_SWITCH(attr);
#undef PY_AS_NATIVE
}
#undef PYGPU_AS_NATIVE_SWITCH
#undef WARN_TYPE_LIMIT_PUSH
#undef WARN_TYPE_LIMIT_POP
static bool pygpu_vertbuf_fill_impl(gpu::VertBuf *vbo,
uint data_id,
PyObject *seq,
const char *error_prefix)
{
const char *exc_str_size_mismatch = "Expected a %s of size %d, got %u";
bool ok = true;
const GPUVertAttr *attr = &GPU_vertbuf_get_format(vbo)->attrs[data_id];
uint vert_len = GPU_vertbuf_get_vertex_len(vbo);
if (PyObject_CheckBuffer(seq)) {
Py_buffer pybuffer;
if (PyObject_GetBuffer(seq, &pybuffer, PyBUF_STRIDES | PyBUF_ND) == -1) {
/* PyObject_GetBuffer raise a PyExc_BufferError */
return false;
}
const uint comp_len = pybuffer.ndim == 1 ? 1 : uint(pybuffer.shape[1]);
if (pybuffer.shape[0] != vert_len) {
PyErr_Format(
PyExc_ValueError, exc_str_size_mismatch, "sequence", vert_len, pybuffer.shape[0]);
ok = false;
}
else if (comp_len != attr->type.comp_len()) {
PyErr_Format(
PyExc_ValueError, exc_str_size_mismatch, "component", attr->type.comp_len(), comp_len);
ok = false;
}
else {
GPU_vertbuf_attr_fill_stride(vbo, data_id, pybuffer.strides[0], pybuffer.buf);
}
PyBuffer_Release(&pybuffer);
}
else {
GPUVertBufRaw data_step;
GPU_vertbuf_attr_get_raw_data(vbo, data_id, &data_step);
PyObject *seq_fast = PySequence_Fast(seq, "Vertex buffer fill");
if (seq_fast == nullptr) {
return false;
}
const uint seq_len = PySequence_Fast_GET_SIZE(seq_fast);
if (seq_len != vert_len) {
PyErr_Format(PyExc_ValueError, exc_str_size_mismatch, "sequence", vert_len, seq_len);
}
PyObject **seq_items = PySequence_Fast_ITEMS(seq_fast);
if (attr->type.comp_len() == 1) {
for (uint i = 0; i < seq_len; i++) {
uchar *data = static_cast<uchar *>(GPU_vertbuf_raw_step(&data_step));
PyObject *item = seq_items[i];
pygpu_fill_format_elem(data, item, attr);
}
}
else {
for (uint i = 0; i < seq_len; i++) {
uchar *data = static_cast<uchar *>(GPU_vertbuf_raw_step(&data_step));
PyObject *seq_fast_item = PySequence_Fast(seq_items[i], error_prefix);
if (seq_fast_item == nullptr) {
ok = false;
goto finally;
}
if (PySequence_Fast_GET_SIZE(seq_fast_item) != attr->type.comp_len()) {
PyErr_Format(PyExc_ValueError,
exc_str_size_mismatch,
"sequence",
attr->type.comp_len(),
PySequence_Fast_GET_SIZE(seq_fast_item));
ok = false;
Py_DECREF(seq_fast_item);
goto finally;
}
/* May trigger error, check below */
pygpu_fill_format_sequence(data, seq_fast_item, attr);
Py_DECREF(seq_fast_item);
}
}
if (PyErr_Occurred()) {
ok = false;
}
finally:
Py_DECREF(seq_fast);
}
return ok;
}
static int pygpu_vertbuf_fill(gpu::VertBuf *buf,
int id,
PyObject *py_seq_data,
const char *error_prefix)
{
if (id < 0 || id >= GPU_vertbuf_get_format(buf)->attr_len) {
PyErr_Format(PyExc_ValueError, "Format id %d out of range", id);
return 0;
}
if (buf->data<char>().data() == nullptr) {
PyErr_SetString(PyExc_ValueError, "Can't fill, static buffer already in use");
return 0;
}
if (!pygpu_vertbuf_fill_impl(buf, uint(id), py_seq_data, error_prefix)) {
return 0;
}
return 1;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name VertBuf Type
* \{ */
static PyObject *pygpu_vertbuf__tp_new(PyTypeObject * /*type*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
struct {
PyObject *py_fmt;
uint len;
} params;
static const char *_keywords[] = {"format", "len", nullptr};
static _PyArg_Parser _parser = {
"O!" /* `format` */
"I" /* `len` */
":GPUVertBuf.__new__",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kwds, &_parser, &BPyGPUVertFormat_Type, &params.py_fmt, &params.len))
{
return nullptr;
}
const GPUVertFormat &fmt = (reinterpret_cast<BPyGPUVertFormat *>(params.py_fmt))->fmt;
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(fmt);
GPU_vertbuf_data_alloc(*vbo, params.len);
return BPyGPUVertBuf_CreatePyObject(vbo);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_vertbuf_attr_fill_doc,
".. method:: attr_fill(id, data)\n"
"\n"
" Insert data into the buffer for a single attribute.\n"
"\n"
" :param id: Either the name or the id of the attribute.\n"
" :type id: int | str\n"
" :param data: Buffer or sequence of data that should be stored in the buffer\n"
" :type data: Buffer | "
"Sequence[float] | Sequence[int] | Sequence[Sequence[float]] | Sequence[Sequence[int]]\n");
static PyObject *pygpu_vertbuf_attr_fill(BPyGPUVertBuf *self, PyObject *args, PyObject *kwds)
{
PyObject *data;
PyObject *identifier;
static const char *_keywords[] = {"id", "data", nullptr};
static _PyArg_Parser _parser = {
"O" /* `id` */
"O" /* `data` */
":attr_fill",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, &identifier, &data)) {
return nullptr;
}
int id;
if (PyLong_Check(identifier)) {
id = PyLong_AsLong(identifier);
}
else if (PyUnicode_Check(identifier)) {
const GPUVertFormat *format = GPU_vertbuf_get_format(self->buf);
const char *name = PyUnicode_AsUTF8(identifier);
id = GPU_vertformat_attr_id_get(format, name);
if (id == -1) {
PyErr_Format(PyExc_ValueError, "Unknown attribute '%s'", name);
return nullptr;
}
}
else {
PyErr_SetString(PyExc_TypeError, "expected int or str type as identifier");
return nullptr;
}
if (!pygpu_vertbuf_fill(self->buf, id, data, "GPUVertBuf.attr_fill")) {
return nullptr;
}
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 pygpu_vertbuf__tp_methods[] = {
{"attr_fill",
reinterpret_cast<PyCFunction>(pygpu_vertbuf_attr_fill),
METH_VARARGS | METH_KEYWORDS,
pygpu_vertbuf_attr_fill_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
static void pygpu_vertbuf__tp_dealloc(BPyGPUVertBuf *self)
{
GPU_vertbuf_discard(self->buf);
Py_TYPE(self)->tp_free(self);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_vertbuf__tp_doc,
".. class:: GPUVertBuf\n"
"\n"
" Contains a VBO.\n"
"\n"
" .. method:: __init__(format, len)\n"
"\n"
" :param format: Vertex format.\n"
" :type format: :class:`gpu.types.GPUVertFormat`\n"
" :param len: Amount of vertices that will fit into this buffer.\n"
" :type len: int\n");
PyTypeObject BPyGPUVertBuf_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUVertBuf",
/*tp_basicsize*/ sizeof(BPyGPUVertBuf),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(pygpu_vertbuf__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_vertbuf__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_vertbuf__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ nullptr,
/*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*/ pygpu_vertbuf__tp_new,
/*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,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
PyObject *BPyGPUVertBuf_CreatePyObject(gpu::VertBuf *buf)
{
BPyGPUVertBuf *self;
self = PyObject_New(BPyGPUVertBuf, &BPyGPUVertBuf_Type);
self->buf = buf;
return reinterpret_cast<PyObject *>(self);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "BLI_compiler_attrs.h"
namespace blender {
namespace gpu {
class VertBuf;
}
extern PyTypeObject BPyGPUVertBuf_Type;
#define BPyGPUVertBuf_Check(v) (Py_TYPE(v) == &BPyGPUVertBuf_Type)
struct BPyGPUVertBuf {
PyObject_HEAD
/* The buf is owned, we may support thin wrapped batches later. */
gpu::VertBuf *buf;
};
[[nodiscard]] PyObject *BPyGPUVertBuf_CreatePyObject(gpu::VertBuf *buf) ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,265 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*
* - Use `bpygpu_` for local API.
* - Use `BPyGPU` for public API.
*/
#include <Python.h>
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "gpu_py.hh"
#include "gpu_py_vertex_format.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Enum Conversion
*
* Use with PyArg_ParseTuple's "O&" formatting.
* \{ */
static PyC_StringEnumItems pygpu_vertcomptype_items[] = {
{GPU_COMP_I8, "I8"},
{GPU_COMP_U8, "U8"},
{GPU_COMP_I16, "I16"},
{GPU_COMP_U16, "U16"},
{GPU_COMP_I32, "I32"},
{GPU_COMP_U32, "U32"},
{GPU_COMP_F32, "F32"},
{GPU_COMP_I10, "I10"},
{0, nullptr},
};
static PyC_StringEnumItems pygpu_vertfetchmode_items[] = {
{GPU_FETCH_FLOAT, "FLOAT"},
{GPU_FETCH_INT, "INT"},
{GPU_FETCH_INT_TO_FLOAT_UNIT, "INT_TO_FLOAT_UNIT"},
{0, nullptr},
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name VertFormat Type
* \{ */
static PyObject *pygpu_vertformat__tp_new(PyTypeObject * /*type*/, PyObject *args, PyObject *kwds)
{
BPYGPU_IS_INIT_OR_ERROR_OBJ;
if (PyTuple_GET_SIZE(args) || (kwds && PyDict_Size(kwds))) {
PyErr_SetString(PyExc_ValueError, "This function takes no arguments");
return nullptr;
}
return BPyGPUVertFormat_CreatePyObject(nullptr);
}
static uint attr_size(GPUVertCompType type, int len)
{
if (type == GPU_COMP_I10) {
return 4; /* Always packed as 10_10_10_2. */
}
BLI_assert(type <= GPU_COMP_F32); /* Other types have irregular sizes (not bytes). */
const uint sizes[] = {1, 1, 2, 2, 4, 4, 4};
return len * sizes[type];
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_vertformat_attr_add_doc,
".. method:: attr_add(id, comp_type, len, fetch_mode)\n"
"\n"
" Add a new attribute to the format.\n"
"\n"
" :param id: Name of the attribute. Often ``position``, ``normal``, ...\n"
" :type id: str\n"
" :param comp_type: The data type that will be used to store the value in memory.\n"
" :type comp_type: Literal['I8', 'U8', 'I16', 'U16', 'I32', 'U32', 'F32', 'I10']\n"
" :param len: How many individual values the attribute consists of\n"
" (e.g. 2 for uv coordinates).\n"
" :type len: int\n"
" :param fetch_mode: How values from memory will be converted when used in the shader.\n"
" This is mainly useful for memory optimizations when you want to store values with\n"
" reduced precision. E.g. you can store a float in only 1 byte but it will be\n"
" converted to a normal 4 byte float when used.\n"
" :type fetch_mode: Literal['FLOAT', 'INT', 'INT_TO_FLOAT_UNIT']\n");
static PyObject *pygpu_vertformat_attr_add(BPyGPUVertFormat *self, PyObject *args, PyObject *kwds)
{
const char *id;
uint len;
PyC_StringEnum comp_type = {pygpu_vertcomptype_items, GPU_COMP_I8};
PyC_StringEnum fetch_mode = {pygpu_vertfetchmode_items, GPU_FETCH_FLOAT};
if (self->fmt.attr_len == GPU_VERT_ATTR_MAX_LEN) {
PyErr_SetString(PyExc_ValueError, "Maximum attr reached " STRINGIFY(GPU_VERT_ATTR_MAX_LEN));
return nullptr;
}
static const char *_keywords[] = {"id", "comp_type", "len", "fetch_mode", nullptr};
static _PyArg_Parser _parser = {
"$" /* Keyword only arguments. */
"s" /* `id` */
"O&" /* `comp_type` */
"I" /* `len` */
"O&" /* `fetch_mode` */
":attr_add",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kwds,
&_parser,
&id,
PyC_ParseStringEnum,
&comp_type,
&len,
PyC_ParseStringEnum,
&fetch_mode))
{
return nullptr;
}
GPUVertCompType comp_type_enum = GPUVertCompType(comp_type.value_found);
GPUVertFetchMode fetch_mode_enum = GPUVertFetchMode(fetch_mode.value_found);
if (len > 4) {
PyErr_WarnEx(
PyExc_DeprecationWarning,
"Using GPUVertFormat.attr_add(...) with component count greater than 4 is deprecated. "
"Use several attributes for each matrix columns instead.",
1);
}
if (attr_size(comp_type_enum, len) % 4 != 0) {
PyErr_WarnEx(PyExc_DeprecationWarning,
"Using GPUVertFormat.attr_add(...) with a format that is not 4 bytes aligned is "
"deprecated. Add padding components and/or higher precision integers.",
1);
}
uint attr_id = GPU_vertformat_attr_add_legacy(
&self->fmt, id, comp_type_enum, len, fetch_mode_enum);
return PyLong_FromLong(attr_id);
}
#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 pygpu_vertformat__tp_methods[] = {
{"attr_add",
reinterpret_cast<PyCFunction>(pygpu_vertformat_attr_add),
METH_VARARGS | METH_KEYWORDS,
pygpu_vertformat_attr_add_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
static void pygpu_vertformat__tp_dealloc(BPyGPUVertFormat *self)
{
Py_TYPE(self)->tp_free(self);
}
PyDoc_STRVAR(
/* Wrap. */
pygpu_vertformat__tp_doc,
".. class:: GPUVertFormat()\n"
"\n"
" This object contains information about the structure of a vertex buffer.\n");
PyTypeObject BPyGPUVertFormat_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "GPUVertFormat",
/*tp_basicsize*/ sizeof(BPyGPUVertFormat),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(pygpu_vertformat__tp_dealloc),
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
/*tp_doc*/ pygpu_vertformat__tp_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ pygpu_vertformat__tp_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ nullptr,
/*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*/ pygpu_vertformat__tp_new,
/*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,
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
PyObject *BPyGPUVertFormat_CreatePyObject(GPUVertFormat *fmt)
{
BPyGPUVertFormat *self;
self = PyObject_New(BPyGPUVertFormat, &BPyGPUVertFormat_Type);
if (fmt) {
self->fmt = *fmt;
}
else {
memset(&self->fmt, 0, sizeof(self->fmt));
}
return reinterpret_cast<PyObject *>(self);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bpygpu
*/
#pragma once
#include <Python.h>
#include "GPU_vertex_format.hh"
namespace blender {
extern PyTypeObject BPyGPUVertFormat_Type;
#define BPyGPUVertFormat_Check(v) (Py_TYPE(v) == &BPyGPUVertFormat_Type)
struct BPyGPUVertFormat {
PyObject_HEAD
GPUVertFormat fmt;
};
[[nodiscard]] PyObject *BPyGPUVertFormat_CreatePyObject(GPUVertFormat *fmt);
} // namespace blender

View File

@@ -0,0 +1,362 @@
# SPDX-FileCopyrightText: 2006 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
..
../../editors/include
../../imbuf/intern/oiio
../../makesrna
../../../../intern/mantaflow/extern
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
)
set(SRC
bpy.cc
bpy_app.cc
bpy_app_alembic.cc
bpy_app_build_options.cc
bpy_app_ffmpeg.cc
bpy_app_handlers.cc
bpy_app_icons.cc
bpy_app_ocio.cc
bpy_app_oiio.cc
bpy_app_opensubdiv.cc
bpy_app_openvdb.cc
bpy_app_sdl.cc
bpy_app_timers.cc
bpy_app_translations.cc
bpy_app_usd.cc
bpy_audaspace.cc
bpy_capi_utils.cc
bpy_cli_command.cc
bpy_driver.cc
bpy_driver_bytecode.cc
bpy_geometry_set.cc
bpy_gizmo_wrap.cc
bpy_inline_shader_nodes.cc
bpy_interface.cc
bpy_interface_atexit.cc
bpy_interface_run.cc
bpy_intern_string.cc
bpy_library_load.cc
bpy_library_write.cc
bpy_msgbus.cc
bpy_operator.cc
bpy_operator_function.cc
bpy_operator_wrap.cc
bpy_path.cc
bpy_props.cc
bpy_rna.cc
bpy_rna_anim.cc
bpy_rna_array.cc
bpy_rna_callback.cc
bpy_rna_context.cc
bpy_rna_data.cc
bpy_rna_driver.cc
bpy_rna_gizmo.cc
bpy_rna_id_collection.cc
bpy_rna_operator.cc
bpy_rna_text.cc
bpy_rna_types_capi.cc
bpy_rna_ui.cc
bpy_rna_wm.cc
bpy_traceback.cc
bpy_utils_previews.cc
bpy_utils_units.cc
bpy.hh
bpy_app.hh
bpy_app_alembic.hh
bpy_app_build_options.hh
bpy_app_ffmpeg.hh
bpy_app_handlers.hh
bpy_app_icons.hh
bpy_app_ocio.hh
bpy_app_oiio.hh
bpy_app_opensubdiv.hh
bpy_app_openvdb.hh
bpy_app_sdl.hh
bpy_app_timers.hh
bpy_app_translations.hh
bpy_app_usd.hh
bpy_audaspace.hh
bpy_capi_utils.hh
bpy_cli_command.hh
bpy_driver.hh
bpy_driver_bytecode.hh
bpy_geometry_set.hh
bpy_gizmo_wrap.hh
bpy_inline_shader_nodes.hh
bpy_intern_string.hh
bpy_library.hh
bpy_msgbus.hh
bpy_operator.hh
bpy_operator_function.hh
bpy_operator_wrap.hh
bpy_path.hh
bpy_props.hh
bpy_rna.hh
bpy_rna_anim.hh
bpy_rna_callback.hh
bpy_rna_context.hh
bpy_rna_data.hh
bpy_rna_driver.hh
bpy_rna_gizmo.hh
bpy_rna_id_collection.hh
bpy_rna_operator.hh
bpy_rna_text.hh
bpy_rna_types_capi.hh
bpy_rna_ui.hh
bpy_rna_wm.hh
bpy_traceback.hh
bpy_utils_previews.hh
bpy_utils_units.hh
../BPY_extern.hh
../BPY_extern_clog.hh
../BPY_extern_python.hh
../BPY_extern_run.hh
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::blenloader
PRIVATE bf::blentranslation
PRIVATE bf::depsgraph
PRIVATE bf::dna
bf_editor_animation
bf_editor_interface
bf_editor_space_api
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
PRIVATE bf::animrig
bf_python_gpu
PRIVATE bf::imbuf::opencolorio
PRIVATE bf::nodes
PRIVATE bf::windowmanager
PRIVATE bf::dependencies::optional::python
PRIVATE bf::dependencies::optional::ffmpeg
PRIVATE bf::dependencies::optional::sdl
)
# Only to check if `buildinfo` is available.
if(WITH_BUILDINFO)
add_definitions(-DBUILD_DATE)
endif()
if(WITH_INSTALL_PORTABLE)
add_definitions(-DWITH_INSTALL_PORTABLE)
endif()
if(WITH_PYTHON_MODULE)
add_definitions(-DWITH_PYTHON_MODULE)
endif()
# Find the SSL certificate for the portable Blender installation.
# Without this, the absolute path on the builder is used, causing HTTPS access to fail.
# For example `urllib.request.urlopen("https://projects.blender.org")` fails
# (or any other HTTPS site). see: #102300 for details.
# NOTE: that this isn't necessary on WIN32.
if(WITH_PYTHON AND WITH_PYTHON_INSTALL AND (APPLE OR WITH_INSTALL_PORTABLE) AND (NOT WIN32))
# - `PYTHON_SSL_CERT_FILE` absolute path to the PEM file.
find_python_module_file("certifi/cacert.pem" PYTHON_SSL_CERT_FILE _python_ssl_cert_file_relative)
mark_as_advanced(PYTHON_SSL_CERT_FILE)
if(PYTHON_SSL_CERT_FILE)
add_definitions(-DPYTHON_SSL_CERT_FILE="${_python_ssl_cert_file_relative}")
else()
message(WARNING
"Unable to find \"certifi/cacert.pem\" within \"${PYTHON_LIBPATH}\", "
"this build will not be able to use bundled certificates with the \"ssl\" module!"
)
endif()
unset(_python_ssl_cert_file_relative)
endif()
if(WITH_PYTHON_SAFETY)
add_definitions(-DWITH_PYTHON_SAFETY)
endif()
if(WITH_AUDASPACE)
# It's possible to build with AUDASPACE (for file IO) but without the `aud` Python API,
# when building without NUMPY so define both `WITH_AUDASPACE` & `DWITH_AUDASPACE_PY`.
add_definitions(-DWITH_AUDASPACE)
if(WITH_PYTHON_NUMPY)
add_definitions(-DWITH_AUDASPACE_PY)
list(APPEND LIB bf::dependencies::optional::audaspace)
endif()
endif()
if(WITH_BULLET)
add_definitions(-DWITH_BULLET)
endif()
if(WITH_CODEC_SNDFILE)
add_definitions(-DWITH_SNDFILE)
endif()
if(WITH_CYCLES)
list(APPEND INC
../../../../intern/cycles/blender
)
list(APPEND LIB
bf_intern_cycles
)
add_definitions(-DWITH_CYCLES)
endif()
if(WITH_CYCLES_OSL)
add_definitions(-DWITH_CYCLES_OSL)
endif()
if(WITH_CYCLES_EMBREE)
add_definitions(-DWITH_CYCLES_EMBREE)
endif()
if(WITH_FREESTYLE)
list(APPEND INC
../../freestyle/intern/python
)
add_definitions(-DWITH_FREESTYLE)
endif()
if(WITH_IMAGE_CINEON)
add_definitions(-DWITH_IMAGE_CINEON)
endif()
if(WITH_IMAGE_OPENJPEG)
add_definitions(-DWITH_IMAGE_OPENJPEG)
endif()
if(WITH_IMAGE_WEBP)
add_definitions(-DWITH_IMAGE_WEBP)
endif()
if(WITH_INPUT_NDOF)
add_definitions(-DWITH_INPUT_NDOF)
endif()
if(WITH_INTERNATIONAL)
add_definitions(-DWITH_INTERNATIONAL)
endif()
if(WITH_OPENAL)
add_definitions(-DWITH_OPENAL)
endif()
if(WITH_OPENSUBDIV)
add_definitions(-DWITH_OPENSUBDIV)
list(APPEND INC
../../../../intern/opensubdiv
)
endif()
if(WITH_JACK)
add_definitions(-DWITH_JACK)
endif()
if(WITH_COREAUDIO)
add_definitions(-DWITH_COREAUDIO)
endif()
if(WITH_LIBMV)
add_definitions(-DWITH_LIBMV)
endif()
if(WITH_PULSEAUDIO)
add_definitions(-DWITH_PULSEAUDIO)
endif()
if(WITH_WASAPI)
add_definitions(-DWITH_WASAPI)
endif()
if(WITH_MOD_OCEANSIM)
add_definitions(-DWITH_OCEANSIM)
endif()
if(WITH_MOD_REMESH)
add_definitions(-DWITH_MOD_REMESH)
endif()
if(WITH_MOD_FLUID)
add_definitions(-DWITH_FLUID)
endif()
if(WITH_IO_WAVEFRONT_OBJ)
add_definitions(-DWITH_IO_WAVEFRONT_OBJ)
endif()
if(WITH_IO_PLY)
add_definitions(-DWITH_IO_PLY)
endif()
if(WITH_IO_STL)
add_definitions(-DWITH_IO_STL)
endif()
if(WITH_IO_FBX)
add_definitions(-DWITH_IO_FBX)
endif()
if(WITH_IO_GREASE_PENCIL)
add_definitions(-DWITH_IO_GREASE_PENCIL)
endif()
if(WITH_ALEMBIC)
add_definitions(-DWITH_ALEMBIC)
list(APPEND INC
../../io/alembic
)
endif()
if(WITH_USD)
add_definitions(-DWITH_USD)
list(APPEND INC
../../io/usd
)
endif()
if(WITH_XR_OPENXR)
add_definitions(-DWITH_XR_OPENXR)
endif()
if(WITH_POTRACE)
add_definitions(-DWITH_POTRACE)
endif()
if(WITH_PUGIXML)
add_definitions(-DWITH_PUGIXML)
endif()
if(WITH_HARU)
add_definitions(-DWITH_HARU)
endif()
if(WITH_SDL)
add_definitions(-DWITH_SDL)
endif()
if(WITH_HYDRA)
list(APPEND LIB
bf_render_hydra
)
add_definitions(-DWITH_HYDRA)
endif()
if(WITH_EXPERIMENTAL_FEATURES)
add_definitions(-DWITH_EXPERIMENTAL_FEATURES)
endif()
blender_add_lib(bf_python "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# RNA_prototypes.hh
add_dependencies(bf_python bf_rna)

View File

@@ -0,0 +1,862 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pythonintern
*
* This file defines the '_bpy' module which is used by python's 'bpy' package
* to access C defined builtin functions.
* A script writer should never directly access this module.
*/
/* Future-proof, See https://docs.python.org/3/c-api/arg.html#strings-and-buffers */
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <optional>
#include "BLI_string.h"
#include "BLI_string_utils.hh"
#include "BLI_utildefines.h"
#include "BKE_appdir.hh"
#include "BKE_blender_version.h"
#include "BKE_bpath.hh"
#include "BKE_global.hh" /* XXX, G_MAIN only */
#include "RNA_access.hh"
#include "RNA_enum_types.hh"
#include "RNA_prototypes.hh"
#include "GPU_state.hh"
#include "WM_api.hh" /* For #WM_ghost_backend */
#include "bpy.hh"
#include "bpy_app.hh"
#include "bpy_cli_command.hh"
#include "bpy_driver.hh"
#include "bpy_geometry_set.hh"
#include "bpy_inline_shader_nodes.hh"
#include "bpy_library.hh"
#include "bpy_operator.hh"
#include "bpy_props.hh"
#include "bpy_rna.hh"
#include "bpy_rna_data.hh"
#include "bpy_rna_gizmo.hh"
#include "bpy_rna_id_collection.hh"
#include "bpy_rna_types_capi.hh"
#include "bpy_utils_previews.hh"
#include "bpy_utils_units.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "../generic/python_utildefines.hh"
/* external util modules */
#include "../generic/idprop_py_api.hh"
#include "../generic/idprop_py_ui_api.hh"
#include "bpy_msgbus.hh"
#ifdef WITH_FREESTYLE
# include "BPy_Freestyle.h"
#endif
namespace blender {
PyObject *bpy_package_py = nullptr;
PyDoc_STRVAR(
/* Wrap. */
bpy_script_paths_doc,
".. function:: script_paths()\n"
"\n"
" Return 2 paths to blender scripts directories.\n"
"\n"
" :return: (system, user) strings will be empty when not found.\n"
" :rtype: tuple[str, str]\n");
static PyObject *bpy_script_paths(PyObject * /*self*/)
{
PyObject *ret = PyTuple_New(2);
PyObject *item;
std::optional<std::string> path = BKE_appdir_folder_id(BLENDER_SYSTEM_SCRIPTS, nullptr);
item = PyC_UnicodeFromStdStr(path.value_or(""));
BLI_assert(item != nullptr);
PyTuple_SET_ITEM(ret, 0, item);
path = BKE_appdir_folder_id(BLENDER_USER_SCRIPTS, nullptr);
item = PyC_UnicodeFromStdStr(path.value_or(""));
BLI_assert(item != nullptr);
PyTuple_SET_ITEM(ret, 1, item);
return ret;
}
static bool bpy_blend_foreach_path_cb(BPathForeachPathData *bpath_data,
char * /*path_dst*/,
size_t /*path_dst_maxncpy*/,
const char *path_src)
{
PyObject *py_list = static_cast<PyObject *>(bpath_data->user_data);
PyList_APPEND(py_list, PyC_UnicodeFromBytes(path_src));
return false; /* Never edits the path. */
}
PyDoc_STRVAR(
/* Wrap. */
bpy_blend_paths_doc,
".. function:: blend_paths(*, absolute=False, packed=False, local=False)\n"
"\n"
" Returns a list of paths to external files referenced by the loaded .blend file.\n"
"\n"
" :param absolute: When true the paths returned are made absolute.\n"
" :type absolute: bool\n"
" :param packed: When true include file paths for packed data.\n"
" :type packed: bool\n"
" :param local: When true skip linked library paths.\n"
" :type local: bool\n"
" :return: path list.\n"
" :rtype: list[str]\n");
static PyObject *bpy_blend_paths(PyObject * /*self*/, PyObject *args, PyObject *kw)
{
eBPathForeachFlag flag = eBPathForeachFlag(0);
PyObject *list;
bool absolute = false;
bool packed = false;
bool local = false;
static const char *_keywords[] = {"absolute", "packed", "local", nullptr};
static _PyArg_Parser _parser = {
"|$" /* Optional keyword only arguments. */
"O&" /* `absolute` */
"O&" /* `packed` */
"O&" /* `local` */
":blend_paths",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kw,
&_parser,
PyC_ParseBool,
&absolute,
PyC_ParseBool,
&packed,
PyC_ParseBool,
&local))
{
return nullptr;
}
if (absolute) {
flag |= BKE_BPATH_FOREACH_PATH_ABSOLUTE;
}
if (!packed) {
flag |= BKE_BPATH_FOREACH_PATH_SKIP_PACKED;
}
if (local) {
flag |= BKE_BPATH_FOREACH_PATH_SKIP_LINKED;
}
list = PyList_New(0);
BPathForeachPathData path_data{};
path_data.bmain = G_MAIN;
path_data.callback_function = bpy_blend_foreach_path_cb;
path_data.flag = flag;
path_data.user_data = list;
BKE_bpath_foreach_path_main(&path_data);
return list;
}
PyDoc_STRVAR(
/* Wrap. */
bpy_flip_name_doc,
".. function:: flip_name(name, *, strip_digits=False)\n"
"\n"
" Flip a name between left/right sides, useful for \n"
" mirroring bone names.\n"
"\n"
" :param name: Bone name to flip.\n"
" :type name: str\n"
" :param strip_digits: Whether to remove ``.###`` suffix.\n"
" :type strip_digits: bool\n"
" :return: The flipped name.\n"
" :rtype: str\n");
static PyObject *bpy_flip_name(PyObject * /*self*/, PyObject *args, PyObject *kw)
{
const char *name_src = nullptr;
Py_ssize_t name_src_len;
bool strip_digits = false;
static const char *_keywords[] = {"", "strip_digits", nullptr};
static _PyArg_Parser _parser = {
"s#" /* `name` */
"|$" /* Optional, keyword only arguments. */
"O&" /* `strip_digits` */
":flip_name",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kw, &_parser, &name_src, &name_src_len, PyC_ParseBool, &strip_digits))
{
return nullptr;
}
/* Worst case we gain one extra byte (besides null-terminator) by changing
* "Left" to "Right", because only the first appearance of "Left" gets replaced. */
const size_t size = name_src_len + 2;
char *name_dst = static_cast<char *>(PyMem_MALLOC(size));
const size_t name_dst_len = BLI_string_flip_side_name(name_dst, name_src, strip_digits, size);
PyObject *result = PyUnicode_FromStringAndSize(name_dst, name_dst_len);
PyMem_FREE(name_dst);
return result;
}
/* `bpy_user_resource_doc`, Now in `bpy/utils/__init__.py`. */
static PyObject *bpy_user_resource(PyObject * /*self*/, PyObject *args, PyObject *kw)
{
const PyC_StringEnumItems type_items[] = {
{BLENDER_USER_DATAFILES, "DATAFILES"},
{BLENDER_USER_CONFIG, "CONFIG"},
{BLENDER_USER_SCRIPTS, "SCRIPTS"},
{BLENDER_USER_EXTENSIONS, "EXTENSIONS"},
{0, nullptr},
};
PyC_StringEnum type = {type_items};
PyC_UnicodeAsBytesAndSize_Data subdir_data = {nullptr};
static const char *_keywords[] = {"type", "path", nullptr};
static _PyArg_Parser _parser = {
"O&" /* `type` */
"|$" /* Optional keyword only arguments. */
"O&" /* `path` */
":user_resource",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kw,
&_parser,
PyC_ParseStringEnum,
&type,
PyC_ParseUnicodeAsBytesAndSize,
&subdir_data))
{
return nullptr;
}
/* same logic as BKE_appdir_folder_id_create(),
* but best leave it up to the script author to create */
const std::optional<std::string> path = BKE_appdir_folder_id_user_notest(type.value_found,
subdir_data.value);
Py_XDECREF(subdir_data.value_coerce);
return PyC_UnicodeFromStdStr(path.value_or(""));
}
PyDoc_STRVAR(
/* Wrap. */
bpy_system_resource_doc,
".. function:: system_resource(type, *, path=\"\")\n"
"\n"
" Return a system resource path.\n"
"\n"
" :param type: string in ['DATAFILES', 'SCRIPTS', 'EXTENSIONS', 'PYTHON'].\n"
" :type type: str\n"
" :param path: Optional subdirectory.\n"
" :type path: str | bytes\n");
static PyObject *bpy_system_resource(PyObject * /*self*/, PyObject *args, PyObject *kw)
{
const PyC_StringEnumItems type_items[] = {
{BLENDER_SYSTEM_DATAFILES, "DATAFILES"},
{BLENDER_SYSTEM_SCRIPTS, "SCRIPTS"},
{BLENDER_SYSTEM_EXTENSIONS, "EXTENSIONS"},
{BLENDER_SYSTEM_PYTHON, "PYTHON"},
{0, nullptr},
};
PyC_StringEnum type = {type_items};
PyC_UnicodeAsBytesAndSize_Data subdir_data = {nullptr};
static const char *_keywords[] = {"type", "path", nullptr};
static _PyArg_Parser _parser = {
"O&" /* `type` */
"|$" /* Optional keyword only arguments. */
"O&" /* `path` */
":system_resource",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kw,
&_parser,
PyC_ParseStringEnum,
&type,
PyC_ParseUnicodeAsBytesAndSize,
&subdir_data))
{
return nullptr;
}
std::optional<std::string> path = BKE_appdir_folder_id(type.value_found, subdir_data.value);
Py_XDECREF(subdir_data.value_coerce);
return PyC_UnicodeFromStdStr(path.value_or(""));
}
PyDoc_STRVAR(
/* Wrap. */
bpy_resource_path_doc,
".. function:: resource_path(type, *, major=None, minor=None)\n"
"\n"
" Return the base path for storing system files.\n"
"\n"
" :param type: The resource type.\n"
" :type type: Literal['USER', 'LOCAL', 'SYSTEM', 'SYSTEM_LIBS']\n"
" :param major: Major version. None (the default) uses ``bpy.app.version[0]``.\n"
" :type major: int | None\n"
" :param minor: Minor version. None (the default) uses ``bpy.app.version[1]``.\n"
" :type minor: int | None\n"
" :return: the resource path (not necessarily existing).\n"
" :rtype: str\n"
"\n"
" .. note::\n"
"\n"
" ``SYSTEM_LIBS`` mirrors ``SYSTEM`` "
"but resolves to the directory for architecture-dependent libraries "
"(typically under ``/usr/lib/...`` rather than ``/usr/share/...``). "
"It is an empty string on builds without a separate library directory, "
"such as portable builds and the Python module.\n");
static PyObject *bpy_resource_path(PyObject * /*self*/, PyObject *args, PyObject *kw)
{
const PyC_StringEnumItems type_items[] = {
{BLENDER_RESOURCE_PATH_USER, "USER"},
{BLENDER_RESOURCE_PATH_LOCAL, "LOCAL"},
{BLENDER_RESOURCE_PATH_SYSTEM, "SYSTEM"},
{BLENDER_RESOURCE_PATH_SYSTEM_LIBS, "SYSTEM_LIBS"},
{0, nullptr},
};
PyC_StringEnum type = {type_items};
std::optional<int> major;
std::optional<int> minor;
static const char *_keywords[] = {"type", "major", "minor", nullptr};
static _PyArg_Parser _parser = {
"O&" /* `type` */
"|$" /* Optional keyword only arguments. */
"O&" /* `major` */
"O&" /* `minor` */
":resource_path",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kw,
&_parser,
PyC_ParseStringEnum,
&type,
PyC_ParseOptionalInt,
&major,
PyC_ParseOptionalInt,
&minor))
{
return nullptr;
}
/* `None` (or missing) selects the current Blender version. */
const int version = major.value_or(BLENDER_VERSION / 100) * 100 +
minor.value_or(BLENDER_VERSION % 100);
const std::optional<std::string> path = BKE_appdir_resource_path_id_with_version(
type.value_found, false, version);
return PyC_UnicodeFromStdStr(path.value_or(""));
}
/* This is only exposed for tests, see: `tests/python/bl_pyapi_bpy_driver_secure_eval.py`. */
PyDoc_STRVAR(
/* Wrap. */
bpy_driver_secure_code_test_doc,
".. function:: _driver_secure_code_test(code, *, namespace=None, verbose=False)\n"
"\n"
" Test if the script should be considered trusted.\n"
"\n"
" :param code: The code to test.\n"
" :type code: code\n"
" :param namespace: The namespace of values which are allowed.\n"
" :type namespace: dict[str, Any] | None\n"
" :param verbose: Print the reason for considering insecure to the ``stderr``.\n"
" :type verbose: bool\n"
" :return: True when the script is considered trusted.\n"
" :rtype: bool\n");
static PyObject *bpy_driver_secure_code_test(PyObject * /*self*/, PyObject *args, PyObject *kw)
{
PyObject *py_code;
PyObject *py_namespace = nullptr;
PyC_TypeOrNone py_namespace_or_none = {&PyDict_Type, &py_namespace};
bool verbose = false;
static const char *_keywords[] = {"code", "namespace", "verbose", nullptr};
static _PyArg_Parser _parser = {
"O!" /* `expression` */
"|$" /* Optional keyword only arguments. */
"O&" /* `namespace` */
"O&" /* `verbose` */
":driver_secure_code_test",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args,
kw,
&_parser,
&PyCode_Type,
&py_code,
PyC_ParseTypeOrNone,
&py_namespace_or_none,
PyC_ParseBool,
&verbose))
{
return nullptr;
}
return PyBool_FromLong(BPY_driver_secure_bytecode_test(py_code, py_namespace, verbose));
}
PyDoc_STRVAR(
/* Wrap. */
bpy_escape_identifier_doc,
".. function:: escape_identifier(string)\n"
"\n"
" Simple string escaping function used for animation paths.\n"
"\n"
" :param string: text\n"
" :type string: str\n"
" :return: The escaped string.\n"
" :rtype: str\n");
static PyObject *bpy_escape_identifier(PyObject * /*self*/, PyObject *value)
{
Py_ssize_t value_str_len;
const char *value_str = PyUnicode_AsUTF8AndSize(value, &value_str_len);
if (value_str == nullptr) {
PyErr_SetString(PyExc_TypeError, "expected a string");
return nullptr;
}
const size_t size = (value_str_len * 2) + 1;
char *value_escape_str = static_cast<char *>(PyMem_MALLOC(size));
const Py_ssize_t value_escape_str_len = BLI_str_escape(value_escape_str, value_str, size);
PyObject *value_escape;
if (value_escape_str_len == value_str_len) {
Py_INCREF(value);
value_escape = value;
}
else {
value_escape = PyUnicode_FromStringAndSize(value_escape_str, value_escape_str_len);
}
PyMem_FREE(value_escape_str);
return value_escape;
}
PyDoc_STRVAR(
/* Wrap. */
bpy_unescape_identifier_doc,
".. function:: unescape_identifier(string)\n"
"\n"
" Simple string un-escape function used for animation paths.\n"
" This performs the reverse of :func:`escape_identifier`.\n"
"\n"
" :param string: text\n"
" :type string: str\n"
" :return: The un-escaped string.\n"
" :rtype: str\n");
static PyObject *bpy_unescape_identifier(PyObject * /*self*/, PyObject *value)
{
Py_ssize_t value_str_len;
const char *value_str = PyUnicode_AsUTF8AndSize(value, &value_str_len);
if (value_str == nullptr) {
PyErr_SetString(PyExc_TypeError, "expected a string");
return nullptr;
}
const size_t size = value_str_len + 1;
char *value_unescape_str = static_cast<char *>(PyMem_MALLOC(size));
const Py_ssize_t value_unescape_str_len = BLI_str_unescape(value_unescape_str, value_str, size);
PyObject *value_unescape;
if (value_unescape_str_len == value_str_len) {
Py_INCREF(value);
value_unescape = value;
}
else {
value_unescape = PyUnicode_FromStringAndSize(value_unescape_str, value_unescape_str_len);
}
PyMem_FREE(value_unescape_str);
return value_unescape;
}
extern "C" const char *buttons_context_dir[];
extern "C" const char *clip_context_dir[];
extern "C" const char *file_context_dir[];
extern "C" const char *image_context_dir[];
extern "C" const char *node_context_dir[];
extern "C" const char *screen_context_dir[];
extern "C" const char *sequencer_context_dir[];
extern "C" const char *text_context_dir[];
extern "C" const char *view3d_context_dir[];
/**
* \note only exposed for generating documentation, see: `doc/python_api/sphinx_doc_gen.py`.
*/
PyDoc_STRVAR(
/* Wrap. */
bpy_context_members_doc,
".. function:: context_members()\n"
"\n"
" :return: A dict where the key is the context and the value is a tuple of it's members.\n"
" :rtype: dict[str, tuple[str]]\n");
static PyObject *bpy_context_members(PyObject * /*self*/)
{
struct {
const char *name;
const char **dir;
} context_members_all[] = {
{"buttons", buttons_context_dir},
{"clip", clip_context_dir},
{"file", file_context_dir},
{"image", image_context_dir},
{"node", node_context_dir},
{"screen", screen_context_dir},
{"sequencer", sequencer_context_dir},
{"text", text_context_dir},
{"view3d", view3d_context_dir},
};
PyObject *result = _PyDict_NewPresized(ARRAY_SIZE(context_members_all));
for (int context_index = 0; context_index < ARRAY_SIZE(context_members_all); context_index++) {
const char *name = context_members_all[context_index].name;
const char **dir = context_members_all[context_index].dir;
int i;
for (i = 0; dir[i]; i++) {
/* Pass. */
}
PyObject *members = PyTuple_New(i);
for (i = 0; dir[i]; i++) {
PyTuple_SET_ITEM(members, i, PyUnicode_FromString(dir[i]));
}
PyDict_SetItemString(result, name, members);
Py_DECREF(members);
}
BLI_assert(PyDict_GET_SIZE(result) == ARRAY_SIZE(context_members_all));
return result;
}
/**
* \note only exposed for generating documentation, see: `doc/python_api/sphinx_doc_gen.py`.
*/
PyDoc_STRVAR(
/* Wrap. */
bpy_rna_enum_items_static_doc,
".. function:: rna_enum_items_static()\n"
"\n"
" :return: A dict where the key the name of the enum, the value is a tuple of enum items.\n"
" :rtype: dict[str, tuple[:class:`bpy.types.EnumPropertyItem`]]\n");
static PyObject *bpy_rna_enum_items_static(PyObject * /*self*/)
{
#define DEF_ENUM(id) {STRINGIFY(id), id},
struct {
const char *id;
const EnumPropertyItem *items;
} enum_info[] = {
#include "RNA_enum_items.hh"
};
PyObject *result = _PyDict_NewPresized(ARRAY_SIZE(enum_info));
for (int i = 0; i < ARRAY_SIZE(enum_info); i++) {
/* Include all items (including headings & separators), can be shown in documentation. */
const EnumPropertyItem *items = enum_info[i].items;
const int items_count = RNA_enum_items_count(items);
PyObject *value = PyTuple_New(items_count);
for (int item_index = 0; item_index < items_count; item_index++) {
PointerRNA ptr = RNA_pointer_create_discrete(
nullptr, RNA_EnumPropertyItem, (void *)&items[item_index]);
PyTuple_SET_ITEM(value, item_index, pyrna_struct_CreatePyObject(&ptr));
}
PyDict_SetItemString(result, enum_info[i].id, value);
Py_DECREF(value);
}
return result;
}
/* This is only exposed for (Unix/Linux), see: #GHOST_ISystem::getSystemBackend for details. */
PyDoc_STRVAR(
/* Wrap. */
bpy_ghost_backend_doc,
".. function:: _ghost_backend()\n"
"\n"
" :return: An identifier for the GHOST back-end.\n"
" :rtype: str\n");
static PyObject *bpy_ghost_backend(PyObject * /*self*/)
{
return PyUnicode_FromString(WM_ghost_backend());
}
/* NOTE(@ideasman42): This is a private function because the keys in the returned dictionary,
* are not considered stable. Sometimes a function is temporarily only supported by one platform.
* Once all platforms support the functionality there is no need for the flag
* and it can be removed. This is at odds with a public API that has values which are
* intended to be kept between releases.
* If this were to be made public we would have to document that this is subject to change. */
PyDoc_STRVAR(
/* Wrap. */
bpy_wm_capabilities_doc,
".. function:: _wm_capabilities()\n"
"\n"
" :return: A dictionary of capabilities (string keys, boolean values).\n"
" :rtype: dict[str, bool]\n");
static PyObject *bpy_wm_capabilities(PyObject *self)
{
PyObject *py_id_capabilities = PyUnicode_FromString("_wm_capabilities_");
PyObject *result = nullptr;
switch (PyObject_GetOptionalAttr(self, py_id_capabilities, &result)) {
case 1: {
BLI_assert(result != nullptr);
break;
}
case 0: {
result = PyDict_New();
const eWM_CapabilitiesFlag flag = WM_capabilities_flag();
#define SetFlagItem(x) \
PyDict_SetItemString(result, STRINGIFY(x), PyBool_FromLong((WM_CAPABILITY_##x) & flag));
/* Only exposed flags which are used, by Blender's built-in scripts
* since this is a private API. */
SetFlagItem(TRACKPAD_PHYSICAL_DIRECTION);
SetFlagItem(KEYBOARD_HYPER_KEY);
#undef SetFlagItem
PyObject_SetAttr(self, py_id_capabilities, result);
break;
}
default:
/* Unlikely, but there may be an error, forward it. */
BLI_assert(result == nullptr);
break;
}
Py_DECREF(py_id_capabilities);
return result;
}
#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_methods[] = {
{"script_paths",
reinterpret_cast<PyCFunction>(bpy_script_paths),
METH_NOARGS,
bpy_script_paths_doc},
{"blend_paths",
reinterpret_cast<PyCFunction>(bpy_blend_paths),
METH_VARARGS | METH_KEYWORDS,
bpy_blend_paths_doc},
{"flip_name",
reinterpret_cast<PyCFunction>(bpy_flip_name),
METH_VARARGS | METH_KEYWORDS,
bpy_flip_name_doc},
{"user_resource",
reinterpret_cast<PyCFunction>(bpy_user_resource),
METH_VARARGS | METH_KEYWORDS,
nullptr},
{"system_resource",
reinterpret_cast<PyCFunction>(bpy_system_resource),
METH_VARARGS | METH_KEYWORDS,
bpy_system_resource_doc},
{"resource_path",
reinterpret_cast<PyCFunction>(bpy_resource_path),
METH_VARARGS | METH_KEYWORDS,
bpy_resource_path_doc},
{"escape_identifier",
static_cast<PyCFunction>(bpy_escape_identifier),
METH_O,
bpy_escape_identifier_doc},
{"unescape_identifier",
static_cast<PyCFunction>(bpy_unescape_identifier),
METH_O,
bpy_unescape_identifier_doc},
{"context_members",
reinterpret_cast<PyCFunction>(bpy_context_members),
METH_NOARGS,
bpy_context_members_doc},
{"rna_enum_items_static",
reinterpret_cast<PyCFunction>(bpy_rna_enum_items_static),
METH_NOARGS,
bpy_rna_enum_items_static_doc},
/* Private functions (not part of the public API and may be removed at any time). */
{"_driver_secure_code_test",
reinterpret_cast<PyCFunction>(bpy_driver_secure_code_test),
METH_VARARGS | METH_KEYWORDS,
bpy_driver_secure_code_test_doc},
{"_ghost_backend",
reinterpret_cast<PyCFunction>(bpy_ghost_backend),
METH_NOARGS,
bpy_ghost_backend_doc},
{"_wm_capabilities",
reinterpret_cast<PyCFunction>(bpy_wm_capabilities),
METH_NOARGS,
bpy_wm_capabilities_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
static PyObject *bpy_import_test(const char *modname)
{
PyObject *mod = PyImport_ImportModuleLevel(modname, nullptr, nullptr, nullptr, 0);
if (mod) {
Py_DECREF(mod);
}
else {
PyErr_Print();
}
return mod;
}
void BPy_init_modules(bContext *C)
{
PyObject *mod;
/* Needs to be first since this dir is needed for future modules */
const std::optional<std::string> modpath = BKE_appdir_folder_id(BLENDER_SYSTEM_SCRIPTS,
"modules");
if (modpath.has_value()) {
// printf("bpy: found module path '%s'.\n", modpath);
PyObject *sys_path = PySys_GetObject("path"); /* borrow */
PyObject *py_modpath = PyC_UnicodeFromStdStr(modpath.value());
PyList_Insert(sys_path, 0, py_modpath); /* add first */
Py_DECREF(py_modpath);
}
else {
printf("bpy: couldn't find 'scripts/modules', blender probably won't start.\n");
}
/* stand alone utility modules not related to blender directly */
IDProp_Init_Types(); /* not actually a submodule, just types */
IDPropertyUIData_Init_Types();
#ifdef WITH_FREESTYLE
Freestyle_Init();
#endif
mod = PyModule_New("_bpy");
/* add the module so we can import it */
PyDict_SetItemString(PyImport_GetModuleDict(), "_bpy", mod);
Py_DECREF(mod);
/* Needs to be first so `_bpy_types` can run. */
PyObject *bpy_types = BPY_rna_types();
PyModule_AddObject(bpy_types, "GeometrySet", BPyInit_geometry_set_type());
PyModule_AddObject(bpy_types, "InlineShaderNodes", BPyInit_inline_shader_nodes_type());
PyModule_AddObject(bpy_types, "BlendDataPathMeta", BPyInit_blend_data_path_meta_type());
PyModule_AddObject(mod, "types", bpy_types);
/* Needs to be first so `_bpy_types` can run. */
BPY_library_load_type_ready();
BPY_rna_data_context_type_ready();
BPY_rna_gizmo_module(mod);
/* Important to internalizes `_bpy_types` before creating RNA instances. */
{
/* Set a dummy module so the `_bpy_types.py` can access `bpy.types.ID`
* without a null pointer dereference when instancing types. */
PyObject *bpy_types_dict_dummy = PyDict_New();
BPY_rna_types_dict_set(bpy_types_dict_dummy);
PyObject *bpy_types_module_py = bpy_import_test("_bpy_types");
/* Something has gone wrong if this is ever populated. */
BLI_assert(PyDict_GET_SIZE(bpy_types_dict_dummy) == 0);
Py_DECREF(bpy_types_dict_dummy);
PyObject *bpy_types_module_py_dict = PyModule_GetDict(bpy_types_module_py);
BPY_rna_types_dict_set(bpy_types_module_py_dict);
}
PyModule_AddObject(mod, "data", BPY_rna_module());
BPY_rna_types_finalize_external_types(bpy_types);
PyModule_AddObject(mod, "props", BPY_rna_props());
PyModule_AddObject(mod, "ops", BPY_operator_module());
PyModule_AddObject(mod, "app", BPY_app_struct());
PyModule_AddObject(mod, "_utils_units", BPY_utils_units());
PyModule_AddObject(mod, "_utils_previews", BPY_utils_previews_module());
PyModule_AddObject(mod, "msgbus", BPY_msgbus_module());
PointerRNA ctx_ptr = RNA_pointer_create_discrete(nullptr, RNA_Context, C);
bpy_context_module = reinterpret_cast<BPy_StructRNA *>(pyrna_struct_CreatePyObject(&ctx_ptr));
PyModule_AddObject(mod, "context", reinterpret_cast<PyObject *>(bpy_context_module));
/* Register methods and property get/set for RNA types. */
BPY_rna_types_extend_capi(bpy_types);
#define PYMODULE_ADD_METHOD(mod, meth) \
PyModule_AddObject(mod, (meth)->ml_name, (PyObject *)PyCFunction_New(meth, mod))
for (int i = 0; bpy_methods[i].ml_name; i++) {
PyMethodDef *m = &bpy_methods[i];
/* Currently there is no need to support these. */
BLI_assert((m->ml_flags & (METH_CLASS | METH_STATIC)) == 0);
PYMODULE_ADD_METHOD(mod, m);
}
/* Register functions (`bpy_rna.cc`). */
PYMODULE_ADD_METHOD(mod, &meth_bpy_register_class);
PYMODULE_ADD_METHOD(mod, &meth_bpy_unregister_class);
PYMODULE_ADD_METHOD(mod, &meth_bpy_owner_id_get);
PYMODULE_ADD_METHOD(mod, &meth_bpy_owner_id_set);
/* Register command functions. */
PYMODULE_ADD_METHOD(mod, &BPY_cli_command_register_def);
PYMODULE_ADD_METHOD(mod, &BPY_cli_command_unregister_def);
#undef PYMODULE_ADD_METHOD
/* add our own modules dir, this is a python package */
bpy_package_py = bpy_import_test("bpy");
}
} // namespace blender

View File

@@ -0,0 +1,32 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pythonintern
*/
#pragma once
#include <Python.h>
struct CLG_LogRef;
namespace blender {
struct bContext;
/** Creates the `bpy` module and adds it to `sys.modules` for importing. */
void BPy_init_modules(bContext *C);
extern PyObject *bpy_package_py;
/* `bpy_interface_atexit.cc` */
void BPY_atexit_register();
void BPY_atexit_unregister();
extern CLG_LogRef *BPY_LOG_RNA;
extern CLG_LogRef *BPY_LOG_INTERFACE;
} // namespace blender

View File

@@ -0,0 +1,881 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pythonintern
*
* This file defines a 'PyStructSequence' accessed via 'bpy.app', mostly
* exposing static applications variables such as version and buildinfo
* however some writable variables have been added such as 'debug' and 'tempdir'
*/
#include <Python.h>
#include "bpy_app.hh"
#include "bpy_app_alembic.hh"
#include "bpy_app_build_options.hh"
#include "bpy_app_ffmpeg.hh"
#include "bpy_app_ocio.hh"
#include "bpy_app_oiio.hh"
#include "bpy_app_opensubdiv.hh"
#include "bpy_app_openvdb.hh"
#include "bpy_app_sdl.hh"
#include "bpy_app_usd.hh"
#include "bpy_app_translations.hh"
#include "bpy_app_handlers.hh"
#include "bpy_capi_utils.hh"
#include "bpy_driver.hh"
#include "BPY_extern_python.hh" /* For #BPY_python_app_help_text_fn. */
/* modules */
#include "bpy_app_icons.hh"
#include "bpy_app_timers.hh"
#include "BLI_utildefines.h"
#include "BKE_appdir.hh"
#include "BKE_blender_version.h"
#include "BKE_global.hh"
#include "BKE_main.hh"
#include "GPU_init_exit.hh"
#include "GPU_shader.hh"
#include "UI_interface_icons.hh"
#include "ED_undo.hh"
#include "MEM_guardedalloc.h"
#include "RNA_enum_types.hh" /* For `rna_enum_wm_job_type_items`. */
/* for notifiers */
#include "WM_api.hh"
#include "WM_types.hh"
#include "../generic/py_capi_rna.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
namespace blender {
#ifdef BUILD_DATE
extern "C" char build_date[];
extern "C" char build_time[];
extern "C" ulong build_commit_timestamp;
extern "C" char build_commit_date[];
extern "C" char build_commit_time[];
extern "C" char build_hash[];
extern "C" char build_branch[];
extern "C" char build_platform[];
extern "C" char build_type[];
extern "C" char build_cflags[];
extern "C" char build_cxxflags[];
extern "C" char build_linkflags[];
extern "C" char build_system[];
#endif
static PyTypeObject BlenderAppType;
static PyStructSequence_Field app_info_fields[] = {
{"version",
"The Blender version as a tuple of 3 numbers (major, minor, micro). eg. (4, 3, 1)\n"
"\n"
":type: tuple[int, int, int]\n"},
{"version_file",
"The Blender File version, as a tuple of 3 numbers (major, minor, file sub-version), that "
"will be used to save a .blend file. The last item in this tuple indicates the file "
"sub-version, which is different from the release micro version (the last item of the "
"``bpy.app.version`` tuple). The file sub-version can be incremented multiple times while a "
"Blender version is under development. This value is, and should be, used for handling "
"compatibility changes between Blender versions\n"
"\n"
":type: tuple[int, int, int]\n"},
{"version_string",
"The Blender version formatted as a string\n"
"\n"
":type: str\n"},
{"version_cycle",
"The release status of this build alpha/beta/rc/release\n"
"\n"
":type: str\n"},
{"background",
"Boolean, True when blender is running without a user interface (started with -b)\n"
"\n"
":type: bool\n"},
{"module",
"Boolean, True when running Blender as a python module\n"
"\n"
":type: bool\n"},
{"factory_startup",
"Boolean, True when blender is running with --factory-startup\n"
"\n"
":type: bool\n"},
{"portable",
"Boolean, True unless blender was built to reference absolute paths (on UNIX).\n"
"\n"
":type: bool\n"},
/* buildinfo */
{"build_date",
"The date this Blender instance was built\n"
"\n"
":type: bytes\n"},
{"build_time",
"The time this Blender instance was built\n"
"\n"
":type: bytes\n"},
{"build_commit_timestamp",
"The unix timestamp of the commit this Blender instance was built from\n"
"\n"
":type: int\n"},
{"build_commit_date",
"The date of the commit this Blender instance was built from\n"
"\n"
":type: bytes\n"},
{"build_commit_time",
"The time of the commit this Blender instance was built from\n"
"\n"
":type: bytes\n"},
{"build_hash",
"The commit hash this Blender instance was built with\n"
"\n"
":type: bytes\n"},
{"build_branch",
"The branch this blender instance was built from\n"
"\n"
":type: bytes\n"},
{"build_platform",
"The platform this blender instance was built for\n"
"\n"
":type: bytes\n"},
{"build_type",
"The type of build (Release, Debug)\n"
"\n"
":type: bytes\n"},
{"build_cflags",
"C compiler flags\n"
"\n"
":type: bytes\n"},
{"build_cxxflags",
"C++ compiler flags\n"
"\n"
":type: bytes\n"},
{"build_linkflags",
"Binary linking flags\n"
"\n"
":type: bytes\n"},
{"build_system",
"Build system used\n"
"\n"
":type: bytes\n"},
/* submodules */
{"alembic", "Alembic library information backend"},
{"usd", "USD library information backend"},
{"ffmpeg", "FFmpeg library information backend"},
{"ocio", "OpenColorIO library information backend"},
{"oiio", "OpenImageIO library information backend"},
{"opensubdiv", "OpenSubdiv library information backend"},
{"openvdb", "OpenVDB library information backend"},
{"sdl", "SDL library information backend"},
{"build_options", "A set containing most important enabled optional build features"},
{"handlers", "Application handler callbacks"},
{"translations", "Application and addons internationalization API"},
/* Modules (not struct sequence). */
{"icons", "Manage custom icons"},
{"timers", "Manage timers"},
{nullptr},
};
PyDoc_STRVAR(
/* Wrap. */
bpy_app_doc,
"This module contains application values that remain unchanged during runtime.\n");
static PyStructSequence_Desc app_info_desc = {
/*name*/ "bpy.app",
/*doc*/ bpy_app_doc,
/*fields*/ app_info_fields,
/*n_in_sequence*/ ARRAY_SIZE(app_info_fields) - 1,
};
static PyObject *make_app_info()
{
PyObject *app_info;
int pos = 0;
app_info = PyStructSequence_New(&BlenderAppType);
if (app_info == nullptr) {
return nullptr;
}
#define SetIntItem(flag) PyStructSequence_SET_ITEM(app_info, pos++, PyLong_FromLong(flag))
#define SetStrItem(str) PyStructSequence_SET_ITEM(app_info, pos++, PyUnicode_FromString(str))
#define SetBytesItem(str) PyStructSequence_SET_ITEM(app_info, pos++, PyBytes_FromString(str))
#define SetObjItem(obj) PyStructSequence_SET_ITEM(app_info, pos++, obj)
SetObjItem(
PyC_Tuple_Pack_I32({BLENDER_VERSION / 100, BLENDER_VERSION % 100, BLENDER_VERSION_PATCH}));
SetObjItem(PyC_Tuple_Pack_I32(
{BLENDER_FILE_VERSION / 100, BLENDER_FILE_VERSION % 100, BLENDER_FILE_SUBVERSION}));
SetStrItem(BKE_blender_version_string());
SetStrItem(STRINGIFY(BLENDER_VERSION_CYCLE));
SetObjItem(PyBool_FromLong(G.background));
#ifdef WITH_PYTHON_MODULE
SetObjItem(Py_NewRef(Py_True));
#else
SetObjItem(Py_NewRef(Py_False));
#endif
SetObjItem(PyBool_FromLong(G.factory_startup));
#ifdef WITH_INSTALL_PORTABLE
SetObjItem(Py_NewRef(Py_True));
#else
SetObjItem(Py_NewRef(Py_False));
#endif
/* build info, use bytes since we can't assume _any_ encoding:
* see patch #30154 for issue */
#ifdef BUILD_DATE
SetBytesItem(build_date);
SetBytesItem(build_time);
SetIntItem(build_commit_timestamp);
SetBytesItem(build_commit_date);
SetBytesItem(build_commit_time);
SetBytesItem(build_hash);
SetBytesItem(build_branch);
SetBytesItem(build_platform);
SetBytesItem(build_type);
SetBytesItem(build_cflags);
SetBytesItem(build_cxxflags);
SetBytesItem(build_linkflags);
SetBytesItem(build_system);
#else
SetBytesItem("Unknown");
SetBytesItem("Unknown");
SetIntItem(0);
SetBytesItem("Unknown");
SetBytesItem("Unknown");
SetBytesItem("Unknown");
SetBytesItem("Unknown");
SetBytesItem("Unknown");
SetBytesItem("Unknown");
SetBytesItem("Unknown");
SetBytesItem("Unknown");
SetBytesItem("Unknown");
SetBytesItem("Unknown");
#endif
SetObjItem(BPY_app_alembic_struct());
SetObjItem(BPY_app_usd_struct());
SetObjItem(BPY_app_ffmpeg_struct());
SetObjItem(BPY_app_ocio_struct());
SetObjItem(BPY_app_oiio_struct());
SetObjItem(BPY_app_opensubdiv_struct());
SetObjItem(BPY_app_openvdb_struct());
SetObjItem(BPY_app_sdl_struct());
SetObjItem(BPY_app_build_options_struct());
SetObjItem(BPY_app_handlers_struct());
SetObjItem(BPY_app_translations_struct());
/* modules */
SetObjItem(BPY_app_icons_module());
SetObjItem(BPY_app_timers_module());
#undef SetIntItem
#undef SetStrItem
#undef SetBytesItem
#undef SetObjItem
if (PyErr_Occurred()) {
Py_DECREF(app_info);
return nullptr;
}
return app_info;
}
/* a few getsets because it makes sense for them to be in bpy.app even though
* they are not static */
PyDoc_STRVAR(
/* Wrap. */
bpy_app_debug_doc,
"Boolean, for debug info "
"(started with ``--debug`` / ``--debug-*`` matching this attribute name).\n"
"\n"
":type: bool\n");
static PyObject *bpy_app_debug_get(PyObject * /*self*/, void *closure)
{
const int flag = POINTER_AS_INT(closure);
return PyBool_FromLong(G.debug & flag);
}
static int bpy_app_debug_set(PyObject * /*self*/, PyObject *value, void *closure)
{
const int flag = POINTER_AS_INT(closure);
const int param = PyObject_IsTrue(value);
if (param == -1) {
PyErr_SetString(PyExc_TypeError, "bpy.app.debug can only be True/False");
return -1;
}
if (param) {
G.debug |= flag;
}
else {
G.debug &= ~flag;
}
return 0;
}
PyDoc_STRVAR(
/* Wrap. */
bpy_app_internet_offline_doc,
"Boolean, true when internet access is allowed by Blender & 3rd party scripts "
"(read-only).\n"
"\n"
":type: bool\n");
PyDoc_STRVAR(
/* Wrap. */
bpy_app_internet_offline_override_doc,
"Boolean, true when internet access preference is overridden by the command line "
"(read-only).\n"
"\n"
":type: bool\n");
PyDoc_STRVAR(
/* Wrap. */
bpy_app_global_flag_doc,
"Boolean, for application behavior "
"(started with ``--enable-*`` matching this attribute name)\n"
"\n"
":type: bool\n");
PyDoc_STRVAR(
/* Wrap. */
bpy_app_autoexec_fail_doc,
"Boolean, True when auto-execution of scripts failed (read-only).\n"
"\n"
":type: bool\n");
PyDoc_STRVAR(
/* Wrap. */
bpy_app_autoexec_fail_quiet_doc,
"Boolean, True when auto-execution failure should be quiet, set after the warning is shown "
"once for the current blend file (read-only).\n"
"\n"
":type: bool\n");
static PyObject *bpy_app_global_flag_get(PyObject * /*self*/, void *closure)
{
const int flag = POINTER_AS_INT(closure);
return PyBool_FromLong(G.f & flag);
}
static int bpy_app_global_flag_set(PyObject * /*self*/, PyObject *value, void *closure)
{
const int flag = POINTER_AS_INT(closure);
const int param = PyObject_IsTrue(value);
if (param == -1) {
PyErr_SetString(PyExc_TypeError, "bpy.app.use_* can only be True/False");
return -1;
}
if (param) {
G.f |= flag;
}
else {
G.f &= ~flag;
}
return 0;
}
static int bpy_app_global_flag_set__only_disable(PyObject * /*self*/,
PyObject *value,
void *closure)
{
const int param = PyObject_IsTrue(value);
if (param == 1) {
PyErr_SetString(PyExc_ValueError, "This bpy.app.use_* option can only be disabled");
return -1;
}
return bpy_app_global_flag_set(nullptr, value, closure);
}
PyDoc_STRVAR(
/* Wrap. */
bpy_app_debug_value_doc,
"Integer value which can be set to non-zero values for testing purposes.\n"
"\n"
":type: int\n");
static PyObject *bpy_app_debug_value_get(PyObject * /*self*/, void * /*closure*/)
{
return PyLong_FromLong(G.debug_value);
}
static int bpy_app_debug_value_set(PyObject * /*self*/, PyObject *value, void * /*closure*/)
{
const short param = PyC_Long_AsI16(value);
if (param == -1 && PyErr_Occurred()) {
PyC_Err_SetString_Prefix(PyExc_TypeError,
"bpy.app.debug_value can only be set to a whole number");
return -1;
}
G.debug_value = param;
WM_main_add_notifier(NC_WINDOW, nullptr);
return 0;
}
PyDoc_STRVAR(
/* Wrap. */
bpy_app_tempdir_doc,
"String, the temp directory used by blender (read-only).\n"
"\n"
":type: str\n");
static PyObject *bpy_app_tempdir_get(PyObject * /*self*/, void * /*closure*/)
{
return PyC_UnicodeFromBytes(BKE_tempdir_session());
}
PyDoc_STRVAR(
/* Wrap. */
bpy_app_cachedir_doc,
"String, the cache directory used by blender (read-only).\n"
"\n"
"In rare cases the default cache directory may not be available;\n"
"in this case a temporary directory is used.\n"
"\n"
":type: str\n");
static PyObject *bpy_app_cachedir_get(PyObject * /*self*/, void * /*closure*/)
{
char cache_path[FILE_MAX];
BKE_appdir_folder_caches(cache_path, sizeof(cache_path));
BLI_assert_msg(cache_path[0], "BKE_appdir_folder_caches must never return an empty path");
return PyC_UnicodeFromBytes(cache_path);
}
PyDoc_STRVAR(
/* Wrap. */
bpy_app_driver_dict_doc,
"Dictionary for drivers namespace, editable in-place, reset on file load (read-only).\n"
"\n"
":type: dict[str, Any]\n");
static PyObject *bpy_app_driver_dict_get(PyObject * /*self*/, void * /*closure*/)
{
if (bpy_pydriver_Dict == nullptr) {
if (bpy_pydriver_create_dict() != 0) {
PyErr_SetString(PyExc_RuntimeError, "bpy.app.driver_namespace failed to create dictionary");
return nullptr;
}
}
return Py_NewRef(bpy_pydriver_Dict);
}
PyDoc_STRVAR(
/* Wrap. */
bpy_app_render_icon_size_doc,
"Reference size for icon renders (read-only).\n"
"\n"
":type: int\n");
PyDoc_STRVAR(
/* Wrap. */
bpy_app_render_preview_size_doc,
"Reference size for preview renders (read-only).\n"
"\n"
":type: int\n");
static PyObject *bpy_app_render_preview_or_icon_size_get(PyObject * /*self*/, void *closure)
{
return PyLong_FromLong(
long(ui::icon_preview_to_render_size(eIconSizes(POINTER_AS_INT(closure)))));
}
PyDoc_STRVAR(
/* Wrap. */
bpy_app_autoexec_fail_message_doc,
"String, message describing the auto-execution failure (read-only).\n"
"\n"
":type: str\n");
static PyObject *bpy_app_autoexec_fail_message_get(PyObject * /*self*/, void * /*closure*/)
{
return PyC_UnicodeFromBytes(G.autoexec_fail);
}
PyDoc_STRVAR(
/* Wrap. */
bpy_app_python_args_doc,
"Leading arguments to use when calling Python directly (via ``sys.executable``). "
"These arguments match settings Blender uses to "
"ensure Python runs with a compatible environment (read-only).\n"
"\n"
":type: tuple[str, ...]\n");
static PyObject *bpy_app_python_args_get(PyObject * /*self*/, void * /*closure*/)
{
const char *args[1];
int args_num = 0;
if (!BPY_python_use_system_env_get()) {
/* Isolated Python environment. */
args[args_num++] = "-I";
}
return PyC_Tuple_PackArray_String(args, args_num);
}
PyDoc_STRVAR(
/* Wrap. */
bpy_app_binary_path_doc,
"The location of Blender's executable, useful for utilities that open new instances. "
"Read-only unless Blender is built as a Python module - in this case the value is "
"an empty string which script authors may point to a Blender binary.\n"
"\n"
":type: str\n");
static PyObject *bpy_app_binary_path_get(PyObject * /*self*/, void * /*closure*/)
{
return PyC_UnicodeFromBytes(BKE_appdir_program_path());
}
static int bpy_app_binary_path_set(PyObject * /*self*/, PyObject *value, void * /*closure*/)
{
#ifndef WITH_PYTHON_MODULE
PyErr_SetString(PyExc_AttributeError,
"bpy.app.binary_path is only writable when built as a Python module");
return -1;
#endif
PyObject *value_coerce = nullptr;
const char *filepath = PyC_UnicodeAsBytes(value, &value_coerce);
if (filepath == nullptr) {
PyErr_Format(PyExc_ValueError, "expected a string or bytes, got %s", Py_TYPE(value)->tp_name);
return -1;
}
BKE_appdir_program_path_init(filepath);
Py_XDECREF(value_coerce);
return 0;
}
static PyGetSetDef bpy_app_getsets[] = {
{"debug",
bpy_app_debug_get,
bpy_app_debug_set,
bpy_app_debug_doc,
reinterpret_cast<void *>(G_DEBUG)},
{"debug_freestyle",
bpy_app_debug_get,
bpy_app_debug_set,
bpy_app_debug_doc,
reinterpret_cast<void *>(G_DEBUG_FREESTYLE)},
{"debug_python",
bpy_app_debug_get,
bpy_app_debug_set,
bpy_app_debug_doc,
reinterpret_cast<void *>(G_DEBUG_PYTHON)},
{"debug_events",
bpy_app_debug_get,
bpy_app_debug_set,
bpy_app_debug_doc,
reinterpret_cast<void *>(G_DEBUG_EVENTS)},
{"debug_handlers",
bpy_app_debug_get,
bpy_app_debug_set,
bpy_app_debug_doc,
reinterpret_cast<void *>(G_DEBUG_HANDLERS)},
{"debug_wm",
bpy_app_debug_get,
bpy_app_debug_set,
bpy_app_debug_doc,
reinterpret_cast<void *>(G_DEBUG_WM)},
{"debug_depsgraph",
bpy_app_debug_get,
bpy_app_debug_set,
bpy_app_debug_doc,
reinterpret_cast<void *>(G_DEBUG_DEPSGRAPH)},
{"debug_depsgraph_build",
bpy_app_debug_get,
bpy_app_debug_set,
bpy_app_debug_doc,
reinterpret_cast<void *>(G_DEBUG_DEPSGRAPH_BUILD)},
{"debug_depsgraph_eval",
bpy_app_debug_get,
bpy_app_debug_set,
bpy_app_debug_doc,
reinterpret_cast<void *>(G_DEBUG_DEPSGRAPH_EVAL)},
{"debug_depsgraph_tag",
bpy_app_debug_get,
bpy_app_debug_set,
bpy_app_debug_doc,
reinterpret_cast<void *>(G_DEBUG_DEPSGRAPH_TAG)},
{"debug_depsgraph_time",
bpy_app_debug_get,
bpy_app_debug_set,
bpy_app_debug_doc,
reinterpret_cast<void *>(G_DEBUG_DEPSGRAPH_TIME)},
{"debug_depsgraph_pretty",
bpy_app_debug_get,
bpy_app_debug_set,
bpy_app_debug_doc,
reinterpret_cast<void *>(G_DEBUG_DEPSGRAPH_PRETTY)},
{"debug_simdata",
bpy_app_debug_get,
bpy_app_debug_set,
bpy_app_debug_doc,
reinterpret_cast<void *>(G_DEBUG_SIMDATA)},
{"debug_io",
bpy_app_debug_get,
bpy_app_debug_set,
bpy_app_debug_doc,
reinterpret_cast<void *>(G_DEBUG_IO)},
{"use_event_simulate",
bpy_app_global_flag_get,
bpy_app_global_flag_set__only_disable,
bpy_app_global_flag_doc,
reinterpret_cast<void *>(G_FLAG_EVENT_SIMULATE)},
{"use_userpref_skip_save_on_exit",
bpy_app_global_flag_get,
bpy_app_global_flag_set,
bpy_app_global_flag_doc,
reinterpret_cast<void *>(G_FLAG_USERPREF_NO_SAVE_ON_EXIT)},
{"debug_value",
bpy_app_debug_value_get,
bpy_app_debug_value_set,
bpy_app_debug_value_doc,
nullptr},
{"tempdir", bpy_app_tempdir_get, nullptr, bpy_app_tempdir_doc, nullptr},
{"cachedir", bpy_app_cachedir_get, nullptr, bpy_app_cachedir_doc, nullptr},
{"driver_namespace", bpy_app_driver_dict_get, nullptr, bpy_app_driver_dict_doc, nullptr},
{"render_icon_size",
bpy_app_render_preview_or_icon_size_get,
nullptr,
bpy_app_render_icon_size_doc,
reinterpret_cast<void *>(ICON_SIZE_ICON)},
{"render_preview_size",
bpy_app_render_preview_or_icon_size_get,
nullptr,
bpy_app_render_preview_size_doc,
reinterpret_cast<void *>(ICON_SIZE_PREVIEW)},
{"online_access",
bpy_app_global_flag_get,
nullptr,
bpy_app_internet_offline_doc,
reinterpret_cast<void *>(G_FLAG_INTERNET_ALLOW)},
{"online_access_override",
bpy_app_global_flag_get,
nullptr,
bpy_app_internet_offline_override_doc,
reinterpret_cast<void *> G_FLAG_INTERNET_OVERRIDE_PREF_ANY},
/* security */
{"autoexec_fail",
bpy_app_global_flag_get,
nullptr,
bpy_app_autoexec_fail_doc,
reinterpret_cast<void *>(G_FLAG_SCRIPT_AUTOEXEC_FAIL)},
{"autoexec_fail_quiet",
bpy_app_global_flag_get,
nullptr,
bpy_app_autoexec_fail_quiet_doc,
reinterpret_cast<void *>(G_FLAG_SCRIPT_AUTOEXEC_FAIL_QUIET)},
{"autoexec_fail_message",
bpy_app_autoexec_fail_message_get,
nullptr,
bpy_app_autoexec_fail_message_doc,
nullptr},
{"python_args", bpy_app_python_args_get, nullptr, bpy_app_python_args_doc, nullptr},
/* Support script authors setting the Blender binary path to use, otherwise this value
* is not known when built as a Python module. */
{"binary_path",
bpy_app_binary_path_get,
bpy_app_binary_path_set,
bpy_app_binary_path_doc,
nullptr},
{nullptr, nullptr, nullptr, nullptr, nullptr},
};
PyDoc_STRVAR(
/* Wrap. */
bpy_app_is_job_running_doc,
".. function:: is_job_running(job_type)\n"
"\n"
" Check whether a job of the given type is running.\n"
"\n"
" :param job_type: job type in :ref:`rna_enum_wm_job_type_items`.\n"
" :type job_type: str\n"
" :return: Whether a job of the given type is currently running.\n"
" :rtype: bool\n");
static PyObject *bpy_app_is_job_running(PyObject * /*self*/, PyObject *args, PyObject *kwds)
{
BPy_EnumProperty_Parse job_type_enum{};
job_type_enum.items = rna_enum_wm_job_type_items;
job_type_enum.value = 0;
static const char *_keywords[] = {"job_type", nullptr};
static _PyArg_Parser _parser = {
"O&" /* `job_type` */
":is_job_running",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kwds, &_parser, pyrna_enum_value_parse_string, &job_type_enum))
{
return nullptr;
}
wmWindowManager *wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
if (job_type_enum.value == WM_JOB_TYPE_SHADER_COMPILATION) {
/* Shader compilation no longer uses the WM_job API, so we handle this as a special case
* to avoid breaking the Python API. */
return PyBool_FromLong(GPU_is_init() && GPU_shader_compiler_has_pending_work());
}
return PyBool_FromLong(WM_jobs_has_running_type(wm, job_type_enum.value));
}
char *(*BPY_python_app_help_text_fn)(bool all) = nullptr;
PyDoc_STRVAR(
/* Wrap. */
bpy_app_help_text_doc,
".. function:: help_text(*, all=False)\n"
"\n"
" Return the help text as a string.\n"
"\n"
" :param all: Return all arguments, "
"even those which aren't available for the current platform.\n"
" :type all: bool\n"
" :return: Help text.\n"
" :rtype: str\n");
static PyObject *bpy_app_help_text(PyObject * /*self*/, PyObject *args, PyObject *kwds)
{
bool all = false;
static const char *_keywords[] = {"all", nullptr};
static _PyArg_Parser _parser = {
"|$" /* Optional keyword only arguments. */
"O&" /* `all` */
":help_text",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, PyC_ParseBool, &all)) {
return nullptr;
}
char *buf = BPY_python_app_help_text_fn(all);
PyObject *result = PyUnicode_FromString(buf);
MEM_delete(buf);
return result;
}
#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
PyDoc_STRVAR(
/* Wrap. */
bpy_app_memory_usage_undo_doc,
".. function:: memory_usage_undo()\n"
"\n"
" Get undo memory usage information.\n"
"\n"
" :return: Memory usage of the undo stack in bytes.\n"
" :rtype: int\n");
static PyObject *bpy_app_memory_usage_undo(PyObject * /*self*/, PyObject * /*args*/)
{
size_t total_memory = 0;
UndoStack *ustack = ED_undo_stack_get();
if (ustack) {
total_memory = ED_undosys_total_memory_calc(ustack);
}
return PyLong_FromSize_t(total_memory);
}
static PyMethodDef bpy_app_methods[] = {
{"is_job_running",
reinterpret_cast<PyCFunction>(bpy_app_is_job_running),
METH_VARARGS | METH_KEYWORDS | METH_STATIC,
bpy_app_is_job_running_doc},
{"help_text",
reinterpret_cast<PyCFunction>(bpy_app_help_text),
METH_VARARGS | METH_KEYWORDS | METH_STATIC,
bpy_app_help_text_doc},
{"memory_usage_undo",
static_cast<PyCFunction>(bpy_app_memory_usage_undo),
METH_NOARGS | METH_STATIC,
bpy_app_memory_usage_undo_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
static void py_struct_seq_getset_init()
{
/* tricky dynamic members, not to py-spec! */
for (PyGetSetDef *getset = bpy_app_getsets; getset->name; getset++) {
PyObject *item = PyDescr_NewGetSet(&BlenderAppType, getset);
PyDict_SetItem(BlenderAppType.tp_dict, PyDescr_NAME(item), item);
Py_DECREF(item);
}
}
static void py_struct_seq_method_init()
{
for (PyMethodDef *method = bpy_app_methods; method->ml_name; method++) {
BLI_assert_msg(method->ml_flags & METH_STATIC, "Only static methods make sense for 'bpy.app'");
PyObject *item = PyCFunction_New(method, nullptr);
PyDict_SetItemString(BlenderAppType.tp_dict, method->ml_name, item);
Py_DECREF(item);
}
}
/* end dynamic bpy.app */
PyObject *BPY_app_struct()
{
PyObject *ret;
PyStructSequence_InitType(&BlenderAppType, &app_info_desc);
ret = make_app_info();
/* prevent user from creating new instances */
BlenderAppType.tp_init = nullptr;
BlenderAppType.tp_new = nullptr;
/* Without this we can't do `set(sys.modules)` #29635. */
BlenderAppType.tp_hash = reinterpret_cast<hashfunc>(Py_HashPointer);
/* Kind of a hack on top of #PyStructSequence. */
py_struct_seq_getset_init();
py_struct_seq_method_init();
return ret;
}
} // namespace blender

View File

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

View File

@@ -0,0 +1,97 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pythonintern
*/
#include "BLI_utildefines.h"
#include <Python.h>
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "bpy_app_alembic.hh"
#include "../generic/py_capi_utils.hh"
#ifdef WITH_ALEMBIC
# include "ABC_alembic.h"
#endif
namespace blender {
static PyTypeObject BlenderAppABCType;
static PyStructSequence_Field app_alembic_info_fields[] = {
{"supported", "Boolean, True when Blender is built with Alembic support"},
{"version", "The Alembic version as a tuple of 3 numbers"},
{"version_string", "The Alembic version formatted as a string"},
{nullptr},
};
static PyStructSequence_Desc app_alembic_info_desc = {
/*name*/ "bpy.app.alembic",
/*doc*/ "This module contains information about Alembic blender is linked against",
/*fields*/ app_alembic_info_fields,
/*n_in_sequence*/ ARRAY_SIZE(app_alembic_info_fields) - 1,
};
static PyObject *make_alembic_info()
{
PyObject *alembic_info = PyStructSequence_New(&BlenderAppABCType);
if (alembic_info == nullptr) {
return nullptr;
}
int pos = 0;
#ifndef WITH_ALEMBIC
# define SetStrItem(str) PyStructSequence_SET_ITEM(alembic_info, pos++, PyUnicode_FromString(str))
#endif
#define SetObjItem(obj) PyStructSequence_SET_ITEM(alembic_info, pos++, obj)
#ifdef WITH_ALEMBIC
const int curversion = ABC_get_version();
const int major = curversion / 10000;
const int minor = (curversion / 100) - (major * 100);
const int patch = curversion - ((curversion / 100) * 100);
SetObjItem(PyBool_FromLong(1));
SetObjItem(PyC_Tuple_Pack_I32({major, minor, patch}));
SetObjItem(PyUnicode_FromFormat("%2d, %2d, %2d", major, minor, patch));
#else
SetObjItem(PyBool_FromLong(0));
SetObjItem(PyC_Tuple_Pack_I32({0, 0, 0}));
SetStrItem("Unknown");
#endif
if (UNLIKELY(PyErr_Occurred())) {
Py_DECREF(alembic_info);
return nullptr;
}
#undef SetStrItem
#undef SetObjItem
return alembic_info;
}
PyObject *BPY_app_alembic_struct()
{
PyStructSequence_InitType(&BlenderAppABCType, &app_alembic_info_desc);
PyObject *ret = make_alembic_info();
/* prevent user from creating new instances */
BlenderAppABCType.tp_init = nullptr;
BlenderAppABCType.tp_new = nullptr;
/* Without this we can't do `set(sys.modules)` #29635. */
BlenderAppABCType.tp_hash = reinterpret_cast<hashfunc>(Py_HashPointer);
return ret;
}
} // namespace blender

View File

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

View File

@@ -0,0 +1,356 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pythonintern
*/
#include <Python.h>
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "BLI_utildefines.h"
#include "bpy_app_build_options.hh"
namespace blender {
static PyTypeObject BlenderAppBuildOptionsType;
static PyStructSequence_Field app_builtopts_info_fields[] = {
/* names mostly follow CMake options, lowercase, after `WITH_` */
{"bullet", nullptr},
{"codec_avi", nullptr},
{"codec_ffmpeg", nullptr},
{"codec_sndfile", nullptr},
{"compositor_cpu", nullptr},
{"cycles", nullptr},
{"cycles_osl", nullptr},
{"freestyle", nullptr},
{"image_cineon", nullptr},
{"image_dds", nullptr},
{"image_hdr", nullptr},
{"image_openexr", nullptr},
{"image_openjpeg", nullptr},
{"image_tiff", nullptr},
{"image_webp", nullptr},
{"input_ndof", nullptr},
{"audaspace", nullptr},
{"international", nullptr},
{"openal", nullptr},
{"opensubdiv", nullptr},
{"sdl", nullptr},
{"coreaudio", nullptr},
{"jack", nullptr},
{"pulseaudio", nullptr},
{"wasapi", nullptr},
{"libmv", nullptr},
{"mod_oceansim", nullptr},
{"mod_remesh", nullptr},
{"io_wavefront_obj", nullptr},
{"io_ply", nullptr},
{"io_stl", nullptr},
{"io_fbx", nullptr},
{"io_gpencil", nullptr},
{"opencolorio", nullptr},
{"openmp", nullptr},
{"openvdb", nullptr},
{"alembic", nullptr},
{"usd", nullptr},
{"fluid", nullptr},
{"xr_openxr", nullptr},
{"potrace", nullptr},
{"pugixml", nullptr},
{"haru", nullptr},
{"experimental_features", nullptr},
/* Sentinel (this line prevents `clang-format` wrapping into columns). */
{nullptr},
};
static PyStructSequence_Desc app_builtopts_info_desc = {
/*name*/ "bpy.app.build_options",
/*doc*/ "This module contains information about options blender is built with",
/*fields*/ app_builtopts_info_fields,
/*n_in_sequence*/ ARRAY_SIZE(app_builtopts_info_fields) - 1,
};
static PyObject *make_builtopts_info()
{
PyObject *builtopts_info;
int pos = 0;
builtopts_info = PyStructSequence_New(&BlenderAppBuildOptionsType);
if (builtopts_info == nullptr) {
return nullptr;
}
#define SetObjIncref(item) \
PyStructSequence_SET_ITEM(builtopts_info, pos++, (Py_IncRef(item), item))
#ifdef WITH_BULLET
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
/* AVI */
SetObjIncref(Py_False);
#ifdef WITH_FFMPEG
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_SNDFILE
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
/* Compositor. */
SetObjIncref(Py_True);
#ifdef WITH_CYCLES
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_CYCLES_OSL
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_FREESTYLE
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_IMAGE_CINEON
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
/* DDS */
SetObjIncref(Py_True);
/* HDR */
SetObjIncref(Py_True);
/* OpenEXR */
SetObjIncref(Py_True);
#ifdef WITH_IMAGE_OPENJPEG
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
/* TIFF */
SetObjIncref(Py_True);
#ifdef WITH_IMAGE_WEBP
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_INPUT_NDOF
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_AUDASPACE
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_INTERNATIONAL
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_OPENAL
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_OPENSUBDIV
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_SDL
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_COREAUDIO
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_JACK
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_PULSEAUDIO
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_WASAPI
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_LIBMV
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_OCEANSIM
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_MOD_REMESH
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_IO_WAVEFRONT_OBJ
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_IO_PLY
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_IO_STL
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_IO_FBX
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_IO_GREASE_PENCIL
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
/* OpenColorIO */
SetObjIncref(Py_True);
#ifdef _OPENMP
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_OPENVDB
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_ALEMBIC
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_USD
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_FLUID
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_XR_OPENXR
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_POTRACE
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_PUGIXML
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_HARU
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_EXPERIMENTAL_FEATURES
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#undef SetObjIncref
return builtopts_info;
}
PyObject *BPY_app_build_options_struct()
{
PyObject *ret;
PyStructSequence_InitType(&BlenderAppBuildOptionsType, &app_builtopts_info_desc);
ret = make_builtopts_info();
/* prevent user from creating new instances */
BlenderAppBuildOptionsType.tp_init = nullptr;
BlenderAppBuildOptionsType.tp_new = nullptr;
/* Without this we can't do `set(sys.modules)` #29635. */
BlenderAppBuildOptionsType.tp_hash = reinterpret_cast<hashfunc>(Py_HashPointer);
return ret;
}
} // namespace blender

View File

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

View File

@@ -0,0 +1,142 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pythonintern
*/
#include "BLI_utildefines.h"
#include <Python.h>
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "bpy_app_ffmpeg.hh"
#include "../generic/py_capi_utils.hh"
namespace blender {
#ifdef WITH_FFMPEG
extern "C" {
# include <libavcodec/avcodec.h>
# include <libavdevice/avdevice.h>
# include <libavformat/avformat.h>
# include <libavutil/avutil.h>
# include <libswscale/swscale.h>
}
#endif
static PyTypeObject BlenderAppFFmpegType;
#define DEF_FFMPEG_LIB_VERSION(lib) \
{(#lib "_version"), ("The " #lib " version as a tuple of 3 numbers")}, \
{ \
(#lib "_version_string"), ("The " #lib " version formatted as a string") \
}
static PyStructSequence_Field app_ffmpeg_info_fields[] = {
{"supported", "Boolean, True when Blender is built with FFmpeg support"},
DEF_FFMPEG_LIB_VERSION(avcodec),
DEF_FFMPEG_LIB_VERSION(avdevice),
DEF_FFMPEG_LIB_VERSION(avformat),
DEF_FFMPEG_LIB_VERSION(avutil),
DEF_FFMPEG_LIB_VERSION(swscale),
{nullptr},
};
#undef DEF_FFMPEG_LIB_VERSION
static PyStructSequence_Desc app_ffmpeg_info_desc = {
/*name*/ "bpy.app.ffmpeg",
/*doc*/ "This module contains information about FFmpeg blender is linked against",
/*fields*/ app_ffmpeg_info_fields,
/*n_in_sequence*/ ARRAY_SIZE(app_ffmpeg_info_fields) - 1,
};
static PyObject *make_ffmpeg_info()
{
PyObject *ffmpeg_info;
int pos = 0;
#ifdef WITH_FFMPEG
int curversion;
#endif
ffmpeg_info = PyStructSequence_New(&BlenderAppFFmpegType);
if (ffmpeg_info == nullptr) {
return nullptr;
}
#if 0 /* UNUSED */
# define SetIntItem(flag) PyStructSequence_SET_ITEM(ffmpeg_info, pos++, PyLong_FromLong(flag))
#endif
#ifndef WITH_FFMPEG
# define SetStrItem(str) PyStructSequence_SET_ITEM(ffmpeg_info, pos++, PyUnicode_FromString(str))
#endif
#define SetObjItem(obj) PyStructSequence_SET_ITEM(ffmpeg_info, pos++, obj)
#ifdef WITH_FFMPEG
# define FFMPEG_LIB_VERSION(lib) \
{ \
curversion = lib##_version(); \
SetObjItem( \
PyC_Tuple_Pack_I32({curversion >> 16, (curversion >> 8) % 256, curversion % 256})); \
SetObjItem(PyUnicode_FromFormat( \
"%2d, %2d, %2d", curversion >> 16, (curversion >> 8) % 256, curversion % 256)); \
} \
(void)0
#else
# define FFMPEG_LIB_VERSION(lib) \
{ \
SetStrItem("Unknown"); \
SetStrItem("Unknown"); \
} \
(void)0
#endif
#ifdef WITH_FFMPEG
SetObjItem(PyBool_FromLong(1));
#else
SetObjItem(PyBool_FromLong(0));
#endif
FFMPEG_LIB_VERSION(avcodec);
FFMPEG_LIB_VERSION(avdevice);
FFMPEG_LIB_VERSION(avformat);
FFMPEG_LIB_VERSION(avutil);
FFMPEG_LIB_VERSION(swscale);
#undef FFMPEG_LIB_VERSION
if (UNLIKELY(PyErr_Occurred())) {
Py_DECREF(ffmpeg_info);
return nullptr;
}
// #undef SetIntItem
#undef SetStrItem
#undef SetObjItem
return ffmpeg_info;
}
PyObject *BPY_app_ffmpeg_struct()
{
PyObject *ret;
PyStructSequence_InitType(&BlenderAppFFmpegType, &app_ffmpeg_info_desc);
ret = make_ffmpeg_info();
/* prevent user from creating new instances */
BlenderAppFFmpegType.tp_init = nullptr;
BlenderAppFFmpegType.tp_new = nullptr;
/* Without this we can't do `set(sys.modules)` #29635. */
BlenderAppFFmpegType.tp_hash = reinterpret_cast<hashfunc>(Py_HashPointer);
return ret;
}
} // namespace blender

View File

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

View File

@@ -0,0 +1,499 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pythonintern
*
* This file defines a #PyStructSequence accessed via `bpy.app.handlers`,
* which exposes various lists that the script author can add callback
* functions into (called via blenders generic BLI_cb API)
*/
#include "BLI_utildefines.h"
#include <Python.h>
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "BKE_callbacks.hh"
#include "RNA_access.hh"
#include "bpy_app_handlers.hh"
#include "bpy_rna.hh"
#include "BPY_extern.hh"
namespace blender {
void bpy_app_generic_callback(Main *main,
PointerRNA **pointers,
const int pointers_num,
void *arg);
static PyTypeObject BlenderAppCbType;
#define FILEPATH_SAVE_ARG \
"Accepts one argument: " \
"the file being saved, an empty string for the startup-file."
#define PYDOC_FILEPATH_SAVE_TYPE "\n\n:type: list[Callable[[str], None]]"
#define FILEPATH_LOAD_ARG \
"Accepts one argument: " \
"the file being loaded, an empty string for the startup-file."
#define PYDOC_FILEPATH_LOAD_TYPE "\n\n:type: list[Callable[[str], None]]"
#define RENDER_STATS_ARG \
"Accepts one argument: " \
"the render progress as a string containing current frame, current sample, render time and " \
"saving time."
#define PYDOC_RENDER_STATS_TYPE "\n\n:type: list[Callable[[str], None]]"
#define DEPSGRAPH_UPDATE_ARG \
"Accepts one or two arguments: " \
"The scene data-block, and optionally the dependency graph being updated"
#define PYDOC_DEPSGRAPH_UPDATE_TYPE \
"\n\n:type: list[" \
"Callable[[bpy.types.Scene, bpy.types.Depsgraph], None] | " \
"Callable[[bpy.types.Scene], None]" \
"]"
#define RENDER_ARG \
"Accepts one argument: " \
"the scene data-block being rendered"
#define PYDOC_RENDER_TYPE "\n\n:type: list[Callable[[bpy.types.Scene], None]]"
#define OBJECT_BAKE_ARG \
"Accepts one argument: " \
"the object data-block being baked"
#define PYDOC_OBJECT_BAKE_TYPE "\n\n:type: list[Callable[[bpy.types.Object], None]]"
#define COMPOSITE_ARG \
"Accepts one argument: " \
"the scene data-block"
#define PYDOC_COMPOSITE_TYPE "\n\n:type: list[Callable[[bpy.types.Scene], None]]"
#define ANNOTATION_ARG \
"Accepts two arguments: " \
"the annotation data-block and dependency graph"
#define PYDOC_ANNOTATION_TYPE \
"\n\n:type: list[Callable[[bpy.types.GreasePencil, bpy.types.Depsgraph], None]]"
#define BLENDIMPORT_ARG \
"Accepts one argument: " \
"a BlendImportContext"
#define PYDOC_BLENDIMPORT_TYPE "\n\n:type: list[Callable[[bpy.types.BlendImportContext], None]]"
#define SCENE_ARG \
"Accepts one argument: " \
"the scene data-block"
#define PYDOC_SCENE_TYPE "\n\n:type: list[Callable[[bpy.types.Scene], None]]"
#define PYDOC_HANDLER_TYPE_NONE "\n\n:type: list[Callable[[], None]]"
#define PYDOC_HANDLER_TYPE_BOOL "\n\n:type: list[Callable[[bool], None]]"
/**
* See `BKE_callbacks.hh` #eCbEvent declaration for the policy on naming.
*/
static PyStructSequence_Field app_cb_info_fields[] = {
{"frame_change_pre",
"Called when a frame change is triggered for playback and rendering, "
"before any data is evaluated for the new frame. "
"This makes it possible to change data and relations (for example swap an object "
"to another mesh) for the new frame. Note that this handler is **not** to be used as 'before "
"the frame changes' event. The dependency graph is not available in this handler, as data "
"and relations may have been altered and the dependency graph has not yet been updated for "
"that. " DEPSGRAPH_UPDATE_ARG PYDOC_DEPSGRAPH_UPDATE_TYPE},
{"frame_change_post",
"Called after frame change for playback and rendering, after the data has been evaluated "
"for the new frame. " DEPSGRAPH_UPDATE_ARG PYDOC_DEPSGRAPH_UPDATE_TYPE},
{"render_pre", "on render (before). " RENDER_ARG PYDOC_RENDER_TYPE},
{"render_post", "on render (after). " RENDER_ARG PYDOC_RENDER_TYPE},
{"render_write",
"on writing a render frame (directly after the frame is written). " RENDER_ARG
PYDOC_RENDER_TYPE},
{"render_stats", "on printing render statistics. " RENDER_STATS_ARG PYDOC_RENDER_STATS_TYPE},
{"render_init", "on initialization of a render job. " RENDER_ARG PYDOC_RENDER_TYPE},
{"render_complete", "on completion of render job. " RENDER_ARG PYDOC_RENDER_TYPE},
{"render_cancel", "on canceling a render job. " RENDER_ARG PYDOC_RENDER_TYPE},
{"load_pre",
"on loading a new blend file (before). " FILEPATH_LOAD_ARG PYDOC_FILEPATH_LOAD_TYPE},
{"load_post",
"on loading a new blend file (after). " FILEPATH_LOAD_ARG PYDOC_FILEPATH_LOAD_TYPE},
{"load_post_fail",
"on failure to load a new blend file (after). " FILEPATH_LOAD_ARG PYDOC_FILEPATH_LOAD_TYPE},
{"save_pre", "on saving a blend file (before). " FILEPATH_SAVE_ARG PYDOC_FILEPATH_SAVE_TYPE},
{"save_post", "on saving a blend file (after). " FILEPATH_SAVE_ARG PYDOC_FILEPATH_SAVE_TYPE},
{"save_post_fail",
"on failure to save a blend file (after). " FILEPATH_SAVE_ARG PYDOC_FILEPATH_SAVE_TYPE},
{"undo_pre", "on loading an undo step (before). " SCENE_ARG PYDOC_SCENE_TYPE},
{"undo_post", "on loading an undo step (after). " SCENE_ARG PYDOC_SCENE_TYPE},
{"redo_pre", "on loading a redo step (before). " SCENE_ARG PYDOC_SCENE_TYPE},
{"redo_post", "on loading a redo step (after). " SCENE_ARG PYDOC_SCENE_TYPE},
{"depsgraph_update_pre",
"on depsgraph update (pre). " DEPSGRAPH_UPDATE_ARG PYDOC_DEPSGRAPH_UPDATE_TYPE},
{"depsgraph_update_post",
"on depsgraph update (post). " DEPSGRAPH_UPDATE_ARG PYDOC_DEPSGRAPH_UPDATE_TYPE},
{"version_update", "on ending the versioning code" PYDOC_HANDLER_TYPE_NONE},
{"load_factory_preferences_post",
"on loading factory preferences (after)" PYDOC_HANDLER_TYPE_NONE},
{"load_factory_startup_post", "on loading factory startup (after)" PYDOC_HANDLER_TYPE_NONE},
{"xr_session_start_pre", "on starting an xr session (before)" PYDOC_HANDLER_TYPE_NONE},
{"annotation_pre", "on drawing an annotation (before). " ANNOTATION_ARG PYDOC_ANNOTATION_TYPE},
{"annotation_post", "on drawing an annotation (after). " ANNOTATION_ARG PYDOC_ANNOTATION_TYPE},
{"object_bake_pre", "before starting a bake job. " OBJECT_BAKE_ARG PYDOC_OBJECT_BAKE_TYPE},
{"object_bake_complete",
"on completing a bake job; will be called in the main "
"thread. " OBJECT_BAKE_ARG PYDOC_OBJECT_BAKE_TYPE},
{"object_bake_cancel",
"on canceling a bake job; will be called in the main "
"thread. " OBJECT_BAKE_ARG PYDOC_OBJECT_BAKE_TYPE},
{"composite_pre",
"on a compositing background job (before). " COMPOSITE_ARG PYDOC_COMPOSITE_TYPE},
{"composite_post",
"on a compositing background job (after). " COMPOSITE_ARG PYDOC_COMPOSITE_TYPE},
{"composite_cancel",
"on a compositing background job (cancel). " COMPOSITE_ARG PYDOC_COMPOSITE_TYPE},
{"animation_playback_pre",
"on starting animation playback. " DEPSGRAPH_UPDATE_ARG PYDOC_DEPSGRAPH_UPDATE_TYPE},
{"animation_playback_post",
"on ending animation playback. " DEPSGRAPH_UPDATE_ARG PYDOC_DEPSGRAPH_UPDATE_TYPE},
{"translation_update_post", "on translation settings update" PYDOC_HANDLER_TYPE_NONE},
/* NOTE(@ideasman42): This avoids bad-level calls into BPY API
* but should not be considered part of the public Python API.
* If there is a compelling reason to make these public, the leading `_` can be removed. */
{"_extension_repos_update_pre", "on changes to extension repos (before)"},
{"_extension_repos_update_post", "on changes to extension repos (after)"},
{"_extension_repos_sync", "on creating or synchronizing the active repository"},
{"_extension_repos_files_clear",
"remove files from the repository directory (uses as a string argument)"},
{"blend_import_pre",
"on linking or appending data (before). " BLENDIMPORT_ARG PYDOC_BLENDIMPORT_TYPE},
{"blend_import_post",
"on linking or appending data (after). " BLENDIMPORT_ARG PYDOC_BLENDIMPORT_TYPE},
{"exit_pre",
"just before Blender shuts down, while all data is still valid. "
"Accepts one boolean argument. True indicates either that a user has been using Blender and "
"exited, or that Blender is exiting in a circumstance that should be treated as if that were "
"the case. False indicates that Blender is running in background mode, or is exiting due to "
"failed command line arguments, etc." PYDOC_HANDLER_TYPE_BOOL},
/* sets the permanent tag */
#define APP_CB_OTHER_FIELDS 1
{"persistent",
"Function decorator for callback functions not to be removed when loading new files"
"\n\n:type: type"},
{nullptr},
};
static PyStructSequence_Desc app_cb_info_desc = {
/*name*/ "bpy.app.handlers",
/*doc*/ "This module contains callback lists",
/*fields*/ app_cb_info_fields,
/*n_in_sequence*/ ARRAY_SIZE(app_cb_info_fields) - 1,
};
#if 0
# if (BKE_CB_EVT_TOT != ARRAY_SIZE(app_cb_info_fields))
# error "Callbacks are out of sync"
# endif
#endif
/* -------------------------------------------------------------------- */
/** \name Permanent Tagging Code
* \{ */
#define PERMINENT_CB_ID "_bpy_persistent"
static PyObject *bpy_app_handlers_persistent_new(PyTypeObject * /*type*/,
PyObject *args,
PyObject * /*kwds*/)
{
PyObject *value;
if (!PyArg_ParseTuple(args, "O:bpy.app.handlers.persistent", &value)) {
return nullptr;
}
if (PyFunction_Check(value)) {
PyObject **dict_ptr = _PyObject_GetDictPtr(value);
if (dict_ptr == nullptr) {
PyErr_SetString(PyExc_ValueError,
"bpy.app.handlers.persistent wasn't able to "
"get the dictionary from the function passed");
return nullptr;
}
/* set id */
if (*dict_ptr == nullptr) {
*dict_ptr = PyDict_New();
}
PyDict_SetItemString(*dict_ptr, PERMINENT_CB_ID, Py_None);
Py_INCREF(value);
return value;
}
PyErr_SetString(PyExc_ValueError, "bpy.app.handlers.persistent expected a function");
return nullptr;
}
/** Dummy type because decorators can't be a #PyCFunction. */
static PyTypeObject BPyPersistent_Type = {
#if defined(_MSC_VER)
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
#else
/*ob_base*/ PyVarObject_HEAD_INIT(&PyType_Type, 0)
#endif
/*tp_name*/ "persistent",
/*tp_basicsize*/ 0,
/*tp_itemsize*/ 0,
/*tp_dealloc*/ nullptr,
/*tp_vectorcall_offset*/ 0,
/*tp_getattr*/ nullptr,
/*tp_setattr*/ nullptr,
/*tp_as_async*/ nullptr,
/*tp_repr*/ nullptr,
/*tp_as_number*/ nullptr,
/*tp_as_sequence*/ nullptr,
/*tp_as_mapping*/ nullptr,
/*tp_hash*/ nullptr,
/*tp_call*/ nullptr,
/*tp_str*/ nullptr,
/*tp_getattro*/ nullptr,
/*tp_setattro*/ nullptr,
/*tp_as_buffer*/ nullptr,
/*tp_flags*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
/*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*/ nullptr,
/*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*/ bpy_app_handlers_persistent_new,
/*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,
};
/** \} */
static PyObject *py_cb_array[BKE_CB_EVT_TOT] = {nullptr};
static PyObject *make_app_cb_info()
{
PyObject *app_cb_info;
int pos;
app_cb_info = PyStructSequence_New(&BlenderAppCbType);
if (app_cb_info == nullptr) {
return nullptr;
}
for (pos = 0; pos < BKE_CB_EVT_TOT; pos++) {
if (app_cb_info_fields[pos].name == nullptr) {
Py_FatalError("invalid callback slots 1");
}
PyStructSequence_SET_ITEM(app_cb_info, pos, (py_cb_array[pos] = PyList_New(0)));
}
if (app_cb_info_fields[pos + APP_CB_OTHER_FIELDS].name != nullptr) {
Py_FatalError("invalid callback slots 2");
}
/* custom function */
PyStructSequence_SET_ITEM(app_cb_info, pos++, (PyObject *)&BPyPersistent_Type);
return app_cb_info;
}
PyObject *BPY_app_handlers_struct()
{
PyObject *ret;
#if defined(_MSC_VER)
BPyPersistent_Type.ob_base.ob_base.ob_type = &PyType_Type;
#endif
if (PyType_Ready(&BPyPersistent_Type) < 0) {
BLI_assert_msg(0, "error initializing 'bpy.app.handlers.persistent'");
}
PyStructSequence_InitType(&BlenderAppCbType, &app_cb_info_desc);
ret = make_app_cb_info();
/* prevent user from creating new instances */
BlenderAppCbType.tp_init = nullptr;
BlenderAppCbType.tp_new = nullptr;
/* Without this we can't do `set(sys.modules)` #29635. */
BlenderAppCbType.tp_hash = reinterpret_cast<hashfunc>(Py_HashPointer);
/* assign the C callbacks */
if (ret) {
static bCallbackFuncStore funcstore_array[BKE_CB_EVT_TOT] = {{nullptr}};
bCallbackFuncStore *funcstore;
int pos = 0;
for (pos = 0; pos < BKE_CB_EVT_TOT; pos++) {
funcstore = &funcstore_array[pos];
funcstore->func = bpy_app_generic_callback;
funcstore->alloc = 0;
funcstore->arg = POINTER_FROM_INT(pos);
BKE_callback_add(funcstore, eCbEvent(pos));
}
}
return ret;
}
void BPY_app_handlers_reset(const bool do_all)
{
PyGILState_STATE gilstate = PyGILState_Ensure();
int pos = 0;
if (do_all) {
for (pos = 0; pos < BKE_CB_EVT_TOT; pos++) {
/* clear list */
PyList_SetSlice(py_cb_array[pos], 0, PY_SSIZE_T_MAX, nullptr);
}
}
else {
/* save string conversion thrashing */
PyObject *perm_id_str = PyUnicode_FromString(PERMINENT_CB_ID);
for (pos = 0; pos < BKE_CB_EVT_TOT; pos++) {
/* clear only items without PERMINENT_CB_ID */
PyObject *ls = py_cb_array[pos];
Py_ssize_t i;
for (i = PyList_GET_SIZE(ls) - 1; i >= 0; i--) {
PyObject *item = PyList_GET_ITEM(ls, i);
if (PyMethod_Check(item)) {
PyObject *item_test = PyMethod_GET_FUNCTION(item);
if (item_test) {
item = item_test;
}
}
PyObject **dict_ptr;
if (PyFunction_Check(item) && (dict_ptr = _PyObject_GetDictPtr(item)) && (*dict_ptr) &&
(PyDict_GetItem(*dict_ptr, perm_id_str) != nullptr))
{
/* keep */
}
else {
/* remove */
// PySequence_DelItem(ls, i); /* more obvious but slower */
PyList_SetSlice(ls, i, i + 1, nullptr);
}
}
}
Py_DECREF(perm_id_str);
}
PyGILState_Release(gilstate);
}
static PyObject *choose_arguments(PyObject *func, PyObject *args_all, PyObject *args_single)
{
if (!PyFunction_Check(func)) {
return args_all;
}
PyCodeObject *code = reinterpret_cast<PyCodeObject *>(PyFunction_GetCode(func));
if (code->co_argcount == 1) {
return args_single;
}
return args_all;
}
/* the actual callback - not necessarily called from py */
void bpy_app_generic_callback(Main * /*main*/,
PointerRNA **pointers,
const int pointers_num,
void *arg)
{
PyObject *cb_list = py_cb_array[POINTER_AS_INT(arg)];
if (PyList_GET_SIZE(cb_list) > 0) {
const PyGILState_STATE gilstate = PyGILState_Ensure();
const int num_arguments = 2;
PyObject *args_all = PyTuple_New(num_arguments); /* save python creating each call */
PyObject *args_single = PyTuple_New(1);
PyObject *func;
PyObject *ret;
Py_ssize_t pos;
/* setup arguments */
for (int i = 0; i < pointers_num; ++i) {
PyTuple_SET_ITEM(
args_all, i, pyrna_struct_CreatePyObject_with_primitive_support(pointers[i]));
}
for (int i = pointers_num; i < num_arguments; ++i) {
PyTuple_SET_ITEM(args_all, i, Py_NewRef(Py_None));
}
if (pointers_num == 0) {
PyTuple_SET_ITEM(args_single, 0, Py_NewRef(Py_None));
}
else {
PyTuple_SET_ITEM(
args_single, 0, pyrna_struct_CreatePyObject_with_primitive_support(pointers[0]));
}
/* Iterate the list and run the callbacks
* NOTE: don't store the list size since the scripts may remove themselves. */
for (pos = 0; pos < PyList_GET_SIZE(cb_list); pos++) {
func = PyList_GET_ITEM(cb_list, pos);
PyObject *args = choose_arguments(func, args_all, args_single);
ret = PyObject_Call(func, args, nullptr);
if (ret == nullptr) {
/* Don't set last system variables because they might cause some
* dangling pointers to external render engines (when exception
* happens during rendering) which will break logic of render pipeline
* which expects to be the only user of render engine when rendering
* is finished. */
/* Note the handler called, the exception itself typically has the function name. */
PySys_WriteStderr("Error in bpy.app.handlers.%s[%d]:\n",
app_cb_info_fields[POINTER_AS_INT(arg)].name,
int(pos));
PyErr_PrintEx(0);
}
else {
Py_DECREF(ret);
}
}
Py_DECREF(args_all);
Py_DECREF(args_single);
PyGILState_Release(gilstate);
}
}
} // namespace blender

View File

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

View File

@@ -0,0 +1,219 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pythonintern
*
* Runtime defined icons.
*/
#include <Python.h>
#include "MEM_guardedalloc.h"
#include "BKE_icons.hh"
#include "../generic/py_capi_utils.hh"
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "bpy_app_icons.hh"
namespace blender {
/* We may want to load direct from file. */
PyDoc_STRVAR(
/* Wrap. */
bpy_app_icons_new_triangles_doc,
".. function:: new_triangles(range, coords, colors)\n"
"\n"
" Create a new icon from triangle geometry.\n"
"\n"
" :param range: Pair of ints.\n"
" :type range: tuple[int, int]\n"
" :param coords: Sequence of bytes (6 floats for one triangle) for (X, Y) coordinates.\n"
" :type coords: bytes\n"
" :param colors: Sequence of bytes (12 for one triangle) for RGBA.\n"
" :type colors: bytes\n"
" :return: Unique icon value (pass to interface ``icon_value`` argument).\n"
" :rtype: int\n");
static PyObject *bpy_app_icons_new_triangles(PyObject * /*self*/, PyObject *args, PyObject *kw)
{
/* bytes */
uchar coords_range[2];
PyObject *py_coords, *py_colors;
static const char *_keywords[] = {"range", "coords", "colors", nullptr};
static _PyArg_Parser _parser = {
"(BB)" /* `range` */
"S" /* `coords` */
"S" /* `colors` */
":new_triangles",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kw, &_parser, &coords_range[0], &coords_range[1], &py_coords, &py_colors))
{
return nullptr;
}
const int coords_len = PyBytes_GET_SIZE(py_coords);
const int tris_len = coords_len / 6;
if (tris_len * 6 != coords_len) {
PyErr_SetString(PyExc_ValueError, "coords must be multiple of 6");
return nullptr;
}
if (PyBytes_GET_SIZE(py_colors) != 2 * coords_len) {
PyErr_SetString(PyExc_ValueError, "colors must be twice size of coords");
return nullptr;
}
const size_t items_num = size_t(tris_len) * 3;
uchar(*coords)[2] = MEM_new_array_uninitialized<uchar[2]>(items_num, __func__);
uchar(*colors)[4] = MEM_new_array_uninitialized<uchar[4]>(items_num, __func__);
memcpy(coords, PyBytes_AS_STRING(py_coords), sizeof(*coords) * items_num);
memcpy(colors, PyBytes_AS_STRING(py_colors), sizeof(*colors) * items_num);
Icon_Geom *geom = MEM_new_uninitialized<Icon_Geom>(__func__);
geom->coords_len = tris_len;
geom->coords_range[0] = coords_range[0];
geom->coords_range[1] = coords_range[1];
geom->coords = coords;
geom->colors = colors;
geom->icon_id = 0;
geom->mem = nullptr;
const int icon_id = BKE_icon_geom_ensure(geom);
return PyLong_FromLong(icon_id);
}
PyDoc_STRVAR(
/* Wrap. */
bpy_app_icons_new_triangles_from_file_doc,
".. function:: new_triangles_from_file(filepath)\n"
"\n"
" Create a new icon from triangle geometry.\n"
"\n"
" :param filepath: File path.\n"
" :type filepath: str | bytes\n"
" :return: Unique icon value (pass to interface ``icon_value`` argument).\n"
" :rtype: int\n");
static PyObject *bpy_app_icons_new_triangles_from_file(PyObject * /*self*/,
PyObject *args,
PyObject *kw)
{
PyC_UnicodeAsBytesAndSize_Data filepath_data = {nullptr};
static const char *_keywords[] = {"filepath", nullptr};
static _PyArg_Parser _parser = {
"O&" /* `filepath` */
":new_triangles_from_file",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(
args, kw, &_parser, PyC_ParseUnicodeAsBytesAndSize, &filepath_data))
{
return nullptr;
}
Icon_Geom *geom = BKE_icon_geom_from_file(filepath_data.value);
Py_XDECREF(filepath_data.value_coerce);
if (geom == nullptr) {
PyErr_SetString(PyExc_ValueError, "Unable to load from file");
return nullptr;
}
const int icon_id = BKE_icon_geom_ensure(geom);
return PyLong_FromLong(icon_id);
}
PyDoc_STRVAR(
/* Wrap. */
bpy_app_icons_release_doc,
".. function:: release(icon_id)\n"
"\n"
" Release the icon.\n"
"\n"
" :param icon_id: The icon id to release.\n"
" :type icon_id: int\n");
static PyObject *bpy_app_icons_release(PyObject * /*self*/, PyObject *args, PyObject *kw)
{
int icon_id;
static const char *_keywords[] = {"icon_id", nullptr};
static _PyArg_Parser _parser = {
"i" /* `icon_id` */
":release",
_keywords,
nullptr,
};
if (!_PyArg_ParseTupleAndKeywordsFast(args, kw, &_parser, &icon_id)) {
return nullptr;
}
if (!BKE_icon_delete_unmanaged(icon_id)) {
PyErr_SetString(PyExc_ValueError, "invalid icon_id");
return nullptr;
}
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 M_AppIcons_methods[] = {
{"new_triangles",
reinterpret_cast<PyCFunction>(bpy_app_icons_new_triangles),
METH_VARARGS | METH_KEYWORDS,
bpy_app_icons_new_triangles_doc},
{"new_triangles_from_file",
reinterpret_cast<PyCFunction>(bpy_app_icons_new_triangles_from_file),
METH_VARARGS | METH_KEYWORDS,
bpy_app_icons_new_triangles_from_file_doc},
{"release",
reinterpret_cast<PyCFunction>(bpy_app_icons_release),
METH_VARARGS | METH_KEYWORDS,
bpy_app_icons_release_doc},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
static PyModuleDef M_AppIcons_module_def = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "bpy.app.icons",
/*m_doc*/ nullptr,
/*m_size*/ 0,
/*m_methods*/ M_AppIcons_methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
PyObject *BPY_app_icons_module()
{
PyObject *sys_modules = PyImport_GetModuleDict();
PyObject *mod = PyModule_Create(&M_AppIcons_module_def);
PyC_Module_AddToSysModules(sys_modules, mod);
return mod;
}
} // namespace blender

View File

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

View File

@@ -0,0 +1,83 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pythonintern
*/
#include "BLI_utildefines.h"
#include <Python.h>
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
#include "bpy_app_ocio.hh"
#include "../generic/py_capi_utils.hh"
#include "OCIO_version.hh"
namespace blender {
static PyTypeObject BlenderAppOCIOType;
static PyStructSequence_Field app_ocio_info_fields[] = {
{"supported", "Boolean, True when Blender is built with OpenColorIO support"},
{"version", "The OpenColorIO version as a tuple of 3 numbers"},
{"version_string", "The OpenColorIO version formatted as a string"},
{nullptr},
};
static PyStructSequence_Desc app_ocio_info_desc = {
/*name*/ "bpy.app.ocio",
/*doc*/ "This module contains information about OpenColorIO blender is linked against",
/*fields*/ app_ocio_info_fields,
/*n_in_sequence*/ ARRAY_SIZE(app_ocio_info_fields) - 1,
};
static PyObject *make_ocio_info()
{
PyObject *ocio_info;
int pos = 0;
ocio_info = PyStructSequence_New(&BlenderAppOCIOType);
if (ocio_info == nullptr) {
return nullptr;
}
#define SetObjItem(obj) PyStructSequence_SET_ITEM(ocio_info, pos++, obj)
const ocio::Version ocio_version = ocio::get_version();
SetObjItem(PyBool_FromLong(1));
SetObjItem(PyC_Tuple_Pack_I32({ocio_version.major, ocio_version.minor, ocio_version.patch}));
SetObjItem(PyUnicode_FromFormat(
"%2d, %2d, %2d", ocio_version.major, ocio_version.minor, ocio_version.patch));
if (UNLIKELY(PyErr_Occurred())) {
Py_DECREF(ocio_info);
return nullptr;
}
#undef SetObjItem
return ocio_info;
}
PyObject *BPY_app_ocio_struct()
{
PyObject *ret;
PyStructSequence_InitType(&BlenderAppOCIOType, &app_ocio_info_desc);
ret = make_ocio_info();
/* prevent user from creating new instances */
BlenderAppOCIOType.tp_init = nullptr;
BlenderAppOCIOType.tp_new = nullptr;
/* Without this we can't do `set(sys.modules)` #29635. */
BlenderAppOCIOType.tp_hash = reinterpret_cast<hashfunc>(Py_HashPointer);
return ret;
}
} // namespace blender

View File

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

Some files were not shown because too many files have changed in this diff Show More