Tests: Add UI test for common mask operations in sculpt mode

This test exercises a common pair of mask operations in sculpt mode, a
dense mesh is created, the sculpt expand operator is used to mask a
region starting from the center, and then this region is inverted.

Pull Request: https://projects.blender.org/blender/blender/pulls/151875
This commit is contained in:
Sean Kim 2026-01-14 21:52:10 +01:00 • committed by Sean Kim
parent 9387f42887
commit ee680df0b2
2 changed files with 79 additions and 0 deletions

View file

@ -1492,6 +1492,7 @@ if(WITH_UI_TESTS)
# list explicitly so changes bisecting/updated are sure to re-run CMake.
set(_ui_tests
test_sculpt.asset_shelf_brush_selection
test_sculpt.mask_expand_and_invert
test_tools.sculpt_mode_toolbar
test_undo.compositor_make_group
test_undo.text_editor_edit_mode_mix

View file

@ -86,3 +86,81 @@ def asset_shelf_brush_selection():
import bpy
current_brush = bpy.context.tool_settings.sculpt.brush
t.assertEqual(current_brush.name, "Blob")
def _view3d_startup_area_maximized(e):
"""
Set the 3D viewport and set the area full-screen so no other regions.
"""
yield e.shift.f5() # 3D Viewport.
yield e.ctrl.alt.space() # Full-screen.
yield e.a() # Select all.
yield e.delete().ret() # Delete all.
def _subdivide_mesh(e, times):
yield e.tab() # Enter Edit mode.
for i in range(times):
yield e.ctrl.e().d() # Subdivide.
yield e.tab() # Leave Edit mode.
def _cursor_motion_data_x(e, start_position, pixels):
for x in range(pixels // 10):
position = (start_position[0] + x * 10, start_position[1])
yield e.cursor_position_set(*position, move=True)
def _num_fully_masked_vertices():
import bpy
import numpy as np
mesh = bpy.context.object.data
if not mesh.attributes.get('.sculpt_mask'):
return 0
mask_attr = mesh.attributes['.sculpt_mask']
num_vertices = mesh.attributes.domain_size('POINT')
mask_data = np.zeros(num_vertices, dtype=np.float32)
mask_attr.data.foreach_get('value', mask_data)
return np.count_nonzero(mask_data == 1.0)
def mask_expand_and_invert():
e, t = _test_vars(window := _test_window())
yield from _view3d_startup_area_maximized(e)
yield from _call_menu(e, "Add -> Mesh -> Monkey")
yield e.numpad_period() # View monkey
yield from _subdivide_mesh(e, 3)
yield e.ctrl.tab().s() # Sculpt via pie menu.
area = _window_area_get_by_type(window, 'VIEW_3D')
position = (area.x + area.width // 2, area.y + area.height // 2)
yield e.cursor_position_set(*position, move=True) # Move mouse to center
yield e.shift.a() # Expand operator
yield from _cursor_motion_data_x(e, position, 200)
e.leftmouse.tap()
yield
initial_masked_verts = _num_fully_masked_vertices()
t.assertEqual(initial_masked_verts, 8548)
yield e.a() # Mask pie menu
yield e.i() # Invert
inverted_masked_verts = _num_fully_masked_vertices()
t.assertEqual(inverted_masked_verts, 22598)
import bpy
mesh = bpy.context.object.data
total_verts = mesh.attributes.domain_size('POINT')
t.assertEqual(initial_masked_verts + inverted_masked_verts, total_verts)