From 89523999b3b44e3d33c8a9ea29680e2e8d615275 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Fri, 24 Jul 2026 08:42:43 +0300 Subject: [PATCH] Bonsai: fix UnboundLocalError crash in polyline angle calculation angle_round_threshold was only assigned inside the `distance > 0` branch of calculate_distance_and_angle, but read unconditionally whenever should_round is True. When the mouse sample coincides with the last placed point (distance == 0), such as the first mouse move after placing a wall's start point on a YZ plane view, this crashed the modal wall tool. angle_round_threshold is a fixed cutoff unrelated to whether distance is currently zero, so it is now assigned once before the branch. Fixes #8597. Generated with the assistance of an AI coding tool. --- src/bonsai/bonsai/tool/polyline.py | 6 +++--- src/bonsai/test/tool/test_polyline.py | 28 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/bonsai/bonsai/tool/polyline.py b/src/bonsai/bonsai/tool/polyline.py index 90f81dc909..a3f9069f2f 100644 --- a/src/bonsai/bonsai/tool/polyline.py +++ b/src/bonsai/bonsai/tool/polyline.py @@ -168,12 +168,12 @@ class Polyline(bonsai.core.tool.Polyline): distance = (mouse_vector - last_point).length if distance < 0: return - angle, orientation_angle, angle_round_threshold = None, None, None + angle, orientation_angle = None, None + angle_round_threshold = 1000 # Avoids rounding when distance is too big if distance > 0: angle = tool.Cad.angle_3_vectors( second_to_last_point, last_point, mouse_vector, new_angle=None, degrees=True ) - angle_round_threshold = 1000 # Avoids rounding when distance is too big # Round angle to the nearest 0.05 angle = round(angle / 0.05) * 0.05 if distance < angle_round_threshold else angle @@ -189,7 +189,7 @@ class Polyline(bonsai.core.tool.Polyline): angle = 0 orientation_angle = 0 if input_ui: - assert angle is not None and orientation_angle is not None and angle_round_threshold is not None + assert angle is not None and orientation_angle is not None if should_round: angle_snap = tool.Snap.get_angle_snap_value(context) angle = angle_snap * round(angle / angle_snap) if distance < angle_round_threshold else angle diff --git a/src/bonsai/test/tool/test_polyline.py b/src/bonsai/test/tool/test_polyline.py index b55c6b52a6..89e03da473 100644 --- a/src/bonsai/test/tool/test_polyline.py +++ b/src/bonsai/test/tool/test_polyline.py @@ -56,3 +56,31 @@ class TestValidateInput(NewFile): # Angle. assert subject.validate_input("25", "A") == (True, "25.0") + + +class TestCalculateDistanceAndAngle(NewFile): + def test_it_does_not_crash_when_distance_is_zero_and_should_round(self, monkeypatch): + # Regression test for #8597: right after placing the first polyline + # point, the initial mouse sample can equal the last placed point + # (distance == 0), e.g. entering the viewport on a YZ plane wall. + # angle_round_threshold used to only be assigned in the + # `distance > 0` branch, crashing when should_round reads it here. + # get_increment_snap_value requires a real 3D viewport rv3d, which + # is unrelated to this bug, so it's stubbed out for a headless run. + monkeypatch.setattr(tool.Snap, "get_increment_snap_value", classmethod(lambda cls, context: 1.0)) + + polyline_props = tool.Model.get_polyline_props() + mouse_point = polyline_props.snap_mouse_point.add() + mouse_point.x, mouse_point.y, mouse_point.z = 0, 0, 0 + + tool_state = subject.create_tool_state() + tool_state.is_input_on = False + tool_state.use_default_container = False + tool_state.plane_method = "YZ" + + input_ui = subject.create_input_ui(input_options=["D", "A", "X", "Y", "Z"]) + + subject.calculate_distance_and_angle(bpy.context, input_ui, tool_state, should_round=True) + + assert input_ui.get_number_value("D") == 0 + assert input_ui.get_number_value("A") == 0