Blender V5.0
bpy_geometry_set.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
15
16#include <sstream>
17
18#include "BKE_duplilist.hh"
19#include "BKE_geometry_set.hh"
21#include "BKE_idtype.hh"
22#include "BKE_instances.hh"
23#include "BKE_lib_id.hh"
24#include "BKE_mesh_wrapper.hh"
25#include "BKE_pointcloud.hh"
26
28
29#include "DNA_ID.h"
31#include "DNA_mesh_types.h"
32#include "DNA_object_types.h"
34
35#include "RNA_enum_types.hh"
36#include "RNA_prototypes.hh"
37
38#include "bpy_geometry_set.hh"
39#include "bpy_rna.hh"
40
42
44
45extern PyTypeObject bpy_geometry_set_Type;
46
52
54{
55 BPy_GeometrySet *self = reinterpret_cast<BPy_GeometrySet *>(
57 if (self == nullptr) {
58 return nullptr;
59 }
60 new (&self->geometry) GeometrySet(std::move(geometry));
61 self->instances_pointcloud = nullptr;
62 /* We can't safely give access to shared geometries via the Python API currently, because
63 * constness can't be enforced. Therefore, ensure that this Python object has its own copy of
64 * each data-block. Note that attributes may still be shared with other data in Blender. */
65 self->geometry.ensure_no_shared_components();
66 return self;
67}
68
69static PyObject *BPy_GeometrySet_new(PyTypeObject * /*type*/, PyObject *args, PyObject *kwds)
70{
71 static const char *kwlist[] = {nullptr};
72 if (!PyArg_ParseTupleAndKeywords(args, kwds, "", const_cast<char **>(kwlist))) {
73 return nullptr;
74 }
75 return reinterpret_cast<PyObject *>(python_object_from_geometry_set());
76}
77
79{
80 std::destroy_at(&self->geometry);
81 if (self->instances_pointcloud) {
82 BKE_id_free(nullptr, self->instances_pointcloud);
83 }
84 Py_TYPE(self)->tp_free(reinterpret_cast<PyObject *>(self));
85}
86
88 /* Wrap. */
89 bpy_geometry_set_from_evaluated_object_doc,
90 ".. staticmethod:: from_evaluated_object(evaluated_object)\n"
91 "\n"
92 " Create a geometry set from the evaluated geometry of an evaluated object.\n"
93 " Typically, it's more convenient to use :func:`bpy.types.Object.evaluated_geometry`.\n"
94 "\n"
95 " :arg evaluated_object: The evaluated object to create a geometry set from.\n"
96 " :type evaluated_object: bpy.types.Object\n");
98 PyObject *args,
99 PyObject *kwds)
100{
101 using namespace blender;
102 static const char *kwlist[] = {"evaluated_object", nullptr};
103 PyObject *py_evaluated_object;
104 if (!PyArg_ParseTupleAndKeywords(
105 args, kwds, "O", const_cast<char **>(kwlist), &py_evaluated_object))
106 {
107 return nullptr;
108 }
109 ID *evaluated_object_id = nullptr;
110 if (!pyrna_id_FromPyObject(py_evaluated_object, &evaluated_object_id)) {
111 PyErr_Format(
112 PyExc_TypeError, "Expected an Object, not %.200s", Py_TYPE(py_evaluated_object)->tp_name);
113 return nullptr;
114 }
115
116 if (GS(evaluated_object_id->name) != ID_OB) {
117 PyErr_Format(PyExc_TypeError,
118 "Expected an Object, not %.200s",
119 BKE_idtype_idcode_to_name(GS(evaluated_object_id->name)));
120 return nullptr;
121 }
122 Object *evaluated_object = reinterpret_cast<Object *>(evaluated_object_id);
123 if (!DEG_is_evaluated(evaluated_object)) {
124 PyErr_SetString(PyExc_TypeError, "Expected an evaluated object");
125 return nullptr;
126 }
127 const bool is_instance_collection = evaluated_object->type == OB_EMPTY &&
128 evaluated_object->instance_collection;
129 const bool valid_object_type = OB_TYPE_IS_GEOMETRY(evaluated_object->type) ||
130 is_instance_collection;
131 if (!valid_object_type) {
132 const char *ob_type_name = "<unknown>";
133 RNA_enum_name_from_value(rna_enum_object_type_items, evaluated_object->type, &ob_type_name);
134 PyErr_Format(PyExc_TypeError, "Expected a geometry object, not %.200s", ob_type_name);
135 return nullptr;
136 }
137 if (!DEG_object_geometry_is_evaluated(*evaluated_object)) {
138 PyErr_SetString(PyExc_TypeError,
139 "Object geometry is not yet evaluated, is the depsgraph evaluated?");
140 return nullptr;
141 }
142 Depsgraph *depsgraph = DEG_get_depsgraph_by_id(*evaluated_object_id);
143 if (!depsgraph) {
144 PyErr_SetString(PyExc_TypeError, "Object is not owned by a depsgraph");
145 return nullptr;
146 }
148
150 if (is_instance_collection) {
151 bke::Instances *instances = new bke::Instances();
152 instances->add_new_reference(bke::InstanceReference{*evaluated_object->instance_collection});
153 instances->add_instance(0, float4x4::identity());
154 geometry.replace_instances(instances);
155 }
156 else {
158 *depsgraph, *scene, *evaluated_object);
159 geometry = bke::object_get_evaluated_geometry_set(*evaluated_object, false);
160 if (instances.instances_num() > 0) {
161 geometry.replace_instances(new bke::Instances(std::move(instances)));
162 }
163 }
165 return self;
166}
167
169{
170 std::stringstream ss;
171 ss << self->geometry;
172 std::string str = ss.str();
174}
175
177 /* Wrap. */
178 bpy_geometry_set_get_instances_pointcloud_doc,
179 ".. method:: instances_pointcloud()\n"
180 "\n"
181 " Get a pointcloud that encodes information about the instances of the geometry.\n"
182 " The returned pointcloud should not be modified.\n"
183 " There is a point per instance and per-instance data is stored in point attributes.\n"
184 " The local transforms are stored in the ``instance_transform`` attribute.\n"
185 " The data instanced by each point is referenced by the ``.reference_index`` attribute,\n"
186 " indexing into the list returned by :func:`bpy.types.GeometrySet.instance_references`.\n"
187 "\n"
188 " :rtype: bpy.types.PointCloud\n");
190{
191 using namespace blender;
192 const bke::Instances *instances = self->geometry.get_instances();
193 if (!instances) {
194 Py_RETURN_NONE;
195 }
196 if (self->instances_pointcloud == nullptr) {
197 const int instances_num = instances->instances_num();
198 PointCloud *pointcloud = BKE_pointcloud_new_nomain(instances_num);
202 {},
203 IndexMask(instances_num),
204 pointcloud->attributes_for_write());
205 self->instances_pointcloud = pointcloud;
206 }
207 return pyrna_id_CreatePyObject(&self->instances_pointcloud->id);
208}
209
211 /* Wrap. */
212 bpy_geometry_set_get_instance_references_doc,
213 ".. method:: instance_references()\n"
214 "\n"
215 " This returns a list of geometries that is indexed by the ``.reference_index``\n"
216 " attribute of the pointcloud returned by \n"
217 " :func:`bpy.types.GeometrySet.instances_pointcloud`.\n"
218 " It may contain other geometry sets, objects, collections and None values.\n"
219 "\n"
220 " :rtype: list[None | bpy.types.Object | bpy.types.Collection | bpy.types.GeometrySet]\n");
222{
223 using namespace blender;
224 const bke::Instances *instances = self->geometry.get_instances();
225 if (!instances) {
226 return PyList_New(0);
227 }
228 const Span<bke::InstanceReference> references = instances->references();
229 PyObject *py_references = PyList_New(references.size());
230 for (const int i : references.index_range()) {
231 const bke::InstanceReference &reference = references[i];
232 switch (reference.type()) {
234 PyList_SET_ITEM(py_references, i, Py_NewRef(Py_None));
235 break;
236 }
238 Object &object = reference.object();
239 PyList_SET_ITEM(py_references, i, pyrna_id_CreatePyObject(&object.id));
240 break;
241 }
243 Collection &collection = reference.collection();
244 PyList_SET_ITEM(py_references, i, pyrna_id_CreatePyObject(&collection.id));
245 break;
246 }
248 const bke::GeometrySet &geometry_set = reference.geometry_set();
249 PyList_SET_ITEM(py_references, i, python_object_from_geometry_set(geometry_set));
250 break;
251 }
252 }
253 }
254 return py_references;
255}
256
258 /* Wrap. */
259 bpy_geometry_set_name_doc,
260 "The name of the geometry set. It can be used for debugging purposes and is not unique.\n"
261 "\n"
262 ":type: str\n");
263static PyObject *BPy_GeometrySet_get_name(BPy_GeometrySet *self, void * /*closure*/)
264{
265 return PyC_UnicodeFromStdStr(self->geometry.name);
266}
267
268static int BPy_GeometrySet_set_name(BPy_GeometrySet *self, PyObject *value, void * /*closure*/)
269{
270 if (!PyUnicode_Check(value)) {
271 PyErr_SetString(PyExc_TypeError, "expected a string");
272 return -1;
273 }
274 const char *name = PyUnicode_AsUTF8(value);
275 self->geometry.name = name;
276 return 0;
277}
278
280 /* Wrap. */
281 bpy_geometry_set_mesh_doc,
282 "The mesh data-block in the geometry set.\n"
283 "\n"
284 ":type: :class:`bpy.types.Mesh`\n");
285static PyObject *BPy_GeometrySet_get_mesh(BPy_GeometrySet *self, void * /*closure*/)
286{
287 Mesh *base_mesh = self->geometry.get_mesh_for_write();
288 if (!base_mesh) {
289 Py_RETURN_NONE;
290 }
291 Mesh *mesh = BKE_mesh_wrapper_ensure_subdivision(base_mesh);
292 return pyrna_id_CreatePyObject(reinterpret_cast<ID *>(mesh));
293}
294
296 /* Wrap. */
297 bpy_geometry_set_mesh_base_doc,
298 "The mesh data-block in the geometry set without final subdivision.\n"
299 "\n"
300 ":type: :class:`bpy.types.Mesh`\n");
301static PyObject *BPy_GeometrySet_get_mesh_base(BPy_GeometrySet *self, void * /*closure*/)
302{
303 Mesh *base_mesh = self->geometry.get_mesh_for_write();
304 return pyrna_id_CreatePyObject(reinterpret_cast<ID *>(base_mesh));
305}
306
308 /* Wrap. */
309 bpy_geometry_set_pointcloud_doc,
310 "The point cloud data-block in the geometry set.\n"
311 "\n"
312 ":type: :class:`bpy.types.PointCloud`\n");
313static PyObject *BPy_GeometrySet_get_pointcloud(BPy_GeometrySet *self, void * /*closure*/)
314{
316 reinterpret_cast<ID *>(self->geometry.get_pointcloud_for_write()));
317}
318
320 /* Wrap. */
321 bpy_geometry_set_curves_doc,
322 "The curves data-block in the geometry set.\n"
323 "\n"
324 ":type: :class:`bpy.types.Curves`\n");
325static PyObject *BPy_GeometrySet_get_curves(BPy_GeometrySet *self, void * /*closure*/)
326{
327 return pyrna_id_CreatePyObject(reinterpret_cast<ID *>(self->geometry.get_curves_for_write()));
328}
329
331 /* Wrap. */
332 bpy_geometry_set_volume_doc,
333 "The volume data-block in the geometry set.\n"
334 "\n"
335 ":type: :class:`bpy.types.Volume`\n");
336static PyObject *BPy_GeometrySet_get_volume(BPy_GeometrySet *self, void * /*closure*/)
337{
338 return pyrna_id_CreatePyObject(reinterpret_cast<ID *>(self->geometry.get_volume_for_write()));
339}
340
342 /* Wrap. */
343 bpy_geometry_set_grease_pencil_doc,
344 "The Grease Pencil data-block in the geometry set.\n"
345 "\n"
346 ":type: :class:`bpy.types.GreasePencil`\n");
347static PyObject *BPy_GeometrySet_get_grease_pencil(BPy_GeometrySet *self, void * /*closure*/)
348{
350 reinterpret_cast<ID *>(self->geometry.get_grease_pencil_for_write()));
351}
352
353static PyGetSetDef BPy_GeometrySet_getseters[] = {
354 {
355 "name",
356 reinterpret_cast<getter>(BPy_GeometrySet_get_name),
357 reinterpret_cast<setter>(BPy_GeometrySet_set_name),
358 bpy_geometry_set_name_doc,
359 nullptr,
360 },
361 {
362 "mesh",
363 reinterpret_cast<getter>(BPy_GeometrySet_get_mesh),
364 nullptr,
365 bpy_geometry_set_mesh_doc,
366 nullptr,
367 },
368 {
369 "mesh_base",
370 reinterpret_cast<getter>(BPy_GeometrySet_get_mesh_base),
371 nullptr,
372 bpy_geometry_set_mesh_base_doc,
373 nullptr,
374 },
375 {
376 "pointcloud",
377 reinterpret_cast<getter>(BPy_GeometrySet_get_pointcloud),
378 nullptr,
379 bpy_geometry_set_pointcloud_doc,
380 nullptr,
381 },
382 {
383 "curves",
384 reinterpret_cast<getter>(BPy_GeometrySet_get_curves),
385 nullptr,
386 bpy_geometry_set_curves_doc,
387 nullptr,
388 },
389 {
390 "volume",
391 reinterpret_cast<getter>(BPy_GeometrySet_get_volume),
392 nullptr,
393 bpy_geometry_set_volume_doc,
394 nullptr,
395 },
396 {
397 "grease_pencil",
398 reinterpret_cast<getter>(BPy_GeometrySet_get_grease_pencil),
399 nullptr,
400 bpy_geometry_set_grease_pencil_doc,
401 nullptr,
402 },
403 {nullptr},
404
405};
406
407#ifdef __GNUC__
408# ifdef __clang__
409# pragma clang diagnostic push
410# pragma clang diagnostic ignored "-Wcast-function-type"
411# else
412# pragma GCC diagnostic push
413# pragma GCC diagnostic ignored "-Wcast-function-type"
414# endif
415#endif
416
417static PyMethodDef BPy_GeometrySet_methods[] = {
418 {"from_evaluated_object",
419 reinterpret_cast<PyCFunction>(BPy_GeometrySet_static_from_evaluated_object),
420 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
421 bpy_geometry_set_from_evaluated_object_doc},
422 {"instances_pointcloud",
423 reinterpret_cast<PyCFunction>(BPy_GeometrySet_get_instances_pointcloud),
424 METH_NOARGS,
425 bpy_geometry_set_get_instances_pointcloud_doc},
426 {"instance_references",
427 reinterpret_cast<PyCFunction>(BPy_GeometrySet_get_instance_references),
428 METH_NOARGS,
429 bpy_geometry_set_get_instance_references_doc},
430 {nullptr, nullptr, 0, nullptr},
431};
432
433#ifdef __GNUC__
434# ifdef __clang__
435# pragma clang diagnostic pop
436# else
437# pragma GCC diagnostic pop
438# endif
439#endif
440
442 /* Wrap. */
443 bpy_geometry_set_doc,
444 "Stores potentially multiple geometry components of different types.\n"
445 "For example, it might contain a mesh, curves and nested instances.\n");
446PyTypeObject bpy_geometry_set_Type = {
447 /*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
448 /*tp_name*/ "GeometrySet",
449 /*tp_basicsize*/ sizeof(BPy_GeometrySet),
450 /*tp_itemsize*/ 0,
451 /*tp_dealloc*/ reinterpret_cast<destructor>(BPy_GeometrySet_dealloc),
452 /*tp_vectorcall_offset*/ 0,
453 /*tp_getattr*/ nullptr,
454 /*tp_setattr*/ nullptr,
455 /*tp_as_async*/ nullptr,
456 /*tp_repr*/ reinterpret_cast<reprfunc>(BPy_GeometrySet_repr),
457 /*tp_as_number*/ nullptr,
458 /*tp_as_sequence*/ nullptr,
459 /*tp_as_mapping*/ nullptr,
460 /*tp_hash*/ nullptr,
461 /*tp_call*/ nullptr,
462 /*tp_str*/ nullptr,
463 /*tp_getattro*/ nullptr,
464 /*tp_setattro*/ nullptr,
465 /*tp_as_buffer*/ nullptr,
466 /*tp_flags*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
467 /*tp_doc*/ bpy_geometry_set_doc,
468 /*tp_traverse*/ nullptr,
469 /*tp_clear*/ nullptr,
470 /*tp_richcompare*/ nullptr,
471 /*tp_weaklistoffset*/ 0,
472 /*tp_iter*/ nullptr,
473 /*tp_iternext*/ nullptr,
474 /*tp_methods*/ BPy_GeometrySet_methods,
475 /*tp_members*/ nullptr,
476 /*tp_getset*/ BPy_GeometrySet_getseters,
477 /*tp_base*/ nullptr,
478 /*tp_dict*/ nullptr,
479 /*tp_descr_get*/ nullptr,
480 /*tp_descr_set*/ nullptr,
481 /*tp_dictoffset*/ 0,
482 /*tp_init*/ nullptr,
483 /*tp_alloc*/ nullptr,
484 /*tp_new*/ BPy_GeometrySet_new,
485};
486
488{
489 if (PyType_Ready(&bpy_geometry_set_Type) < 0) {
490 return nullptr;
491 }
492 return reinterpret_cast<PyObject *>(&bpy_geometry_set_Type);
493}
blender::bke::Instances object_duplilist_legacy_instances(Depsgraph &depsgraph, Scene &scene, Object &ob)
const char * BKE_idtype_idcode_to_name(short idcode)
Definition idtype.cc:164
void BKE_id_free(Main *bmain, void *idv)
Mesh * BKE_mesh_wrapper_ensure_subdivision(Mesh *mesh)
General operations for point clouds.
PointCloud * BKE_pointcloud_new_nomain(int totpoint)
bool DEG_is_evaluated(const T *id)
Depsgraph * DEG_get_depsgraph_by_id(const ID &id)
bool DEG_object_geometry_is_evaluated(const Object &object)
Scene * DEG_get_input_scene(const Depsgraph *graph)
ID and Library types, which are fundamental for SDNA.
@ ID_OB
Object groups, one object can be in many groups at once.
Object is a sort of wrapper for general info.
#define OB_TYPE_IS_GEOMETRY(_type)
@ OB_EMPTY
PyObject * self
BPy_StructRNA * depsgraph
PyDoc_STRVAR(bpy_geometry_set_from_evaluated_object_doc, ".. staticmethod:: from_evaluated_object(evaluated_object)\n" "\n" " Create a geometry set from the evaluated geometry of an evaluated object.\n" " Typically, it's more convenient to use :func:`bpy.types.Object.evaluated_geometry`.\n" "\n" " :arg evaluated_object: The evaluated object to create a geometry set from.\n" " :type evaluated_object: bpy.types.Object\n")
static void BPy_GeometrySet_dealloc(BPy_GeometrySet *self)
static PyObject * BPy_GeometrySet_get_pointcloud(BPy_GeometrySet *self, void *)
PyObject * BPyInit_geometry_set_type()
static PyObject * BPy_GeometrySet_new(PyTypeObject *, PyObject *args, PyObject *kwds)
static BPy_GeometrySet * BPy_GeometrySet_static_from_evaluated_object(PyObject *, PyObject *args, PyObject *kwds)
static PyObject * BPy_GeometrySet_get_name(BPy_GeometrySet *self, void *)
static PyObject * BPy_GeometrySet_get_mesh_base(BPy_GeometrySet *self, void *)
static PyObject * BPy_GeometrySet_get_instances_pointcloud(BPy_GeometrySet *self)
static PyObject * BPy_GeometrySet_get_volume(BPy_GeometrySet *self, void *)
PyTypeObject bpy_geometry_set_Type
static PyObject * BPy_GeometrySet_get_curves(BPy_GeometrySet *self, void *)
static PyObject * BPy_GeometrySet_get_grease_pencil(BPy_GeometrySet *self, void *)
static PyObject * BPy_GeometrySet_get_mesh(BPy_GeometrySet *self, void *)
static PyGetSetDef BPy_GeometrySet_getseters[]
static PyObject * BPy_GeometrySet_repr(BPy_GeometrySet *self)
static PyMethodDef BPy_GeometrySet_methods[]
static PyObject * BPy_GeometrySet_get_instance_references(BPy_GeometrySet *self)
static int BPy_GeometrySet_set_name(BPy_GeometrySet *self, PyObject *value, void *)
static BPy_GeometrySet * python_object_from_geometry_set(GeometrySet geometry={})
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
Collection & collection() const
int add_new_reference(const InstanceReference &reference)
Definition instances.cc:269
void add_instance(int instance_handle, const float4x4 &transform)
Definition instances.cc:204
Span< InstanceReference > references() const
Definition instances.cc:275
bke::AttributeAccessor attributes() const
Definition instances.cc:64
int instances_num() const
Definition instances.cc:393
#define str(s)
#define GS(x)
PyObject * pyrna_id_CreatePyObject(ID *id)
Definition bpy_rna.cc:8601
bool pyrna_id_FromPyObject(PyObject *obj, ID **id)
Definition bpy_rna.cc:8611
void gather_attributes(AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, const IndexMask &selection, MutableAttributeAccessor dst_attributes)
GeometrySet object_get_evaluated_geometry_set(const Object &object, bool apply_subdiv=true)
PyObject * PyC_UnicodeFromStdStr(const std::string &str)
const char * name
bool RNA_enum_name_from_value(const EnumPropertyItem *item, int value, const char **r_name)
const EnumPropertyItem rna_enum_object_type_items[]
PyObject_HEAD GeometrySet geometry
PointCloud * instances_pointcloud
Definition DNA_ID.h:414
char name[258]
Definition DNA_ID.h:432
struct Collection * instance_collection
i
Definition text_draw.cc:230