Sculpt: Add primitive tool in Sculpt Mode.

This commit exposes the Add Mesh tools of Object and Edit Mode to
Sculpt Mode.

This functionality only works when Multires or Dyntopo are disabled.

At a high level, the mesh drawn by the gizmo in the 3D viewport is
created as a `BMesh`, converted to a `Mesh`, and then joined to the
existing geometry.

No new functionality or defaults are changed compared to the existing
tools. Further changes are planned for future PRs.

Addresses the first point of #149216

Pull Request: https://projects.blender.org/blender/blender/pulls/152856
This commit is contained in:
kunal 2026-02-26 01:06:33 +01:00 • committed by Sean Kim
parent 865d6fd489
commit 9c185de6bd
7 changed files with 432 additions and 130 deletions

View file

@ -312,3 +312,38 @@ def trim_gestures():
yield from ui.call_operator(e, "Line Trim")
yield from e.leftmouse.cursor_motion(ui.cursor_motion_data_y(window))
t.assertEqual(mesh.attributes.domain_size('POINT'), 10)
def primitive_tool_add():
import bpy
e, t, window = ui.test_window()
yield e.ctrl.tab().s() # Sculpt via pie menu.
yield from ui.call_menu(e, "Sculpt -> Add Primitive -> Add Cube") # Select add cube tool
area = ui.get_window_area_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
e.leftmouse.press()
yield
pixels = 10
for delta in range(pixels):
position = (position[0] + delta, position[1] + delta)
yield e.cursor_position_set(*position, move=True)
e.leftmouse.release()
yield
for delta in range(pixels):
position = (position[0] - delta, position[1] - delta)
yield e.cursor_position_set(*position, move=True)
e.leftmouse.tap()
yield
mesh = bpy.context.object.data
num_faces = mesh.attributes.domain_size('FACE')
t.assertEqual(num_faces, 12) # There should be 6 + 6 faces

View file

@ -38,36 +38,56 @@ def sculpt_mode_toolbar():
yield e.four()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin_brush.draw_face_sets")
yield e.shift.space()
yield e.five()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.primitive_cube_add")
yield e.shift.space()
yield e.six()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.primitive_cone_add")
yield e.shift.space()
yield e.seven()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.primitive_cylinder_add")
yield e.shift.space()
yield e.eight()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.primitive_uv_sphere_add")
yield e.shift.space()
yield e.nine()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.primitive_ico_sphere_add")
yield e.shift.space()
yield e.b()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.box_mask")
yield e.shift.space()
yield e.eight()
yield e.shift.three()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.box_hide")
yield e.shift.space()
yield e.shift.two()
yield e.shift.seven()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.box_face_set")
yield e.shift.space()
yield e.shift.six()
yield e.ctrl.one()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.box_trim")
yield e.shift.space()
yield e.shift.zero()
yield e.ctrl.five()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.line_project")
yield e.shift.space()
yield e.ctrl.one()
yield e.ctrl.six()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.mesh_filter")
yield e.shift.space()
yield e.ctrl.two()
yield e.ctrl.seven()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.cloth_filter")
yield e.shift.space()
yield e.ctrl.three()
yield e.ctrl.eight()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.color_filter")
yield e.shift.space()
@ -75,19 +95,19 @@ def sculpt_mode_toolbar():
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.face_set_edit")
yield e.shift.space()
yield e.ctrl.four()
yield e.ctrl.nine()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.mask_by_color")
yield e.shift.space()
yield e.ctrl.five()
yield e.ctrl.zero()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.move")
yield e.shift.space()
yield e.ctrl.six()
yield e.alt.one()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.rotate")
yield e.shift.space()
yield e.ctrl.seven()
yield e.alt.two()
t.assertEqual(window.workspace.tools.from_space_view3d_mode('SCULPT').idname, "builtin.scale")
yield e.shift.space()