Skip to content

Commit

Permalink
chore(deps): make Qt backend optional (#350)
Browse files Browse the repository at this point in the history
* chore(deps): make Qt backend optional

TODO:
- [ ] Add relevant entry in CHANGELOG
- [ ] Update install documentation
- [ ] Make sure `manim-slides convert` can run without any Qt backend
- [ ] Make sure test suite works (partially) without any Qt backend
- [ ] Make sure we can import `manim_slides` without any Qt backend

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* chore(deps): some fixes but wip

* chore(docs): update

* chore(deps): support PyQt6

* chore(deps): make Qt backend optional

TODO:
- [ ] Add relevant entry in CHANGELOG
- [ ] Update install documentation
- [ ] Make sure `manim-slides convert` can run without any Qt backend
- [ ] Make sure test suite works (partially) without any Qt backend
- [ ] Make sure we can import `manim_slides` without any Qt backend

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* chore(deps): some fixes but wip

* chore(docs): update

* chore(deps): support PyQt6

* fix(deps): ci and docs

* fix(lib): missing package

* chore(ci): does it work?

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* chore(test): skip failing

* chore(docs): update

* chore(docs): update

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix(docs): typo

* fix(test): quit instead of shutdown

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
jeertmans and pre-commit-ci[bot] authored Jan 26, 2024
1 parent f260d0d commit 16f740d
Show file tree
Hide file tree
Showing 21 changed files with 935 additions and 822 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
uses: nikeee/setup-pandoc@v1

- name: Install local Python package
run: pdm install -dGdocs -dGgithub-action
run: pdm sync -Gdocs -Ggithub-action

- name: Install IPython kernel
run: pdm run ipython kernel install --name "manim-slides" --user
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
pyversion: ['3.8', '3.9', '3.10', '3.11']
pyversion: ['3.9', '3.10', '3.11', '3.12']
runs-on: ${{ matrix.os }}
env:
QT_QPA_PLATFORM: offscreen
Expand Down Expand Up @@ -69,7 +69,7 @@ jobs:

- name: Install Manim Slides
run: |
pdm install -dGgithub-action -dGtest
pdm sync -Ggithub-action -Gtest
- name: Run pytest
if: matrix.os != 'ubuntu-latest' || matrix.pyversion != '3.11'
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#335](https://github.com/jeertmans/manim-slides/pull/335)
- Changed build backend to PDM and reflected on docs.
[#354](https://github.com/jeertmans/manim-slides/pull/354)
- Dropped Python 3.8 support.
[#350](https://github.com/jeertmans/manim-slides/pull/350)
- Made Qt backend optional and support PyQt6 too.
[#350](https://github.com/jeertmans/manim-slides/pull/350)
- Documentated how to create and use a custom HTML template.
[#357](https://github.com/jeertmans/manim-slides/pull/357)

Expand Down
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,15 @@ manim-slides render example.py BasicExample
# or use ManimGL
manim-slides render --GL example.py BasicExample
```
<!-- end usage -->

> [!NOTE]
> Using `manim-slides render` makes sure the use the `manim`
> (or `manimlib`) library that was installed in the Python same environment.
> Put simply, this is a wrapper of `manim render [ARGS]...` (or `manimgl [ARGS]...`).
> Using `manim-slides render` makes sure to use the `manim`
> (or `manimlib`) library that was installed in the same Python environment.
> Put simply, this is a wrapper around
> `manim render [ARGS]...` (or `manimgl [ARGS]...`).
<!-- start more-usage -->

To start the presentation using `Scene1`, `Scene2` and so on, run:

Expand All @@ -106,7 +110,7 @@ In our example:
manim-slides BasicExample
```

<!-- end usage -->
<!-- end more-usage -->

<p align="center">
<img alt="Example GIF" src="https://raw.githubusercontent.com/jeertmans/manim-slides/main/static/example.gif">
Expand Down
6 changes: 3 additions & 3 deletions docs/source/contributing/workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ pdm install -Gmanimgl # For ManimGL
Additionnally, Manim Slides comes with groups of dependencies for development purposes:

```bash
pdm install -dGdev # For linters and formatters
pdm install -Gdev # For linters and formatters
# or
pdm install --dGdocs # To build the documentation locally
pdm install -Gdocs # To build the documentation locally
# or
pdm install --dGtests # To run tests
pdm install -Gtest # To run tests
```

:::{note}
Expand Down
79 changes: 64 additions & 15 deletions docs/source/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,23 @@ If you install Manim from its git repository, as suggested by ManimGL,
make sure to first check out a supported version (e.g., `git checkout tags/v1.6.1`
for ManimGL), otherwise it might install an unsupported version of Manim!
See [#314](https://github.com/jeertmans/manim-slides/issues/314).

Also, note that ManimGL uses outdated dependencies, and may
not work out-of-the-box. One example is NumPy: ManimGL
does not specify any restriction on this package, but
only `numpy<1.25` will work, see
[#2053](https://github.com/3b1b/manim/issues/2053).
:::

<!-- end deps -->

## Pip Install

The recommended way to install the latest release is to use pip:
The recommended way to install the latest release
with all features is to use pipx:

```bash
pipx install -U manim-slides
pipx install -U "manim-slides[pyside6-full]"
```

:::{tip}
Expand All @@ -52,6 +59,28 @@ like to upgrade to the latest version available,
if Manim Slides is already installed.
:::

:::{note}
The quotes `"` are added because not all shell support unquoted
brackets (e.g., zsh) or commas (e.g., Windows).
:::

You can check that Manim Slides was correctly installed with:

```bash
manim-slides --version
```

## Custom install

If you want more control on what dependencies are installed,
you can always install the bare minimal dependencies with:

```bash
pipx install -U manim-slides
```

And install additional dependencies later.

Optionally, you can also install Manim or ManimGL using extras[^1]:

```bash
Expand All @@ -60,11 +89,8 @@ pipx install -U "manim-slides[manim]" # For Manim
pipx install -U "manim-slides[manimgl]" # For ManimGL
```

You can check that Manim Slides was correctly installed with:

```bash
manim-slides --version
```
For optional dependencies documentation, see
[next section](#optional-dependencies).

:::{warning}
If you are installing with pipx, this is mandatory to at least include
Expand All @@ -74,35 +100,58 @@ either `manim` or `manimgl`.
[^1]: You still need to have Manim or ManimGL platform-specific dependencies
installed on your computer.

## Optional Dependencies
## Optional dependencies

Along with the optional dependencies for Manim and ManimGL,
Manim Slides offers additional *extras*, that can be activated
using optional dependencies:

- `full`, to include `magic`, `manim`, `manimgl`, and
`sphinx-directive` extras (see below);
- `magic`, to include a Jupyter magic to render
animations inside notebooks. This automatically installs `manim`,
and does not work with ManimGL;
- `manim` and `manimgl`, for installing the corresponding
dependencies;
- `pyqt6` to include PyQt6 Qt bindings. Those bindings are available
on most platforms and Python version, but produce a weird black
screen between slide with `manim-slides present`,
see [#QTBUG-118501](https://bugreports.qt.io/browse/QTBUG-118501);
- `pyqt6-full` to include `full` and `pyqt6`;
- `pyside6` to include PySide6 Qt bindings. Those bindings are available
on most platforms and Python version, except on Python 3.12[^2];
- `pyside6-full` to include `full` and `pyside6`;
- `sphinx-directive`, to generate presentation inside your Sphinx
documentation. This automatically installs `manim`,
and does not work with ManimGL;
and does not work with ManimGL.

Installing those extras can be done with the following syntax:

```bash
pipx install -U "manim-slides[extra1,extra2]"
```

:::{note}
The quotes `"` are added because not all shell support unquoted
brackets (e.g., zsh) or commas (e.g., Windows).
:::
[^2]: Actually, PySide6 can be installed on Python 3.12, but you will then
observe the same visual bug as with PyQt6.

## When you need a Qt backend

Before `v5.1`, Manim Slides automatically included PySide6 as
a Qt backend. As only `manim-slides present` and `manim-slides wizard`
command need a graphical library, and installing PySide6 on all platforms
and Python version can be sometimes complicated, Manim Slides chooses
**not to include** any Qt backend.

The use can choose between PySide6 (best) and PyQt6, depending on their
availability and licensing rules.

As of `v5.1`, you **need** to have Qt bindings installed to use
`manim-slides present` or `manim-slides wizard`. The recommended way to
install those are via optional dependencies, as explained above.

## Install From Repository
## Install from source

An alternative way to install Manim Slides is to clone the git repository,
and install from there: read the
and build the package from source. Read the
[contributing guide](./contributing/workflow)
to know how to process.
13 changes: 13 additions & 0 deletions docs/source/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ see [installation](./installation).
:end-before: <!-- end usage -->
```

:::{note}
Using `manim-slides render` makes sure to use the `manim`
(or `manimlib`) library that was installed in the same Python environment.
Put simply, this is a wrapper around
`manim render [ARGS]...` (or `manimgl [ARGS]...`).
:::


```{include} ../../README.md
:start-after: <!-- start more-usage -->
:end-before: <!-- end more-usage -->
```

The output slides should look this this:

```{eval-rst}
Expand Down
37 changes: 24 additions & 13 deletions manim_slides/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
model_validator,
)
from pydantic_extra_types.color import Color
from PySide6.QtCore import Qt

from .logger import logger

Expand All @@ -38,6 +37,13 @@ def emit(self, *args: Any) -> None:
receiver(*args)


def key_id(name: str) -> PositiveInt:
"""Avoid importing Qt too early."""
from qtpy.QtCore import Qt

return getattr(Qt, f"Key_{name}")


class Key(BaseModel): # type: ignore[misc]
"""Represents a list of key codes, with optionally a name."""

Expand Down Expand Up @@ -73,14 +79,22 @@ def connect(self, function: Receiver) -> None:


class Keys(BaseModel): # type: ignore[misc]
QUIT: Key = Key(ids=[Qt.Key_Q], name="QUIT")
PLAY_PAUSE: Key = Key(ids=[Qt.Key_Space], name="PLAY / PAUSE")
NEXT: Key = Key(ids=[Qt.Key_Right], name="NEXT")
PREVIOUS: Key = Key(ids=[Qt.Key_Left], name="PREVIOUS")
REVERSE: Key = Key(ids=[Qt.Key_V], name="REVERSE")
REPLAY: Key = Key(ids=[Qt.Key_R], name="REPLAY")
FULL_SCREEN: Key = Key(ids=[Qt.Key_F], name="TOGGLE FULL SCREEN")
HIDE_MOUSE: Key = Key(ids=[Qt.Key_H], name="HIDE / SHOW MOUSE")
QUIT: Key = Field(default_factory=lambda: Key(ids=[key_id("Q")], name="QUIT"))
PLAY_PAUSE: Key = Field(
default_factory=lambda: Key(ids=[key_id("Space")], name="PLAY / PAUSE")
)
NEXT: Key = Field(default_factory=lambda: Key(ids=[key_id("Right")], name="NEXT"))
PREVIOUS: Key = Field(
default_factory=lambda: Key(ids=[key_id("Left")], name="PREVIOUS")
)
REVERSE: Key = Field(default_factory=lambda: Key(ids=[key_id("V")], name="REVERSE"))
REPLAY: Key = Field(default_factory=lambda: Key(ids=[key_id("R")], name="REPLAY"))
FULL_SCREEN: Key = Field(
default_factory=lambda: Key(ids=[key_id("F")], name="TOGGLE FULL SCREEN")
)
HIDE_MOUSE: Key = Field(
default_factory=lambda: Key(ids=[key_id("H")], name="HIDE / SHOW MOUSE")
)

@model_validator(mode="before")
@classmethod
Expand Down Expand Up @@ -121,7 +135,7 @@ def dispatch(key: PositiveInt) -> None:
class Config(BaseModel): # type: ignore[misc]
"""General Manim Slides config."""

keys: Keys = Keys()
keys: Keys = Field(default_factory=Keys)

@classmethod
def from_file(cls, path: Path) -> "Config":
Expand Down Expand Up @@ -326,6 +340,3 @@ def copy_to(
shutil.copy(rev_file, rev_dest)

return self


DEFAULT_CONFIG = Config()
6 changes: 4 additions & 2 deletions manim_slides/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,14 +648,16 @@ def callback(ctx: Context, param: Parameter, value: bool) -> None:
"config_options",
multiple=True,
callback=validate_config_option,
help="Configuration options passed to the converter. E.g., pass `-cslide_number=true` to display slide numbers.",
help="Configuration options passed to the converter. "
"E.g., pass ``-cslide_number=true`` to display slide numbers.",
)
@click.option(
"--use-template",
"template",
metavar="FILE",
type=click.Path(exists=True, dir_okay=False, path_type=Path),
help="Use the template given by FILE instead of default one. To echo the default template, use `--show-template`.",
help="Use the template given by FILE instead of default one. "
"To echo the default template, use ``--show-template``.",
)
@show_template_option
@show_config_options
Expand Down
24 changes: 14 additions & 10 deletions manim_slides/present/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,10 @@
import click
from click import Context, Parameter
from pydantic import ValidationError
from PySide6.QtCore import Qt

from ..commons import config_path_option, folder_path_option, verbosity_option
from ..config import Config, PresentationConfig
from ..logger import logger
from ..qt_utils import qapp
from .player import Player

ASPECT_RATIO_MODES = {
"keep": Qt.KeepAspectRatio,
"ignore": Qt.IgnoreAspectRatio,
}


@click.command()
Expand Down Expand Up @@ -130,7 +122,8 @@ def str_to_int_or_none(value: str) -> Optional[int]:
return tuple(map(str_to_int_or_none, values_tuple))

raise click.BadParameter(
f"exactly 2 arguments are expected but you gave {n_values}, please use commas to separate them",
f"exactly 2 arguments are expected but you gave {n_values}, "
"please use commas to separate them",
ctx=ctx,
param=param,
)
Expand Down Expand Up @@ -283,6 +276,8 @@ def present(
if start_at[1]:
start_at_slide_number = start_at[1]

from ..qt_utils import qapp

app = qapp()
app.setApplicationName("Manim Slides")

Expand All @@ -298,6 +293,15 @@ def present(
else:
screen = None

from qtpy.QtCore import Qt

aspect_ratio_modes = {
"keep": Qt.KeepAspectRatio,
"ignore": Qt.IgnoreAspectRatio,
}

from .player import Player

player = Player(
config,
presentation_configs,
Expand All @@ -306,7 +310,7 @@ def present(
skip_all=skip_all,
exit_after_last_slide=exit_after_last_slide,
hide_mouse=hide_mouse,
aspect_ratio_mode=ASPECT_RATIO_MODES[aspect_ratio],
aspect_ratio_mode=aspect_ratio_modes[aspect_ratio],
presentation_index=start_at_scene_number,
slide_index=start_at_slide_number,
screen=screen,
Expand Down
Loading

0 comments on commit 16f740d

Please sign in to comment.