diff --git a/source/blender/editors/curves/intern/curves_pen.cc b/source/blender/editors/curves/intern/curves_pen.cc index 188a8bbd4aa..3da8288201d 100644 --- a/source/blender/editors/curves/intern/curves_pen.cc +++ b/source/blender/editors/curves/intern/curves_pen.cc @@ -479,7 +479,9 @@ static bool move_handles_in_curve(const PenToolOperation &ptd, offset = snap_8_angles(offset); } - if (ptd.point_added) { + /* Set both handles to be `Aligned` if this point is newly added or is + * no longer control freely. */ + if (ptd.point_added || ptd.handle_moved) { handle_types_left[point_i] = BEZIER_HANDLE_ALIGN; handle_types_right[point_i] = BEZIER_HANDLE_ALIGN; } @@ -1181,6 +1183,8 @@ wmOperatorStatus PenToolOperation::invoke(bContext *C, wmOperator *op, const wmE this->move_entire = false; this->snap_angle = false; + this->handle_moved = false; + if (!(ELEM(event->type, LEFTMOUSE) && ELEM(event->val, KM_PRESS, KM_DBL_CLICK))) { return OPERATOR_RUNNING_MODAL; } @@ -1223,6 +1227,11 @@ wmOperatorStatus PenToolOperation::modal(bContext *C, wmOperator *op, const wmEv } else if (event->val == int(PenModal::MoveHandle)) { this->move_handle = !this->move_handle; + + /* Record if handle has every been moved. */ + if (this->move_handle) { + this->handle_moved = true; + } } } diff --git a/source/blender/editors/include/ED_curves.hh b/source/blender/editors/include/ED_curves.hh index 5fe9f5e1232..b2a599fb03b 100644 --- a/source/blender/editors/include/ED_curves.hh +++ b/source/blender/editors/include/ED_curves.hh @@ -91,6 +91,8 @@ class PenToolOperation { bool point_added; bool point_removed; + /* Used to go back to `aligned` after `move_handle` becomes `false` */ + bool handle_moved; float4x4 projection; float2 mouse_co; diff --git a/tests/files/animation/vertex_groups.blend b/tests/files/animation/vertex_groups.blend new file mode 100644 index 00000000000..f8479f00f1d --- /dev/null +++ b/tests/files/animation/vertex_groups.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d22b1a372530a0feddd3456eb25e60e12af60c014fd6b15a19ef8cdb504f7749 +size 98575 diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 27dda2c6bad..e6ea98f6a5e 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -608,6 +608,13 @@ if(TEST_SRC_DIR_EXISTS) -- --testdir "${TEST_SRC_DIR}/animation" ) + + add_blender_test( + vertex_group_painting + --python ${CMAKE_CURRENT_LIST_DIR}/vertex_group_painting.py + -- + --testdir "${TEST_SRC_DIR}/animation" + ) endif() # ------------------------------------------------------------------------------ diff --git a/tests/python/vertex_group_painting.py b/tests/python/vertex_group_painting.py new file mode 100644 index 00000000000..b89e5e65870 --- /dev/null +++ b/tests/python/vertex_group_painting.py @@ -0,0 +1,76 @@ +# SPDX-FileCopyrightText: 2025 Blender Authors +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import unittest +import sys +import pathlib + +import bpy + +""" +blender -b --factory-startup --python tests/python/vertex_group_paint.py -- --testdir tests/files/animation/ +""" + + +class NormalizeAllTest(unittest.TestCase): + """Test for normalizing all vertex groups on a mesh.""" + + def setUp(self) -> None: + bpy.ops.wm.read_homefile(use_factory_startup=True) + self._load_test_file() + + # Make sure the initial state is what we expect/need for the tests. + self.cube = bpy.data.objects['Cube'] + self.assertEqual(bpy.context.active_object, self.cube) + self.assertEqual(len(self.cube.vertex_groups), 2) + + def _load_test_file(self): + blendpath = str(args.testdir / "vertex_groups.blend") + bpy.ops.wm.read_homefile(use_factory_startup=True) # Just to be sure. + bpy.ops.wm.open_mainfile(filepath=blendpath, load_ui=False) + + def test_normalize_all(self): + # Initial state. + self.cube.data.vertices[0].groups[0].weight = 0.375 + self.cube.data.vertices[0].groups[1].weight = 0.125 + self.cube.vertex_groups[0].lock_weight = False + self.cube.vertex_groups[1].lock_weight = False + + # Attempt to normalize all vertex groups. Should succeed. + bpy.ops.object.vertex_group_normalize_all(group_select_mode='ALL', lock_active=False) + self.assertEqual(self.cube.data.vertices[0].groups[0].weight, 0.75) + self.assertEqual(self.cube.data.vertices[0].groups[1].weight, 0.25) + + def test_normalize_all_locked(self): + # Initial state. + self.cube.data.vertices[0].groups[0].weight = 0.375 + self.cube.data.vertices[0].groups[1].weight = 0.125 + self.cube.vertex_groups[0].lock_weight = True + self.cube.vertex_groups[1].lock_weight = True + + # Attempt to normalize all vertex groups. Should fail, leaving the weights as-is. + with self.assertRaises(RuntimeError): + bpy.ops.object.vertex_group_normalize_all(group_select_mode='ALL', lock_active=False) + self.assertEqual(self.cube.data.vertices[0].groups[0].weight, 0.375) + self.assertEqual(self.cube.data.vertices[0].groups[1].weight, 0.125) + + +def main(): + global args + import argparse + + if '--' in sys.argv: + argv = [sys.argv[0]] + sys.argv[sys.argv.index('--') + 1:] + else: + argv = sys.argv + + parser = argparse.ArgumentParser() + parser.add_argument('--testdir', required=True, type=pathlib.Path) + args, remaining = parser.parse_known_args(argv) + + unittest.main(argv=remaining) + + +if __name__ == "__main__": + main()