3b1b/manim · error · ValueError

The stroke shader no longer declares {declaration!r}, so a f

Error message

The stroke shader no longer declares {declaration!r}, so a fill's border would be compiled as an ordinary stroke and vanish

What it means

This ValueError is an internal invariant check in the fill-border rendering pipeline: a fill's outline is drawn by recompiling the stroke shader with IS_FILL_BORDER flipped from false to true (drawing.py:350 declares border_declaration = 'const IS_FILL_BORDER: bool = false;'). module_specs verifies that declaration still exists in the stroke shader source before doing the string replacement; if a fork or edit removed/renamed it, borders would silently render as ordinary strokes and vanish, so the code fails loudly instead.

Source

Thrown at manimlib/renderer/drawing.py:370

    @classmethod
    def draws(cls, mobject: Mobject) -> bool:
        """Always, the two shaders being named here rather than by the mobject"""
        return True

    @classmethod
    def key(cls, mobject: Mobject) -> tuple:
        return (*super().key(mobject), mobject.shader_code_target)

    @classmethod
    def module_specs(cls, mobject: Mobject) -> list[ModuleSpec]:
        """
        Three modules from two sources: the border around a fill is the stroke shader with one
        constant compiled the other way.
        """
        source = read_shader_file(cls.stroke_file)
        declaration = cls.border_declaration
        if declaration not in source:
            raise ValueError(
                f"The stroke shader no longer declares {declaration!r}, so a fill's border "
                f"would be compiled as an ordinary stroke and vanish"
            )
        border = {declaration: declaration.replace("false", "true")}

        # A snippet is meant for one of the two sources where it names one of their fields,
        # since a snippet reading stroke_rgba would not compile against the fill
        target = mobject.shader_code_target
        replacements = mobject.shader_code_replacements
        nothing: dict[str, str] = dict()
        for_fill = replacements if target in (None, "fill") else nothing
        for_stroke = replacements if target in (None, "stroke") else nothing
        return [
            ("fill", cls.fill_file, for_fill),
            ("stroke", cls.stroke_file, for_stroke),
            ("border", cls.stroke_file, {**for_stroke, **border}),
        ]

View on GitHub (pinned to dee01804d4)

Solutions

  1. Restore or keep the exact line 'const IS_FILL_BORDER: bool = false;' in the stroke shader file
  2. If the constant was renamed intentionally, update border_declaration in drawing.py to match the new declaration string
  3. Ensure drawing.py and the shaders directory are from the same manim version (reinstall or git checkout the matching commit)

Example fix

// before (stroke shader)
// (declaration deleted during refactor)
// after
const IS_FILL_BORDER: bool = false;
Defensive patterns

Strategy: validation

Validate before calling

from manimlib.renderer.drawing import StrokedFillPath  # or the class owning stroke_file
src = open(cls.stroke_file).read()
assert cls.border_declaration in src, "stroke shader lost the IS_FILL_BORDER declaration"

Prevention

When it happens

Trigger: Editing manimlib/shaders/quadratic_bezier_stroke/ (or whichever stroke_file the class uses) and deleting or altering the line 'const IS_FILL_BORDER: bool = false;'; renaming the constant; changing 'false' to another literal. It fires the first time any fill mobject with a stroke is rendered.

Common situations: Custom shader work in a fork of manim; upstream shader refactors in a modified local install; version mismatches where drawing.py and the shader directory come from different commits.

Related errors


AI-assisted analysis of 3b1b/manim@dee01804d4 (2026-08-14). Data as JSON: /api/errors/c8921079bece37c3. Report an issue: GitHub.