Files
test2/tests/python/bl_pyapi_bmesh.py
Campbell Barton 62d72bd0b5 UV: initial implementation of UV sync select
Support sync selection in the UV editor, with face-corner selection,
so it's possible to select individual UV vertices/edges in the UV editor
without UV's attached to the same underlying edge also becoming selected.

There is limited support for maintaining the UV selection when selecting
from the 3D viewport, common operations such as picking &
box/circle/lasso select support this, however other selection operations
such as "Select Random" or "Select Similar" will clear this data,
causing all UV's connected to selected mesh elements to become selected.
We may add support for additional operators as needed.

Details:

- UV Sync Selection is now enabled by default.
- In edit-mode the UV selection is stored in BMLoop/BMFace which are
  written to custom-data layers when converted to a Mesh.
- To avoid unnecessary overhead - this data is created on demand.
  Operators may clear this data - selecting all or none do so,
  as there is no reason to store this data for a uniform selection.
- The Python API includes functions to synchronize the selection to/from
  UV's as well as flushing based on the mode.
- Python scripts that manipulate the selection will either need to clear
  this synchronized state or maintain it.

See:
- Design task: #78393.
- Implementation task: #131642.

Ref !138197
2025-10-07 01:41:16 +00:00

695 lines
25 KiB
Python

