ManimCommunity / ManimCommunity/manim

SVGMobject passing NoneType to svgelements

Open
#3,949 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
40.9k
Forks
3.1k
Avg merge
3d 12h
Merged PRs (30d)
25

Description

Description of bug / unexpected behavior

When using an SVG plot with a legend (below), Manim passes a NoneType to svgelements. This results in an error:
TypeError: object of type 'NoneType' has no len()

When using an otherwise identical plot with no legend (below), there is no such error and Manim is able to parse the SVG image successfully.

I'm unsure if this is fundamentally an issue with Manim or svgelements.

Expected behavior

Both plots should be easily parsable without modifying svgelements.

How to reproduce the issue

Create the following simple scene with Manim using the two attached plots.

Code for reproducing the problem
class LoadSVGPlots(Scene):
    def construct(self):
        plot0 = SVGMobject('plot_without_legend.svg')
        print('loaded plot_without_legend')
        plot1 = SVGMobject('plot_with_legend.svg')
        print('loaded plot_with_legend')

Additional media files

Images/GIFs

plot_with_legend
plot_without_legend

Logs

Terminal output

After running the command: manim -qh -v DEBUG scene.py LoadSVGPlotsmanim -qh -v DEBUG scene.py LoadSVGPlots

Manim Community v0.18.1

loaded plot_without_legend
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /home/mikhailj/Downloads/anaconda3/envs/my-manim-environment/lib/python3.10/site-packages/manim/ │
│ cli/render/commands.py:120 in render                                                             │
│                                                                                                  │
│   117 │   │   │   try:                                                                           │
│   118 │   │   │   │   with tempconfig({}):                                                       │
│   119 │   │   │   │   │   scene = SceneClass()                                                   │
│ ❱ 120 │   │   │   │   │   scene.render()                                                         │
│   121 │   │   │   except Exception:                                                              │
│   122 │   │   │   │   error_console.print_exception()                                            │
│   123 │   │   │   │   sys.exit(1)                                                                │
│                                                                                                  │
│ /home/mikhailj/Downloads/anaconda3/envs/my-manim-environment/lib/python3.10/site-packages/manim/ │
│ scene/scene.py:229 in render                                                                     │
│                                                                                                  │
│    226 │   │   """                                                                               │
│    227 │   │   self.setup()                                                                      │
│    228 │   │   try:                                                                              │
│ ❱  229 │   │   │   self.construct()                                                              │
│    230 │   │   except EndSceneEarlyException:                                                    │
│    231 │   │   │   pass                                                                          │
│    232 │   │   except RerunSceneException as e:                                                  │
│                                                                                                  │
│ /home/mikhailj/Videos/Manim/uniform_combination/scene.py:331 in construct                        │
│                                                                                                  │
│   328 │   def construct(self):                                                                   │
│   329 │   │   plot0 = SVGMobject('plot_without_legend.svg')                                      │
│   330 │   │   print('loaded plot_without_legend')                                                │
│ ❱ 331 │   │   plot1 = SVGMobject('plot_with_legend.svg')                                         │
│   332 │   │   print('loaded plot_with_legend')                                                   │
│   333                                                                                            │
│   334 #####################                                                                      │
│                                                                                                  │
│ /home/mikhailj/Downloads/anaconda3/envs/my-manim-environment/lib/python3.10/site-packages/manim/ │
│ mobject/svg/svg_mobject.py:145 in __init__                                                       │
│                                                                                                  │
│   142 │   │   │   path_string_config = {}                                                        │
│   143 │   │   self.path_string_config = path_string_config                                       │
│   144 │   │                                                                                      │
│ ❱ 145 │   │   self.init_svg_mobject(use_svg_cache=use_svg_cache)                                 │
│   146 │   │                                                                                      │
│   147 │   │   self.set_style(                                                                    │
│   148 │   │   │   fill_color=fill_color,                                                         │
│                                                                                                  │
│ /home/mikhailj/Downloads/anaconda3/envs/my-manim-environment/lib/python3.10/site-packages/manim/ │
│ mobject/svg/svg_mobject.py:171 in init_svg_mobject                                               │
│                                                                                                  │
│   168 │   │   │   │   self.add(*mob)                                                             │
│   169 │   │   │   │   return                                                                     │
│   170 │   │                                                                                      │
│ ❱ 171 │   │   self.generate_mobject()                                                            │
│   172 │   │   if use_svg_cache:                                                                  │
│   173 │   │   │   SVG_HASH_TO_MOB_MAP[hash_val] = self.copy()                                    │
│   174                                                                                            │
│                                                                                                  │
│ /home/mikhailj/Downloads/anaconda3/envs/my-manim-environment/lib/python3.10/site-packages/manim/ │
│ mobject/svg/svg_mobject.py:199 in generate_mobject                                               │
│                                                                                                  │
│   196 │   │   modified_file_path = file_path.with_name(f"{file_path.stem}_{file_path.suffix}")   │
│   197 │   │   new_tree.write(modified_file_path)                                                 │
│   198 │   │                                                                                      │
│ ❱ 199 │   │   svg = se.SVG.parse(modified_file_path)                                             │
│   200 │   │   modified_file_path.unlink()                                                        │
│   201 │   │                                                                                      │
│   202 │   │   mobjects = self.get_mobjects_from(svg)                                             │
│                                                                                                  │
│ /home/mikhailj/Downloads/anaconda3/envs/my-manim-environment/lib/python3.10/site-packages/svgele │
│ ments/svgelements.py:9236 in parse                                                               │
│                                                                                                  │
│   9233 │   │   │   │   │   │   if SVG_TAG_PATH == tag:                                           │
│   9234 │   │   │   │   │   │   │   # Delayed path parsing, for partial paths.                    │
│   9235 │   │   │   │   │   │   │   s = Path(values, pathd_loaded=True)                           │
│ ❱ 9236 │   │   │   │   │   │   │   s.parse(values.get(SVG_ATTR_DATA))                            │
│   9237 │   │   │   │   │   │   elif SVG_TAG_CIRCLE == tag:                                       │
│   9238 │   │   │   │   │   │   │   s = Circle(values)                                            │
│   9239 │   │   │   │   │   │   elif SVG_TAG_ELLIPSE == tag:                                      │
│                                                                                                  │
│ /home/mikhailj/Downloads/anaconda3/envs/my-manim-environment/lib/python3.10/site-packages/svgele │
│ ments/svgelements.py:5969 in parse                                                               │
│                                                                                                  │
│   5966 │   def parse(self, pathdef):                                                             │
│   5967 │   │   """Parses the SVG path."""                                                        │
│   5968 │   │   tokens = SVGLexicalParser()                                                       │
│ ❱ 5969 │   │   tokens.parse(self, pathdef)                                                       │
│   5970 │                                                                                         │
│   5971 │   def validate_connections(self):                                                       │
│   5972 │   │   """                                                                               │
│                                                                                                  │
│ /home/mikhailj/Downloads/anaconda3/envs/my-manim-environment/lib/python3.10/site-packages/svgele │
│ ments/svgelements.py:347 in parse                                                                │
│                                                                                                  │
│    344 │   │   self.parser.start()                                                               │
│    345 │   │   self.pathd = pathd                                                                │
│    346 │   │   self.pos = 0                                                                      │
│ ❱  347 │   │   self.limit = len(pathd)                                                           │
│    348 │   │   while True:                                                                       │
│    349 │   │   │   cmd = self._command()                                                         │
│    350 │   │   │   if cmd is None:                                                               │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
TypeError: object of type 'NoneType' has no len()

