mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-07-17 16:28:21 +08:00
chore(deps): make Qt backend optional (#350)
* 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>
This commit is contained in:
2
.github/workflows/pages.yml
vendored
2
.github/workflows/pages.yml
vendored
@ -52,7 +52,7 @@ jobs:
|
|||||||
uses: nikeee/setup-pandoc@v1
|
uses: nikeee/setup-pandoc@v1
|
||||||
|
|
||||||
- name: Install local Python package
|
- name: Install local Python package
|
||||||
run: pdm install -dGdocs -dGgithub-action
|
run: pdm sync -Gdocs -Ggithub-action
|
||||||
|
|
||||||
- name: Install IPython kernel
|
- name: Install IPython kernel
|
||||||
run: pdm run ipython kernel install --name "manim-slides" --user
|
run: pdm run ipython kernel install --name "manim-slides" --user
|
||||||
|
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
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 }}
|
runs-on: ${{ matrix.os }}
|
||||||
env:
|
env:
|
||||||
QT_QPA_PLATFORM: offscreen
|
QT_QPA_PLATFORM: offscreen
|
||||||
@ -69,7 +69,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install Manim Slides
|
- name: Install Manim Slides
|
||||||
run: |
|
run: |
|
||||||
pdm install -dGgithub-action -dGtest
|
pdm sync -Ggithub-action -Gtest
|
||||||
|
|
||||||
- name: Run pytest
|
- name: Run pytest
|
||||||
if: matrix.os != 'ubuntu-latest' || matrix.pyversion != '3.11'
|
if: matrix.os != 'ubuntu-latest' || matrix.pyversion != '3.11'
|
||||||
|
@ -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)
|
[#335](https://github.com/jeertmans/manim-slides/pull/335)
|
||||||
- Changed build backend to PDM and reflected on docs.
|
- Changed build backend to PDM and reflected on docs.
|
||||||
[#354](https://github.com/jeertmans/manim-slides/pull/354)
|
[#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.
|
- Documentated how to create and use a custom HTML template.
|
||||||
[#357](https://github.com/jeertmans/manim-slides/pull/357)
|
[#357](https://github.com/jeertmans/manim-slides/pull/357)
|
||||||
|
|
||||||
|
12
README.md
12
README.md
@ -88,11 +88,15 @@ manim-slides render example.py BasicExample
|
|||||||
# or use ManimGL
|
# or use ManimGL
|
||||||
manim-slides render --GL example.py BasicExample
|
manim-slides render --GL example.py BasicExample
|
||||||
```
|
```
|
||||||
|
<!-- end usage -->
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Using `manim-slides render` makes sure the use the `manim`
|
> Using `manim-slides render` makes sure to use the `manim`
|
||||||
> (or `manimlib`) library that was installed in the Python same environment.
|
> (or `manimlib`) library that was installed in the same Python environment.
|
||||||
> Put simply, this is a wrapper of `manim render [ARGS]...` (or `manimgl [ARGS]...`).
|
> 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:
|
To start the presentation using `Scene1`, `Scene2` and so on, run:
|
||||||
|
|
||||||
@ -106,7 +110,7 @@ In our example:
|
|||||||
manim-slides BasicExample
|
manim-slides BasicExample
|
||||||
```
|
```
|
||||||
|
|
||||||
<!-- end usage -->
|
<!-- end more-usage -->
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="Example GIF" src="https://raw.githubusercontent.com/jeertmans/manim-slides/main/static/example.gif">
|
<img alt="Example GIF" src="https://raw.githubusercontent.com/jeertmans/manim-slides/main/static/example.gif">
|
||||||
|
@ -48,11 +48,11 @@ pdm install -Gmanimgl # For ManimGL
|
|||||||
Additionnally, Manim Slides comes with groups of dependencies for development purposes:
|
Additionnally, Manim Slides comes with groups of dependencies for development purposes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pdm install -dGdev # For linters and formatters
|
pdm install -Gdev # For linters and formatters
|
||||||
# or
|
# or
|
||||||
pdm install --dGdocs # To build the documentation locally
|
pdm install -Gdocs # To build the documentation locally
|
||||||
# or
|
# or
|
||||||
pdm install --dGtests # To run tests
|
pdm install -Gtest # To run tests
|
||||||
```
|
```
|
||||||
|
|
||||||
:::{note}
|
:::{note}
|
||||||
|
@ -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`
|
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!
|
for ManimGL), otherwise it might install an unsupported version of Manim!
|
||||||
See [#314](https://github.com/jeertmans/manim-slides/issues/314).
|
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 -->
|
<!-- end deps -->
|
||||||
|
|
||||||
## Pip Install
|
## 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
|
```bash
|
||||||
pipx install -U manim-slides
|
pipx install -U "manim-slides[pyside6-full]"
|
||||||
```
|
```
|
||||||
|
|
||||||
:::{tip}
|
:::{tip}
|
||||||
@ -52,6 +59,28 @@ like to upgrade to the latest version available,
|
|||||||
if Manim Slides is already installed.
|
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]:
|
Optionally, you can also install Manim or ManimGL using extras[^1]:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -60,11 +89,8 @@ pipx install -U "manim-slides[manim]" # For Manim
|
|||||||
pipx install -U "manim-slides[manimgl]" # For ManimGL
|
pipx install -U "manim-slides[manimgl]" # For ManimGL
|
||||||
```
|
```
|
||||||
|
|
||||||
You can check that Manim Slides was correctly installed with:
|
For optional dependencies documentation, see
|
||||||
|
[next section](#optional-dependencies).
|
||||||
```bash
|
|
||||||
manim-slides --version
|
|
||||||
```
|
|
||||||
|
|
||||||
:::{warning}
|
:::{warning}
|
||||||
If you are installing with pipx, this is mandatory to at least include
|
If you are installing with pipx, this is mandatory to at least include
|
||||||
@ -74,20 +100,30 @@ either `manim` or `manimgl`.
|
|||||||
[^1]: You still need to have Manim or ManimGL platform-specific dependencies
|
[^1]: You still need to have Manim or ManimGL platform-specific dependencies
|
||||||
installed on your computer.
|
installed on your computer.
|
||||||
|
|
||||||
## Optional Dependencies
|
## Optional dependencies
|
||||||
|
|
||||||
Along with the optional dependencies for Manim and ManimGL,
|
Along with the optional dependencies for Manim and ManimGL,
|
||||||
Manim Slides offers additional *extras*, that can be activated
|
Manim Slides offers additional *extras*, that can be activated
|
||||||
using optional dependencies:
|
using optional dependencies:
|
||||||
|
|
||||||
|
- `full`, to include `magic`, `manim`, `manimgl`, and
|
||||||
|
`sphinx-directive` extras (see below);
|
||||||
- `magic`, to include a Jupyter magic to render
|
- `magic`, to include a Jupyter magic to render
|
||||||
animations inside notebooks. This automatically installs `manim`,
|
animations inside notebooks. This automatically installs `manim`,
|
||||||
and does not work with ManimGL;
|
and does not work with ManimGL;
|
||||||
- `manim` and `manimgl`, for installing the corresponding
|
- `manim` and `manimgl`, for installing the corresponding
|
||||||
dependencies;
|
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
|
- `sphinx-directive`, to generate presentation inside your Sphinx
|
||||||
documentation. This automatically installs `manim`,
|
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:
|
Installing those extras can be done with the following syntax:
|
||||||
|
|
||||||
@ -95,14 +131,27 @@ Installing those extras can be done with the following syntax:
|
|||||||
pipx install -U "manim-slides[extra1,extra2]"
|
pipx install -U "manim-slides[extra1,extra2]"
|
||||||
```
|
```
|
||||||
|
|
||||||
:::{note}
|
[^2]: Actually, PySide6 can be installed on Python 3.12, but you will then
|
||||||
The quotes `"` are added because not all shell support unquoted
|
observe the same visual bug as with PyQt6.
|
||||||
brackets (e.g., zsh) or commas (e.g., Windows).
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Install From Repository
|
## 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 source
|
||||||
|
|
||||||
An alternative way to install Manim Slides is to clone the git repository,
|
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)
|
[contributing guide](./contributing/workflow)
|
||||||
to know how to process.
|
to know how to process.
|
||||||
|
@ -10,6 +10,19 @@ see [installation](./installation).
|
|||||||
:end-before: <!-- end usage -->
|
: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:
|
The output slides should look this this:
|
||||||
|
|
||||||
```{eval-rst}
|
```{eval-rst}
|
||||||
|
@ -17,7 +17,6 @@ from pydantic import (
|
|||||||
model_validator,
|
model_validator,
|
||||||
)
|
)
|
||||||
from pydantic_extra_types.color import Color
|
from pydantic_extra_types.color import Color
|
||||||
from PySide6.QtCore import Qt
|
|
||||||
|
|
||||||
from .logger import logger
|
from .logger import logger
|
||||||
|
|
||||||
@ -38,6 +37,13 @@ class Signal(BaseModel): # type: ignore[misc]
|
|||||||
receiver(*args)
|
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]
|
class Key(BaseModel): # type: ignore[misc]
|
||||||
"""Represents a list of key codes, with optionally a name."""
|
"""Represents a list of key codes, with optionally a name."""
|
||||||
|
|
||||||
@ -73,14 +79,22 @@ class Key(BaseModel): # type: ignore[misc]
|
|||||||
|
|
||||||
|
|
||||||
class Keys(BaseModel): # type: ignore[misc]
|
class Keys(BaseModel): # type: ignore[misc]
|
||||||
QUIT: Key = Key(ids=[Qt.Key_Q], name="QUIT")
|
QUIT: Key = Field(default_factory=lambda: Key(ids=[key_id("Q")], name="QUIT"))
|
||||||
PLAY_PAUSE: Key = Key(ids=[Qt.Key_Space], name="PLAY / PAUSE")
|
PLAY_PAUSE: Key = Field(
|
||||||
NEXT: Key = Key(ids=[Qt.Key_Right], name="NEXT")
|
default_factory=lambda: Key(ids=[key_id("Space")], name="PLAY / PAUSE")
|
||||||
PREVIOUS: Key = Key(ids=[Qt.Key_Left], name="PREVIOUS")
|
)
|
||||||
REVERSE: Key = Key(ids=[Qt.Key_V], name="REVERSE")
|
NEXT: Key = Field(default_factory=lambda: Key(ids=[key_id("Right")], name="NEXT"))
|
||||||
REPLAY: Key = Key(ids=[Qt.Key_R], name="REPLAY")
|
PREVIOUS: Key = Field(
|
||||||
FULL_SCREEN: Key = Key(ids=[Qt.Key_F], name="TOGGLE FULL SCREEN")
|
default_factory=lambda: Key(ids=[key_id("Left")], name="PREVIOUS")
|
||||||
HIDE_MOUSE: Key = Key(ids=[Qt.Key_H], name="HIDE / SHOW MOUSE")
|
)
|
||||||
|
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")
|
@model_validator(mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -121,7 +135,7 @@ class Keys(BaseModel): # type: ignore[misc]
|
|||||||
class Config(BaseModel): # type: ignore[misc]
|
class Config(BaseModel): # type: ignore[misc]
|
||||||
"""General Manim Slides config."""
|
"""General Manim Slides config."""
|
||||||
|
|
||||||
keys: Keys = Keys()
|
keys: Keys = Field(default_factory=Keys)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_file(cls, path: Path) -> "Config":
|
def from_file(cls, path: Path) -> "Config":
|
||||||
@ -326,6 +340,3 @@ class PresentationConfig(BaseModel): # type: ignore[misc]
|
|||||||
shutil.copy(rev_file, rev_dest)
|
shutil.copy(rev_file, rev_dest)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_CONFIG = Config()
|
|
||||||
|
@ -648,14 +648,16 @@ def show_template_option(function: Callable[..., Any]) -> Callable[..., Any]:
|
|||||||
"config_options",
|
"config_options",
|
||||||
multiple=True,
|
multiple=True,
|
||||||
callback=validate_config_option,
|
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(
|
@click.option(
|
||||||
"--use-template",
|
"--use-template",
|
||||||
"template",
|
"template",
|
||||||
metavar="FILE",
|
metavar="FILE",
|
||||||
type=click.Path(exists=True, dir_okay=False, path_type=Path),
|
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_template_option
|
||||||
@show_config_options
|
@show_config_options
|
||||||
|
@ -6,18 +6,10 @@ from typing import List, Optional, Tuple
|
|||||||
import click
|
import click
|
||||||
from click import Context, Parameter
|
from click import Context, Parameter
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
from PySide6.QtCore import Qt
|
|
||||||
|
|
||||||
from ..commons import config_path_option, folder_path_option, verbosity_option
|
from ..commons import config_path_option, folder_path_option, verbosity_option
|
||||||
from ..config import Config, PresentationConfig
|
from ..config import Config, PresentationConfig
|
||||||
from ..logger import logger
|
from ..logger import logger
|
||||||
from ..qt_utils import qapp
|
|
||||||
from .player import Player
|
|
||||||
|
|
||||||
ASPECT_RATIO_MODES = {
|
|
||||||
"keep": Qt.KeepAspectRatio,
|
|
||||||
"ignore": Qt.IgnoreAspectRatio,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@ -130,7 +122,8 @@ def start_at_callback(
|
|||||||
return tuple(map(str_to_int_or_none, values_tuple))
|
return tuple(map(str_to_int_or_none, values_tuple))
|
||||||
|
|
||||||
raise click.BadParameter(
|
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,
|
ctx=ctx,
|
||||||
param=param,
|
param=param,
|
||||||
)
|
)
|
||||||
@ -283,6 +276,8 @@ def present(
|
|||||||
if start_at[1]:
|
if start_at[1]:
|
||||||
start_at_slide_number = start_at[1]
|
start_at_slide_number = start_at[1]
|
||||||
|
|
||||||
|
from ..qt_utils import qapp
|
||||||
|
|
||||||
app = qapp()
|
app = qapp()
|
||||||
app.setApplicationName("Manim Slides")
|
app.setApplicationName("Manim Slides")
|
||||||
|
|
||||||
@ -298,6 +293,15 @@ def present(
|
|||||||
else:
|
else:
|
||||||
screen = None
|
screen = None
|
||||||
|
|
||||||
|
from qtpy.QtCore import Qt
|
||||||
|
|
||||||
|
aspect_ratio_modes = {
|
||||||
|
"keep": Qt.KeepAspectRatio,
|
||||||
|
"ignore": Qt.IgnoreAspectRatio,
|
||||||
|
}
|
||||||
|
|
||||||
|
from .player import Player
|
||||||
|
|
||||||
player = Player(
|
player = Player(
|
||||||
config,
|
config,
|
||||||
presentation_configs,
|
presentation_configs,
|
||||||
@ -306,7 +310,7 @@ def present(
|
|||||||
skip_all=skip_all,
|
skip_all=skip_all,
|
||||||
exit_after_last_slide=exit_after_last_slide,
|
exit_after_last_slide=exit_after_last_slide,
|
||||||
hide_mouse=hide_mouse,
|
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,
|
presentation_index=start_at_scene_number,
|
||||||
slide_index=start_at_slide_number,
|
slide_index=start_at_slide_number,
|
||||||
screen=screen,
|
screen=screen,
|
||||||
|
@ -2,11 +2,11 @@ from datetime import datetime
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from PySide6.QtCore import Qt, QTimer, QUrl, Signal, Slot
|
from qtpy.QtCore import Qt, QTimer, QUrl, Signal, Slot
|
||||||
from PySide6.QtGui import QCloseEvent, QIcon, QKeyEvent, QScreen
|
from qtpy.QtGui import QCloseEvent, QIcon, QKeyEvent, QScreen
|
||||||
from PySide6.QtMultimedia import QMediaPlayer
|
from qtpy.QtMultimedia import QMediaPlayer
|
||||||
from PySide6.QtMultimediaWidgets import QVideoWidget
|
from qtpy.QtMultimediaWidgets import QVideoWidget
|
||||||
from PySide6.QtWidgets import (
|
from qtpy.QtWidgets import (
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QLabel,
|
QLabel,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
@ -271,7 +271,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
|
|
||||||
def media_status_changed(status: QMediaPlayer.MediaStatus) -> None:
|
def media_status_changed(status: QMediaPlayer.MediaStatus) -> None:
|
||||||
self.media_player.setLoops(1) # Otherwise looping slides never end
|
self.media_player.setLoops(1) # Otherwise looping slides never end
|
||||||
if status == QMediaPlayer.EndOfMedia:
|
if status == QMediaPlayer.MediaStatus.EndOfMedia:
|
||||||
self.load_next_slide()
|
self.load_next_slide()
|
||||||
|
|
||||||
self.media_player.mediaStatusChanged.connect(media_status_changed)
|
self.media_player.mediaStatusChanged.connect(media_status_changed)
|
||||||
@ -280,7 +280,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
|
|
||||||
def media_status_changed(status: QMediaPlayer.MediaStatus) -> None:
|
def media_status_changed(status: QMediaPlayer.MediaStatus) -> None:
|
||||||
if (
|
if (
|
||||||
status == QMediaPlayer.EndOfMedia
|
status == QMediaPlayer.MediaStatus.EndOfMedia
|
||||||
and self.current_slide_config.auto_next
|
and self.current_slide_config.auto_next
|
||||||
):
|
):
|
||||||
self.load_next_slide()
|
self.load_next_slide()
|
||||||
@ -390,7 +390,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def load_current_media(self, start_paused: bool = False) -> None:
|
def load_current_media(self, start_paused: bool = False) -> None:
|
||||||
url = QUrl.fromLocalFile(self.current_file)
|
url = QUrl.fromLocalFile(str(self.current_file))
|
||||||
self.media_player.setSource(url)
|
self.media_player.setSource(url)
|
||||||
|
|
||||||
if self.playing_reversed_slide:
|
if self.playing_reversed_slide:
|
||||||
@ -475,7 +475,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
|
|
||||||
def preview_next_slide(self) -> None:
|
def preview_next_slide(self) -> None:
|
||||||
if slide_config := self.next_slide_config:
|
if slide_config := self.next_slide_config:
|
||||||
url = QUrl.fromLocalFile(slide_config.file)
|
url = QUrl.fromLocalFile(str(slide_config.file))
|
||||||
self.info.next_media_player.setSource(url)
|
self.info.next_media_player.setSource(url)
|
||||||
self.info.next_media_player.play()
|
self.info.next_media_player.play()
|
||||||
|
|
||||||
@ -493,7 +493,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def next(self) -> None:
|
def next(self) -> None:
|
||||||
if self.media_player.playbackState() == QMediaPlayer.PausedState:
|
if self.media_player.playbackState() == QMediaPlayer.PlaybackState.PausedState:
|
||||||
self.media_player.play()
|
self.media_player.play()
|
||||||
elif self.next_terminates_loop and self.media_player.loops() != 1:
|
elif self.next_terminates_loop and self.media_player.loops() != 1:
|
||||||
position = self.media_player.position()
|
position = self.media_player.position()
|
||||||
@ -521,9 +521,9 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
@Slot()
|
@Slot()
|
||||||
def play_pause(self) -> None:
|
def play_pause(self) -> None:
|
||||||
state = self.media_player.playbackState()
|
state = self.media_player.playbackState()
|
||||||
if state == QMediaPlayer.PausedState:
|
if state == QMediaPlayer.PlaybackState.PausedState:
|
||||||
self.media_player.play()
|
self.media_player.play()
|
||||||
elif state == QMediaPlayer.PlayingState:
|
elif state == QMediaPlayer.PlaybackState.PlayingState:
|
||||||
self.media_player.pause()
|
self.media_player.pause()
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
@ -540,11 +540,9 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
else:
|
else:
|
||||||
self.setCursor(Qt.BlankCursor)
|
self.setCursor(Qt.BlankCursor)
|
||||||
|
|
||||||
@Slot()
|
|
||||||
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
|
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
@Slot()
|
|
||||||
def keyPressEvent(self, event: QKeyEvent) -> None: # noqa: N802
|
def keyPressEvent(self, event: QKeyEvent) -> None: # noqa: N802
|
||||||
key = event.key()
|
key = event.key()
|
||||||
self.dispatch(key)
|
self.dispatch(key)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Qt utils."""
|
"""Qt utils."""
|
||||||
|
|
||||||
from PySide6.QtWidgets import QApplication
|
from qtpy.QtWidgets import QApplication
|
||||||
|
|
||||||
|
|
||||||
def qapp() -> QApplication:
|
def qapp() -> QApplication:
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
# Created by: The Resource Compiler for Qt version 6.4.0
|
# Created by: The Resource Compiler for Qt version 6.4.0
|
||||||
# WARNING! All changes made in this file will be lost!
|
# WARNING! All changes made in this file will be lost!
|
||||||
|
|
||||||
from PySide6 import QtCore
|
from qtpy import QtCore
|
||||||
|
|
||||||
qt_resource_data = b"\
|
qt_resource_data = b"\
|
||||||
\x00\x00\x08\x1c\
|
\x00\x00\x08\x1c\
|
||||||
|
85
manim_slides/wizard/__init__.py
Normal file
85
manim_slides/wizard/__init__.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import click
|
||||||
|
|
||||||
|
from ..commons import config_options, verbosity_option
|
||||||
|
from ..config import Config
|
||||||
|
from ..defaults import CONFIG_PATH
|
||||||
|
from ..logger import logger
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@config_options
|
||||||
|
@click.help_option("-h", "--help")
|
||||||
|
@verbosity_option
|
||||||
|
def wizard(config_path: Path, force: bool, merge: bool) -> None:
|
||||||
|
"""Launch configuration wizard."""
|
||||||
|
return _init(config_path, force, merge, skip_interactive=False)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@config_options
|
||||||
|
@click.help_option("-h", "--help")
|
||||||
|
@verbosity_option
|
||||||
|
def init(
|
||||||
|
config_path: Path, force: bool, merge: bool, skip_interactive: bool = False
|
||||||
|
) -> None:
|
||||||
|
"""Initialize a new default configuration file."""
|
||||||
|
return _init(config_path, force, merge, skip_interactive=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _init(
|
||||||
|
config_path: Path, force: bool, merge: bool, skip_interactive: bool = False
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Actual initialization code for configuration file, with optional interactive
|
||||||
|
mode.
|
||||||
|
"""
|
||||||
|
if config_path.exists():
|
||||||
|
click.secho(f"The `{CONFIG_PATH}` configuration file exists")
|
||||||
|
|
||||||
|
if not force and not merge:
|
||||||
|
choice = click.prompt(
|
||||||
|
"Do you want to continue and (o)verwrite / (m)erge it, or (q)uit?",
|
||||||
|
type=click.Choice(["o", "m", "q"], case_sensitive=False),
|
||||||
|
)
|
||||||
|
|
||||||
|
force = choice == "o"
|
||||||
|
merge = choice == "m"
|
||||||
|
|
||||||
|
if not force and not merge:
|
||||||
|
logger.debug("Exiting without doing anything")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
config = Config()
|
||||||
|
|
||||||
|
if force:
|
||||||
|
logger.debug(f"Overwriting `{config_path}` if exists")
|
||||||
|
elif merge:
|
||||||
|
logger.debug("Merging new config into `{config_path}`")
|
||||||
|
|
||||||
|
if not skip_interactive:
|
||||||
|
if config_path.exists():
|
||||||
|
config = Config.from_file(config_path)
|
||||||
|
|
||||||
|
from ..qt_utils import qapp
|
||||||
|
from .wizard import Wizard
|
||||||
|
|
||||||
|
app = qapp()
|
||||||
|
app.setApplicationName("Manim Slides Wizard")
|
||||||
|
window = Wizard(config)
|
||||||
|
window.show()
|
||||||
|
app.exec()
|
||||||
|
|
||||||
|
if window.closed_without_saving:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
config = window.config
|
||||||
|
|
||||||
|
if merge:
|
||||||
|
config = Config.from_file(config_path).merge_with(config)
|
||||||
|
|
||||||
|
config.to_file(config_path)
|
||||||
|
|
||||||
|
click.secho(f"Configuration file successfully saved to `{config_path}`")
|
@ -1,12 +1,9 @@
|
|||||||
import sys
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import click
|
from qtpy.QtCore import Qt
|
||||||
from PySide6.QtCore import Qt
|
from qtpy.QtGui import QIcon, QKeyEvent
|
||||||
from PySide6.QtGui import QIcon, QKeyEvent
|
from qtpy.QtWidgets import (
|
||||||
from PySide6.QtWidgets import (
|
|
||||||
QDialog,
|
QDialog,
|
||||||
QDialogButtonBox,
|
QDialogButtonBox,
|
||||||
QGridLayout,
|
QGridLayout,
|
||||||
@ -17,12 +14,9 @@ from PySide6.QtWidgets import (
|
|||||||
QWidget,
|
QWidget,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .commons import config_options, verbosity_option
|
from ..config import Config, Key
|
||||||
from .config import Config, Key
|
from ..logger import logger
|
||||||
from .defaults import CONFIG_PATH
|
from ..resources import * # noqa: F403
|
||||||
from .logger import logger
|
|
||||||
from .qt_utils import qapp
|
|
||||||
from .resources import * # noqa: F403
|
|
||||||
|
|
||||||
WINDOW_NAME: str = "Configuration Wizard"
|
WINDOW_NAME: str = "Configuration Wizard"
|
||||||
|
|
||||||
@ -125,76 +119,3 @@ class Wizard(QWidget): # type: ignore
|
|||||||
key_name = keymap[dialog.key]
|
key_name = keymap[dialog.key]
|
||||||
key.set_ids(dialog.key)
|
key.set_ids(dialog.key)
|
||||||
button.setText(key_name)
|
button.setText(key_name)
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@config_options
|
|
||||||
@click.help_option("-h", "--help")
|
|
||||||
@verbosity_option
|
|
||||||
def wizard(config_path: Path, force: bool, merge: bool) -> None:
|
|
||||||
"""Launch configuration wizard."""
|
|
||||||
return _init(config_path, force, merge, skip_interactive=False)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@config_options
|
|
||||||
@click.help_option("-h", "--help")
|
|
||||||
@verbosity_option
|
|
||||||
def init(
|
|
||||||
config_path: Path, force: bool, merge: bool, skip_interactive: bool = False
|
|
||||||
) -> None:
|
|
||||||
"""Initialize a new default configuration file."""
|
|
||||||
return _init(config_path, force, merge, skip_interactive=True)
|
|
||||||
|
|
||||||
|
|
||||||
def _init(
|
|
||||||
config_path: Path, force: bool, merge: bool, skip_interactive: bool = False
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Actual initialization code for configuration file, with optional interactive
|
|
||||||
mode.
|
|
||||||
"""
|
|
||||||
if config_path.exists():
|
|
||||||
click.secho(f"The `{CONFIG_PATH}` configuration file exists")
|
|
||||||
|
|
||||||
if not force and not merge:
|
|
||||||
choice = click.prompt(
|
|
||||||
"Do you want to continue and (o)verwrite / (m)erge it, or (q)uit?",
|
|
||||||
type=click.Choice(["o", "m", "q"], case_sensitive=False),
|
|
||||||
)
|
|
||||||
|
|
||||||
force = choice == "o"
|
|
||||||
merge = choice == "m"
|
|
||||||
|
|
||||||
if not force and not merge:
|
|
||||||
logger.debug("Exiting without doing anything")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
config = Config()
|
|
||||||
|
|
||||||
if force:
|
|
||||||
logger.debug(f"Overwriting `{config_path}` if exists")
|
|
||||||
elif merge:
|
|
||||||
logger.debug("Merging new config into `{config_path}`")
|
|
||||||
|
|
||||||
if not skip_interactive:
|
|
||||||
if config_path.exists():
|
|
||||||
config = Config.from_file(config_path)
|
|
||||||
|
|
||||||
app = qapp()
|
|
||||||
app.setApplicationName("Manim Slides Wizard")
|
|
||||||
window = Wizard(config)
|
|
||||||
window.show()
|
|
||||||
app.exec()
|
|
||||||
|
|
||||||
if window.closed_without_saving:
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
config = window.config
|
|
||||||
|
|
||||||
if merge:
|
|
||||||
config = Config.from_file(config_path).merge_with(config)
|
|
||||||
|
|
||||||
config.to_file(config_path)
|
|
||||||
|
|
||||||
click.secho(f"Configuration file successfully saved to `{config_path}`")
|
|
@ -6,10 +6,10 @@ requires = ["pdm-backend", "setuptools"]
|
|||||||
authors = [{name = "Jérome Eertmans", email = "jeertmans@icloud.com"}]
|
authors = [{name = "Jérome Eertmans", email = "jeertmans@icloud.com"}]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.8",
|
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Topic :: Multimedia :: Video",
|
"Topic :: Multimedia :: Video",
|
||||||
@ -27,8 +27,8 @@ dependencies = [
|
|||||||
"pillow>=9.5.0",
|
"pillow>=9.5.0",
|
||||||
"pydantic>=2.0.1",
|
"pydantic>=2.0.1",
|
||||||
"pydantic-extra-types>=2.0.0",
|
"pydantic-extra-types>=2.0.0",
|
||||||
"pyside6==6.5.2",
|
|
||||||
"python-pptx>=0.6.21",
|
"python-pptx>=0.6.21",
|
||||||
|
"qtpy>=2.4.1",
|
||||||
"requests>=2.28.1",
|
"requests>=2.28.1",
|
||||||
"rich>=13.3.2",
|
"rich>=13.3.2",
|
||||||
"rtoml>=0.9.0",
|
"rtoml>=0.9.0",
|
||||||
@ -40,15 +40,19 @@ keywords = ["manim", "slides", "plugin", "manimgl"]
|
|||||||
license = {text = "MIT"}
|
license = {text = "MIT"}
|
||||||
name = "manim-slides"
|
name = "manim-slides"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.8,<3.12"
|
requires-python = ">=3.9,<3.13"
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
all = [
|
full = [
|
||||||
"manim-slides[magic,manim,manimgl,sphinx-directive]",
|
"manim-slides[magic,manim,manimgl,sphinx-directive]",
|
||||||
]
|
]
|
||||||
magic = ["manim-slides[manim]", "ipython>=8.12.2"]
|
magic = ["manim-slides[manim]", "ipython>=8.12.2"]
|
||||||
manim = ["manim>=0.17.3"]
|
manim = ["manim>=0.17.3"]
|
||||||
manimgl = ["manimgl>=1.6.1"]
|
manimgl = ["manimgl>=1.6.1"]
|
||||||
|
pyqt6 = ["pyqt6>=6.6.1"]
|
||||||
|
pyqt6-full = ["manim-slides[full,pyqt6]"]
|
||||||
|
pyside6 = ["pyside6>=6.5.1,<6.5.3;python_version<'3.12'"]
|
||||||
|
pyside6-full = ["manim-slides[full,pyside6]"]
|
||||||
sphinx-directive = ["docutils>=0.20.1", "manim-slides[manim]"]
|
sphinx-directive = ["docutils>=0.20.1", "manim-slides[manim]"]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
@ -63,10 +67,10 @@ Repository = "https://github.com/jeertmans/manim-slides"
|
|||||||
|
|
||||||
[tool.coverage.report]
|
[tool.coverage.report]
|
||||||
exclude_lines = [
|
exclude_lines = [
|
||||||
'pragma: no cover',
|
"pragma: no cover",
|
||||||
'raise NotImplementedError',
|
"raise NotImplementedError",
|
||||||
'if TYPE_CHECKING:',
|
"if TYPE_CHECKING:",
|
||||||
'if typing.TYPE_CHECKING:',
|
"if typing.TYPE_CHECKING:",
|
||||||
]
|
]
|
||||||
precision = 2
|
precision = 2
|
||||||
|
|
||||||
@ -95,7 +99,7 @@ docs = [
|
|||||||
]
|
]
|
||||||
github-action = ["setuptools"]
|
github-action = ["setuptools"]
|
||||||
test = [
|
test = [
|
||||||
"manim-slides[manim,manimgl]",
|
"manim-slides[manim,manimgl,pyqt6]",
|
||||||
"pytest>=7.4.0",
|
"pytest>=7.4.0",
|
||||||
"pytest-cov>=4.1.0",
|
"pytest-cov>=4.1.0",
|
||||||
"pytest-env>=0.8.2",
|
"pytest-env>=0.8.2",
|
||||||
@ -103,6 +107,10 @@ test = [
|
|||||||
"pytest-xdist>=3.3.1",
|
"pytest-xdist>=3.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.pdm.resolution.overrides]
|
||||||
|
manimpango = "<1.0.0,>=0.5.0" # This conflicts with ManimGL, hopefully not an issue
|
||||||
|
skia-pathops = "0.8.0.post1" # From manim 0.18.0 (Python 3.12 support)
|
||||||
|
|
||||||
[tool.pdm.version]
|
[tool.pdm.version]
|
||||||
path = "manim_slides/__version__.py"
|
path = "manim_slides/__version__.py"
|
||||||
source = "file"
|
source = "file"
|
||||||
@ -134,6 +142,6 @@ extend-ignore = [
|
|||||||
]
|
]
|
||||||
extend-include = ["*.ipynb"]
|
extend-include = ["*.ipynb"]
|
||||||
extend-select = ["B", "C90", "D", "I", "N", "RUF", "UP", "T"]
|
extend-select = ["B", "C90", "D", "I", "N", "RUF", "UP", "T"]
|
||||||
isort = {known-first-party = ['manim_slides', 'tests']}
|
isort = {known-first-party = ["manim_slides", "tests"]}
|
||||||
line-length = 88
|
line-length = 88
|
||||||
target-version = "py38"
|
target-version = "py38"
|
||||||
|
@ -3,7 +3,7 @@ from typing import Iterator, Tuple
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
from PySide6.QtWidgets import QApplication
|
from qtpy.QtWidgets import QApplication
|
||||||
|
|
||||||
from manim_slides.present import present
|
from manim_slides.present import present
|
||||||
|
|
||||||
@ -11,12 +11,12 @@ from manim_slides.present import present
|
|||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def auto_shutdown_qapp() -> Iterator[None]:
|
def auto_shutdown_qapp() -> Iterator[None]:
|
||||||
if app := QApplication.instance():
|
if app := QApplication.instance():
|
||||||
app.shutdown()
|
app.quit()
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
if app := QApplication.instance():
|
if app := QApplication.instance():
|
||||||
app.shutdown()
|
app.quit()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from PySide6.QtWidgets import QApplication
|
from qtpy.QtWidgets import QApplication
|
||||||
|
|
||||||
from manim_slides.qt_utils import qapp
|
from manim_slides.qt_utils import qapp
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import random
|
|||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
import pytest
|
import pytest
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
from manim import (
|
from manim import (
|
||||||
@ -17,6 +18,7 @@ from manim import (
|
|||||||
GrowFromCenter,
|
GrowFromCenter,
|
||||||
Text,
|
Text,
|
||||||
)
|
)
|
||||||
|
from packaging import version
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from manim_slides.config import PresentationConfig
|
from manim_slides.config import PresentationConfig
|
||||||
@ -29,7 +31,13 @@ from manim_slides.slide.manim import Slide
|
|||||||
"renderer",
|
"renderer",
|
||||||
[
|
[
|
||||||
"--CE",
|
"--CE",
|
||||||
|
pytest.param(
|
||||||
"--GL",
|
"--GL",
|
||||||
|
marks=pytest.mark.skipif(
|
||||||
|
version.parse(np.__version__) >= version.parse("1.25"),
|
||||||
|
reason="ManimGL requires numpy<1.25, which is outdate",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_render_basic_slide(
|
def test_render_basic_slide(
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
from PySide6.QtCore import Qt
|
from pytest import MonkeyPatch
|
||||||
from PySide6.QtWidgets import (
|
from pytestqt.qtbot import QtBot
|
||||||
|
from qtpy.QtCore import Qt
|
||||||
|
from qtpy.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
)
|
)
|
||||||
from pytest import MonkeyPatch
|
|
||||||
from pytestqt.qtbot import QtBot
|
|
||||||
|
|
||||||
from manim_slides.config import Config, Key
|
from manim_slides.config import Config, Key
|
||||||
from manim_slides.defaults import CONFIG_PATH
|
from manim_slides.defaults import CONFIG_PATH
|
||||||
from manim_slides.wizard import KeyInput, Wizard, init, wizard
|
from manim_slides.wizard import init, wizard
|
||||||
|
from manim_slides.wizard.wizard import KeyInput, Wizard
|
||||||
|
|
||||||
|
|
||||||
class TestKeyInput:
|
class TestKeyInput:
|
||||||
|
Reference in New Issue
Block a user