janim 0.1__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.
- janim-0.1/.gitignore +12 -0
- janim-0.1/.readthedocs.yaml +37 -0
- janim-0.1/.vscode/settings.json +16 -0
- janim-0.1/.vscode/tasks.json +13 -0
- janim-0.1/LICENSE +21 -0
- janim-0.1/PKG-INFO +51 -0
- janim-0.1/README.md +19 -0
- janim-0.1/janim/__init__.py +3 -0
- janim-0.1/janim/__main__.py +270 -0
- janim-0.1/janim/anims/animation.py +119 -0
- janim-0.1/janim/anims/composition.py +167 -0
- janim-0.1/janim/anims/creation.py +188 -0
- janim-0.1/janim/anims/display.py +29 -0
- janim-0.1/janim/anims/fading.py +118 -0
- janim-0.1/janim/anims/rotation.py +45 -0
- janim-0.1/janim/anims/timeline.py +489 -0
- janim-0.1/janim/anims/transform.py +228 -0
- janim-0.1/janim/anims/updater.py +131 -0
- janim-0.1/janim/camera/camera.py +145 -0
- janim-0.1/janim/camera/camera_info.py +79 -0
- janim-0.1/janim/components/component.py +266 -0
- janim-0.1/janim/components/depth.py +87 -0
- janim-0.1/janim/components/image.py +45 -0
- janim-0.1/janim/components/points.py +1006 -0
- janim-0.1/janim/components/radius.py +105 -0
- janim-0.1/janim/components/rgbas.py +254 -0
- janim-0.1/janim/components/vpoints.py +510 -0
- janim-0.1/janim/constants/__init__.py +5 -0
- janim-0.1/janim/constants/alignment.py +7 -0
- janim-0.1/janim/constants/colors.py +66 -0
- janim-0.1/janim/constants/coord.py +20 -0
- janim-0.1/janim/constants/degrees.py +8 -0
- janim-0.1/janim/examples.py +32 -0
- janim-0.1/janim/gui/anim_viewer.py +746 -0
- janim-0.1/janim/gui/application.py +11 -0
- janim-0.1/janim/gui/export.png +0 -0
- janim-0.1/janim/gui/fixed_ratio_widget.py +36 -0
- janim-0.1/janim/gui/glwidget.py +33 -0
- janim-0.1/janim/gui/selector.py +222 -0
- janim-0.1/janim/imports.py +34 -0
- janim-0.1/janim/items/boolean_ops.py +149 -0
- janim-0.1/janim/items/geometry/arc.py +313 -0
- janim-0.1/janim/items/geometry/arrow.py +210 -0
- janim-0.1/janim/items/geometry/line.py +207 -0
- janim-0.1/janim/items/geometry/polygon.py +161 -0
- janim-0.1/janim/items/image_item.py +171 -0
- janim-0.1/janim/items/item.py +540 -0
- janim-0.1/janim/items/points.py +110 -0
- janim-0.1/janim/items/relation.py +199 -0
- janim-0.1/janim/items/svg/svg_item.py +118 -0
- janim-0.1/janim/items/svg/typst.py +67 -0
- janim-0.1/janim/items/svg/typst_template.typ +3 -0
- janim-0.1/janim/items/text/text.py +397 -0
- janim-0.1/janim/items/vitem.py +154 -0
- janim-0.1/janim/logger.py +13 -0
- janim-0.1/janim/render/base.py +103 -0
- janim-0.1/janim/render/file_writer.py +122 -0
- janim-0.1/janim/render/impl.py +235 -0
- janim-0.1/janim/render/shaders/dotcloud.frag.glsl +21 -0
- janim-0.1/janim/render/shaders/dotcloud.geom.glsl +50 -0
- janim-0.1/janim/render/shaders/dotcloud.vert.glsl +19 -0
- janim-0.1/janim/render/shaders/image.frag.glsl +13 -0
- janim-0.1/janim/render/shaders/image.vert.glsl +18 -0
- janim-0.1/janim/render/shaders/vitem.frag.glsl +254 -0
- janim-0.1/janim/render/shaders/vitem.vert.glsl +14 -0
- janim-0.1/janim/render/texture.py +32 -0
- janim-0.1/janim/typing.py +31 -0
- janim-0.1/janim/utils/bezier.py +405 -0
- janim-0.1/janim/utils/config.py +156 -0
- janim-0.1/janim/utils/data.py +11 -0
- janim-0.1/janim/utils/file_ops.py +29 -0
- janim-0.1/janim/utils/font.py +79 -0
- janim-0.1/janim/utils/font_manager.py +177 -0
- janim-0.1/janim/utils/iterables.py +206 -0
- janim-0.1/janim/utils/paths.py +60 -0
- janim-0.1/janim/utils/rate_functions.py +111 -0
- janim-0.1/janim/utils/refresh.py +64 -0
- janim-0.1/janim/utils/signal.py +280 -0
- janim-0.1/janim/utils/simple_functions.py +81 -0
- janim-0.1/janim/utils/space_ops.py +360 -0
- janim-0.1/janim/utils/unique_nparray.py +37 -0
- janim-0.1/pyproject.toml +49 -0
janim-0.1/.gitignore
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# .readthedocs.yaml
|
|
2
|
+
# Read the Docs configuration file
|
|
3
|
+
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
|
4
|
+
|
|
5
|
+
# Required
|
|
6
|
+
version: 2
|
|
7
|
+
|
|
8
|
+
# Set the OS, Python version and other tools you might need
|
|
9
|
+
build:
|
|
10
|
+
os: ubuntu-22.04
|
|
11
|
+
tools:
|
|
12
|
+
python: "3.12"
|
|
13
|
+
# You can also specify other tool versions:
|
|
14
|
+
# nodejs: "19"
|
|
15
|
+
# rust: "1.64"
|
|
16
|
+
# golang: "1.19"
|
|
17
|
+
|
|
18
|
+
# Build documentation in the "docs/" directory with Sphinx
|
|
19
|
+
sphinx:
|
|
20
|
+
# configuration: docs/conf.py
|
|
21
|
+
configuration: doc/source/conf.py
|
|
22
|
+
|
|
23
|
+
# Optionally build your docs in additional formats such as PDF and ePub
|
|
24
|
+
# formats:
|
|
25
|
+
# - pdf
|
|
26
|
+
# - epub
|
|
27
|
+
|
|
28
|
+
# Optional but recommended, declare the Python requirements required
|
|
29
|
+
# to build your documentation
|
|
30
|
+
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
|
|
31
|
+
# python:
|
|
32
|
+
# install:
|
|
33
|
+
# - requirements: doc/source/requirements.txt
|
|
34
|
+
python:
|
|
35
|
+
install:
|
|
36
|
+
- method: pip
|
|
37
|
+
path: .[doc]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"todo-tree.tree.scanMode": "workspace only",
|
|
3
|
+
"flake8.args": [
|
|
4
|
+
"--max-line-length=120",
|
|
5
|
+
"--ignore=E701,E704,E731,F821,F811",
|
|
6
|
+
"--exclude=test/*,refactor_deprecated/*"
|
|
7
|
+
],
|
|
8
|
+
"todo-tree.highlights.customHighlight": {
|
|
9
|
+
"TODO": {
|
|
10
|
+
"iconColour": "yellow"
|
|
11
|
+
},
|
|
12
|
+
"REFACTOR": {
|
|
13
|
+
"icon": "tools"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
|
3
|
+
// for the documentation about the tasks.json format
|
|
4
|
+
"version": "2.0.0",
|
|
5
|
+
"tasks": [
|
|
6
|
+
{
|
|
7
|
+
"label": "pyside6-uic",
|
|
8
|
+
"type": "shell",
|
|
9
|
+
"command": "pyside6-uic ${fileDirname}\\${fileBasename} -o ${fileDirname}\\ui_${fileBasenameNoExtension}.py",
|
|
10
|
+
"problemMatcher": []
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
janim-0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 jkjkil4
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
janim-0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: janim
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: a library for simple animation effects
|
|
5
|
+
Author: jkjkil4
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Classifier: Programming Language :: Python
|
|
9
|
+
Requires-Dist: numpy
|
|
10
|
+
Requires-Dist: scipy
|
|
11
|
+
Requires-Dist: colour
|
|
12
|
+
Requires-Dist: rich
|
|
13
|
+
Requires-Dist: glm
|
|
14
|
+
Requires-Dist: moderngl
|
|
15
|
+
Requires-Dist: tqdm
|
|
16
|
+
Requires-Dist: psutil
|
|
17
|
+
Requires-Dist: skia-pathops
|
|
18
|
+
Requires-Dist: fontTools
|
|
19
|
+
Requires-Dist: freetype-py
|
|
20
|
+
Requires-Dist: pillow
|
|
21
|
+
Requires-Dist: svgelements
|
|
22
|
+
Requires-Dist: sphinx ; extra == "doc"
|
|
23
|
+
Requires-Dist: furo ; extra == "doc"
|
|
24
|
+
Requires-Dist: pyside6 ; extra == "gui"
|
|
25
|
+
Requires-Dist: qdarkstyle ; extra == "gui"
|
|
26
|
+
Project-URL: Documentation, https://janim.rtfd.io
|
|
27
|
+
Project-URL: Home, https://github.com/jkjkil4/JAnim
|
|
28
|
+
Project-URL: Source, https://github.com/jkjkil4/JAnim
|
|
29
|
+
Provides-Extra: doc
|
|
30
|
+
Provides-Extra: gui
|
|
31
|
+
|
|
32
|
+
# JAnim
|
|
33
|
+
|
|
34
|
+
#### 介绍
|
|
35
|
+
|
|
36
|
+
- 功能不完善,千万不要用!
|
|
37
|
+
- 代码很混乱,千万不要看!
|
|
38
|
+
|
|
39
|
+
#### 安装教程
|
|
40
|
+
|
|
41
|
+
1. xxxx
|
|
42
|
+
2. xxxx
|
|
43
|
+
3. xxxx
|
|
44
|
+
|
|
45
|
+
#### 使用说明
|
|
46
|
+
|
|
47
|
+
1. xxxx
|
|
48
|
+
2. xxxx
|
|
49
|
+
3. xxxx
|
|
50
|
+
|
|
51
|
+
|
janim-0.1/README.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import time
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import subprocess as sp
|
|
6
|
+
from argparse import ArgumentParser, Namespace
|
|
7
|
+
|
|
8
|
+
from janim.anims.timeline import Timeline
|
|
9
|
+
from janim.logger import log
|
|
10
|
+
from janim.utils.config import Config, default_config
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main() -> None:
|
|
14
|
+
parser = ArgumentParser(description='A library for simple animation effects')
|
|
15
|
+
parser.set_defaults(func=None)
|
|
16
|
+
|
|
17
|
+
sp = parser.add_subparsers()
|
|
18
|
+
run_parser(sp.add_parser('run', help='Run timeline(s) from specific namespace'))
|
|
19
|
+
write_parser(sp.add_parser('write', help='Generate video file(s) of timeline(s) from specific namesapce'))
|
|
20
|
+
examples_parser(sp.add_parser('examples', help='Show examples of janim'))
|
|
21
|
+
|
|
22
|
+
args = parser.parse_args()
|
|
23
|
+
if args.func is None:
|
|
24
|
+
parser.print_help()
|
|
25
|
+
else:
|
|
26
|
+
args.func(args)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def render_args(parser: ArgumentParser) -> None:
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
'namespace',
|
|
32
|
+
help='Namespace to file holding the python code for the timeline'
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
'timeline_names',
|
|
36
|
+
nargs='*',
|
|
37
|
+
help='Name of the Timeline class you want to see'
|
|
38
|
+
)
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
'-a', '--all',
|
|
41
|
+
action='store_true',
|
|
42
|
+
help='Render all timelines from a file'
|
|
43
|
+
)
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
'-c', '--config',
|
|
46
|
+
nargs=2,
|
|
47
|
+
metavar=('key', 'value'),
|
|
48
|
+
action='append',
|
|
49
|
+
help='Override config'
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def run_parser(parser: ArgumentParser) -> None:
|
|
54
|
+
render_args(parser)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
'-i', '--interact',
|
|
57
|
+
action='store_true',
|
|
58
|
+
help='Enable the network socket for interacting'
|
|
59
|
+
)
|
|
60
|
+
parser.set_defaults(func=run)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def write_parser(parser: ArgumentParser) -> None:
|
|
64
|
+
render_args(parser)
|
|
65
|
+
parser.add_argument(
|
|
66
|
+
'-o', '--open',
|
|
67
|
+
action='store_true',
|
|
68
|
+
help='Open the file after writing'
|
|
69
|
+
)
|
|
70
|
+
parser.add_argument(
|
|
71
|
+
'--format',
|
|
72
|
+
choices=['mp4', 'mov'],
|
|
73
|
+
default='mp4',
|
|
74
|
+
help='Format of the output file'
|
|
75
|
+
)
|
|
76
|
+
parser.set_defaults(func=write)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def examples_parser(parser: ArgumentParser) -> None:
|
|
80
|
+
parser.set_defaults(namespace='janim.examples')
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
'timeline_names',
|
|
83
|
+
nargs='*',
|
|
84
|
+
help='Name of the example you want to see'
|
|
85
|
+
)
|
|
86
|
+
parser.set_defaults(all=None)
|
|
87
|
+
parser.set_defaults(config=None)
|
|
88
|
+
parser.set_defaults(func=run)
|
|
89
|
+
parser.set_defaults(interact=False)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def run(args: Namespace) -> None:
|
|
93
|
+
module = get_module(args.namespace)
|
|
94
|
+
if module is None:
|
|
95
|
+
return
|
|
96
|
+
modify_default_config(args)
|
|
97
|
+
|
|
98
|
+
timelines = extract_timelines_from_module(args, module)
|
|
99
|
+
if not timelines:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
auto_play = len(timelines) == 1
|
|
103
|
+
|
|
104
|
+
from janim.gui.anim_viewer import AnimViewer
|
|
105
|
+
from janim.gui.application import Application
|
|
106
|
+
from PySide6.QtCore import QPoint, QTimer
|
|
107
|
+
|
|
108
|
+
app = Application()
|
|
109
|
+
|
|
110
|
+
log.info('======')
|
|
111
|
+
|
|
112
|
+
widgets: list[AnimViewer] = []
|
|
113
|
+
for timeline in timelines:
|
|
114
|
+
viewer = AnimViewer(timeline().build(), auto_play, args.interact)
|
|
115
|
+
widgets.append(viewer)
|
|
116
|
+
|
|
117
|
+
log.info('======')
|
|
118
|
+
log.info('Constructing window')
|
|
119
|
+
|
|
120
|
+
t = time.time()
|
|
121
|
+
|
|
122
|
+
for i, widget in enumerate(widgets):
|
|
123
|
+
if i != 0:
|
|
124
|
+
widget.move(widgets[i - 1].pos() + QPoint(24, 24))
|
|
125
|
+
widget.show()
|
|
126
|
+
|
|
127
|
+
QTimer.singleShot(200, widgets[-1].activateWindow)
|
|
128
|
+
|
|
129
|
+
log.info(f'Finished constructing in {time.time() - t:.2f} s')
|
|
130
|
+
log.info('======')
|
|
131
|
+
|
|
132
|
+
app.exec()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def write(args: Namespace) -> None:
|
|
136
|
+
module = get_module(args.namespace)
|
|
137
|
+
if module is None:
|
|
138
|
+
return
|
|
139
|
+
modify_default_config(args)
|
|
140
|
+
|
|
141
|
+
timelines = extract_timelines_from_module(args, module)
|
|
142
|
+
if not timelines:
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
from janim.render.file_writer import FileWriter
|
|
146
|
+
|
|
147
|
+
log.info('======')
|
|
148
|
+
|
|
149
|
+
built = [timeline().build() for timeline in timelines]
|
|
150
|
+
|
|
151
|
+
log.info('======')
|
|
152
|
+
|
|
153
|
+
output_dir = os.path.normpath(Config.get.output_dir)
|
|
154
|
+
if not os.path.exists(output_dir):
|
|
155
|
+
os.makedirs(output_dir)
|
|
156
|
+
|
|
157
|
+
log.info(f'fps={Config.get.fps}')
|
|
158
|
+
log.info(f'resolution="{Config.get.pixel_width}x{Config.get.pixel_height}"')
|
|
159
|
+
log.info(f'output_dir="{output_dir}"')
|
|
160
|
+
log.info(f'format="{args.format}"')
|
|
161
|
+
|
|
162
|
+
log.info('======')
|
|
163
|
+
|
|
164
|
+
for anim in built:
|
|
165
|
+
writer = FileWriter(anim)
|
|
166
|
+
writer.write_all(
|
|
167
|
+
os.path.join(Config.get.output_dir,
|
|
168
|
+
f'{anim.timeline.__class__.__name__}.{args.format}')
|
|
169
|
+
)
|
|
170
|
+
if args.open and anim is built[-1]:
|
|
171
|
+
open_file(writer.final_file_path)
|
|
172
|
+
|
|
173
|
+
log.info('======')
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def modify_default_config(args: Namespace) -> None:
|
|
177
|
+
if args.config:
|
|
178
|
+
for key, value in args.config:
|
|
179
|
+
dtype = type(getattr(default_config, key))
|
|
180
|
+
setattr(default_config, key, dtype(value))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def get_module(namespace: str):
|
|
184
|
+
try:
|
|
185
|
+
return importlib.import_module(namespace)
|
|
186
|
+
except ModuleNotFoundError:
|
|
187
|
+
log.error(f'No module named "{namespace}"')
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def extract_timelines_from_module(args: Namespace, module) -> list[type[Timeline]]:
|
|
192
|
+
timelines = []
|
|
193
|
+
err = False
|
|
194
|
+
|
|
195
|
+
if not args.all and args.timeline_names:
|
|
196
|
+
for name in args.timeline_names:
|
|
197
|
+
try:
|
|
198
|
+
timelines.append(module.__dict__[name])
|
|
199
|
+
except KeyError:
|
|
200
|
+
log.error(f'No timeline named "{name}"')
|
|
201
|
+
err = True
|
|
202
|
+
else:
|
|
203
|
+
import inspect
|
|
204
|
+
|
|
205
|
+
classes = [
|
|
206
|
+
value
|
|
207
|
+
for value in module.__dict__.values()
|
|
208
|
+
if isinstance(value, type) and issubclass(value, Timeline) and value.__module__ == module.__name__
|
|
209
|
+
]
|
|
210
|
+
if len(classes) <= 1:
|
|
211
|
+
return classes
|
|
212
|
+
classes.sort(key=lambda x: inspect.getsourcelines(x)[1])
|
|
213
|
+
if args.all:
|
|
214
|
+
return classes
|
|
215
|
+
|
|
216
|
+
max_digits = len(str(len(classes)))
|
|
217
|
+
|
|
218
|
+
name_to_class = {}
|
|
219
|
+
for idx, timeline_class in enumerate(classes, start=1):
|
|
220
|
+
name = timeline_class.__name__
|
|
221
|
+
print(f"{str(idx).zfill(max_digits)}: {name}")
|
|
222
|
+
name_to_class[name] = timeline_class
|
|
223
|
+
|
|
224
|
+
user_input = input(
|
|
225
|
+
"\nThat module has multiple timelines, "
|
|
226
|
+
"which ones would you like to render?"
|
|
227
|
+
"\nTimeline Name or Number: "
|
|
228
|
+
)
|
|
229
|
+
for split_str in user_input.replace(' ', '').split(','):
|
|
230
|
+
if not split_str:
|
|
231
|
+
continue
|
|
232
|
+
if split_str.isnumeric():
|
|
233
|
+
idx = int(split_str) - 1
|
|
234
|
+
if 0 <= idx < len(classes):
|
|
235
|
+
timelines.append(classes[idx])
|
|
236
|
+
else:
|
|
237
|
+
log.error(f'Invaild number {idx + 1}')
|
|
238
|
+
err = True
|
|
239
|
+
else:
|
|
240
|
+
try:
|
|
241
|
+
timelines.append(name_to_class[split_str])
|
|
242
|
+
except KeyError:
|
|
243
|
+
log.error(f'No timeline named {split_str}')
|
|
244
|
+
err = True
|
|
245
|
+
|
|
246
|
+
return [] if err else timelines
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def open_file(file_path: str) -> None:
|
|
250
|
+
current_os = platform.system()
|
|
251
|
+
if current_os == "Windows":
|
|
252
|
+
os.startfile(file_path)
|
|
253
|
+
else:
|
|
254
|
+
commands = []
|
|
255
|
+
if current_os == "Linux":
|
|
256
|
+
commands.append("xdg-open")
|
|
257
|
+
elif current_os.startswith("CYGWIN"):
|
|
258
|
+
commands.append("cygstart")
|
|
259
|
+
else: # Assume macOS
|
|
260
|
+
commands.append("open")
|
|
261
|
+
|
|
262
|
+
commands.append(file_path)
|
|
263
|
+
|
|
264
|
+
FNULL = open(os.devnull, 'w')
|
|
265
|
+
sp.call(commands, stdout=FNULL, stderr=sp.STDOUT)
|
|
266
|
+
FNULL.close()
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
if __name__ == '__main__':
|
|
270
|
+
main()
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextvars import ContextVar
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING, Callable
|
|
6
|
+
|
|
7
|
+
from janim.components.depth import Cmpt_Depth
|
|
8
|
+
from janim.utils.rate_functions import RateFunc, smooth
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from janim.anims.composition import AnimGroup
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class TimeRange:
|
|
16
|
+
at: float
|
|
17
|
+
duration: float
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def end(self) -> float:
|
|
21
|
+
return self.at + self.duration
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class RenderCall:
|
|
26
|
+
'''
|
|
27
|
+
绘制调用
|
|
28
|
+
|
|
29
|
+
- ``depth``: 该绘制的深度
|
|
30
|
+
- ``func``: 该绘制所调用的函数
|
|
31
|
+
|
|
32
|
+
具体机制:
|
|
33
|
+
|
|
34
|
+
- 在每个动画对象中,都会使用 :meth:`~.Animation.set_render_call_list` 来设置该动画进行绘制时所执行的函数
|
|
35
|
+
- 在进行渲染(具体参考 :meth:`~.TimelineAnim.render_all` )时,会按照深度进行排序,依次对 ``func`` 进行调用,深度越高的越先调用
|
|
36
|
+
|
|
37
|
+
例:
|
|
38
|
+
|
|
39
|
+
- 在 :class:`~.Display` 中,设置了单个 :class:`RenderCall` ,作用是绘制物件
|
|
40
|
+
- 在 :class:`~.Transform` 中,对于每个插值物件都设置了 :class:`RenderCall`,绘制所有的插值物件
|
|
41
|
+
'''
|
|
42
|
+
depth: Cmpt_Depth
|
|
43
|
+
func: Callable[[], None]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Animation:
|
|
47
|
+
'''
|
|
48
|
+
动画基类
|
|
49
|
+
|
|
50
|
+
- 创建一个从 ``at`` 持续至 ``at + duration`` 的动画
|
|
51
|
+
- 指定 ``rate_func`` 可以设定插值函数,默认为 :meth:`janim.utils.rate_functions.smooth` 即平滑插值
|
|
52
|
+
'''
|
|
53
|
+
label_color: tuple[float, float, float] = (128, 132, 137)
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
*,
|
|
58
|
+
at: float = 0,
|
|
59
|
+
duration: float = 1.0,
|
|
60
|
+
rate_func: RateFunc = smooth,
|
|
61
|
+
):
|
|
62
|
+
from janim.anims.timeline import Timeline
|
|
63
|
+
self.timeline = Timeline.get_context()
|
|
64
|
+
self.parent: AnimGroup = None
|
|
65
|
+
self.current_alpha = None
|
|
66
|
+
|
|
67
|
+
self.local_range = TimeRange(at, duration)
|
|
68
|
+
self.global_range = None
|
|
69
|
+
self.rate_func = rate_func
|
|
70
|
+
|
|
71
|
+
self.render_call_list: list[RenderCall] = []
|
|
72
|
+
|
|
73
|
+
def set_global_range(self, at: float, duration: float | None = None) -> None:
|
|
74
|
+
'''
|
|
75
|
+
设置在 :class:`~.Timeline` 上的时间范围
|
|
76
|
+
|
|
77
|
+
不需要手动设置,该方法是被 :meth:`~.AnimGroup.set_global_range` 调用以计算的
|
|
78
|
+
'''
|
|
79
|
+
if duration is None:
|
|
80
|
+
duration = self.local_range.duration
|
|
81
|
+
self.global_range = TimeRange(at, duration)
|
|
82
|
+
|
|
83
|
+
def set_render_call_list(self, lst: list[RenderCall]) -> None:
|
|
84
|
+
'''
|
|
85
|
+
设置绘制调用,具体参考 :class:`RenderCall`
|
|
86
|
+
'''
|
|
87
|
+
self.render_call_list = sorted(lst, key=lambda x: x.depth, reverse=True)
|
|
88
|
+
|
|
89
|
+
def anim_pre_init(self) -> None: '''在 :meth:`~.Timeline.detect_changes_of_all` 执行之前调用的初始化方法'''
|
|
90
|
+
|
|
91
|
+
def anim_init(self) -> None: '''在 :meth:`~.Timeline.detect_changes_of_all` 执行之前调用的初始化方法'''
|
|
92
|
+
|
|
93
|
+
def anim_on(self, local_t: float) -> None:
|
|
94
|
+
'''
|
|
95
|
+
将 ``local_t`` 换算为 ``alpha`` 并调用 :meth:`anim_on_alpha`
|
|
96
|
+
'''
|
|
97
|
+
alpha = self.rate_func(local_t / self.local_range.duration)
|
|
98
|
+
self.anim_on_alpha(alpha)
|
|
99
|
+
|
|
100
|
+
def get_alpha_on_global_t(self, global_t: float) -> float:
|
|
101
|
+
'''
|
|
102
|
+
传入全局 ``global_t``,得到物件在该时刻应当处于哪个 ``alpha`` 的插值
|
|
103
|
+
'''
|
|
104
|
+
if self.parent is None:
|
|
105
|
+
return self.rate_func((global_t - self.global_range.at) / self.global_range.duration)
|
|
106
|
+
|
|
107
|
+
anim_t = self.parent.get_anim_t(self.parent.get_alpha_on_global_t(global_t), self)
|
|
108
|
+
return self.rate_func(anim_t / self.local_range.duration)
|
|
109
|
+
|
|
110
|
+
global_t_ctx: ContextVar[float] = ContextVar('Animation.global_t_ctx')
|
|
111
|
+
'''
|
|
112
|
+
对该值进行设置,使得进行 :meth:`anim_on` 和 :meth:`render` 时不需要将 ``global_t`` 作为参数传递也能获取到
|
|
113
|
+
'''
|
|
114
|
+
|
|
115
|
+
def anim_on_alpha(self, alpha: float) -> None:
|
|
116
|
+
'''
|
|
117
|
+
动画在 ``alpha`` 处的行为
|
|
118
|
+
'''
|
|
119
|
+
pass
|