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

  1. Use an open path (Line, Arc, CubicBezier) instead of a closed loop for put_start_and_end_on
  2. For a circle between two points, construct Arc or CurvedArcBetweenPoints-style geometry instead
  3. 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

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


AI-assisted analysis of 3b1b/manim@dee01804d4 (2026-08-14). Data as JSON: /api/errors/85dd57e4ed92aa34. Report an issue: GitHub.