Files
test2/source/blender/python/intern/bpy_inline_shader_nodes.cc
Jacques Lucke c3f49cd24e Shader Nodes: add Python API for inlined shader nodes
This makes the shader node inlining from #141936 available to external renderers
which use the Python API. Existing external renderer add-ons need to be updated
to get the inlined node tree from a material like below instead of using the
original node tree of the material directly.

The main contribution are these three methods: `Material.inline_shader_nodes()`,
`Light.inline_shader_nodes()` and `World.inline_shader_nodes()`.

In theory, there could be an inlining API for node trees more generally, but
some aspects of the inlining are specific to shader nodes currently. For example
the detection of output nodes and implicit input handling. Furthermore, having
the method on e.g. `Material` instead of on the node tree might be more future
proof for the case when we want to store input properties of the material on the
`Material` which are then passed into the shader node tree.

Example from API docs:
```python
import bpy

# The materials should be retrieved from the evaluated object to make sure that
# e.g. edits of Geometry Nodes are applied.
depsgraph = bpy.context.view_layer.depsgraph
ob = bpy.context.active_object
ob_eval = depsgraph.id_eval_get(ob)
material_eval = ob_eval.material_slots[0].material

# Compute the inlined shader nodes.
# Important: Do not loose the reference to this object while accessing the inlined
#   node tree. Otherwise there will be a crash due to a dangling pointer.
inline_shader_nodes = material_eval.inline_shader_nodes()

# Get the actual inlined `bpy.types.NodeTree`.
tree = inline_shader_nodes.node_tree

for node in tree.nodes:
    print(node.name)
```

Pull Request: https://projects.blender.org/blender/blender/pulls/145811
2025-09-11 06:08:30 +02:00

270 lines
8.6 KiB
C++

