Add `rounded corners` to round lines automatically
Description of bug / unexpected behavior
I wanted to use a tikz-equivalent of rounded corners 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)
Would you consider adding a way to round corners of arbitrary lines to make this kind of code significantly simpler?
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
Ahah sorry I wasn't criticising the quality of your code at all, I'm really grateful. My point is that such rounded lines are so common that they disearve a built-in one-line solution like in tikz instead on a long code (irrespective of its quality).