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
270 lines
8.6 KiB
C++
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);
|
|
}
|