Search code examples
pythonmanim

How to create a buffer around a line with manim?


I'm very new to manim! As such I hope my question is not inappropriate. I have a line mobject and I would like to create a shape of specific size around, a so called 'buffer'. In the image you see a blue line, and the shape around it is what I like to create. Is there some method which creates this sort of shape for line objects?

I really did try to find something by myself, but maybe I just don't use the correct wording in my search.

Is there some method in the library which does this?

I'm very sorry if my question is too broad or just in other ways not appropriate. I just started to learn manim! enter image description here


UPDATE I tried to accomplish this task by setting the stroke_width to a very high level. However, the ends are not round but sharp.

This is the code for it:

class CreateLineWithBuffer(Scene):
    def construct(self):
        street=Line(color=PINK).rotate(angle=45)
        street.scale(8)
        line = Line(stroke_width=50, stroke_opacity=0.5).rotate(angle=45)
        line.scale(8)
        
        self.play(Create(street), run_time=3)
        self.play(Create(line), run_time=3)
        self.wait()

enter image description here


Solution

  • Here is a possible solution. Note that the code can definitely be optimized/refactored, and I didn't test it that much (so bugs are included ;) ). But you can use that as a starting point.

    from manim import *
    import math
    import numpy as np
    from manim.typing import Point3D
    
    
    def interpolate(
        start: int | float | Point3D, end: int | float | Point3D, alpha: float | Point3D
    ) -> float | Point3D:
        return (1 - alpha) * start + alpha * end
    
    
    class BigLine(VMobject):
        def __init__(
            self,
            start: Point3D = LEFT,
            end: Point3D = RIGHT,
            buff: float = 0.2,
            **kwargs,
        ) -> None:
            self.start = start
            self.end = end
            self.buff = buff
            super().__init__(**kwargs)
    
        def generate_points(self) -> None:
            points = []
            start_point_pos_normal = self.start + self.buff * self.calc_normal(
                self.start, self.end
            )
            start_point_neg_normal = self.start - self.buff * self.calc_normal(
                self.start, self.end
            )
            end_point_pos_normal = self.end + self.buff * self.calc_normal(
                self.start, self.end
            )
            end_point_neg_normal = self.end - self.buff * self.calc_normal(
                self.start, self.end
            )
    
            first_arc = ArcBetweenPoints(
                end_point_pos_normal, end_point_neg_normal, angle=-TAU / 2
            )
            second_arc = ArcBetweenPoints(
                start_point_neg_normal, start_point_pos_normal, angle=-TAU / 2
            )
    
            nppcc = self.n_points_per_cubic_curve
            points.extend(
                interpolate(start_point_pos_normal, end_point_pos_normal, a)
                for a in np.linspace(0, 1, nppcc)
            )
            points.extend(first_arc.points)
            points.extend(
                interpolate(end_point_neg_normal, start_point_neg_normal, a)
                for a in np.linspace(0, 1, nppcc)
            )
            points.extend(second_arc.points)
            points.append(start_point_pos_normal)
            self.set_points(points)
    
        def calc_normal(self, first, second) -> Point3D:
            dx = second[0] - first[0]
            dy = second[1] - first[1]
            normal_vec = np.array([-dy, dx, 0])
            return normal_vec/np.linalg.norm(normal_vec)
    
    
    class LineWidth(Scene):
        def construct(self):
            line = Line(2 * UL, 2 * DR, stroke_width=3)
            big_line = BigLine(2 * UL, 2 * DR, buff=1, fill_color=RED, fill_opacity=0.3)
    
            self.add(line, big_line)
    
            self.wait(3)
    

    Result