From 9719eac4171aae2bf846bfeeb3bbd73408055a20 Mon Sep 17 00:00:00 2001 From: Jake Kinchen Date: Mon, 24 Nov 2025 13:50:09 -0600 Subject: [PATCH 1/2] Fix duplicated arrow tips in DashedVMobject and add tests --- manim/mobject/types/vectorized_mobject.py | 22 +++++++++- .../test_dashed_vmobject.py | 43 +++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 tests/module/mobject/types/vectorized_mobject/test_dashed_vmobject.py diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index 69c3f59061..68589b447e 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -2819,6 +2819,21 @@ def __init__( self.dashed_ratio = dashed_ratio self.num_dashes = num_dashes super().__init__(color=color, **kwargs) + + # Work on a copy to avoid mutating the caller's mobject (e.g. removing tips). + base_vmobject = vmobject + vmobject = base_vmobject.copy() + + # TipableVMobject instances (Arrow, Vector, etc.) carry tips as submobjects. + # When dashing such objects, each subcurve would otherwise include its own + # tip, leading to many overlapping arrowheads. Pop tips from the working + # copy and re-attach them only once after the dashes are created. + tips = None + if hasattr(vmobject, "pop_tips"): + popped_tips = vmobject.pop_tips() + if len(popped_tips.submobjects) > 0: + tips = popped_tips + r = self.dashed_ratio n = self.num_dashes if n > 0: @@ -2904,6 +2919,9 @@ def __init__( # Family is already taken care of by get_subcurve # implementation if config.renderer == RendererType.OPENGL: - self.match_style(vmobject, recurse=False) + self.match_style(base_vmobject, recurse=False) else: - self.match_style(vmobject, family=False) + self.match_style(base_vmobject, family=False) + + if tips is not None: + self.add(*tips.submobjects) diff --git a/tests/module/mobject/types/vectorized_mobject/test_dashed_vmobject.py b/tests/module/mobject/types/vectorized_mobject/test_dashed_vmobject.py new file mode 100644 index 0000000000..22b46be9e2 --- /dev/null +++ b/tests/module/mobject/types/vectorized_mobject/test_dashed_vmobject.py @@ -0,0 +1,43 @@ +from manim import Arrow, DashedVMobject, ORIGIN, UR, VGroup +from manim.mobject.geometry.tips import ArrowTip, StealthTip + + +def _collect_tips(mobject): + return [mob for mob in mobject.get_family() if isinstance(mob, ArrowTip)] + + +def test_dashed_arrow_has_single_tip(): + dashed = DashedVMobject(Arrow(ORIGIN, 2 * UR)) + tips = _collect_tips(dashed) + + assert len(tips) == 1 + + +def test_dashed_arrow_tip_not_duplicated_in_group_opacity(): + base_arrow = Arrow(ORIGIN, 2 * UR) + faded_arrow = base_arrow.copy().set_fill(opacity=0.4).set_stroke(opacity=0.4) + + dashed_group = ( + VGroup(DashedVMobject(faded_arrow)) + .set_fill(opacity=0.4, family=True) + .set_stroke(opacity=0.4, family=True) + ) + + tips = _collect_tips(dashed_group) + + assert len(tips) == 1 + + +def test_dashed_arrow_custom_tip_shape_has_single_tip(): + dashed = DashedVMobject(Arrow(ORIGIN, 2 * UR, tip_shape=StealthTip)) + tips = _collect_tips(dashed) + + assert len(tips) == 1 + assert isinstance(tips[0], StealthTip) + + +def test_dashed_arrow_with_start_tip_has_two_tips(): + dashed = DashedVMobject(Arrow(ORIGIN, 2 * UR).add_tip(at_start=True)) + tips = _collect_tips(dashed) + + assert len(tips) == 2 From 6b8f9896119d93244f15ae3c6fe422512cbe30f7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:27:13 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../mobject/types/vectorized_mobject/test_dashed_vmobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/module/mobject/types/vectorized_mobject/test_dashed_vmobject.py b/tests/module/mobject/types/vectorized_mobject/test_dashed_vmobject.py index 22b46be9e2..809c9c47f7 100644 --- a/tests/module/mobject/types/vectorized_mobject/test_dashed_vmobject.py +++ b/tests/module/mobject/types/vectorized_mobject/test_dashed_vmobject.py @@ -1,4 +1,4 @@ -from manim import Arrow, DashedVMobject, ORIGIN, UR, VGroup +from manim import ORIGIN, UR, Arrow, DashedVMobject, VGroup from manim.mobject.geometry.tips import ArrowTip, StealthTip