System specifications

System Details
  • OS (with version, e.g., Windows 10 v2004 or macOS 10.15 (Catalina)): Debian GNU/Linux 10 (buster)
  • RAM:
  • Python version (python/py/python3 --version): Python 3.10.14
  • Installed modules (provide output from pip list):
Package              Version
-------------------- -----------
Brotli               1.1.0
build                1.2.2
CacheControl         0.14.0
certifi              2024.8.30
cffi                 1.17.1
charset-normalizer   3.3.2
cleo                 2.1.0
click                8.1.7
click-default-group  1.2.4
cloup                3.0.5
colorama             0.4.6
crashtest            0.4.1
cryptography         43.0.1
decorator            5.1.1
distlib              0.3.8
dulwich              0.21.7
fastjsonschema       2.20.0
filelock             3.16.0
future               1.0.0
glcontext            3.0.0
h2                   4.1.0
hpack                4.0.0
hyperframe           6.0.1
idna                 3.10
importlib_metadata   8.5.0
installer            0.7.0
isosurfaces          0.1.2
jaraco.classes       3.4.0
jeepney              0.8.0
keyring              24.3.1
manim                0.18.1
ManimPango           0.6.0
mapbox_earcut        1.0.1
markdown-it-py       3.0.0
mdurl                0.1.2
moderngl             5.10.0
moderngl-window      2.4.1
more-itertools       10.5.0
msgpack              1.1.0
multipledispatch     0.6.0
networkx             3.3
numpy                1.26.4
packaging            24.1
pexpect              4.9.0
pillow               10.4.0
pip                  24.2
pkginfo              1.11.1
platformdirs         4.3.3
poetry               1.8.3
poetry-core          1.9.0
poetry-plugin-export 1.8.0
ptyprocess           0.7.0
pycairo              1.27.0
pycparser            2.22
pydub                0.25.1
pyglet               1.5.16
Pygments             2.18.0
pyproject_hooks      1.1.0
pyrr                 0.10.3
PySocks              1.7.1
PyYAML               6.0.2
rapidfuzz            3.9.7
requests             2.32.3
requests-toolbelt    1.0.0
rich                 13.8.1
scipy                1.14.1
screeninfo           0.8.1
SecretStorage        3.3.3
setuptools           73.0.1
shellingham          1.5.4
six                  1.16.0
skia-pathops         0.8.0.post1
srt                  3.5.3
svg.path             6.3
svgelements          1.9.6
tomli                2.0.1
tomlkit              0.13.2
tqdm                 4.66.5
trove-classifiers    2024.9.12
typing_extensions    4.12.2
urllib3              2.2.3
virtualenv           20.26.4
watchdog             5.0.2
wheel                0.44.0
zipp                 3.20.2
zstandard            0.23.0
LaTeX details
  • LaTeX distribution (e.g. TeX Live 2020):
  • Installed LaTeX packages:

Additional comments

I was able to avoid the error by modifying svgelements/svgelements.py class SVGLexicalParser method parse. I replaced

    def parse(self, parser, pathd):
        self.parser = parser
        self.parser.start()
        self.pathd = pathd
        self.pos = 0
        self.limit = len(pathd)
        # the rest of the method

with

    def parse(self, parser, pathd):
        self.parser = parser
        self.parser.start()
        self.pathd = pathd
        self.pos = 0
        if pathd is not None:
            self.limit = len(pathd)
        else:
            self.limit = 0
        # the rest of the method

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the failure with the LoadSVGPlots scene and compare the two supplied SVG plots. Start in manim/mobject/svg/svg_mobject.py at generate_mobject(), then inspect the svgelements SVG.parse and Path.parse calls shown in the traceback; done means the legend plot loads without the NoneType len() error.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
computer-graphics
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.