3b1b/manim · error · Exception
Cannot position endpoints of closed loop
Error message
Cannot position endpoints of closed loop
What it means
Exception('Cannot position endpoints of closed loop') raised by Mobject.put_start_and_end_on (manimlib/mobject/mobject.py:1286). The method works by scaling and rotating the vector from get_start() to get_end(); when that vector is zero (start equals end — a closed loop like a Circle, or a degenerate zero-length path) there is no direction or length to scale/rotate, so the math is impossible and manim raises.
Source
Thrown at manimlib/mobject/mobject.py:1286
) -> Self:
self.replace(mobject, dim_to_match, stretch)
length = mobject.length_over_dim(dim_to_match)
self.scale((length + buff) / length)
return self
def put_start_on(self, point: Vect3) -> Self:
self.shift(point - self.get_start())
return self
def put_end_on(self, point: Vect3) -> Self:
self.shift(point - self.get_end())
return self
def put_start_and_end_on(self, start: Vect3, end: Vect3) -> Self:
curr_start, curr_end = self.get_start_and_end()
curr_vect = curr_end - curr_start
if np.all(curr_vect == 0):
raise Exception("Cannot position endpoints of closed loop")
target_vect = end - start
self.scale(
get_norm(target_vect) / get_norm(curr_vect),
about_point=curr_start,
)
self.rotate(
angle_of_vector(target_vect) - angle_of_vector(curr_vect),
)
self.rotate(
np.arctan2(curr_vect[2], get_norm(curr_vect[:2])) - np.arctan2(target_vect[2], get_norm(target_vect[:2])),
axis=np.array([-target_vect[1], target_vect[0], 0]),
)
self.shift(start - self.get_start())
return self
# Color functions
def set_rgba_array(View on GitHub (pinned to dee01804d4)
Solutions
- Use an open path (Line, Arc, CubicBezier) instead of a closed loop for put_start_and_end_on
- For a circle between two points, construct Arc or CurvedArcBetweenPoints-style geometry instead
- Check curr_start != curr_end / get_start_and_end() before calling
Example fix
# before circle = Circle() circle.put_start_and_end_on(LEFT, RIGHT) # raises: closed loop # after from manimlib import ArcBetweenPoints arc = ArcBetweenPoints(LEFT, RIGHT, angle=TAU) # open path version # or use Line(LEFT, RIGHT) for a straight connector
Defensive patterns
Strategy: validation
Validate before calling
start, end = mob.get_start_and_end()
if np.all(end - start == 0):
raise ValueError("mobject is a closed loop; use Arc/Line instead")
mob.put_start_and_end_on(new_start, new_end) Type guard
def is_open_path(mob) -> bool:
s, e = mob.get_start_and_end()
return not np.all(e - s == 0) Prevention
- Check get_start_and_end() differ before calling put_start_and_end_on
- Use open paths (Line/Arc/ArcBetweenPoints) for endpoint positioning
When it happens
Trigger: Calling put_start_and_end_on(a, b) on a Circle, Ellipse, or any closed curve whose start and end points coincide; calling it on a degenerate VMobject with all points identical; using Line helpers that wrap put_start_and_end_on after points were collapsed.
Common situations: Trying to 'stretch' a Circle between two points; generic vector-shape code that assumes open paths; mobjects whose points were scaled to zero or replaced by a single point.
Related errors
- Unsupported: {path_verb}
- tip not found
- Mobject cannot contain self
- Trying to restore without having saved
- Need at least one color
AI-assisted analysis of 3b1b/manim@dee01804d4 (2026-08-14).
Data as JSON: /api/errors/85dd57e4ed92aa34.
Report an issue: GitHub.