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
- Restore or keep the exact line 'const IS_FILL_BORDER: bool = false;' in the stroke shader file
- If the constant was renamed intentionally, update border_declaration in drawing.py to match the new declaration string
- 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
- In shader forks, grep for IS_FILL_BORDER after every edit: it must stay exactly 'const IS_FILL_BORDER: bool = false;'
- Keep drawing.py and shaders/ in the same git commit; never mix versions
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
- An array in a block holds {ARRAY_ELEMENT_SIZE} floats to an
- No room in a block for {name}, of {size} floats
AI-assisted analysis of 3b1b/manim@dee01804d4 (2026-08-14).
Data as JSON: /api/errors/c8921079bece37c3.
Report an issue: GitHub.