/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup pythonintern
*/
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_node.hh"
#include "DNA_light_types.h"
#include "DNA_material_types.h"
#include "DNA_node_types.h"
#include "DNA_world_types.h"
#include "NOD_shader_nodes_inline.hh"
#include "bpy_inline_shader_nodes.hh"
#include "bpy_rna.hh"
#include "../generic/py_capi_utils.hh"
extern PyTypeObject bpy_inline_shader_nodes_Type;
struct BPy_InlineShaderNodes {
PyObject_HEAD
bNodeTree *inline_node_tree;
};
static BPy_InlineShaderNodes *create_from_shader_node_tree(const bNodeTree &tree)
{
BPy_InlineShaderNodes *self = reinterpret_cast<BPy_InlineShaderNodes *>(
bpy_inline_shader_nodes_Type.tp_alloc(&bpy_inline_shader_nodes_Type, 0));
if (!self) {
return nullptr;
}
self->inline_node_tree = blender::bke::node_tree_add_tree(
nullptr, (blender::StringRef(tree.id.name) + " Inlined").c_str(), tree.idname);
blender::nodes::InlineShaderNodeTreeParams params;
blender::nodes::inline_shader_node_tree(tree, *self->inline_node_tree, params);
return self;
}
PyDoc_STRVAR(
/* Wrap. */
bpy_inline_shader_nodes_from_material_doc,
".. staticmethod:: from_material(material)\n"
"\n"
" Create an inlined shader node tree from a material.\n"
"\n"
" :arg material: The material to inline the node tree of.\n"
" :type material: bpy.types.Material\n");
static BPy_InlineShaderNodes *BPy_InlineShaderNodes_static_from_material(PyObject * /*self*/,
PyObject *args,
PyObject *kwds)
{
static const char *kwlist[] = {"material", nullptr};
PyObject *py_material;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", const_cast<char **>(kwlist), &py_material)) {
return nullptr;
}
ID *material_id = nullptr;
if (!pyrna_id_FromPyObject(py_material, &material_id)) {
PyErr_Format(
PyExc_TypeError, "Expected a Material, not %.200s", Py_TYPE(py_material)->tp_name);
return nullptr;
}
if (GS(material_id->name) != ID_MA) {
PyErr_Format(PyExc_TypeError,
"Expected a Material, not %.200s",
BKE_idtype_idcode_to_name(GS(material_id->name)));
return nullptr;
}
Material *material = blender::id_cast<Material *>(material_id);
if (!material->nodetree) {
PyErr_Format(PyExc_TypeError, "Material '%s' has no node tree", BKE_id_name(*material_id));
return nullptr;
}
return create_from_shader_node_tree(*material->nodetree);
}
PyDoc_STRVAR(
/* Wrap. */
bpy_inline_shader_nodes_from_light_doc,
".. staticmethod:: from_light(light)\n"
"\n"
" Create an inlined shader node tree from a light.\n"
"\n"
" :arg light: The light to online the node tree of.\n"
" :type light: bpy.types.Light\n");
static BPy_InlineShaderNodes *BPy_InlineShaderNodes_static_from_light(PyObject * /*self*/,
PyObject *args,
PyObject *kwds)
{
static const char *kwlist[] = {"light", nullptr};
PyObject *py_light;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", const_cast<char **>(kwlist), &py_light)) {
return nullptr;
}
ID *light_id = nullptr;
if (!pyrna_id_FromPyObject(py_light, &light_id)) {
PyErr_Format(PyExc_TypeError, "Expected a Light, not %.200s", Py_TYPE(py_light)->tp_name);
return nullptr;
}
if (GS(light_id->name) != ID_LA) {
PyErr_Format(PyExc_TypeError,
"Expected a Light, not %.200s",
BKE_idtype_idcode_to_name(GS(light_id->name)));
return nullptr;
}
Light *light = blender::id_cast<Light *>(light_id);
if (!light->nodetree) {
PyErr_Format(PyExc_TypeError, "Light '%s' has no node tree", BKE_id_name(*light_id));
return nullptr;
}
return create_from_shader_node_tree(*light->nodetree);
}
PyDoc_STRVAR(
/* Wrap. */
bpy_inline_shader_nodes_from_world_doc,
".. staticmethod:: from_world(world)\n"
"\n"
" Create an inlined shader node tree from a world.\n"
"\n"
" :arg world: The world to inline the node tree of.\n"
" :type world: bpy.types.World\n");
static BPy_InlineShaderNodes *BPy_InlineShaderNodes_static_from_world(PyObject * /*self*/,
PyObject *args,
PyObject *kwds)
{
static const char *kwlist[] = {"world", nullptr};
PyObject *py_world;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", const_cast<char **>(kwlist), &py_world)) {
return nullptr;
}
ID *world_id = nullptr;
if (!pyrna_id_FromPyObject(py_world, &world_id)) {
PyErr_Format(PyExc_TypeError, "Expected a World, not %.200s", Py_TYPE(py_world)->tp_name);
return nullptr;
}
if (GS(world_id->name) != ID_WO) {
PyErr_Format(PyExc_TypeError,
"Expected a World, not %.200s",
BKE_idtype_idcode_to_name(GS(world_id->name)));
return nullptr;
}
World *world = blender::id_cast<World *>(world_id);
if (!world->nodetree) {
PyErr_Format(PyExc_TypeError, "World '%s' has no node tree", BKE_id_name(*world_id));
return nullptr;
}
return create_from_shader_node_tree(*world->nodetree);
}
static void BPy_InlineShaderNodes_dealloc(BPy_InlineShaderNodes *self)
{
BKE_id_free(nullptr, self->inline_node_tree);
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject *>(self));
}
PyDoc_STRVAR(
/* Wrap. */
bpy_inline_shader_nodes_node_tree_doc,
"The inlined node tree.\n"
"\n"
":type: :class:`bpy.types.NodeTree`\n");
static PyObject *BPy_InlineShaderNodes_get_node_tree(BPy_InlineShaderNodes *self)
{
return pyrna_id_CreatePyObject(blender::id_cast<ID *>(self->inline_node_tree));
}
static PyGetSetDef BPy_InlineShaderNodes_getseters[] = {
{"node_tree",
(getter)BPy_InlineShaderNodes_get_node_tree,
nullptr,
bpy_inline_shader_nodes_node_tree_doc,
nullptr},
{nullptr},
};
#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_InlineShaderNodes_methods[] = {
{"from_material",
(PyCFunction)BPy_InlineShaderNodes_static_from_material,
METH_VARARGS | METH_KEYWORDS | METH_STATIC,
bpy_inline_shader_nodes_from_material_doc},
{"from_light",
(PyCFunction)BPy_InlineShaderNodes_static_from_light,
METH_VARARGS | METH_KEYWORDS | METH_STATIC,
bpy_inline_shader_nodes_from_light_doc},
{"from_world",
(PyCFunction)BPy_InlineShaderNodes_static_from_world,
METH_VARARGS | METH_KEYWORDS | METH_STATIC,
bpy_inline_shader_nodes_from_world_doc},
{nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
PyDoc_STRVAR(
/* Wrap. */
bpy_inline_shader_nodes_doc,
"An inlined shader node tree.\n");
PyTypeObject bpy_inline_shader_nodes_Type = {
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
/*tp_name*/ "InlineShaderNodes",
/*tp_basicsize*/ sizeof(BPy_InlineShaderNodes),
/*tp_itemsize*/ 0,
/*tp_dealloc*/ reinterpret_cast<destructor>(BPy_InlineShaderNodes_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_BASETYPE,
/*tp_doc*/ bpy_inline_shader_nodes_doc,
/*tp_traverse*/ nullptr,
/*tp_clear*/ nullptr,
/*tp_richcompare*/ nullptr,
/*tp_weaklistoffset*/ 0,
/*tp_iter*/ nullptr,
/*tp_iternext*/ nullptr,
/*tp_methods*/ BPy_InlineShaderNodes_methods,
/*tp_members*/ nullptr,
/*tp_getset*/ BPy_InlineShaderNodes_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,
};
PyObject *BPyInit_inline_shader_nodes_type()
{
if (PyType_Ready(&bpy_inline_shader_nodes_Type) < 0) {
return nullptr;
}
return reinterpret_cast<PyObject *>(&bpy_inline_shader_nodes_Type);
}