diff --git a/doc/python_api/examples/bpy.types.GeometrySet.py b/doc/python_api/examples/bpy.types.GeometrySet.py new file mode 100644 index 00000000000..2668e36cac1 --- /dev/null +++ b/doc/python_api/examples/bpy.types.GeometrySet.py @@ -0,0 +1,53 @@ +""" +Accessing Evaluated Geometry +++++++++++++++++++++++++++++ +""" +import bpy + +# The GeometrySet can only be retrieved from an evaluated object. So one always +# needs a depsgraph that has evaluated the object. +depsgraph = bpy.context.view_layer.depsgraph +ob = bpy.context.active_object +ob_eval = depsgraph.id_eval_get(ob) + +# Get the final evaluated geometry of an object. +geometry = ob_eval.evaluated_geometry() + +# Print basic information like the number of elements. +print(geometry) + +# A geometry set may have a name. It can be set with the Set Geometry Name node. +print(geometry.name) + +# Access "realized" geometry components. +print(geometry.mesh) +print(geometry.pointcloud) +print(geometry.curves) +print(geometry.volume) +print(geometry.grease_pencil) + +# Access the mesh without final subdivision applied. +print(geometry.mesh_base) + +# Accessing instances is a bit more tricky, because there is no specific +# mechanism to expose instances. Instead, two accessors are provided which +# are easy to keep working in the future even if we get a proper Instances type. + +# This is a pointcloud that provides access to all the instance attributes. +# There is a point per instances. May return None if there is no instances data. +instances_pointcloud = geometry.instances_pointcloud() + +if instances_pointcloud is not None: + # This is a list containing the data that is instanced. The list may contain + # None, objects, collections or other GeometrySets. If the geometry does not + # have instances, the list is empty. + references = geometry.instance_references() + + # Besides normal generic attributes, there are also two important + # instance-specific attributes. "instance_transform" is a 4x4 matrix attribute + # containing the transforms of each instance. + instance_transforms = instances_pointcloud.attributes["instance_transform"] + + # ".reference_index" contains indices into the `references` list above and + # determines what geometry each instance uses. + reference_indices = instances_pointcloud.attributes[".reference_index"] diff --git a/doc/python_api/sphinx_doc_gen.py b/doc/python_api/sphinx_doc_gen.py index 1e650f9302e..f86be7a7a1a 100644 --- a/doc/python_api/sphinx_doc_gen.py +++ b/doc/python_api/sphinx_doc_gen.py @@ -1123,49 +1123,52 @@ def pymodule2sphinx(basepath, module_name, module, title, module_all_extra): if heading: fw(title_string(heading, heading_char)) - # May need to be its own function. - if value.__doc__: - if value.__doc__.startswith(".. class::"): - fw(value.__doc__) - else: - fw(".. class:: {:s}\n\n".format(type_name)) - write_indented_lines(" ", fw, value.__doc__, True) - else: - fw(".. class:: {:s}\n\n".format(type_name)) - fw("\n") - - write_example_ref(" ", fw, module_name + "." + type_name) - - descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("_")] - - for key, descr in descr_items: - if type(descr) == ClassMethodDescriptorType: - py_descr2sphinx(" ", fw, descr, module_name, type_name, key) - - # Needed for pure Python classes. - for key, descr in descr_items: - if type(descr) == FunctionType: - pyfunc2sphinx(" ", fw, module_name, type_name, key, descr, is_class=True) - - for key, descr in descr_items: - if type(descr) == MethodDescriptorType: - py_descr2sphinx(" ", fw, descr, module_name, type_name, key) - - for key, descr in descr_items: - if type(descr) == GetSetDescriptorType: - py_descr2sphinx(" ", fw, descr, module_name, type_name, key) - - for key, descr in descr_items: - if type(descr) == StaticMethodType: - descr = getattr(value, key) - write_indented_lines(" ", fw, descr.__doc__ or "Undocumented", False) - fw("\n") - - fw("\n\n") + pyclass2sphinx(fw, module_name, type_name, value) file.close() +def pyclass2sphinx(fw, module_name, type_name, value): + if value.__doc__: + if value.__doc__.startswith(".. class::"): + fw(value.__doc__) + else: + fw(".. class:: {:s}.{:s}\n\n".format(module_name, type_name)) + write_indented_lines(" ", fw, value.__doc__, True) + else: + fw(".. class:: {:s}.{:s}\n\n".format(module_name, type_name)) + fw("\n") + + write_example_ref(" ", fw, module_name + "." + type_name) + + descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("_")] + + for key, descr in descr_items: + if type(descr) == ClassMethodDescriptorType: + py_descr2sphinx(" ", fw, descr, module_name, type_name, key) + + # Needed for pure Python classes. + for key, descr in descr_items: + if type(descr) == FunctionType: + pyfunc2sphinx(" ", fw, module_name, type_name, key, descr, is_class=True) + + for key, descr in descr_items: + if type(descr) == MethodDescriptorType: + py_descr2sphinx(" ", fw, descr, module_name, type_name, key) + + for key, descr in descr_items: + if type(descr) == GetSetDescriptorType: + py_descr2sphinx(" ", fw, descr, module_name, type_name, key) + + for key, descr in descr_items: + if type(descr) == StaticMethodType: + descr = getattr(value, key) + write_indented_lines(" ", fw, descr.__doc__ or "Undocumented", False) + fw("\n") + + fw("\n\n") + + # Changes In Blender will force errors here. context_type_map = { # Support multiple types for each item, where each list item is a possible type: @@ -2101,6 +2104,23 @@ def write_rst_ops_index(basepath): file.close() +def write_rst_geometry_set(basepath): + """ + Write the RST file for ``bpy.types.GeometrySet``. + """ + if 'bpy.types.GeometrySet' in EXCLUDE_MODULES: + return + + # Write the index. + filepath = os.path.join(basepath, "bpy.types.GeometrySet.rst") + file = open(filepath, "w", encoding="utf-8") + fw = file.write + fw(title_string("GeometrySet", "=")) + pyclass2sphinx(fw, "bpy.types", "GeometrySet", bpy.types.GeometrySet) + + EXAMPLE_SET_USED.add("bpy.types.GeometrySet") + + def write_rst_msgbus(basepath): """ Write the RST files of ``bpy.msgbus`` module @@ -2410,6 +2430,7 @@ def rna2sphinx(basepath): write_rst_types_index(basepath) # `bpy.types`. write_rst_ops_index(basepath) # `bpy.ops`. write_rst_msgbus(basepath) # `bpy.msgbus`. + write_rst_geometry_set(basepath) # `bpy.types.GeometrySet`. pyrna2sphinx(basepath) # `bpy.types.*` & `bpy.ops.*`. write_rst_data(basepath) # `bpy.data`. write_rst_importable_modules(basepath) diff --git a/scripts/modules/bpy_types.py b/scripts/modules/bpy_types.py index 1e769004cb9..ed4cc07ef3b 100644 --- a/scripts/modules/bpy_types.py +++ b/scripts/modules/bpy_types.py @@ -254,6 +254,17 @@ class Object(_types.ID): return tuple(scene for scene in bpy.data.scenes if self in scene.objects[:]) + def evaluated_geometry(self): + """ + Get the evaluated geometry set of this evaluated object. This only works for + objects that contain geometry data like meshes and curves but not e.g. cameras. + + :return: The evaluated geometry. + :rtype: :class:`bpy.types.GeometrySet` + """ + from bpy.types import GeometrySet + return GeometrySet.from_evaluated_object(self) + class WindowManager(_types.ID): __slots__ = () diff --git a/source/blender/blenkernel/BKE_duplilist.hh b/source/blender/blenkernel/BKE_duplilist.hh index 29353f89fae..9c11c02132c 100644 --- a/source/blender/blenkernel/BKE_duplilist.hh +++ b/source/blender/blenkernel/BKE_duplilist.hh @@ -8,6 +8,9 @@ * \ingroup bke */ +#include "BKE_geometry_set.hh" +#include "BKE_instances.hh" + struct Depsgraph; struct ID; struct ListBase; @@ -16,9 +19,6 @@ struct ParticleSystem; struct Scene; struct ViewLayer; struct ViewerPath; -namespace blender::bke { -struct GeometrySet; -} /* ---------------------------------------------------- */ /* Dupli-Geometry */ @@ -36,6 +36,22 @@ ListBase *object_duplilist_preview(Depsgraph *depsgraph, const ViewerPath *viewer_path); void free_object_duplilist(ListBase *lb); +/** + * Get the legacy instances of this object. That includes instances coming from these sources: + * - Particles + * - Dupli Verts + * - Dupli Faces + * - "Objects as Font" + * + * This does not include collection instances which are not considered legacy and should be treated + * properly at a higher level. + * + * Also see #get_dupli_generator for the different existing dupli generators. + */ +blender::bke::Instances object_duplilist_legacy_instances(Depsgraph &depsgraph, + Scene &scene, + Object &ob); + constexpr int MAX_DUPLI_RECUR = 8; struct DupliObject { @@ -49,6 +65,8 @@ struct DupliObject { short type; /* From #Object::transflag. */ char no_draw; + /** Depth in the instance hierarchy. */ + int8_t level; /* If this dupli object is belongs to a preview, this is non-null. */ const blender::bke::GeometrySet *preview_base_geometry; /* Index of the top-level instance this dupli is part of or -1 when unused. */ diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc index 9c810a3f24c..07a9a4492e5 100644 --- a/source/blender/blenkernel/intern/object_dupli.cc +++ b/source/blender/blenkernel/intern/object_dupli.cc @@ -271,6 +271,7 @@ static DupliObject *make_dupli(const DupliContext *ctx, dob->type = ctx->gen == nullptr ? 0 : ctx->dupli_gen_type_stack->last(); dob->preview_base_geometry = ctx->preview_base_geometry; dob->preview_instance_index = ctx->preview_instance_index; + dob->level = ctx->level; /* Set persistent id, which is an array with a persistent index for each level * (particle number, vertex number, ..). by comparing this we can find the same @@ -1836,6 +1837,80 @@ ListBase *object_duplilist_preview(Depsgraph *depsgraph, return duplilist; } +blender::bke::Instances object_duplilist_legacy_instances(Depsgraph &depsgraph, + Scene &scene, + Object &ob) +{ + using namespace blender; + + ListBase *duplilist = MEM_callocN("duplilist"); + DupliContext ctx; + Vector instance_stack({&ob}); + Vector dupli_gen_type_stack({0}); + + init_context(&ctx, &depsgraph, &scene, &ob, nullptr, instance_stack, dupli_gen_type_stack); + if (ctx.gen == &gen_dupli_geometry_set) { + /* These are not legacy instances. */ + return {}; + } + if (ctx.gen) { + ctx.duplilist = duplilist; + ctx.gen->make_duplis(&ctx); + } + const bool is_particle_duplis = ctx.gen == &gen_dupli_particles; + /* Particle instances are on the second level, because the first level is the particle system + * itself. */ + const int level_to_use = is_particle_duplis ? 1 : 0; + + Vector top_level_duplis; + LISTBASE_FOREACH (DupliObject *, dob, duplilist) { + BLI_assert(dob->ob != &ob); + /* We only need the top level instances in the end, because when #Instances references an + * object, it implicitly also references all instances of that object. */ + if (dob->level == level_to_use) { + top_level_duplis.append(dob); + } + } + + bke::Instances top_level_instances; + const float4x4 &world_to_object = ob.world_to_object(); + + VectorSet referenced_objects; + const int instances_num = top_level_duplis.size(); + top_level_instances.resize(instances_num); + MutableSpan instances_transforms = top_level_instances.transforms_for_write(); + MutableSpan instances_reference_handles = top_level_instances.reference_handles_for_write(); + bke::SpanAttributeWriter instances_ids = + top_level_instances.attributes_for_write().lookup_or_add_for_write_only_span( + "id", bke::AttrDomain::Instance); + for (const int i : IndexRange(instances_num)) { + DupliObject &dob = *top_level_duplis[i]; + Object &instanced_object = *dob.ob; + if (referenced_objects.add(&instanced_object)) { + top_level_instances.add_new_reference(instanced_object); + } + const int handle = referenced_objects.index_of(&instanced_object); + instances_transforms[i] = world_to_object * float4x4(dob.mat); + instances_reference_handles[i] = handle; + + int id = dob.persistent_id[0]; + if (is_particle_duplis) { + const int particle_system_i = dob.persistent_id[0]; + const int particle_i = dob.persistent_id[1]; + /* Attempt to build a unique ID for each particle. This allows for unique ids as long as + * there are not more than <= 2^26 = 67.108.864 particles per particle system and there are + * <= 2^6 = 64 particle systems. Otherwise there will be duplicate IDs but this is quite + * unlikely in the legacy particle system. */ + id = (particle_system_i << 26) + particle_i; + } + instances_ids.span[i] = id; + } + instances_ids.finish(); + + free_object_duplilist(duplilist); + return top_level_instances; +} + void free_object_duplilist(ListBase *lb) { BLI_freelistN(lb); diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt index bfcb2fcb6bf..0f5ebf77c1e 100644 --- a/source/blender/python/intern/CMakeLists.txt +++ b/source/blender/python/intern/CMakeLists.txt @@ -36,6 +36,7 @@ set(SRC bpy_capi_utils.cc bpy_cli_command.cc bpy_driver.cc + bpy_geometry_set.cc bpy_gizmo_wrap.cc bpy_interface.cc bpy_interface_atexit.cc @@ -84,6 +85,7 @@ set(SRC bpy_capi_utils.hh bpy_cli_command.hh bpy_driver.hh + bpy_geometry_set.hh bpy_gizmo_wrap.hh bpy_intern_string.hh bpy_library.hh diff --git a/source/blender/python/intern/bpy.cc b/source/blender/python/intern/bpy.cc index 83b4793327e..5fb06574a1c 100644 --- a/source/blender/python/intern/bpy.cc +++ b/source/blender/python/intern/bpy.cc @@ -36,6 +36,7 @@ #include "bpy_app.hh" #include "bpy_cli_command.hh" #include "bpy_driver.hh" +#include "bpy_geometry_set.hh" #include "bpy_library.hh" #include "bpy_operator.hh" #include "bpy_props.hh" @@ -744,6 +745,7 @@ void BPy_init_modules(bContext *C) /* 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(mod, "types", bpy_types); /* needs to be first so bpy_types can run */ diff --git a/source/blender/python/intern/bpy_geometry_set.cc b/source/blender/python/intern/bpy_geometry_set.cc new file mode 100644 index 00000000000..45b7bcd6101 --- /dev/null +++ b/source/blender/python/intern/bpy_geometry_set.cc @@ -0,0 +1,457 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "BKE_duplilist.hh" +#include "BKE_geometry_set.hh" +#include "BKE_geometry_set_instances.hh" +#include "BKE_idtype.hh" +#include "BKE_instances.hh" +#include "BKE_lib_id.hh" +#include "BKE_mesh_wrapper.hh" +#include "BKE_pointcloud.hh" + +#include "DEG_depsgraph_query.hh" + +#include "DNA_ID.h" +#include "DNA_collection_types.h" +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" +#include "DNA_pointcloud_types.h" + +#include "RNA_enum_types.hh" +#include "RNA_prototypes.hh" + +#include "bpy_geometry_set.hh" +#include "bpy_rna.hh" + +using blender::bke::GeometrySet; + +extern PyTypeObject bpy_geometry_set_Type; + +struct BPy_GeometrySet { + PyObject_HEAD + GeometrySet geometry; + PointCloud *instances_pointcloud; +}; + +static BPy_GeometrySet *python_object_from_geometry_set(GeometrySet geometry = {}) +{ + BPy_GeometrySet *self = reinterpret_cast( + bpy_geometry_set_Type.tp_alloc(&bpy_geometry_set_Type, 0)); + if (self == nullptr) { + return nullptr; + } + new (&self->geometry) GeometrySet(std::move(geometry)); + self->instances_pointcloud = nullptr; + /* We can't safely give access to shared geometries via the Python API currently, because + * constness can't be enforced. Therefore, ensure that this Python object has its own copy of + * each data-block. Note that attributes may still be shared with other data in Blender. */ + self->geometry.ensure_no_shared_components(); + return self; +} + +static BPy_GeometrySet *BPy_GeometrySet_new(PyTypeObject * /*type*/, + PyObject * /*args*/, + PyObject * /*kwds*/) +{ + return python_object_from_geometry_set(); +} + +static void BPy_GeometrySet_dealloc(BPy_GeometrySet *self) +{ + std::destroy_at(&self->geometry); + if (self->instances_pointcloud) { + BKE_id_free(nullptr, self->instances_pointcloud); + } + Py_TYPE(self)->tp_free(reinterpret_cast(self)); +} + +PyDoc_STRVAR( + /* Wrap. */ + 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 BPy_GeometrySet *BPy_GeometrySet_static_from_evaluated_object(PyObject * /*self*/, + PyObject *args, + PyObject *kwds) +{ + using namespace blender; + static const char *kwlist[] = {"evaluated_object", nullptr}; + PyObject *py_evaluated_object; + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "O", const_cast(kwlist), &py_evaluated_object)) + { + return nullptr; + } + ID *evaluated_object_id = nullptr; + if (!pyrna_id_FromPyObject(py_evaluated_object, &evaluated_object_id)) { + PyErr_Format( + PyExc_TypeError, "Expected an Object, not %.200s", Py_TYPE(py_evaluated_object)->tp_name); + return nullptr; + } + + if (GS(evaluated_object_id->name) != ID_OB) { + PyErr_Format(PyExc_TypeError, + "Expected an Object, not %.200s", + BKE_idtype_idcode_to_name(GS(evaluated_object_id->name))); + return nullptr; + } + Object *evaluated_object = reinterpret_cast(evaluated_object_id); + if (!DEG_is_evaluated_object(evaluated_object)) { + PyErr_SetString(PyExc_TypeError, "Expected an evaluated object"); + return nullptr; + } + const bool is_instance_collection = evaluated_object->type == OB_EMPTY && + evaluated_object->instance_collection; + const bool valid_object_type = OB_TYPE_IS_GEOMETRY(evaluated_object->type) || + is_instance_collection; + if (!valid_object_type) { + const char *ob_type_name = ""; + RNA_enum_name_from_value(rna_enum_object_type_items, evaluated_object->type, &ob_type_name); + PyErr_Format(PyExc_TypeError, "Expected a geometry object, not %.200s", ob_type_name); + return nullptr; + } + if (!DEG_object_geometry_is_evaluated(*evaluated_object)) { + PyErr_SetString(PyExc_TypeError, + "Object geometry is not yet evaluated, is the depsgraph evaluated?"); + return nullptr; + } + Depsgraph *depsgraph = DEG_get_depsgraph_by_id(*evaluated_object_id); + if (!depsgraph) { + PyErr_SetString(PyExc_TypeError, "Object is not owned by a depsgraph"); + return nullptr; + } + Scene *scene = DEG_get_input_scene(depsgraph); + + GeometrySet geometry; + if (is_instance_collection) { + bke::Instances *instances = new bke::Instances(); + instances->add_new_reference(bke::InstanceReference{*evaluated_object->instance_collection}); + instances->add_instance(0, float4x4::identity()); + geometry.replace_instances(instances); + } + else { + bke::Instances instances = object_duplilist_legacy_instances( + *depsgraph, *scene, *evaluated_object); + geometry = bke::object_get_evaluated_geometry_set(*evaluated_object, false); + if (instances.instances_num() > 0) { + geometry.replace_instances(new bke::Instances(std::move(instances))); + } + } + BPy_GeometrySet *self = python_object_from_geometry_set(std::move(geometry)); + return self; +} + +static PyObject *BPy_GeometrySet_repr(BPy_GeometrySet *self) +{ + std::stringstream ss; + ss << self->geometry; + std::string str = ss.str(); + return PyUnicode_FromString(str.c_str()); +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_geometry_set_get_instances_pointcloud_doc, + ".. method:: instances_pointcloud()\n" + "\n" + " Get a pointcloud that encodes information about the instances of the geometry.\n" + " The returned pointcloud should not be modified.\n" + " There is a point per instance and per-instance data is stored in point attributes.\n" + " The local transforms are stored in the ``instance_transform`` attribute.\n" + " The data instanced by each point is referenced by the ``.reference_index`` attribute,\n" + " indexing into the list returned by :func:`bpy.types.GeometrySet.instance_references`.\n" + "\n" + " :rtype: bpy.types.PointCloud\n"); +static PyObject *BPy_GeometrySet_get_instances_pointcloud(BPy_GeometrySet *self) +{ + using namespace blender; + const bke::Instances *instances = self->geometry.get_instances(); + if (!instances) { + Py_RETURN_NONE; + } + if (self->instances_pointcloud == nullptr) { + const int instances_num = instances->instances_num(); + PointCloud *pointcloud = BKE_pointcloud_new_nomain(instances_num); + bke::gather_attributes(instances->attributes(), + bke::AttrDomain::Instance, + bke::AttrDomain::Point, + {}, + IndexMask(instances_num), + pointcloud->attributes_for_write()); + self->instances_pointcloud = pointcloud; + } + return pyrna_id_CreatePyObject(&self->instances_pointcloud->id); +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_geometry_set_get_instance_references_doc, + ".. method:: instance_references()\n" + "\n" + " This returns a list of geometries that is indexed by the ``.reference_index``\n" + " attribute of the pointcloud returned by \n" + " :func:`bpy.types.GeometrySet.instances_pointcloud`.\n" + " It may contain other geometry sets, objects, collections and None values.\n" + "\n" + " :rtype: list[None | bpy.types.Object | bpy.types.Collection | bpy.types.GeometrySet]\n"); +static PyObject *BPy_GeometrySet_get_instance_references(BPy_GeometrySet *self) +{ + using namespace blender; + const bke::Instances *instances = self->geometry.get_instances(); + if (!instances) { + return PyList_New(0); + } + const Span references = instances->references(); + PyObject *py_references = PyList_New(references.size()); + for (const int i : references.index_range()) { + const bke::InstanceReference &reference = references[i]; + switch (reference.type()) { + case bke::InstanceReference::Type::None: { + PyList_SET_ITEM(py_references, i, Py_NewRef(Py_None)); + break; + } + case bke::InstanceReference::Type::Object: { + Object &object = reference.object(); + PyList_SET_ITEM(py_references, i, pyrna_id_CreatePyObject(&object.id)); + break; + } + case bke::InstanceReference::Type::Collection: { + Collection &collection = reference.collection(); + PyList_SET_ITEM(py_references, i, pyrna_id_CreatePyObject(&collection.id)); + break; + } + case bke::InstanceReference::Type::GeometrySet: { + const bke::GeometrySet &geometry_set = reference.geometry_set(); + PyList_SET_ITEM(py_references, i, python_object_from_geometry_set(geometry_set)); + break; + } + } + } + return py_references; +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_geometry_set_name_doc, + "The name of the geometry set.\n" + "\n" + ":type: str\n"); +static PyObject *BPy_GeometrySet_get_name(BPy_GeometrySet *self, void * /*closure*/) +{ + return PyUnicode_FromString(self->geometry.name.c_str()); +} + +static int BPy_GeometrySet_set_name(BPy_GeometrySet *self, PyObject *value, void * /*closure*/) +{ + if (!PyUnicode_Check(value)) { + PyErr_SetString(PyExc_TypeError, "expected a string"); + return -1; + } + const char *name = PyUnicode_AsUTF8(value); + self->geometry.name = name ? name : ""; + return 0; +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_geometry_set_mesh_doc, + "The mesh data-block in the geometry set.\n" + "\n" + ":type: :class:`bpy.types.Mesh`\n"); +static PyObject *BPy_GeometrySet_get_mesh(BPy_GeometrySet *self, void * /*closure*/) +{ + Mesh *base_mesh = self->geometry.get_mesh_for_write(); + Mesh *mesh = BKE_mesh_wrapper_ensure_subdivision(base_mesh); + return pyrna_id_CreatePyObject(reinterpret_cast(mesh)); +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_geometry_set_mesh_base_doc, + "The mesh data-block in the geometry set without final subdivision.\n" + "\n" + ":type: :class:`bpy.types.Mesh`\n"); +static PyObject *BPy_GeometrySet_get_mesh_base(BPy_GeometrySet *self, void * /*closure*/) +{ + Mesh *base_mesh = self->geometry.get_mesh_for_write(); + return pyrna_id_CreatePyObject(reinterpret_cast(base_mesh)); +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_geometry_set_pointcloud_doc, + "The point cloud data-block in the geometry set.\n" + "\n" + ":type: :class:`bpy.types.PointCloud`\n"); +static PyObject *BPy_GeometrySet_get_pointcloud(BPy_GeometrySet *self, void * /*closure*/) +{ + return pyrna_id_CreatePyObject( + reinterpret_cast(self->geometry.get_pointcloud_for_write())); +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_geometry_set_curves_doc, + "The curves data-block in the geometry set.\n" + "\n" + ":type: :class:`bpy.types.Curves`\n"); +static PyObject *BPy_GeometrySet_get_curves(BPy_GeometrySet *self, void * /*closure*/) +{ + return pyrna_id_CreatePyObject(reinterpret_cast(self->geometry.get_curves_for_write())); +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_geometry_set_volume_doc, + "The volume data-block in the geometry set.\n" + "\n" + ":type: :class:`bpy.types.Volume`\n"); +static PyObject *BPy_GeometrySet_get_volume(BPy_GeometrySet *self, void * /*closure*/) +{ + return pyrna_id_CreatePyObject(reinterpret_cast(self->geometry.get_volume_for_write())); +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_geometry_set_grease_pencil_doc, + "The Grease Pencil data-block in the geometry set.\n" + "\n" + ":type: :class:`bpy.types.GreasePencilv3`\n"); +static PyObject *BPy_GeometrySet_get_grease_pencil(BPy_GeometrySet *self, void * /*closure*/) +{ + return pyrna_id_CreatePyObject( + reinterpret_cast(self->geometry.get_grease_pencil_for_write())); +} + +static PyGetSetDef BPy_GeometrySet_getseters[] = { + { + "name", + reinterpret_cast(BPy_GeometrySet_get_name), + reinterpret_cast(BPy_GeometrySet_set_name), + bpy_geometry_set_name_doc, + nullptr, + }, + { + "mesh", + reinterpret_cast(BPy_GeometrySet_get_mesh), + nullptr, + bpy_geometry_set_mesh_doc, + nullptr, + }, + { + "mesh_base", + reinterpret_cast(BPy_GeometrySet_get_mesh_base), + nullptr, + bpy_geometry_set_mesh_base_doc, + nullptr, + }, + { + "pointcloud", + reinterpret_cast(BPy_GeometrySet_get_pointcloud), + nullptr, + bpy_geometry_set_pointcloud_doc, + nullptr, + }, + { + "curves", + reinterpret_cast(BPy_GeometrySet_get_curves), + nullptr, + bpy_geometry_set_curves_doc, + nullptr, + }, + { + "volume", + reinterpret_cast(BPy_GeometrySet_get_volume), + nullptr, + bpy_geometry_set_volume_doc, + nullptr, + }, + { + "grease_pencil", + reinterpret_cast(BPy_GeometrySet_get_grease_pencil), + nullptr, + bpy_geometry_set_grease_pencil_doc, + nullptr, + }, + {nullptr}, + +}; + +static PyMethodDef BPy_GeometrySet_methods[] = { + {"from_evaluated_object", + reinterpret_cast(BPy_GeometrySet_static_from_evaluated_object), + METH_VARARGS | METH_KEYWORDS | METH_STATIC, + bpy_geometry_set_from_evaluated_object_doc}, + {"instances_pointcloud", + reinterpret_cast(BPy_GeometrySet_get_instances_pointcloud), + METH_NOARGS, + bpy_geometry_set_get_instances_pointcloud_doc}, + {"instance_references", + reinterpret_cast(BPy_GeometrySet_get_instance_references), + METH_NOARGS, + bpy_geometry_set_get_instance_references_doc}, + {nullptr, nullptr, 0, nullptr}, +}; + +PyDoc_STRVAR( + /* Wrap. */ + bpy_geometry_set_doc, + "Stores potentially multiple geometry components of different types.\n" + "For example, it might contain a mesh, curves and nested instances.\n"); +PyTypeObject bpy_geometry_set_Type = { + /*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0) + /*tp_name*/ "GeometrySet", + /*tp_basicsize*/ sizeof(BPy_GeometrySet), + /*tp_itemsize*/ 0, + /*tp_dealloc*/ reinterpret_cast(BPy_GeometrySet_dealloc), + /*tp_vectorcall_offset*/ 0, + /*tp_getattr*/ nullptr, + /*tp_setattr*/ nullptr, + /*tp_as_async*/ nullptr, + /*tp_repr*/ reinterpret_cast(BPy_GeometrySet_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 | Py_TPFLAGS_BASETYPE, + /*tp_doc*/ bpy_geometry_set_doc, + /*tp_traverse*/ nullptr, + /*tp_clear*/ nullptr, + /*tp_richcompare*/ nullptr, + /*tp_weaklistoffset*/ 0, + /*tp_iter*/ nullptr, + /*tp_iternext*/ nullptr, + /*tp_methods*/ BPy_GeometrySet_methods, + /*tp_members*/ nullptr, + /*tp_getset*/ BPy_GeometrySet_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*/ reinterpret_cast(BPy_GeometrySet_new), +}; + +PyObject *BPyInit_geometry_set_type() +{ + if (PyType_Ready(&bpy_geometry_set_Type) < 0) { + return nullptr; + } + return reinterpret_cast(&bpy_geometry_set_Type); +} diff --git a/source/blender/python/intern/bpy_geometry_set.hh b/source/blender/python/intern/bpy_geometry_set.hh new file mode 100644 index 00000000000..4684215960f --- /dev/null +++ b/source/blender/python/intern/bpy_geometry_set.hh @@ -0,0 +1,9 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include + +PyObject *BPyInit_geometry_set_type();