.. The Python pseudo-code below is meant to be a straightforward translation of Blender's C++ select functions, so resist the temptation to simplify them. Blender's Python API for selections =================================== .. contents:: Table of contents :local: :backlinks: none Selecting edit mesh vertices, edges and faces --------------------------------------------- In interactive Blender in edit mode, one can select vertices, edges and faces of a mesh and run operators on the selection. The same can be done via Blender's Python API, but it's much more finicky. First, we create a cube and fetch its edit mesh for the examples to follow. >>> import bmesh >>> import bpy >>> >>> bpy.ops.mesh.primitive_cube_add() {'FINISHED'} >>> >>> bpy.ops.object.mode_set_with_submode(mode="EDIT") {'FINISHED'} >>> cube = bpy.context.object >>> bm = bmesh.from_edit_mesh(cube.data) >>> bm.verts.ensure_lookup_table() >>> bm.edges.ensure_lookup_table() >>> bm.faces.ensure_lookup_table() >>> >>> def current_selection(): ... print(f"Selected vertices: {[v.index for v in bm.verts if v.select]}") ... print(f"Deselected vertices: {[v.index for v in bm.verts if not v.select]}") ... print(f"Selected edges: {[e.index for e in bm.edges if e.select]}") ... print(f"Deselected edges: {[e.index for e in bm.edges if not e.select]}") ... print(f"Selected faces: {[f.index for f in bm.faces if f.select]}") ... print(f"Deselected faces: {[f.index for f in bm.faces if not f.select]}") ... As seen in the function body of ``current_selection``, BMesh vertices, edges and faces have a ``select`` attribute. These attribute are Booleans, but are much more complicated than it seems. .. _Valid state: Valid state ----------- `Blender makes several assumptions on the state of a mesh `__, in particular, the documentation states - When an edge is selected, its vertices are selected too. - When a face is selected, its edges and vertices are selected. We will be slightly more precise than Blender's documentation and will call a selection state that satisfies these conditions *valid* and otherwise *invalid*. By contraposition, a selection state is valid if and only if - if any vertex of an edge is deselected, the edge is deselected too and - if any vertex or edge of a face is deselected, the face is deselected too. The Blender Python API makes it easy to create invalid state, which `can even cause crashes `__. Note that - selecting the vertices of an edge doesn't necessarily select the edge, - selecting the edges of a face doesn't necessarily select the face and - selecting the vertices of a face doesn't necessarily select the face's edges and the face. For example, in interactive Blender, after - switching the default cube to edit mode, - setting the mesh select mode *exclusively to faces*, - selecting all faces and - deselecting exactly one face, we see that all vertices and edges remain selected. Repeating these steps with the mesh select mode set to vertices, edges and faces results in deselecting everything except for the opposite face and its vertices and edges. Repeating these steps with the mesh select mode set to edges and faces has yet another result. This shows that selecting is highly context dependent. Flushing to restore valid selection state ----------------------------------------- Blender's Python API has very few restrictions on selections and makes it easy to break valid selection state. `Blender's documentation `__ suggests "flushing" the selection to restore valid selection state. This amounts to - :ref:`select_flush ` - :ref:`select_flush_mode ` - :ref:`select_set ` in three variations for vertices, edges and faces respectively, which are all underdocumented as we'll see below. Blender never explicitly defines what "flushing" is, but one can infer from context that *flushing is changing other objects' selection state* in hopes to restore valid selection state. Note that there are usually multiple ways to restore valid state. Blender's documentation also mentions "flushing down" and "flushing up", for example `here `__ and `here `__. What they mean is that vertices, edges and faces are ordered as such:: UP faces edges vertices DOWN Flushing down from a given "level" means to adjust the selection state of the levels further down to restore valid selection state. Flushing up from a given "level" means to adjust the selection state of the levels further up to restore valid selection state. For example, suppose that a face is selected, but some of its vertices or edges are deselected. In this context, selecting all vertices and edges that belong to the face would be flushing down. For another example, suppose that all boundary edges of a face are selected but the face is deselected. In this context, selecting the face would be flushing up. .. `A comment in the Blender source `__ suggests that The Python API is implemented in ``source/blender/python/bmesh/bmesh_py_types.c`` (`source `__) and calls functions in ``source/blender/bmesh/intern/bmesh_marking.c`` (`source `__). .. _select_flush: select_flush ~~~~~~~~~~~~ `select_flush `__ is implemented by `static PyObject *bpy_bmesh_select_flush(BPy_BMesh *self, PyObject *value) `__. In particular, ``select_flush(False)`` corresponds to ``void BM_mesh_deselect_flush(BMesh *bm)`` (`source `__) which is more or less the same as .. code-block:: text def BM_mesh_deselect_flush(bm): for e in bm.edges: if not e.hide: if e.select: if not e.verts[0].select or not e.verts[1].select: e.select = False if (there exists a face adjacent to e) and not e.select: for f in (faces adjacent to e): f.select = False Note that ``select_flush(False)`` cannot select anything. Its counterpart ``select_flush(True)`` corresponds to ``void BM_mesh_select_flush(BMesh *bm)`` (`source `__) which is more or less the same as .. code-block:: text def BM_mesh_select_flush(bm): for e in bm.edges: if e.verts[0].select and e.verts[1].select and not e.hide: e.select = True for f in bm.faces: ok = True if not f.hide: for v in (vertices incident to f): if not v.select: ok = False else: ok = False if ok: f.select = True Note that ``select_flush(True)`` cannot deselect anything. Note that both ``select_flush`` variants - ignore the mesh selection mode (which is a subset of ``{"VERT", "EDGE", "FACE"}``), - cannot change the selection state of vertices and - always flush upwards. .. _select_flush_mode: select_flush_mode ~~~~~~~~~~~~~~~~~ `select_flush_mode `__ is implemented by `static PyObject *bpy_bmesh_select_flush_mode(BPy_BMesh *self, PyObject *value) `__ and `void BM_mesh_select_mode_flush(BMesh *bm) `__. It is more or less the same as .. code-block:: text def BM_mesh_select_mode_flush(bm): # The conditions are actually bitwise operations involving constants in # source/blender/makesdna/DNA_scene_types.h # # bm.select mode should always match the mesh select mode, see # https://projects.blender.org/blender/blender/src/branch/blender-v3.3-release/source/blender/bmesh/bmesh_class.h#L346 if "VERT" in bm.select_mode: bm_mesh_select_mode_flush_vert_to_edge(bm) if ("VERT" in bm.select_mode) or ("EDGE" in bm.select_mode): bm_mesh_select_mode_flush_edge_to_face(bm) def bm_mesh_select_mode_flush_vert_to_edge(bm): for e in bm.edges: if not e.hide and e.verts[0].select and e.verts[1].select: e.select = True else: e.select = False def bm_mesh_select_mode_flush_edge_to_face(bm): for f in bm.faces: ok = True if not f.hide: if not all(e.select for e in boundary edges of f): ok = False else: ok = False f.select = ok Note that - if the mesh select mode equals ``{"FACE"}``, then ``select_flush_mode`` will neither select or deselect anything, - ``select_flush_mode`` can select *and* deselect edges and faces and - ``select_flush_mode`` can neither select nor deselect vertices. .. _select_set: select_set ~~~~~~~~~~ BMesh vertices, edges and faces each have a method sharing the same name called - ``select_set`` for vertices (`docs `__, `source `__), - ``select_set`` for edges (`docs `__, `source `__) and - ``select_set`` for faces (`docs `__, `source `__). According to the Blender documentation, all three methods set the selection state of a particular vertex, edge or face and flush the selection down in a reasonable fashion (but this isn't quite true, as we'll see in a bit). These methods will never flush up however (this is true). For example, the ``select_set`` for vertices does little more than setting the ``select`` attribute, because vertices are already all the way down, so there is nothing to flush down. Therefore if ``f`` is a selected face with a corner vertex ``v``, then since ``v.select_set(False)`` doesn't flush up, it won't deselect ``f``, causing invalid selection state. The edges incident to ``v`` also retain their selection state, which is another potential source for invalid selection state. select_set depends on the mesh select mode ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. warning:: Blender's documentation doesn't mention this, but the mesh select mode affects ``select_set`` for edges and faces. This can be verified by reading the source code. For example, if ``e`` is an edge, then ``e.select_set(False)`` *may* deselect its vertices unless they belong to other selected edges, maintaining valid selection state if it was valid before. Setting mesh select mode exclusively to edges >>> bpy.ops.object.mode_set_with_submode(mode="EDIT", mesh_select_mode={"EDGE"}) {'FINISHED'} >>> bpy.ops.mesh.select_all(action="SELECT") {'FINISHED'} >>> bm.edges[0].select_set(False) >>> current_selection() Selected vertices: [0, 1, 2, 3, 4, 5, 6, 7] Deselected vertices: [] Selected edges: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] Deselected edges: [0] Selected faces: [0, 1, 2, 3, 4, 5] Deselected faces: [] results in all vertices remaining selected. .. note:: The new selection state won't be visible in the viewport until we run >>> bmesh.update_edit_mesh(cube.data) .. current_selection is much easier to test than changes in the viewport! but we will omit this in the following examples and rely on ``current_selection``. Feel free to update the edit mesh when running the examples yourself. On the other hand, setting the mesh select mode exclusively to vertices and edges >>> bpy.ops.object.mode_set_with_submode(mode="EDIT", mesh_select_mode={"VERT", "EDGE"}) {'FINISHED'} >>> bpy.ops.mesh.select_all(action="SELECT") {'FINISHED'} >>> bm.edges[0].select_set(False) >>> current_selection() Selected vertices: [1, 3, 4, 5, 6, 7] Deselected vertices: [0, 2] Selected edges: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] Deselected edges: [0] Selected faces: [0, 1, 2, 3, 4, 5] Deselected faces: [] ends up deselecting the two vertices incident to the deselected edge. The selection is invalid in both cases because any face containing ``bm.edges[0]`` should've been deselected too. We can restore valid state with >>> bm.select_flush(False) # We pass False because we want to deselect faces. >>> current_selection() Selected vertices: [1, 3, 4, 5, 6, 7] Deselected vertices: [0, 2] Selected edges: [2, 5, 6, 7, 8, 9, 11] Deselected edges: [0, 1, 3, 4, 10] Selected faces: [2, 5] Deselected faces: [0, 1, 3, 4] The ``select_set`` method for faces also depends on the mesh select mode. >>> bpy.ops.object.mode_set_with_submode(mode="EDIT", mesh_select_mode={"EDGE"}) {'FINISHED'} >>> bpy.ops.mesh.select_all(action="SELECT") {'FINISHED'} >>> bm.faces[0].select_set(False) >>> current_selection() Selected vertices: [0, 1, 2, 3, 4, 5, 6, 7] Deselected vertices: [] Selected edges: [4, 5, 6, 7, 8, 9, 10, 11] Deselected edges: [0, 1, 2, 3] Selected faces: [1, 2, 3, 4, 5] Deselected faces: [0] All vertices remain selected. Note that it would have been impossible in interactive Blender to deselect a face with the mesh select mode exclusively set to edges. A different mesh select mode results in different flushing behaviour. >>> bpy.ops.object.mode_set_with_submode( ... mode="EDIT", mesh_select_mode={"VERT", "EDGE", "FACE"} ... ) {'FINISHED'} >>> bpy.ops.mesh.select_all(action="SELECT") {'FINISHED'} >>> bm.faces[0].select_set(False) >>> current_selection() Selected vertices: [4, 5, 6, 7] Deselected vertices: [0, 1, 2, 3] Selected edges: [4, 5, 6, 7, 8, 9, 10, 11] Deselected edges: [0, 1, 2, 3] Selected faces: [1, 2, 3, 4, 5] Deselected faces: [0] The selection state is invalid in both of the previous two examples, because any face containing one of the deselected edges should also be deselected. Because the mesh is a cube, any edge is contained in two faces and since there are 4 deselected edges, there should be more than one deselected face. Since only a single face is deselected, the selection states are invalid. .. warning:: One would think that ``select_set`` for faces should always result in valid selection states, because there is no need to flush upwards. This is true for ``select_set(True)``, but false for ``select_set(False)``, which isn't mentioned in Blender's documentation unless this `comment in the source code `__ counts as documentation. Said comment suggests ``select_flush_mode`` to restore valid state >>> bm.select_flush_mode() >>> current_selection() Selected vertices: [4, 5, 6, 7] Deselected vertices: [0, 1, 2, 3] Selected edges: [6, 7, 8, 9] Deselected edges: [0, 1, 2, 3, 4, 5, 10, 11] Selected faces: [2] Deselected faces: [0, 1, 3, 4, 5] select_flush doesn't guarantee valid selection state ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Suppose all vertices, edges and faces selected and then we deselect one vertex. >>> bpy.ops.object.mode_set_with_submode(mode="EDIT", mesh_select_mode={"EDGE"}) {'FINISHED'} >>> bpy.ops.mesh.select_all(action="SELECT") {'FINISHED'} >>> bm.verts[0].select_set(False) >>> current_selection() Selected vertices: [1, 2, 3, 4, 5, 6, 7] Deselected vertices: [0] Selected edges: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] Deselected edges: [] Selected faces: [0, 1, 2, 3, 4, 5] Deselected faces: [] Since ``select_flush`` never flushes up, the selection state is now invalid, because any edge or face containing ``bm.verts[0]`` should be deselected too. Since ``select_flush(True)`` cannot deselect anything, it doesn't change the current selection state >>> bm.select_flush(True) >>> current_selection() Selected vertices: [1, 2, 3, 4, 5, 6, 7] Deselected vertices: [0] Selected edges: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] Deselected edges: [] Selected faces: [0, 1, 2, 3, 4, 5] Deselected faces: [] which shows that ``select_flush`` doesn't guarantee valid selection state. Here ``select_flush(False)`` accomplishes the intended result >>> bm.select_flush(False) >>> current_selection() Selected vertices: [1, 2, 3, 4, 5, 6, 7] Deselected vertices: [0] Selected edges: [2, 3, 4, 5, 6, 7, 8, 9, 11] Deselected edges: [0, 1, 10] Selected faces: [1, 2, 5] Deselected faces: [0, 3, 4] Summary ------- 1. Selections are complicated. Avoid selections if possible. 2. Use high level operators such as ``bpy.ops.mesh.select_all(action="SELECT")`` and ``bpy.ops.mesh.select_all(action="INVERT")``, because they're probably more reliable when it comes to ensuring valid selection state. 3. Avoid setting `select` attributes manually if possible. 4. Be really careful when using ``select_flush``, ``select_flush_mode`` and the ``select_set`` variants. You'll have to read this document or the source code to really understand what they do.