Add_tip shifts the line
Description of bug / unexpected behavior
I wanted to use a tikz-equivalent of -| in manim to simply write the equivalent of the tikz code:
\draw[rounded corners,-Stealth] (0,0) -| (1,-1);
to obtain something like
but it turned out that the equivalent manim code is a huge mess:
import manim.mobject.geometry.tips as tips
class ellbowarrow(Scene):
def construct(self):
def add_tip(
line,
shorten=True,
tip_shape: type[tips.ArrowTip] | None = None,
tip_length: float | None = None,
tip_width: float | None = None,
):
tip = line.get_unpositioned_tip(
tip_length=tip_length,
tip_shape = tip_shape,
tip_width = tip_width,
)
tipangle = Line(tip.base, tip.tip_point).get_angle()
angle = Line(line.point_from_proportion(0.999),line.point_from_proportion(1.0)).get_angle()
tip.rotate(angle-tipangle,about_point=tip.base)
tip.shift(line.get_end()-tip.tip_point)
if shorten==True:
points = line.get_all_points()
points[-1]=tip.base
line.set_points(points)
line.add(tip)
return line
Line.add_tip = add_tip
grid = NumberPlane().add_coordinates()
self.add(grid)
p1 = Dot([-4,2,0],color=RED)
p2 = Dot([4,-1,0],color=BLUE)
c1 = Dot(p1.get_center()*UP+p2.get_center()*RIGHT-0.5*RIGHT, color=YELLOW)
c2 = Dot(p2.get_center()*RIGHT+p1.get_center()*UP-0.5*UP, color=TEAL)
self.add(p1,p2,c1,c2)
line1 = Line().set_points_as_corners(
[p1.get_center(), p1.get_center()*UP+p2.get_center()*RIGHT-0.5*RIGHT]
)
line1.add_cubic_bezier_curve(
p1.get_center()*UP+p2.get_center()*RIGHT-0.5*RIGHT,
p2.get_center()*RIGHT+p1.get_center()*UP,
p2.get_center()*RIGHT+p1.get_center()*UP,
p2.get_center()*RIGHT+p1.get_center()*UP-.5*UP
)
line1.add_line_to(p2.get_center())
line1.add_tip(shorten=False, tip_length=0.5)
self.play(Create(line1))
self.wait()
(thanks uwezi for the help)
One issue is the lack of rounded corners (see https://github.com/ManimCommunity/manim/issues/4443), but the other issue is that Line.add_tip is broken as discussed in https://discord.com/channels/581738731934056449/1370706466541011055/1370728872416514141:
Indeed, the naive code:
from manim import *
class Terminology(Scene):
def construct(self):
# Grid
grid = NumberPlane()
grid.add_coordinates()
self.add(grid)
plainText = Text("Plaintext", font_size=30, color=YELLOW)
plainText.to_edge(LEFT, buff=0.8)
plainTextRect = SurroundingRectangle(plainText, color=YELLOW, buff=0.3)
self.play(Write(plainText))
self.play(Write(plainTextRect))
encryptionAlgorithm = Text("Encryption\nAlgorithm", font_size=30, color=BLUE)
encryptionAlgorithm.move_to(UP*2)
encryptionAlgorithmRect = SurroundingRectangle(encryptionAlgorithm, color=BLUE)
self.play(Write(encryptionAlgorithm))
self.play(Write(encryptionAlgorithmRect))
line1 = Line()
line1.set_points_as_corners([
plainTextRect.get_top(),
[plainTextRect.get_center()[0], 2, 0],
encryptionAlgorithmRect.get_left()
]).add_tip(tip_length=0.15)
self.play(Write(line1))
cipher = Text("Cipher", font_size=30, color=RED)
cipher.to_edge(RIGHT, buff=0.8)
cipherRect = SurroundingRectangle(cipher, color=RED, buff=0.3)
line2 = Line()
line2.set_points_as_corners([
encryptionAlgorithmRect.get_right(),
[cipherRect.get_center()[0], 2, 0],
cipherRect.get_top()
]).add_tip(tip_length=0.15)
self.play(Write(line2))
self.play(Write(cipher))
self.play(Write(cipherRect))
self.wait()
produces
the equivalent manim code is a huge mess:
I would not call my code a "huge mess" and there are certainly other methods to achieve the desired functionality - I just did not want to spend more than 10 minutes on the problem.
I made a nicer code solution:
from manim import *
import manim.mobject.geometry.tips as tips
class LineFromPoints(Line):
def __init__(self, pointsdirections, radius=0, **kwargs):
super().__init__(**kwargs)
pointsradii = [(pointsdirections[0][0],0)]
for p0,p2 in zip(pointsdirections, pointsdirections[1:]):
if len(p0)==3:
r = p0[2]
else:
r = radius
if len(p0)>1:
if p0[1] == "-|":
p1 = p0[0]*UP + p2[0]*RIGHT
pointsradii.append((p1,r))
elif p0[1] == "|-":
p1 = p0[0]*RIGHT + p2[0]*UP
pointsradii.append((p1,r))
pointsradii.append((p2[0],r))
self.set_points([pointsradii[0][0]])
for p0,p1,p2 in zip(pointsradii,pointsradii[1:],pointsradii[2:]):
hl1 = Line(p0[0],p1[0])
hl2 = Line(p1[0],p2[0])
hl1.scale((hl1.get_length()-p1[1])/hl1.get_length(),about_point=hl1.get_start())
hl2.scale((hl2.get_length()-p1[1])/hl2.get_length(),about_point=hl2.get_end())
self.add_line_to(hl1.get_end())
if r>0:
self.add_cubic_bezier_curve(
hl1.get_end(),
p1[0],
p1[0],
hl2.get_start()
)
self.add_line_to(pointsradii[-1][0])
def add_tip(
line,
shorten=True,
tip_shape: type[tips.ArrowTip] | None = None,
tip_length: float | None = None,
tip_width: float | None = None,
):
tip = line.get_unpositioned_tip(
tip_length=tip_length,
tip_shape = tip_shape,
tip_width = tip_width,
)
tipangle = Line(tip.base, tip.tip_point).get_angle()
angle = Line(line.point_from_proportion(0.999),line.point_from_proportion(1.0)).get_angle()
tip.rotate(angle-tipangle,about_point=tip.base)
tip.shift(line.get_end()-tip.tip_point)
if shorten==True:
points = line.get_all_points()
points[-1]=tip.base
line.set_points(points)
line.add(tip)
return line
class testLineFromPoints1(Scene):
def construct(self):
p1 = Dot([-4,2,0],color=RED)
p2 = Dot([2,-1,0],color=BLUE)
p3 = Dot([6,+3,0],color=YELLOW)
line1 = LineFromPoints(
[
(p1.get_center(),"|-"),
(p2.get_center(),"|-",1.5),
(p3.get_center(),""),
],
radius=0.5
)
line2 = LineFromPoints(
[
(p1.get_center(),"-|"),
(p2.get_center(),"-|"),
(p3.get_center(),""),
],
radius=0.5,
color=GREEN
).add_tip()
line3 = LineFromPoints(
[
(p1.get_center(),""),
(p2.get_center(),""),
(p3.get_center(),""),
],
radius=1,
color=ORANGE
).add_tip(tip_shape=StealthTip, tip_length=0.35)
self.add(p1,p2,p3)
self.add(line1,line2,line3)
class testLineFromPoints2(Scene):
def construct(self):
rad = ValueTracker(0)
p1 = Dot([-4,2,0],color=RED)
p2 = Dot([2,-2,0],color=BLUE)
p3 = Dot([6,+3,0],color=YELLOW)
line2 = always_redraw(lambda:
LineFromPoints(
[
(p1.get_center(),"-|"),
(p2.get_center(),"-|"),
(p3.get_center(),""),
],
radius=rad.get_value(),
color=GREEN
).add_tip()
)
line3 = always_redraw(lambda:
LineFromPoints(
[
(p1.get_center(),""),
(p2.get_center(),""),
(p3.get_center(),""),
],
radius=rad.get_value(),
color=ORANGE
).add_tip(tip_shape=StealthTip, tip_length=0.35)
)
self.add(p1,p2,p3)
self.add(line2,line3)
self.wait()
self.play(
rad.animate.set_value(2.5),
rate_func=rate_functions.there_and_back,
run_time=4
)
self.wait()
https://github.com/user-attachments/assets/ccdbca6b-9307-4168-bb6c-42df20c06403