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.
This commit is contained in:
Petru Conduraru
2026-07-24 08:42:43 +03:00
committed by Dion Moult
parent 51ab38de27
commit 89523999b3
2 changed files with 31 additions and 3 deletions
+3 -3
View File
@@ -168,12 +168,12 @@ class Polyline(bonsai.core.tool.Polyline):
distance = (mouse_vector - last_point).length distance = (mouse_vector - last_point).length
if distance < 0: if distance < 0:
return 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: if distance > 0:
angle = tool.Cad.angle_3_vectors( angle = tool.Cad.angle_3_vectors(
second_to_last_point, last_point, mouse_vector, new_angle=None, degrees=True 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 # Round angle to the nearest 0.05
angle = round(angle / 0.05) * 0.05 if distance < angle_round_threshold else angle 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 angle = 0
orientation_angle = 0 orientation_angle = 0
if input_ui: 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: if should_round:
angle_snap = tool.Snap.get_angle_snap_value(context) angle_snap = tool.Snap.get_angle_snap_value(context)
angle = angle_snap * round(angle / angle_snap) if distance < angle_round_threshold else angle angle = angle_snap * round(angle / angle_snap) if distance < angle_round_threshold else angle
+28
View File
@@ -56,3 +56,31 @@ class TestValidateInput(NewFile):
# Angle. # Angle.
assert subject.validate_input("25", "A") == (True, "25.0") 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