# SPDX-FileCopyrightText: 2025 Blender Authors
#
# SPDX-License-Identifier: Apache-2.0
# ./blender.bin --background --python tests/python/bl_pyapi_bmesh.py -- --verbose
__all__ = (
"main",
)
import bmesh
import unittest
# ------------------------------------------------------------------------------
# Internal Utilities
def save_to_blend_file_for_testing(bm):
"""
Useful for inspecting test data.
"""
import bpy
from bpy import context
bpy.ops.wm.read_factory_settings(use_empty=True)
me = bpy.data.meshes.new("test output")
bm.to_mesh(me)
ob = bpy.data.objects.new("", me)
view_layer = context.view_layer
layer_collection = context.layer_collection or view_layer.active_layer_collection
scene_collection = layer_collection.collection
scene_collection.objects.link(ob)
ob.select_set(True)
view_layer.objects.active = ob
# Write to the $CWD.
bpy.ops.wm.save_as_mainfile(filepath="bl_pyapi_bmesh.blend")
# ------------------------------------------------------------------------------
# Basic Tests
class TestBMeshBasic(unittest.TestCase):
def test_create_uvsphere(self):
bm = bmesh.new()
bmesh.ops.create_uvsphere(
bm,
u_segments=8,
v_segments=5,
radius=1.0,
)
self.assertEqual(len(bm.verts), 34)
self.assertEqual(len(bm.edges), 72)
self.assertEqual(len(bm.faces), 40)
bm.free()
# ------------------------------------------------------------------------------
# UV Selection
def bm_uv_select_check_or_empty(
bm,
sync=False,
flush=False,
contiguous=False,
):
return bmesh.utils.uv_select_check(
bm,
sync=sync,
flush=flush,
contiguous=contiguous,
) or {}
def bm_uv_select_check_non_zero(
bm, /, *,
sync=False,
flush=False,
contiguous=False,
):
"""
Remove all zero keys, so it's convenient to isolate failures.
"""
result = bmesh.utils.uv_select_check(
bm,
sync=sync,
flush=flush,
contiguous=contiguous,
)
if result is not None:
return {key: value for key, value in result.items() if value != 0}
return {}
def bm_uv_select_set_all(bm, /, *, select):
for f in bm.faces:
f.uv_select = select
for l in f.loops:
l.uv_select_vert = select
l.uv_select_edge = select
def bm_uv_layer_from_coords(bm, uv_layer):
for face in bm.faces:
for loop in face.loops:
loop_uv = loop[uv_layer]
# Use XY position of the vertex as a uv coordinate.
loop_uv.uv = loop.vert.co.xy
def bm_loop_select_count_vert_edge_face(bm):
"""
Return a tuple of UV selection counts (vert, edge, face).
Use for tests.
"""
mesh_vert = 0
mesh_edge = 0
mesh_face = 0
uv_vert = 0
uv_edge = 0
uv_face = 0
for v in bm.verts:
if v.hide:
continue
if v.select:
mesh_vert += 1
for e in bm.edges:
if e.hide:
continue
if e.select:
mesh_edge += 1
for f in bm.faces:
if f.hide:
continue
if f.select:
mesh_face += 1
if f.uv_select:
uv_face += 1
for l in f.loops:
if l.uv_select_vert:
uv_vert += 1
if l.uv_select_edge:
uv_edge += 1
return (uv_vert, uv_edge, uv_face), (mesh_vert, mesh_edge, mesh_face)
def bm_uv_select_reset(bm, /, *, select):
bm_uv_select_set_all(bm, select=select)
bm.uv_select_sync_to_mesh()
class TestBMeshUVSelectSimple(unittest.TestCase):
def test_uv_grid(self):
bm = bmesh.new()
bmesh.ops.create_grid(
bm,
x_segments=3,
y_segments=4,
size=1.0,
)
self.assertEqual(len(bm.verts), 20)
self.assertEqual(len(bm.edges), 31)
self.assertEqual(len(bm.faces), 12)
# Nothing selected.
bm.uv_select_sync_valid = True
self.assertEqual(bm_uv_select_check_or_empty(bm, sync=True), {})
# All verts selected, no UV's selected.
for v in bm.verts:
v.select = True
bm.uv_select_sync_valid = True
self.assertEqual(bm_uv_select_check_or_empty(bm, sync=True).get(
"count_uv_vert_none_selected_with_vert_selected", 0), 20)
# No verts selected, all UV's selected.
for v in bm.verts:
v.select = False
for f in bm.faces:
for l in f.loops:
l.uv_select_vert = True
bm.uv_select_sync_valid = True
self.assertTrue(
bm_uv_select_check_or_empty(bm, sync=True).get("count_uv_vert_any_selected_with_vert_unselected", 0),
48)
bm.free()
def test_uv_contiguous_verts(self):
from mathutils import Vector
bm = bmesh.new()
bmesh.ops.create_grid(bm, x_segments=2, y_segments=2, size=1.0)
self.assertEqual((len(bm.verts), len(bm.edges), len(bm.faces)), (9, 12, 4))
faces = list(bm.faces)
# Sort faces so the order is always predictable.
vector_dot = Vector((0.95, 0.05, 0.0))
faces.sort(key=lambda f: vector_dot.dot(f.calc_center_median()))
# Checker de-select UV's, each face has an isolated selection.
for i, f in enumerate(faces):
do_select = bool(i % 2)
for l in f.loops:
l.uv_select_vert = do_select
l.uv_select_edge = do_select
bm.uv_select_sync_valid = True
result = bm_uv_select_check_or_empty(bm, sync=True, flush=True, contiguous=False)
self.assertTrue(result.get("count_uv_vert_any_selected_with_vert_unselected", 0), 48)
bm.free()
def test_uv_select_flush_mode(self):
bm = bmesh.new()
# Do a NOP empty mesh check.
bm.uv_select_sync_valid = True
bm.uv_select_flush_mode()
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
bmesh.ops.create_grid(bm, x_segments=3, y_segments=3, size=1.0)
# Needed for methods that act on UV select.
bm.uv_select_sync_valid = True
# Do a NOP.
bm.uv_select_flush_mode()
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
# Simple tests that selects all elements in a mode: `VERT`.
bm.select_mode = {'VERT'}
bm_uv_select_set_all(bm, select=False)
# Select only verts.
for f in bm.faces:
for l in f.loops:
l.uv_select_vert = True
bm.uv_select_flush_mode()
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((36, 36, 9), (16, 24, 9)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
# Simple tests that selects all elements in a mode: `EDGE`.
bm.select_mode = {'EDGE'}
bm_uv_select_set_all(bm, select=False)
# Select only edges..
for f in bm.faces:
for l in f.loops:
l.uv_select_edge_set(True)
bm.uv_select_flush_mode()
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((36, 36, 9), (16, 24, 9)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
# Simple tests that selects all elements in a mode: `FACE`.
bm.select_mode = {'FACE'}
bm_uv_select_set_all(bm, select=False)
# Select only faces.
for f in bm.faces:
f.uv_select_set(True)
bm.uv_select_flush_mode()
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((36, 36, 9), (16, 24, 9)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
# TODO: Complex mixed selection.
def test_uv_select_flush(self):
from mathutils import Vector
bm = bmesh.new()
# Do a NOP empty mesh check.
bm.uv_select_sync_valid = True
bm.uv_select_flush(True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
bmesh.ops.create_grid(bm, x_segments=3, y_segments=3, size=1.0)
# Needed for methods that act on UV select.
bm.uv_select_sync_valid = True
self.assertEqual((len(bm.verts), len(bm.edges), len(bm.faces)), (16, 24, 9))
# Do a NOP check.
bm.uv_select_flush(True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
faces = list(bm.faces)
# Sort faces so the order is always predictable.
vector_dot = Vector((0.95, 0.05, 0.0))
faces.sort(key=lambda f: vector_dot.dot(f.calc_center_median()))
f_center = faces[len(faces) // 2]
self.assertEqual(f_center.calc_center_median().to_tuple(6), (0.0, 0.0, 0.0))
uv_layer = bm.loops.layers.uv.new()
bm_uv_layer_from_coords(bm, uv_layer)
# Select 4 vertices.
for l in f_center.loops:
l.uv_select_vert = True
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((4, 0, 0), (0, 0, 0)))
# Check.
self.assertEqual(
bm_uv_select_check_non_zero(bm, sync=True, flush=True),
{
"count_uv_edge_unselected_with_all_verts_selected": 4,
"count_uv_face_unselected_with_all_verts_selected": 1,
"count_uv_vert_any_selected_with_vert_unselected": 4,
},
)
bm.uv_select_flush(True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((4, 4, 1), (4, 4, 1)))
self.assertEqual(
bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True),
# Not actually an error as the UV's have intentionally been selected in isolation.
{
"count_uv_vert_non_contiguous_selected": 5,
},
)
# De-select those 4, ensure the selection remains false afterwards.
for l in f_center.loops:
l.uv_select_vert = False
bm.uv_select_flush(False)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {})
# Select a single faces UV's bottom left hand corner (as well as adjacent UV's).
for f in faces:
for l in f.loops:
xy = l.vert.co.xy[:]
if xy[0] > 0.0 or xy[1] > 0.0:
continue
l.uv_select_vert = True
bm.uv_select_flush(True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((9, 6, 1), (4, 4, 1)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {})
# Ensure flushing de-selection does nothing when there is nothing to do.
bm.uv_select_flush(False)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((9, 6, 1), (4, 4, 1)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
self.assertTrue(bm.uv_select_sync_valid)
bm.free()
def test_uv_select_sync_from_mesh(self):
bm = bmesh.new()
uv_layer = bm.loops.layers.uv.new()
del uv_layer
# Do a NOP empty mesh check.
bm.select_flush(True)
bm.uv_select_sync_from_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
bmesh.ops.create_grid(bm, x_segments=4, y_segments=4, size=2.0)
# Needed for methods that act on UV select.
bm.uv_select_sync_valid = True
# Deselect all verts and flush back to the mesh.
for v in bm.verts:
v.select = False
bm.select_flush(True)
bm.uv_select_sync_from_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
# Select all verts and flush back to the mesh.
for v in bm.verts:
v.select = True
bm.select_flush(True)
bm.uv_select_sync_from_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((64, 64, 16), (25, 40, 16)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
# TODO: Complex mixed selection.
def test_uv_select_sync_to_mesh(self):
# Even though this is called in other tests,
# perform some additional checks here such as checking hide is respected.
bm = bmesh.new()
uv_layer = bm.loops.layers.uv.new()
del uv_layer
# Do a NOP empty mesh check.
bm.select_flush(True)
bm.uv_select_sync_from_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
bmesh.ops.create_grid(bm, x_segments=4, y_segments=4, size=2.0)
# Needed for methods that act on UV select.
bm.uv_select_sync_valid = True
# Select a single faces UV's bottom left hand corner (as well as adjacent UV's).
for f in bm.faces:
for l in f.loops:
l.uv_select_vert = True
bm.uv_select_flush(True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((64, 64, 16), (25, 40, 16)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
# Hide all geometry, then check syncing doesn't select them.
for v in bm.verts:
v.hide = True
for e in bm.edges:
e.hide = True
for f in bm.faces:
f.hide = True
bm.uv_select_flush(True)
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
bm.uv_select_sync_to_mesh()
# Nothing should be selected because the mesh is hidden.
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0)))
def test_uv_select_foreach_set(self):
# Select UV's directly, similar to selecting in the UV editor.
bm = bmesh.new()
uv_layer = bm.loops.layers.uv.new()
bm.uv_select_sync_valid = True
# Do a NOP empty mesh check.
bm.uv_select_foreach_set(True)
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
# Do a NOP empty mesh check with empty arguments.
bm.uv_select_foreach_set(True, loop_verts=[], loop_edges=[], faces=[])
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {})
# Use 3 segments to avoid central vertices (simplifies above/below tests when picking half the mesh).
bmesh.ops.create_grid(bm, x_segments=3, y_segments=3, size=2.0)
bm_uv_layer_from_coords(bm, uv_layer)
# Select all vertices with X below 0.0.
verts_x_pos = []
verts_x_neg = []
for v in bm.verts:
(verts_x_pos if v.co.x > 0.0 else verts_x_neg).append(v)
self.assertEqual((len(verts_x_neg), len(verts_x_pos)), (8, 8))
verts_x_pos_as_set = set(verts_x_pos)
# Other elements from the verts (to pass to selection).
faces_x_pos = [
f for f in bm.faces
# Find the loop that spans positive edges.
if len(set(l.vert for l in f.loops) & verts_x_pos_as_set) == 4
]
self.assertEqual(len(faces_x_pos), 3)
loop_edges_x_pos = [
l for f in faces_x_pos
for l in f.loops
]
self.assertEqual(len(loop_edges_x_pos), 12)
loop_verts_x_pos = [next(iter(v.link_loops)) for v in verts_x_pos]
self.assertEqual(len(loop_verts_x_pos), 8)
# NOTE: regarding allowing `count_uv_vert_non_contiguous_selected` when de-selecting edges & faces.
# This occurs because of `uv_select_flush_mode` which doesn't take `contiguous` UV's into account.
# ---------------
# Select by Verts
bm_uv_select_reset(bm, select=False)
bm.uv_select_foreach_set(True, loop_verts=loop_verts_x_pos)
bm.uv_select_flush(True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((18, 15, 3), (8, 10, 3)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {})
# ------------------
# De-Select by Verts
bm_uv_select_reset(bm, select=True)
bm.uv_select_foreach_set(False, loop_verts=loop_verts_x_pos)
bm.uv_select_flush(False)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((18, 15, 3), (8, 10, 3)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {})
# ---------------
# Select by Edges
bm.select_mode = {'EDGE'}
bm_uv_select_reset(bm, select=False)
bm.uv_select_foreach_set(True, loop_edges=loop_edges_x_pos)
bm.uv_select_flush_mode(flush_down=True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((18, 15, 3), (8, 10, 3)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {})
# ------------------
# De-Select by Edges
bm_uv_select_reset(bm, select=True)
bm.uv_select_foreach_set(False, loop_edges=loop_edges_x_pos)
bm.uv_select_flush_mode(flush_down=True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((24, 21, 3), (12, 14, 3)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {
"count_uv_vert_non_contiguous_selected": 4, # Not an error, to be expected.
})
# ---------------
# Select by Faces
bm.select_mode = {'FACE'}
bm_uv_select_reset(bm, select=False)
bm.uv_select_foreach_set(True, faces=faces_x_pos)
bm.uv_select_flush_mode(flush_down=True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((12, 12, 3), (8, 10, 3)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {
"count_uv_vert_non_contiguous_selected": 4, # Not an error, to be expected.
})
# ------------------
# De-Select by Faces
bm_uv_select_reset(bm, select=True)
bm.uv_select_foreach_set(False, faces=faces_x_pos)
bm.uv_select_flush_mode(flush_down=True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((24, 24, 6), (12, 17, 6)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {
"count_uv_vert_non_contiguous_selected": 4, # Not an error, to be expected.
})
# save_to_blend_file_for_testing(bm)
def test_uv_select_foreach_set_from_mesh(self):
"""
Select mesh elements, similar to selecting in the viewport,
which is then flushed to the UV editor.
"""
# Select geometry directly, similar to selecting in the 3D viewport.
bm = bmesh.new()
uv_layer = bm.loops.layers.uv.new()
bm.uv_select_sync_valid = True
# Do a NOP empty mesh check.
bm.uv_select_foreach_set_from_mesh(True)
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {})
# Do a NOP empty mesh check with empty arguments.
bm.uv_select_foreach_set_from_mesh(True, verts=[], edges=[], faces=[])
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {})
# Use 3 segments to avoid central vertices (simplifies above/below tests when picking half the mesh).
bmesh.ops.create_grid(bm, x_segments=3, y_segments=3, size=2.0)
bm_uv_layer_from_coords(bm, uv_layer)
# Select all vertices with X below 0.0.
verts_x_pos = []
verts_x_neg = []
for v in bm.verts:
(verts_x_pos if v.co.x > 0.0 else verts_x_neg).append(v)
self.assertEqual((len(verts_x_neg), len(verts_x_pos)), (8, 8))
verts_x_pos_as_set = set(verts_x_pos)
# Other elements from the verts (to pass to selection).
faces_x_pos = [
f for f in bm.faces
# Find the loop that spans positive edges.
if len(set(l.vert for l in f.loops) & verts_x_pos_as_set) == 4
]
self.assertEqual(len(faces_x_pos), 3)
edges_x_pos = [
e for e in bm.edges
if len(set(e.verts) & verts_x_pos_as_set) == 2
]
self.assertEqual(len(edges_x_pos), 10)
loop_verts_x_pos = [next(iter(v.link_loops)) for v in verts_x_pos]
self.assertEqual(len(loop_verts_x_pos), 8)
# NOTE: regarding allowing `count_uv_vert_non_contiguous_selected` when de-selecting edges & faces.
# This occurs because of `uv_select_flush_mode` which doesn't take `contiguous` UV's into account.
# ---------------
# Select by Verts
bm_uv_select_reset(bm, select=False)
bm.uv_select_foreach_set_from_mesh(True, verts=verts_x_pos)
bm.uv_select_flush(True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((18, 15, 3), (8, 10, 3)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {})
# ------------------
# De-Select by Verts
bm_uv_select_reset(bm, select=True)
bm.uv_select_foreach_set_from_mesh(False, verts=verts_x_pos)
bm.uv_select_flush(False)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((18, 15, 3), (8, 10, 3)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {})
# ---------------
# Select by Edges
bm.select_mode = {'EDGE'}
bm_uv_select_reset(bm, select=False)
bm.uv_select_foreach_set_from_mesh(True, edges=edges_x_pos)
bm.uv_select_flush_mode(flush_down=True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((18, 15, 3), (8, 10, 3)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {})
# ------------------
# De-Select by Edges
bm_uv_select_reset(bm, select=True)
bm.uv_select_foreach_set_from_mesh(False, edges=edges_x_pos)
bm.uv_select_flush_mode(flush_down=True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((24, 21, 3), (12, 14, 3)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {
"count_uv_vert_non_contiguous_selected": 4, # Not an error, to be expected.
})
# ---------------
# Select by Faces
bm.select_mode = {'FACE'}
bm_uv_select_reset(bm, select=False)
bm.uv_select_foreach_set_from_mesh(True, faces=faces_x_pos)
bm.uv_select_flush_mode(flush_down=True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((12, 12, 3), (8, 10, 3)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {
"count_uv_vert_non_contiguous_selected": 4, # Not an error, to be expected.
})
# ------------------
# De-Select by Faces
bm_uv_select_reset(bm, select=True)
bm.uv_select_foreach_set_from_mesh(False, faces=faces_x_pos)
bm.uv_select_flush_mode(flush_down=True)
bm.uv_select_sync_to_mesh()
self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((24, 24, 6), (12, 17, 6)))
self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {
"count_uv_vert_non_contiguous_selected": 4, # Not an error, to be expected.
})
# save_to_blend_file_for_testing(bm)
def main():
import sys
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
unittest.main()
if __name__ == "__main__":
main()