Open
Description
Description of proposed feature
I’d like a way to draw a line virtually connecting the center of two objects, while drawing only the part that is not inside the node. This is interesting e.g. if the node has no background (to see another object behind), or if we draw an arrow.
How can the new feature be used?
To connect two elements, like:
Additional comments
We discussed this functionality here. Abulafia and uwezi came up with some awesome hackish solutions, but it would be nice to integrate it in Manim, and clean it a bit. For instance, it does not work for boolean-produced objects without some additional things like:
self.simpler_enveloppe = body.submobjects[0]
maybe most importantly, it does not work for arrows.
from manim import *
class MyGuy(VGroup):
def __define_shape(self,how_malicious):
horn = Difference(Circle(),Circle().shift(.5*UP)).scale(1.3).shift(UP).set_opacity(0)
head = Circle().set_opacity(0)
mouth = ArcBetweenPoints(-.3*UP+.6*LEFT, -.3*UP-.6*LEFT, angle=how_malicious*-PI/3+(1-how_malicious)*(PI/2)).set_stroke(WHITE,opacity=1).scale(.5).set_z_index(1)
body = Union(horn.copy().scale(how_malicious * self.size_horn),head).set_stroke(interpolate_color(GREEN, RED, how_malicious),opacity=1).set_fill(
interpolate_color(interpolate_color(GREEN,BLACK,.5), interpolate_color(RED,BLACK,.5), how_malicious),
opacity=1)
eye1 = Dot(color=WHITE, fill_opacity=1).shift(.3*UP+.25*LEFT).set_z_index(1)
eye2 = Dot(color=WHITE, fill_opacity=1).shift(.3*UP+.25*RIGHT).set_z_index(1)
eyes = VGroup(eye1, eye2, mouth)
body.scale(self.myScale)
eyes.scale(self.myScale)
return (body, eyes)
def __init__(self, size_horn=1, **kwargs):
super().__init__(**kwargs)
self.myScale = 1
self.size_horn = size_horn
# initialize the vmobject
body, eyes = self.__define_shape(1)
self.become(body)
self.add(eyes)
self.simpler_enveloppe = body.submobjects[0]
class Connection(Line):
def __init__(self, mobj1, mobj2, **kwargs):
super().__init__(mobj1, mobj2, **kwargs)
distance = np.linalg.norm(mobj1.get_center()- mobj2.get_center())
angle = angle_of_vector(mobj2.get_center()-mobj1.get_center())
fake_line = (Rectangle(height=0.001, width=distance)
.move_to(mobj1.get_center(), aligned_edge=LEFT)
.rotate(angle, about_point=mobj1.get_center())
)
segment = Difference(Difference(fake_line, mobj1), mobj2)
self.segment = segment
if segment:
self.set_points_by_ends(
*self.find_end_points(segment.points)
)
else:
self.set_points_by_ends(ORIGIN, ORIGIN)
def find_end_points(self, points):
result = []
connected = []
for i in range(0,len(points), 4):
p = points[i]
if i > 0:
previous = points[i-1]
else:
previous = points[-1]
if np.linalg.norm(p-previous) < 1e-20:
connected.append(p)
else:
result.append(connected)
connected = []
result.append(connected)
if len(result) == 1:
select = 0
else:
select = 1
return result[select][0], result[select][len(result[select])//2]
class Test(Scene):
def construct(self):
sq1 = MyGuy()
circ = Star(outer_radius=2, color=RED).shift(UR+3*RIGHT)
l = Connection(sq1.simpler_enveloppe, circ, color=BLUE)
self.play(Create(sq1), Create(circ))
self.play(Create(l))
self.wait()
l.add_updater(lambda m: m.become(Connection(sq1.simpler_enveloppe, circ, color=BLUE)))
self.play(circ.animate.shift(2.5*DOWN+LEFT))
self.play(circ.animate.shift(8*LEFT))
self.play(circ.animate.shift(5*UP))
self.play(
Rotate(circ, 3*PI,about_point=[0,0,0],run_time=10,rate_func=linear)
)
self.wait(3)
self.clear()
sq1 = Text("C").scale(5)
circ = Text("X").scale(5).shift(2*UP+3*RIGHT)
l = Connection(sq1[0], circ[0], color=BLUE)
self.play(Write(sq1), Create(circ))
self.play(Create(l))
self.wait()
self.add(Dot().move_to(sq1[0].get_center()))
l.add_updater(lambda m: m.become(Connection(sq1[0], circ[0], color=BLUE)))
self.play(circ.animate.shift(2.5*DOWN+LEFT))
self.play(circ.animate.shift(8*LEFT))
self.play(circ.animate.shift(4*UP))
self.play(circ.animate.shift(3*RIGHT))
EDIT
This code can add arrow tips:
from manim import *
class MyGuy(VGroup):
def __define_shape(self,how_malicious):
horn = Difference(Circle(),Circle().shift(.5*UP)).scale(1.3).shift(UP).set_opacity(0)
head = Circle().set_opacity(0)
mouth = ArcBetweenPoints(-.3*UP+.6*LEFT, -.3*UP-.6*LEFT, angle=how_malicious*-PI/3+(1-how_malicious)*(PI/2)).set_stroke(WHITE,opacity=1).scale(.5).set_z_index(1)
body = Union(horn.copy().scale(how_malicious * self.size_horn),head).set_stroke(interpolate_color(GREEN, RED, how_malicious),opacity=1).set_fill(
interpolate_color(interpolate_color(GREEN,BLACK,.5), interpolate_color(RED,BLACK,.5), how_malicious),
opacity=1)
eye1 = Dot(color=WHITE, fill_opacity=1).shift(.3*UP+.25*LEFT).set_z_index(1)
eye2 = Dot(color=WHITE, fill_opacity=1).shift(.3*UP+.25*RIGHT).set_z_index(1)
eyes = VGroup(eye1, eye2, mouth)
body.scale(self.myScale)
eyes.scale(self.myScale)
return (body, eyes)
def __init__(self, size_horn=1, **kwargs):
super().__init__(**kwargs)
self.myScale = 1
self.size_horn = size_horn
# initialize the vmobject
body, eyes = self.__define_shape(1)
self.become(body)
self.add(eyes)
self.simpler_enveloppe = body.submobjects[0]
class Connection(Line):
def __init__(self, mobj1, mobj2, tip=False, **kwargs):
super().__init__(mobj1, mobj2, **kwargs)
distance = np.linalg.norm(mobj1.get_center()- mobj2.get_center())
angle = angle_of_vector(mobj2.get_center()-mobj1.get_center())
fake_line = (Rectangle(height=0.001, width=distance)
.move_to(mobj1.get_center(), aligned_edge=LEFT)
.rotate(angle, about_point=mobj1.get_center())
)
segment = Difference(Difference(fake_line, mobj1), mobj2)
self.segment = segment
if segment:
start, end = self.find_end_points(segment.points)
start, end = sorted([start, end], key=lambda p: np.linalg.norm(p-mobj1.get_center()))
self.set_points_by_ends(start, end)
if tip:
self.add_tip()
else:
self.set_points_by_ends(ORIGIN, ORIGIN)
self.tip = None
def find_end_points(self, points):
result = []
connected = []
for i in range(0,len(points), 4):
p = points[i]
if i > 0:
previous = points[i-1]
else:
previous = points[-1]
if np.linalg.norm(p-previous) < 1e-20:
connected.append(p)
else:
result.append(connected)
connected = []
result.append(connected)
if len(result) == 1:
select = 0
else:
select = 1
return result[select][0], result[select][len(result[select])//2]
class Test(Scene):
def construct(self):
sq1 = MyGuy()
circ = Star(outer_radius=2, color=RED).shift(UR+3*RIGHT)
l = Connection(sq1.simpler_enveloppe, circ, tip=True, color=BLUE)
self.play(Create(sq1), Create(circ))
self.play(Create(l))
self.wait()
l.add_updater(lambda m: m.become(Connection(sq1.simpler_enveloppe, circ, tip=True, color=BLUE)))
self.play(circ.animate.shift(2.5*DOWN+LEFT))
self.play(circ.animate.shift(8*LEFT))
self.play(circ.animate.shift(5*UP))
self.play(
Rotate(circ, 3*PI,about_point=[0,0,0],run_time=10,rate_func=linear)
)
self.wait(3)
self.clear()
sq1 = Text("C").scale(5)
circ = Text("S").scale(5).shift(2*UP+3*RIGHT)
l = Connection(sq1[0], circ[0], color=BLUE)
self.play(Write(sq1), Create(circ))
self.play(Create(l))
self.wait()
self.add(Dot().move_to(sq1[0].get_center()))
l.add_updater(lambda m: m.become(Connection(sq1[0], circ[0], color=BLUE)))
self.play(
Rotate(circ, 3*PI,about_point=[0,0,0],run_time=10,rate_func=linear)
)
Metadata
Metadata
Assignees
Labels
Type
Projects
Status
🆕 New