keyed 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- keyed-0.1.0/LICENSE +28 -0
- keyed-0.1.0/PKG-INFO +121 -0
- keyed-0.1.0/README.md +46 -0
- keyed-0.1.0/pyproject.toml +167 -0
- keyed-0.1.0/setup.cfg +4 -0
- keyed-0.1.0/src/keyed/__init__.py +38 -0
- keyed-0.1.0/src/keyed/animation.py +278 -0
- keyed-0.1.0/src/keyed/annotations.py +49 -0
- keyed-0.1.0/src/keyed/base.py +266 -0
- keyed-0.1.0/src/keyed/cli.py +210 -0
- keyed-0.1.0/src/keyed/color.py +270 -0
- keyed-0.1.0/src/keyed/compositor/__init__.py +2 -0
- keyed-0.1.0/src/keyed/compositor/blend.py +8 -0
- keyed-0.1.0/src/keyed/compositor/compositor_cairo.py +37 -0
- keyed-0.1.0/src/keyed/compositor/compositor_taichi.py +508 -0
- keyed-0.1.0/src/keyed/compositor/core.py +33 -0
- keyed-0.1.0/src/keyed/config.py +107 -0
- keyed-0.1.0/src/keyed/constants.py +163 -0
- keyed-0.1.0/src/keyed/context.py +25 -0
- keyed-0.1.0/src/keyed/curve.py +395 -0
- keyed-0.1.0/src/keyed/easing.py +454 -0
- keyed-0.1.0/src/keyed/effects.py +36 -0
- keyed-0.1.0/src/keyed/extras.py +24 -0
- keyed-0.1.0/src/keyed/geometry.py +218 -0
- keyed-0.1.0/src/keyed/group.py +514 -0
- keyed-0.1.0/src/keyed/helpers.py +78 -0
- keyed-0.1.0/src/keyed/highlight.py +147 -0
- keyed-0.1.0/src/keyed/line.py +424 -0
- keyed-0.1.0/src/keyed/parser.py +53 -0
- keyed-0.1.0/src/keyed/plot.py +592 -0
- keyed-0.1.0/src/keyed/previewer.py +385 -0
- keyed-0.1.0/src/keyed/py.typed +0 -0
- keyed-0.1.0/src/keyed/renderer.py +212 -0
- keyed-0.1.0/src/keyed/scene.py +482 -0
- keyed-0.1.0/src/keyed/shapes.py +516 -0
- keyed-0.1.0/src/keyed/text.py +602 -0
- keyed-0.1.0/src/keyed/transforms.py +1062 -0
- keyed-0.1.0/src/keyed/types.py +28 -0
- keyed-0.1.0/src/keyed.egg-info/PKG-INFO +121 -0
- keyed-0.1.0/src/keyed.egg-info/SOURCES.txt +58 -0
- keyed-0.1.0/src/keyed.egg-info/dependency_links.txt +1 -0
- keyed-0.1.0/src/keyed.egg-info/entry_points.txt +2 -0
- keyed-0.1.0/src/keyed.egg-info/requires.txt +37 -0
- keyed-0.1.0/src/keyed.egg-info/top_level.txt +1 -0
- keyed-0.1.0/tests/test_align.py +32 -0
- keyed-0.1.0/tests/test_char_find.py +27 -0
- keyed-0.1.0/tests/test_common.py +44 -0
- keyed-0.1.0/tests/test_constants.py +158 -0
- keyed-0.1.0/tests/test_curves.py +51 -0
- keyed-0.1.0/tests/test_easing.py +69 -0
- keyed-0.1.0/tests/test_examples.py +173 -0
- keyed-0.1.0/tests/test_expression.py +69 -0
- keyed-0.1.0/tests/test_helpers.py +63 -0
- keyed-0.1.0/tests/test_loop.py +17 -0
- keyed-0.1.0/tests/test_pingpong.py +28 -0
- keyed-0.1.0/tests/test_property.py +204 -0
- keyed-0.1.0/tests/test_scene.py +201 -0
- keyed-0.1.0/tests/test_scene_find.py +35 -0
- keyed-0.1.0/tests/test_selection.py +22 -0
- keyed-0.1.0/tests/test_transforms.py +83 -0
keyed-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, Doug Mercer
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
keyed-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: keyed
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A reactive animation library.
|
|
5
|
+
Author-email: Doug Mercer <dougmerceryt@gmail.com>
|
|
6
|
+
License: BSD 3-Clause License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2024, Doug Mercer
|
|
9
|
+
|
|
10
|
+
Redistribution and use in source and binary forms, with or without
|
|
11
|
+
modification, are permitted provided that the following conditions are met:
|
|
12
|
+
|
|
13
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
14
|
+
list of conditions and the following disclaimer.
|
|
15
|
+
|
|
16
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
17
|
+
this list of conditions and the following disclaimer in the documentation
|
|
18
|
+
and/or other materials provided with the distribution.
|
|
19
|
+
|
|
20
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
21
|
+
contributors may be used to endorse or promote products derived from
|
|
22
|
+
this software without specific prior written permission.
|
|
23
|
+
|
|
24
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
25
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
26
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
27
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
28
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
29
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
30
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
31
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
32
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
33
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
34
|
+
|
|
35
|
+
Keywords: keyed,animation,reactive
|
|
36
|
+
Classifier: Development Status :: 4 - Beta
|
|
37
|
+
Classifier: Intended Audience :: Developers
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Requires-Python: <3.13,>=3.11
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
License-File: LICENSE
|
|
43
|
+
Requires-Dist: pyav
|
|
44
|
+
Requires-Dist: pillow
|
|
45
|
+
Requires-Dist: pycairo
|
|
46
|
+
Requires-Dist: pydantic
|
|
47
|
+
Requires-Dist: pygments
|
|
48
|
+
Requires-Dist: scipy
|
|
49
|
+
Requires-Dist: shapely
|
|
50
|
+
Requires-Dist: signified
|
|
51
|
+
Requires-Dist: tqdm
|
|
52
|
+
Requires-Dist: typer
|
|
53
|
+
Provides-Extra: lint
|
|
54
|
+
Requires-Dist: ruff; extra == "lint"
|
|
55
|
+
Requires-Dist: pyright; extra == "lint"
|
|
56
|
+
Provides-Extra: test
|
|
57
|
+
Requires-Dist: hypothesis; extra == "test"
|
|
58
|
+
Requires-Dist: pytest; extra == "test"
|
|
59
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
60
|
+
Requires-Dist: syrupy; extra == "test"
|
|
61
|
+
Provides-Extra: docs
|
|
62
|
+
Requires-Dist: beautifulsoup4; extra == "docs"
|
|
63
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
64
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
65
|
+
Requires-Dist: mkdocstrings[python]; extra == "docs"
|
|
66
|
+
Requires-Dist: mkdocs-material-extensions; extra == "docs"
|
|
67
|
+
Provides-Extra: previewer
|
|
68
|
+
Requires-Dist: pyside6; extra == "previewer"
|
|
69
|
+
Requires-Dist: watchdog; extra == "previewer"
|
|
70
|
+
Provides-Extra: gpu-compositor
|
|
71
|
+
Requires-Dist: taichi; extra == "gpu-compositor"
|
|
72
|
+
Provides-Extra: all
|
|
73
|
+
Requires-Dist: keyed[docs,gpu-compositor,lint,previewer,test]; extra == "all"
|
|
74
|
+
Dynamic: license-file
|
|
75
|
+
|
|
76
|
+
# Keyed
|
|
77
|
+
|
|
78
|
+
[](https://pypi.org/project/keyed/)
|
|
79
|
+
[](https://pypi.org/project/keyed/)
|
|
80
|
+
[](https://github.com/dougmercer/keyed/actions/workflows/tests.yml?query=branch%3Amain)
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
**Documentation**: [https://dougmercer.github.io/keyed](https://dougmercer.github.io/keyed)
|
|
85
|
+
**Source Code**: [https://github.com/dougmercer/keyed](https://github.com/dougmercer/keyed)
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
Keyed is a Python library for creating programmatically defined animations. Named after [key frames](https://en.wikipedia.org/wiki/Key_frame), the defining points in an animation sequence, Keyed makes it easy to create sophisticated animations through code.
|
|
90
|
+
|
|
91
|
+
## Features
|
|
92
|
+
|
|
93
|
+
- **Reactive Programming Model**: Built using the reactive programming library [signified](https://github.com/dougmercer/signified) to make declaratively defining highly dynamic animations a breeze
|
|
94
|
+
- **Vector Graphics**: [Cairo](https://www.cairographics.org)-based rendering for crisp, scalable graphics
|
|
95
|
+
- **Flexible Shape System**: Define basic lines, shapes, curves, and complex geometries
|
|
96
|
+
- **Code Animation**: Animate syntax highled code snippets
|
|
97
|
+
|
|
98
|
+
## Installation
|
|
99
|
+
|
|
100
|
+
Keyed requires a couple system level dependencies (e.g., [Cairo](https://www.cairographics.org/download/) and [ffmpeg](https://www.ffmpeg.org/)).
|
|
101
|
+
|
|
102
|
+
For detailed installation instructions visit our [Installation Guide](https://dougmercer.github.io/keyed/install)
|
|
103
|
+
.
|
|
104
|
+
|
|
105
|
+
But, once you have the necessary system dependencies installed, installing `keyed` is as simple as,
|
|
106
|
+
|
|
107
|
+
```console
|
|
108
|
+
pip install keyed
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Project Status
|
|
112
|
+
|
|
113
|
+
This project is in beta, so APIs may change.
|
|
114
|
+
|
|
115
|
+
## Alternatives
|
|
116
|
+
While I find `keyed` very fun and useful (particularly for animating syntax highlighted code in my [YouTube videos](https://youtube.com/@dougmercer)), there are several other excellent and far more mature animation libraries that you should probably use instead.
|
|
117
|
+
|
|
118
|
+
Before you decide to use `keyed`, be sure to check out:
|
|
119
|
+
|
|
120
|
+
* [Manim](https://manim.community): Comprehensive mathematical animation system originally created by Grant Sanderson of the YouTube channel 3blue1brown, but later adopted and extended by the manim community.
|
|
121
|
+
* [py5](https://py5coding.org): A Python wrapper for p5, the Java animation library.
|
keyed-0.1.0/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Keyed
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/keyed/)
|
|
4
|
+
[](https://pypi.org/project/keyed/)
|
|
5
|
+
[](https://github.com/dougmercer/keyed/actions/workflows/tests.yml?query=branch%3Amain)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
**Documentation**: [https://dougmercer.github.io/keyed](https://dougmercer.github.io/keyed)
|
|
10
|
+
**Source Code**: [https://github.com/dougmercer/keyed](https://github.com/dougmercer/keyed)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Keyed is a Python library for creating programmatically defined animations. Named after [key frames](https://en.wikipedia.org/wiki/Key_frame), the defining points in an animation sequence, Keyed makes it easy to create sophisticated animations through code.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Reactive Programming Model**: Built using the reactive programming library [signified](https://github.com/dougmercer/signified) to make declaratively defining highly dynamic animations a breeze
|
|
19
|
+
- **Vector Graphics**: [Cairo](https://www.cairographics.org)-based rendering for crisp, scalable graphics
|
|
20
|
+
- **Flexible Shape System**: Define basic lines, shapes, curves, and complex geometries
|
|
21
|
+
- **Code Animation**: Animate syntax highled code snippets
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
Keyed requires a couple system level dependencies (e.g., [Cairo](https://www.cairographics.org/download/) and [ffmpeg](https://www.ffmpeg.org/)).
|
|
26
|
+
|
|
27
|
+
For detailed installation instructions visit our [Installation Guide](https://dougmercer.github.io/keyed/install)
|
|
28
|
+
.
|
|
29
|
+
|
|
30
|
+
But, once you have the necessary system dependencies installed, installing `keyed` is as simple as,
|
|
31
|
+
|
|
32
|
+
```console
|
|
33
|
+
pip install keyed
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Project Status
|
|
37
|
+
|
|
38
|
+
This project is in beta, so APIs may change.
|
|
39
|
+
|
|
40
|
+
## Alternatives
|
|
41
|
+
While I find `keyed` very fun and useful (particularly for animating syntax highlighted code in my [YouTube videos](https://youtube.com/@dougmercer)), there are several other excellent and far more mature animation libraries that you should probably use instead.
|
|
42
|
+
|
|
43
|
+
Before you decide to use `keyed`, be sure to check out:
|
|
44
|
+
|
|
45
|
+
* [Manim](https://manim.community): Comprehensive mathematical animation system originally created by Grant Sanderson of the YouTube channel 3blue1brown, but later adopted and extended by the manim community.
|
|
46
|
+
* [py5](https://py5coding.org): A Python wrapper for p5, the Java animation library.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "keyed"
|
|
7
|
+
description = "A reactive animation library."
|
|
8
|
+
version = "0.1.0"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {file="LICENSE"}
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "Doug Mercer", email = "dougmerceryt@gmail.com"}
|
|
13
|
+
]
|
|
14
|
+
requires-python = ">=3.11,<3.13" # taichi is most restrictive
|
|
15
|
+
dependencies = [
|
|
16
|
+
"pyav",
|
|
17
|
+
"pillow",
|
|
18
|
+
"pycairo",
|
|
19
|
+
"pydantic",
|
|
20
|
+
"pygments",
|
|
21
|
+
"scipy",
|
|
22
|
+
"shapely",
|
|
23
|
+
"signified",
|
|
24
|
+
"tqdm",
|
|
25
|
+
"typer",
|
|
26
|
+
]
|
|
27
|
+
classifiers = [
|
|
28
|
+
"Development Status :: 4 - Beta",
|
|
29
|
+
"Intended Audience :: Developers",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
]
|
|
33
|
+
keywords = ["keyed", "animation", "reactive"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
lint = [
|
|
38
|
+
"ruff",
|
|
39
|
+
"pyright",
|
|
40
|
+
]
|
|
41
|
+
test = [
|
|
42
|
+
"hypothesis",
|
|
43
|
+
"pytest",
|
|
44
|
+
"pytest-cov",
|
|
45
|
+
"syrupy",
|
|
46
|
+
]
|
|
47
|
+
docs = [
|
|
48
|
+
"beautifulsoup4",
|
|
49
|
+
"mkdocs",
|
|
50
|
+
"mkdocs-material",
|
|
51
|
+
"mkdocstrings[python]",
|
|
52
|
+
"mkdocs-material-extensions",
|
|
53
|
+
]
|
|
54
|
+
previewer = [
|
|
55
|
+
"pyside6",
|
|
56
|
+
"watchdog",
|
|
57
|
+
]
|
|
58
|
+
gpu-compositor = [
|
|
59
|
+
"taichi",
|
|
60
|
+
]
|
|
61
|
+
all = ["keyed[lint,test,docs,previewer,gpu-compositor]"]
|
|
62
|
+
|
|
63
|
+
[tool.setuptools.package-data]
|
|
64
|
+
keyed = ["py.typed"]
|
|
65
|
+
|
|
66
|
+
[tool.pytest.ini_options]
|
|
67
|
+
addopts = "--cov=src --cov-report=xml --junitxml=junit/test-results.xml --durations=10"
|
|
68
|
+
filterwarnings = ["ignore::DeprecationWarning"]
|
|
69
|
+
testpaths = ["tests", "src"]
|
|
70
|
+
markers = {snapshot = "marks tests as snapshot (deselect with '-m \"not snapshot\"')"}
|
|
71
|
+
|
|
72
|
+
[tool.coverage.report]
|
|
73
|
+
exclude_also = [
|
|
74
|
+
"pragma: no cover",
|
|
75
|
+
"if TYPE_CHECKING:",
|
|
76
|
+
"pass"
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
[tool.ruff]
|
|
80
|
+
# Exclude a variety of commonly ignored directories.
|
|
81
|
+
exclude = [
|
|
82
|
+
".bzr",
|
|
83
|
+
".direnv",
|
|
84
|
+
".eggs",
|
|
85
|
+
".git",
|
|
86
|
+
".git-rewrite",
|
|
87
|
+
".hg",
|
|
88
|
+
".ipynb_checkpoints",
|
|
89
|
+
".mypy_cache",
|
|
90
|
+
".nox",
|
|
91
|
+
".pants.d",
|
|
92
|
+
".pyenv",
|
|
93
|
+
".pytest_cache",
|
|
94
|
+
".pytype",
|
|
95
|
+
".ruff_cache",
|
|
96
|
+
".svn",
|
|
97
|
+
".tox",
|
|
98
|
+
".venv",
|
|
99
|
+
".vscode",
|
|
100
|
+
".__pycache__",
|
|
101
|
+
"__pypackages__",
|
|
102
|
+
"_build",
|
|
103
|
+
"buck-out",
|
|
104
|
+
"build",
|
|
105
|
+
"dist",
|
|
106
|
+
"docs",
|
|
107
|
+
"envs",
|
|
108
|
+
"htmlcov",
|
|
109
|
+
"results",
|
|
110
|
+
"significant.egg-info",
|
|
111
|
+
"junk",
|
|
112
|
+
".hypothesis",
|
|
113
|
+
"node_modules",
|
|
114
|
+
"site-packages",
|
|
115
|
+
"venv",
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
line-length = 120
|
|
119
|
+
indent-width = 4
|
|
120
|
+
target-version = "py39"
|
|
121
|
+
|
|
122
|
+
[tool.ruff.lint]
|
|
123
|
+
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
|
124
|
+
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
|
|
125
|
+
# McCabe complexity (`C901`) by default.
|
|
126
|
+
select = ["E4", "E7", "E9", "F", "I"]
|
|
127
|
+
ignore = []
|
|
128
|
+
|
|
129
|
+
# Allow fix for all enabled rules (when `--fix`) is provided.
|
|
130
|
+
fixable = ["ALL"]
|
|
131
|
+
unfixable = []
|
|
132
|
+
|
|
133
|
+
# Allow unused variables when underscore-prefixed.
|
|
134
|
+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
135
|
+
|
|
136
|
+
[tool.ruff.format]
|
|
137
|
+
# Like Black, use double quotes for strings.
|
|
138
|
+
quote-style = "double"
|
|
139
|
+
|
|
140
|
+
# Like Black, indent with spaces, rather than tabs.
|
|
141
|
+
indent-style = "space"
|
|
142
|
+
|
|
143
|
+
# Like Black, respect magic trailing commas.
|
|
144
|
+
skip-magic-trailing-comma = false
|
|
145
|
+
|
|
146
|
+
# Like Black, automatically detect the appropriate line ending.
|
|
147
|
+
line-ending = "auto"
|
|
148
|
+
|
|
149
|
+
# Enable auto-formatting of code examples in docstrings. Markdown,
|
|
150
|
+
# reStructuredText code/literal blocks and doctests are all supported.
|
|
151
|
+
#
|
|
152
|
+
# This is currently disabled by default, but it is planned for this
|
|
153
|
+
# to be opt-out in the future.
|
|
154
|
+
docstring-code-format = false
|
|
155
|
+
|
|
156
|
+
# Set the line length limit used when formatting code snippets in
|
|
157
|
+
# docstrings.
|
|
158
|
+
#
|
|
159
|
+
# This only has an effect when the `docstring-code-format` setting is
|
|
160
|
+
# enabled.
|
|
161
|
+
docstring-code-line-length = "dynamic"
|
|
162
|
+
|
|
163
|
+
[tool.ruff.lint.isort]
|
|
164
|
+
known-first-party = ["keyed_*", "helpers"]
|
|
165
|
+
|
|
166
|
+
[project.scripts]
|
|
167
|
+
keyed = "keyed.cli:main"
|
keyed-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""A key-frame focused animation engine."""
|
|
2
|
+
|
|
3
|
+
# Taichi makes it an absolute nightmare to squelch startup noise.
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
os.environ["ENABLE_TAICHI_HEADER_PRINT"] = "False"
|
|
7
|
+
try:
|
|
8
|
+
from taichi._logging import ERROR, set_logging_level # noqa: E402 # type: ignore
|
|
9
|
+
|
|
10
|
+
set_logging_level(ERROR)
|
|
11
|
+
del set_logging_level
|
|
12
|
+
except ImportError:
|
|
13
|
+
pass
|
|
14
|
+
finally:
|
|
15
|
+
del os
|
|
16
|
+
|
|
17
|
+
from . import easing # noqa
|
|
18
|
+
from . import highlight # noqa
|
|
19
|
+
from . import transforms # noqa
|
|
20
|
+
from .animation import * # noqa
|
|
21
|
+
from .annotations import * # noqa
|
|
22
|
+
from .base import * # noqa
|
|
23
|
+
from .text import * # noqa
|
|
24
|
+
from .constants import * # noqa
|
|
25
|
+
from .color import * # noqa
|
|
26
|
+
from .compositor import * # noqa
|
|
27
|
+
from .curve import * # noqa
|
|
28
|
+
from .effects import * # noqa
|
|
29
|
+
from .highlight import * # noqa
|
|
30
|
+
from .line import * # noqa
|
|
31
|
+
from .geometry import * # noqa
|
|
32
|
+
from .plot import * # noqa
|
|
33
|
+
from .scene import * # noqa
|
|
34
|
+
from .group import * # noqa
|
|
35
|
+
from .shapes import * # noqa
|
|
36
|
+
|
|
37
|
+
# This must go last
|
|
38
|
+
from .extras import * # noqa
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""Animation related classes/functions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum, auto
|
|
6
|
+
from functools import partial
|
|
7
|
+
from typing import Any, Generic, TypeVar
|
|
8
|
+
|
|
9
|
+
from signified import Computed, HasValue, ReactiveValue, Signal, computed
|
|
10
|
+
|
|
11
|
+
from .constants import ALWAYS
|
|
12
|
+
from .easing import EasingFunctionT, easing_function, linear_in_out
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"AnimationType",
|
|
16
|
+
"Animation",
|
|
17
|
+
"stagger",
|
|
18
|
+
"Loop",
|
|
19
|
+
"PingPong",
|
|
20
|
+
"step",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AnimationType(Enum):
|
|
25
|
+
"""Specifies the mathematical operation used to combine the original and animated values."""
|
|
26
|
+
|
|
27
|
+
MULTIPLY = auto()
|
|
28
|
+
"""Multiplies the original value by the animated value."""
|
|
29
|
+
ABSOLUTE = auto()
|
|
30
|
+
"""Replaces the original value with the animated value."""
|
|
31
|
+
ADD = auto()
|
|
32
|
+
"""Adds the animated value to the original value."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
T = TypeVar("T")
|
|
36
|
+
A = TypeVar("A")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Animation(Generic[T]):
|
|
40
|
+
"""Define an animation.
|
|
41
|
+
|
|
42
|
+
Animations vary a parameter over time.
|
|
43
|
+
|
|
44
|
+
Generally, Animations become active at ``start_frame`` and smoothly change
|
|
45
|
+
according to the ``easing`` function until terminating to a final value at
|
|
46
|
+
``end_frame``. The animation will remain active (i.e., the parameter will
|
|
47
|
+
not suddenly jump back to it's pre-animation state), but will cease varying.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
start: Frame at which the animation will become active.
|
|
51
|
+
end: Frame at which the animation will stop varying.
|
|
52
|
+
start_value: Value at which the animation will start.
|
|
53
|
+
end_value: Value at which the animation will end.
|
|
54
|
+
ease: The rate in which the value will change throughout the animation.
|
|
55
|
+
animation_type: How the animation value will affect the original value.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
ValueError: When ``start_frame > end_frame``
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
start: int,
|
|
64
|
+
end: int,
|
|
65
|
+
start_value: HasValue[T],
|
|
66
|
+
end_value: HasValue[T],
|
|
67
|
+
ease: EasingFunctionT = linear_in_out,
|
|
68
|
+
animation_type: AnimationType = AnimationType.ABSOLUTE,
|
|
69
|
+
) -> None:
|
|
70
|
+
if start > end:
|
|
71
|
+
raise ValueError("Ending frame must be after starting frame.")
|
|
72
|
+
if not hasattr(self, "start_frame"):
|
|
73
|
+
self.start_frame = start
|
|
74
|
+
if not hasattr(self, "end_frame"):
|
|
75
|
+
self.end_frame = end
|
|
76
|
+
self.start_value = start_value
|
|
77
|
+
self.end_value = end_value
|
|
78
|
+
self.ease = ease
|
|
79
|
+
self.animation_type = animation_type
|
|
80
|
+
|
|
81
|
+
def __call__(self, value: HasValue[A], frame: ReactiveValue[int]) -> Computed[A | T]:
|
|
82
|
+
"""Bind the animation to the input value and frame."""
|
|
83
|
+
easing = easing_function(start=self.start_frame, end=self.end_frame, ease=self.ease, frame=frame)
|
|
84
|
+
|
|
85
|
+
@computed
|
|
86
|
+
def f(value: A, frame: int, easing: float, start: T, end: T) -> A | T:
|
|
87
|
+
eased_value = end * easing + start * (1 - easing) # pyright: ignore[reportOperatorIssue] # noqa: E501
|
|
88
|
+
|
|
89
|
+
match self.animation_type:
|
|
90
|
+
case AnimationType.ABSOLUTE:
|
|
91
|
+
pass
|
|
92
|
+
case AnimationType.ADD:
|
|
93
|
+
eased_value = value + eased_value
|
|
94
|
+
case AnimationType.MULTIPLY:
|
|
95
|
+
eased_value = value * eased_value
|
|
96
|
+
case _:
|
|
97
|
+
raise ValueError("Undefined AnimationType")
|
|
98
|
+
|
|
99
|
+
return value if frame < self.start_frame else eased_value
|
|
100
|
+
|
|
101
|
+
return f(value, frame, easing, self.start_value, self.end_value)
|
|
102
|
+
|
|
103
|
+
def __len__(self) -> int:
|
|
104
|
+
"""Return number of frames in the animation."""
|
|
105
|
+
return self.end_frame - self.start_frame + 1
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Loop(Animation):
|
|
109
|
+
"""Loop an animation.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
animation: The animation to loop.
|
|
113
|
+
n: Number of times to loop the animation.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(self, animation: Animation, n: int = 1):
|
|
117
|
+
self.animation = animation
|
|
118
|
+
self.n = n
|
|
119
|
+
super().__init__(self.start_frame, self.end_frame, 0, 0)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def start_frame(self) -> int: # type: ignore[override]
|
|
123
|
+
"""Frame at which the animation will become active."""
|
|
124
|
+
return self.animation.start_frame
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def end_frame(self) -> int: # type: ignore[override]
|
|
128
|
+
"""Frame at which the animation will stop varying."""
|
|
129
|
+
return self.animation.start_frame + len(self.animation) * self.n
|
|
130
|
+
|
|
131
|
+
def __call__(self, value: HasValue[T], frame: ReactiveValue[int]) -> Computed[T]:
|
|
132
|
+
"""Apply the animation to the current value at the current frame.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
frame: The frame at which the animation is applied.
|
|
136
|
+
value: The initial value.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
The value after the animation.
|
|
140
|
+
"""
|
|
141
|
+
effective_frame = self.animation.start_frame + (frame - self.animation.start_frame) % len(self.animation)
|
|
142
|
+
active_anim = self.animation(value, effective_frame)
|
|
143
|
+
post_anim = self.animation(value, Signal(self.animation.end_frame))
|
|
144
|
+
|
|
145
|
+
@computed
|
|
146
|
+
def f(frame: int, value: Any, active_anim: Any, post_anim: Any) -> Any:
|
|
147
|
+
if frame < self.start_frame:
|
|
148
|
+
return value
|
|
149
|
+
elif frame < self.end_frame:
|
|
150
|
+
return active_anim
|
|
151
|
+
else:
|
|
152
|
+
return post_anim
|
|
153
|
+
|
|
154
|
+
return f(frame, value, active_anim, post_anim)
|
|
155
|
+
|
|
156
|
+
def __repr__(self) -> str:
|
|
157
|
+
return f"Loop(animation={self.animation}, n={self.n})"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class PingPong(Animation):
|
|
161
|
+
"""Play an animation forward, then backwards n times.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
animation: The animation to ping-pong.
|
|
165
|
+
n: Number of full back-and-forth cycles
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
def __init__(self, animation: Animation, n: int = 1):
|
|
169
|
+
self.animation = animation
|
|
170
|
+
self.n = n
|
|
171
|
+
super().__init__(self.start_frame, self.end_frame, 0, 0)
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def start_frame(self) -> int: # type: ignore[override]
|
|
175
|
+
"""Returns the frame at which the animation begins."""
|
|
176
|
+
return self.animation.start_frame
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def end_frame(self) -> int: # type: ignore[override]
|
|
180
|
+
"""Returns the frame at which the animation stops varying.
|
|
181
|
+
|
|
182
|
+
Notes:
|
|
183
|
+
Each cycle consists of going forward and coming back.
|
|
184
|
+
"""
|
|
185
|
+
return self.animation.start_frame + self.cycle_len * self.n
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def cycle_len(self) -> int:
|
|
189
|
+
"""Returns the number of frames in one cycle."""
|
|
190
|
+
return 2 * (len(self.animation) - 1)
|
|
191
|
+
|
|
192
|
+
def __call__(self, value: HasValue[T], frame: ReactiveValue[int]) -> Computed[T]:
|
|
193
|
+
"""Apply the animation to the current value at the current frame.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
frame: The frame at which the animation is applied.
|
|
197
|
+
value: The initial value.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
The value after the animation.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
# Calculate effective frame based on whether we're in the forward or backward cycle
|
|
204
|
+
@computed
|
|
205
|
+
def effective_frame_(frame: int) -> int:
|
|
206
|
+
frame_in_cycle = (frame - self.start_frame) % self.cycle_len
|
|
207
|
+
return (
|
|
208
|
+
self.animation.start_frame + frame_in_cycle
|
|
209
|
+
if frame_in_cycle < len(self.animation)
|
|
210
|
+
else self.animation.end_frame - (frame_in_cycle - len(self.animation) + 1)
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
effective_frame = effective_frame_(frame)
|
|
214
|
+
anim = self.animation(value, effective_frame)
|
|
215
|
+
|
|
216
|
+
@computed
|
|
217
|
+
def f(frame: int, value: Any) -> Any:
|
|
218
|
+
return value if frame < self.start_frame or frame > self.end_frame else anim.value
|
|
219
|
+
|
|
220
|
+
return f(frame, value)
|
|
221
|
+
|
|
222
|
+
def __repr__(self) -> str:
|
|
223
|
+
return f"PingPong(animation={self.animation}, n={self.n})"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def stagger(
|
|
227
|
+
start_value: float = 0,
|
|
228
|
+
end_value: float = 1,
|
|
229
|
+
easing: EasingFunctionT = linear_in_out,
|
|
230
|
+
animation_type: AnimationType = AnimationType.ABSOLUTE,
|
|
231
|
+
) -> partial[Animation]:
|
|
232
|
+
"""Partially-initialize an animation for use with [Group.write_on][keyed.group.Group.write_on].
|
|
233
|
+
|
|
234
|
+
This will set the animations values, easing, and type without setting its start/end frames.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
start_value: Value at which the animation will start.
|
|
238
|
+
end_value: Value at which the animation will end.
|
|
239
|
+
easing: The rate in which the value will change throughout the animation.
|
|
240
|
+
animation_type: How the animation value will affect the original value.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Partially initialized animation.
|
|
244
|
+
"""
|
|
245
|
+
return partial(
|
|
246
|
+
Animation,
|
|
247
|
+
start_value=start_value,
|
|
248
|
+
end_value=end_value,
|
|
249
|
+
ease=easing,
|
|
250
|
+
animation_type=animation_type,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def step(
|
|
255
|
+
value: HasValue[T], frame: int = ALWAYS, animation_type: AnimationType = AnimationType.ABSOLUTE
|
|
256
|
+
) -> Animation[T]:
|
|
257
|
+
"""Return an animation that applies a step function to the Variable at a particular frame.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
value: The value to step to.
|
|
261
|
+
frame: The frame at which the step will be applied.
|
|
262
|
+
animation_type: See :class:`AnimationType`.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
An animation that applies a step function to the Variable at a particular frame.
|
|
266
|
+
"""
|
|
267
|
+
# Can this be simpler? Something like...
|
|
268
|
+
# def step_builder(initial_value: HasValue[A], frame_rx: ReactiveValue[int]) -> Computed[A|T]:
|
|
269
|
+
# return (frame_rx >= frame).where(value, initial_value)
|
|
270
|
+
|
|
271
|
+
# return step_builder # Callable[[HasValue[A], ReactiveValue[int]], Computed[A|T]]
|
|
272
|
+
return Animation(
|
|
273
|
+
start=frame,
|
|
274
|
+
end=frame,
|
|
275
|
+
start_value=value,
|
|
276
|
+
end_value=value,
|
|
277
|
+
animation_type=animation_type,
|
|
278
|
+
)
|