mirror of
https://github.com/ManimCommunity/manim.git
synced 2026-06-22 10:01:47 +00:00
Merge branch 'experimental' of https://github.com/ManimCommunity/manim into exp_replace_openglmob_imports
This commit is contained in:
commit
41f40657c9
38 changed files with 1452 additions and 630 deletions
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -1,10 +1,6 @@
|
|||
<!-- Thank you for contributing to Manim! Learn more about the process in our contributing guidelines: https://docs.manim.community/en/latest/contributing.html -->
|
||||
|
||||
## Overview: What does this pull request change?
|
||||
<!-- If there is more information than the PR title that should be added to our release changelog, add it in the following changelog section. This is optional, but recommended for larger pull requests. -->
|
||||
<!--changelog-start-->
|
||||
|
||||
<!--changelog-end-->
|
||||
|
||||
## Motivation and Explanation: Why and how do your changes improve the library?
|
||||
<!-- Optional for bugfixes, small enhancements, and documentation-related PRs. Otherwise, please give a short reasoning for your changes. -->
|
||||
|
|
|
|||
|
|
@ -2,15 +2,17 @@
|
|||
Changelog
|
||||
#########
|
||||
|
||||
This page contains a list of changes made between releases. Changes
|
||||
from versions that are not listed below (in particular patch-level
|
||||
releases since v0.18.0) are documented on our
|
||||
`GitHub release page <https://github.com/ManimCommunity/manim/releases/>`__.
|
||||
This page contains a list of changes made between releases.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
changelog/experimental
|
||||
changelog/0.19.2-changelog
|
||||
changelog/0.19.1-changelog
|
||||
changelog/0.19.0-changelog
|
||||
changelog/0.18.1-changelog
|
||||
changelog/0.18.0.post0-changelog
|
||||
changelog/0.18.0-changelog
|
||||
changelog/0.17.3-changelog
|
||||
changelog/0.17.2-changelog
|
||||
|
|
|
|||
9
docs/source/changelog/0.18.0.post0-changelog.rst
Normal file
9
docs/source/changelog/0.18.0.post0-changelog.rst
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
*************
|
||||
v0.18.0.post0
|
||||
*************
|
||||
|
||||
:Date: April 08, 2024
|
||||
|
||||
This release is a post-release fixing `#3676
|
||||
<https://github.com/ManimCommunity/manim/issues/3676>`_, a bug caused by a recent
|
||||
change introduced to the way how SVG files of text are generated by Pango.
|
||||
160
docs/source/changelog/0.18.1-changelog.md
Normal file
160
docs/source/changelog/0.18.1-changelog.md
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
---
|
||||
short-title: v0.18.1
|
||||
description: Changelog for Manim v0.18.1
|
||||
---
|
||||
|
||||
# v0.18.1
|
||||
|
||||
Date
|
||||
: April 28, 2024
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Breaking Changes and Deprecations
|
||||
|
||||
* Removed deprecated `manim new` command by {user}`chopan050` in {pr}`3512`
|
||||
* Removed support for dynamic plugin imports by {user}`Viicos` in {pr}`3524`
|
||||
* Remove meth:``.Mobject.wag`` by {user}`JasonGrace2282` in {pr}`3539`
|
||||
* Remove deprecated parameters and animations by {user}`JasonGrace2282` in {pr}`3688`
|
||||
|
||||
|
||||
### New Features
|
||||
|
||||
* Added `cap_style` feature to `VMobject` by {user}`MathItYT` in {pr}`3516`
|
||||
* Allow hiding version splash by {user}`jeertmans` in {pr}`3329`
|
||||
* Added the ability to pass lists and generators to `Scene.play()` by {user}`MrDiver` in {pr}`3365`
|
||||
* Added ``--preview_command`` cli flag by {user}`JasonGrace2282` in {pr}`3615`
|
||||
|
||||
### Fixed Bugs and Enhancements
|
||||
|
||||
* Allow accessing ghost vectors in :class:`.LinearTransformationScene` by {user}`JasonGrace2282` in {pr}`3435`
|
||||
* Optimized `get_unit_normal()` and replaced `np.cross()` with custom `cross()` in `manim.utils.space_ops` by {user}`chopan050` in {pr}`3494`
|
||||
* Implement caching of fonts list to improve runtime performance by {user}`MrDiver` in {pr}`3316`
|
||||
* Reformatting the `--save_sections` output to have the format `<Scene>_<SecNum>_<SecName><extension>` by {user}`doaamuham` in {pr}`3499`
|
||||
* Account for dtype in the pixel array so the maximum value stays correct in the invert function by {user}`jeertmans` in {pr}`3493`
|
||||
* Added `grid_lines` attribute to `Rectangle` to add individual styling to the grid lines by {user}`RobinPH` in {pr}`3428`
|
||||
* Fixed rectangle grid properties (#3082) by {user}`pauluhlenbruck` in {pr}`3513`
|
||||
* Fixed animations with zero runtime length to give a useful error instead of a broken pipe by {user}`MrDiver` in {pr}`3491`
|
||||
* Fixed stroke width being ignored by `StreamLines` with a single color by {user}`yashm277` in {pr}`3436`
|
||||
* Fixed formatting of ``MoveAlongPath`` docs by {user}`JasonGrace2282` in {pr}`3541`
|
||||
* Added helpful hints to `VGroup.add()` error message by {user}`vvolhejn` in {pr}`3561`
|
||||
* Made `earclip_triangulation` more robust by {user}`hydromelvictor` in {pr}`3574`
|
||||
* Refactored `TexTemplate` by {user}`Viicos` in {pr}`3520`
|
||||
* Fixed `write_subcaption_file` error when using OpenGL renderer by {user}`yuan-xy` in {pr}`3546`
|
||||
* Fixed `get_arc_center()` returning reference of point by {user}`sparshg` in {pr}`3599`
|
||||
* Improved handling of specified font name by {user}`staghado` in {pr}`3429`
|
||||
* Fixing the behavior of `.become` to not modify target mobject via side effects fix color linking by {user}`MrDiver` in {pr}`3508`
|
||||
* Fixed bug in :class:`.VMobjectFromSVGPath` by {user}`abul4fia` in {pr}`3677`
|
||||
* Fix for windows cp1252 encoding failure (fix test pipeline) by {user}`JasonGrace2282` in {pr}`3687`
|
||||
* Fix NameError in try... except by {user}`JasonGrace2282` in {pr}`3694`
|
||||
* Fix successive calls of :meth:`.LinearTransformationScene.apply_matrix` by {user}`SirJamesClarkMaxwell` in {pr}`3675`
|
||||
* Fixed `Mobject.put_start_and_end_on` with same start and end point by {user}`MontroyJosh` in {pr}`3718`
|
||||
* Fixed issue where `SpiralIn` doesn't show elements by {user}`Gixtox` in {pr}`3589`
|
||||
* Cleaned `Graph` layouts and increase flexibility by {user}`Nikhil-42` in {pr}`3434`
|
||||
* `AnimationGroup`: optimized `interpolate()` and fixed alpha bug on `finish()` by {user}`chopan050` in {pr}`3542`
|
||||
* Fixed warning about missing plugin `""` by {user}`behackl` in {pr}`3734`
|
||||
|
||||
### Documentation
|
||||
|
||||
* Typo in `indication` documentation by {user}`jcep` in {pr}`3477`
|
||||
* Fixed typo: 360° to 180° in quickstart tutorial by {user}`szchixy` in {pr}`3498`
|
||||
* Fixed typo in mobject docstring: `line` -> `square` by {user}`yuan-xy` in {pr}`3509`
|
||||
* Explain ``.Transform`` vs ``.ReplacementTransform`` in quickstart examples by {user}`JasonGrace2282` in {pr}`3500`
|
||||
* Fixed formatting in building blocks tutorial by {user}`MrDiver` in {pr}`3515`
|
||||
* Fixed `Indicate` docstring typo by {user}`Lawqup` in {pr}`3461`
|
||||
* Added Documentation to `.to_edge` and `to_corner` by {user}`TheMathematicFanatic` in {pr}`3408`
|
||||
* Added some words about Cairo 1.18 by {user}`jeertmans` in {pr}`3530`
|
||||
* Fixed typo of `get_y_axis_label` parameter documentation by {user}`yuan-xy` in {pr}`3547`
|
||||
* Added note in docstring of `ManimColor` about class constructors by {user}`JasonGrace2282` in {pr}`3554`
|
||||
* Improve documentation section about contributing to docs by {user}`chopan050` in {pr}`3555`
|
||||
* Removed duplicated documentation for -s / --save_last_frame CLI flag by {user}`Gixtox` in {pr}`3528`
|
||||
* Updated Docker instructions to use bash from the PATH by {user}`NotWearingPants` in {pr}`3582`
|
||||
* Fixed typo in `value_tracker.py` by {user}`yuan-xy` in {pr}`3594`
|
||||
* Added `ref_class` for `BooleanOperations` in Example Gallery by {user}`JasonGrace2282` in {pr}`3598`
|
||||
* Changed `Vector3` to `Vector3D` in contributing docs by {user}`JasonGrace2282` in {pr}`3639`
|
||||
* Added some examples for `Mobject`/`VMobject` methods by {user}`JasonGrace2282` in {pr}`3641`
|
||||
* Fixed broken link to Poetry's installation guide in the documentation by {user}`biinnnggggg` in {pr}`3692`
|
||||
* Fixed minor grammatical errors found in the index page of the documentation by {user}`biinnnggggg` in {pr}`3690`
|
||||
* Fixed typo on page about translations by {user}`biinnnggggg` in {pr}`3696`
|
||||
* Fixed outdated description of CLI option in Manim's Output Settings by {user}`HairlessVillager` in {pr}`3674`
|
||||
* Mention pixi in installation guide by {user}`pavelzw` in {pr}`3678`
|
||||
* Updated typing guidelines by {user}`JasonGrace2282` in {pr}`3704`
|
||||
* Updated documentation and typings for `ParametricFunction` by {user}`danielzsh` in {pr}`3703`
|
||||
* Fixed docstring markup in `Rotate` by {user}`TheCrowned` in {pr}`3721`
|
||||
* Improve consistency in axis label example by {user}`amrear` in {pr}`3730`
|
||||
|
||||
### Maintenance and Testing
|
||||
|
||||
* Fixed wrong path in action building downloadable docs by {user}`behackl` in {pr}`3450`
|
||||
* Add type hints to `_config` by {user}`Viicos` in {pr}`3440`
|
||||
* Update dependency constraints, fix deprecation warnings by {user}`Viicos` in {pr}`3376`
|
||||
* Update Docker base image to python3.12-slim (#3458) by {user}`PikaBlue107` in {pr}`3459`
|
||||
* Fixed `line_join` to `joint_type` in example_scenes/basic.py by {user}`szchixy` in {pr}`3510`
|
||||
* Fixed :attr:`.Mobject.animate` type-hint to allow LSP autocomplete by {user}`JasonGrace2282` in {pr}`3543`
|
||||
* Finish TODO's in ``contributing/typings.rst`` by {user}`JasonGrace2282` in {pr}`3545`
|
||||
* Fixed use of `Mobject`'s deprecated `get_*()` and `set_*()` methods in Cairo tests by {user}`JasonGrace2282` in {pr}`3549`
|
||||
* Added support for Manim type aliases in Sphinx docs and added new TypeAliases by {user}`chopan050` in {pr}`3484`
|
||||
* Fixed typing of `Animation` by {user}`dandavison` in {pr}`3568`
|
||||
* Added some TODOs for future use of `ManimFrame` by {user}`chopan050` in {pr}`3553`
|
||||
* Fixed typehint of :attr:`InternalPoint2D_Array` by {user}`JasonGrace2282` in {pr}`3592`
|
||||
* Fixed error in Windows CI pipeline by {user}`behackl` in {pr}`3611`
|
||||
* Fixed type hint of indication.py by {user}`yuan-xy` in {pr}`3613`
|
||||
* Revert vector type aliases to NumPy ndarrays by {user}`chopan050` in {pr}`3595`
|
||||
* Run `poetry lock --no-update` by {user}`JasonGrace2282` in {pr}`3621`
|
||||
* Code Cleanup: removing unused imports and global variables by {user}`JasonGrace2282` in {pr}`3620`
|
||||
* Fixed type hint of `Vector` direction parameter by {user}`JasonGrace2282` in {pr}`3640`
|
||||
* Flake8 rule C901 is about McCabe code complexity by {user}`cclauss` in {pr}`3673`
|
||||
* Updated year in license by {user}`JasonGrace2282` in {pr}`3689`
|
||||
* Automated copyright updating for docs by {user}`JasonGrace2282` in {pr}`3708`
|
||||
* Fixed some typehints in `mobject.py` by {user}`JasonGrace2282` in {pr}`3668`
|
||||
* Search for type aliases if TYPE_CHECKING by {user}`JasonGrace2282` in {pr}`3671`
|
||||
* Follow-up to graph layout cleanup: improvements for tests and typing by {user}`behackl` in {pr}`3728`
|
||||
* GH Actions: Changed from macos-latest to macos-13 by {user}`JasonGrace2282` in {pr}`3729`
|
||||
* Fixed return type inconsistency for `get_anchors()` by {user}`JinchuLi2002` in {pr}`3214`
|
||||
* Prepared new release: `v0.18.1` by {user}`behackl` in {pr}`3719`
|
||||
|
||||
#### Dependency Version Changes
|
||||
|
||||
* Bump jupyter-server from 2.9.1 to 2.11.2 by {user}`dependabot` in {pr}`3497`
|
||||
* Bump github/codeql-action from 2 to 3 by {user}`dependabot` in {pr}`3567`
|
||||
* Bump actions/upload-artifact from 3 to 4 by {user}`dependabot` in {pr}`3566`
|
||||
* Bump actions/setup-python from 4 to 5 by {user}`dependabot` in {pr}`3565`
|
||||
* updated several packages (pillow, jupyterlab, notebook, jupyterlab-lsp, jinja2, gitpython) by {user}`behackl` in {pr}`3593`
|
||||
* Update jupyter.rst by {user}`abul4fia` in {pr}`3630`
|
||||
* Bump black from 23.12.1 to 24.3.0 by {user}`dependabot` in {pr}`3649`
|
||||
* Bump cryptography from 42.0.0 to 42.0.4 by {user}`dependabot` in {pr}`3629`
|
||||
* Bump actions/cache from 3 to 4 by {user}`dependabot` in {pr}`3607`
|
||||
* Bump FedericoCarboni/setup-ffmpeg from 2 to 3 by {user}`dependabot` in {pr}`3608`
|
||||
* Bump ssciwr/setup-mesa-dist-win from 1 to 2 by {user}`dependabot` in {pr}`3609`
|
||||
* Bump idna from 3.6 to 3.7 by {user}`dependabot` in {pr}`3693`
|
||||
* Bump pillow from 10.2.0 to 10.3.0 by {user}`dependabot` in {pr}`3672`
|
||||
* [pre-commit.ci] pre-commit autoupdate by {user}`pre-commit-ci` in {pr}`3332`
|
||||
* Updated sphinx deps by {user}`JasonGrace2282` in {pr}`3720`
|
||||
|
||||
|
||||
## New Contributors
|
||||
* {user}`Lawqup` made their first contribution in {pr}`3461`
|
||||
* {user}`jcep` made their first contribution in {pr}`3477`
|
||||
* {user}`szchixy` made their first contribution in {pr}`3498`
|
||||
* {user}`PikaBlue107` made their first contribution in {pr}`3459`
|
||||
* {user}`yuan-xy` made their first contribution in {pr}`3509`
|
||||
* {user}`MathItYT` made their first contribution in {pr}`3516`
|
||||
* {user}`doaamuham` made their first contribution in {pr}`3499`
|
||||
* {user}`RobinPH` made their first contribution in {pr}`3428`
|
||||
* {user}`pauluhlenbruck` made their first contribution in {pr}`3513`
|
||||
* {user}`yashm277` made their first contribution in {pr}`3436`
|
||||
* {user}`TheMathematicFanatic` made their first contribution in {pr}`3408`
|
||||
* {user}`vvolhejn` made their first contribution in {pr}`3561`
|
||||
* {user}`hydromelvictor` made their first contribution in {pr}`3574`
|
||||
* {user}`dandavison` made their first contribution in {pr}`3568`
|
||||
* {user}`Gixtox` made their first contribution in {pr}`3528`
|
||||
* {user}`staghado` made their first contribution in {pr}`3429`
|
||||
* {user}`biinnnggggg` made their first contribution in {pr}`3692`
|
||||
* {user}`HairlessVillager` made their first contribution in {pr}`3674`
|
||||
* {user}`SirJamesClarkMaxwell` made their first contribution in {pr}`3675`
|
||||
* {user}`danielzsh` made their first contribution in {pr}`3703`
|
||||
* {user}`TheCrowned` made their first contribution in {pr}`3721`
|
||||
* {user}`MontroyJosh` made their first contribution in {pr}`3718`
|
||||
* {user}`amrear` made their first contribution in {pr}`3730`
|
||||
|
||||
**Full Changelog**: https://github.com/ManimCommunity/manim/compare/v0.18.0.post0...v0.18.1
|
||||
197
docs/source/changelog/0.19.1-changelog.md
Normal file
197
docs/source/changelog/0.19.1-changelog.md
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
---
|
||||
short-title: v0.19.1
|
||||
description: Changelog for Manim v0.19.1
|
||||
---
|
||||
|
||||
# v0.19.1
|
||||
|
||||
Date
|
||||
: December 01, 2025
|
||||
|
||||
## What's Changed
|
||||
|
||||
### New Features
|
||||
|
||||
* Introduce seed in `random_color` method to produce colors deterministically by {user}`ishu9bansal` in {pr}`4265`
|
||||
* Add support for arithmetic operators `//`, `%`, `*`, `**` and `/` on `ValueTracker` by {user}`fmuenkel` in {pr}`4351`
|
||||
* Add `TangentialArc` mobject by {user}`Brainsucker92` in {pr}`4469`
|
||||
|
||||
|
||||
### Fixed Bugs and Enhancements
|
||||
|
||||
* Fix environment formatting for Tex() mobject by {user}`fmuenkel` in {pr}`4159`
|
||||
* Improved consistency of rate_function implementations by {user}`BenKirkels` in {pr}`4144`
|
||||
* Make new `Code` mobject compatible with OpenGL renderer by {user}`behackl` in {pr}`4164`
|
||||
* Fix HSL color ordering in ManimColor by {user}`thehugwizard` in {pr}`4202`
|
||||
* Fix return type of `Polygram.get_vertex_groups()` and rename variables in `.round_corners()` by {user}`chopan050` in {pr}`4063`
|
||||
* Improve `Mobject.align_data` docstring by {user}`irvanalhaq9` in {pr}`4152`
|
||||
* Fix :meth:`VMobject.pointwise_become_partial` failing when `vmobject` is `self` by {user}`irvanalhaq9` in {pr}`4193`
|
||||
* Fix `add_points_as_corners` not connecting single point to existing path by {user}`irvanalhaq9` in {pr}`4219`
|
||||
* Complete typing for logger_utils.py by {user}`fmuenkel` in {pr}`4134`
|
||||
* Fix(graph): Allow any Line subclass as edge_type in Graph/DiGraph by {user}`Akshat-Mishra-py` in {pr}`4251`
|
||||
* Replace exceptions, remove unused parameters, and fix type hints in `Animation`, `ShowPartial`, `Create`, `ShowPassingFlash`, and `DrawBorderThenFill` by {user}`irvanalhaq9` in {pr}`4214`
|
||||
* Fix: `Axes` submobject colors are not being set properly by {user}`ishu9bansal` in {pr}`4291`
|
||||
* Refactor `Rotating` and add docstrings to `Mobject.rotate()` and `Rotating` by {user}`irvanalhaq9` in {pr}`4147`
|
||||
* Fix default config of `manim init project` to use correct `pixel_height` and `pixel_width` by {user}`StevenH34` in {pr}`4213`
|
||||
* Handle opacity and transparent images by {user}`henrikmidtiby` in {pr}`4313`
|
||||
* Gracefully fall back when version metadata is missing by {user}`mohiuddin-khan-shiam` in {pr}`4324`
|
||||
* Fix for issue 4255 - Do not clear points when the number of curves is zero by {user}`henrikmidtiby` in {pr}`4320`
|
||||
* Use utf-8 encoding to read generated .tex files. by {user}`OliverStrait` in {pr}`4334`
|
||||
* Add zero to vmobject points to remove negative zeros in `get_mobject_key` by {user}`elshorbagyx` in {pr}`4332`
|
||||
* Ensure `stroke_width` attribute of `SVGMobject` is not set to `None` by {user}`henrikmidtiby` in {pr}`4319`
|
||||
* Fix `Prism` incorrectly rendering with `dimensions=[2, 2, 2]` in OpenGL by {user}`ra1u` in {pr}`4003`
|
||||
* Fix `BraceLabel.change_label()` and document `BraceText` by {user}`henrikmidtiby` in {pr}`4347`
|
||||
* Include `Text.gradient` in hash to properly regenerate `Text` when its gradient changes by {user}`AbhilashaTandon` in {pr}`4099`
|
||||
* Fixed surface animations in OpenGL by {user}`nubDotDev` in {pr}`4286`
|
||||
* Add type hints and support for arithmetic operators `+` and `-` on `ValueTracker` by {user}`fmuenkel` in {pr}`4129`
|
||||
* Fix duplicate references in `Scene.mobjects` after `ReplacementTransform` with existing target mobject by {user}`irvanalhaq9` in {pr}`4242`
|
||||
* Optimize `always_redraw()` by reducing `Mobject` copying in `Mobject.become()` by {user}`chopan050` in {pr}`4357`
|
||||
* Enhance `manim cfg show` output and add info-level logging for config files read by {user}`xnov18` in {pr}`4375`
|
||||
* Let `Cube` use Bevel type line joints by {user}`nubDotDev` in {pr}`4361`
|
||||
* Properly define `init_points` methods for use in OpenGL instead of defining `init_points = generate_points` by {user}`chopan050` in {pr}`4360`
|
||||
* Allow passing a tuple to `buff` in `SurroundingRectangle` to specify buffer in x and y direction independently by {user}`nubDotDev` in {pr}`4390`
|
||||
* Rewrite `color_gradient` to always return a list of ManimColors by {user}`henrikmidtiby` in {pr}`4380`
|
||||
* Ensure leading whitespace does not change line height for lines in CodeMobject by {user}`behackl` in {pr}`4392`
|
||||
* Simplify the function `remove_invisible_chars` in `text_mobject.py` by {user}`henrikmidtiby` in {pr}`4394`
|
||||
* Fix some config options specified via `--config_file` not being respected properly by {user}`behackl` in {pr}`4401`
|
||||
* Fix: Correct resolution tuple order to (height, width) by {user}`Nikhil172913832` in {pr}`4440`
|
||||
* Ensure that start and end points are stored as float values in Line3D by {user}`SirJamesClarkMaxwell` in {pr}`4080`
|
||||
* OpenGL: Fix iterated nesting in `DecimalNumber.set_value` by {user}`henrikmidtiby` in {pr}`4373`
|
||||
* Update default resolution in CLI to match Manim’s 1920x1080 default settings by {user}`SASHAKT1290` in {pr}`4452`
|
||||
* Better parsing of color styles in CodeMobject by {user}`SirJamesClarkMaxwell` in {pr}`4454`
|
||||
* Allow selection of all scenes to render using '*' by {user}`NightyStudios` in {pr}`4470`
|
||||
* Prevent mutation of `about_point` in `apply_points_function_about_point` by {user}`Morkunas` in {pr}`4478`
|
||||
* Fix behavior of `Mobject.suspend_updating`: when only suspending parent mobject, let children continue updating by {user}`behackl` in {pr}`4402`
|
||||
* Allow passing a `buff` to `LabeledDot` by {user}`nubDotDev` in {pr}`4403`
|
||||
* Pass ndarrays to `mapbox_earcut.triangulate_float32()` to fix `TypeError` in `mapbox_earcut==2.0.0` by {user}`GuiCT` in {pr}`4479`
|
||||
* Fix duplicated arrow tips in DashedVMobject (issue #3220) by {user}`jakekinchen` in {pr}`4484`
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* Add docstring to :meth:`.Mobject.get_family` by {user}`irvanalhaq9` in {pr}`4127`
|
||||
* Fix link formatting and clarify the distinction between Manim versions in index.rst by {user}`irvanalhaq9` in {pr}`4131`
|
||||
* Add instructions for installing system utilities `cairo` and `pkg-config` via Homebrew on MacOS by {user}`behackl` in {pr}`4146`
|
||||
* Add missing line break in Code of Conduct's conflict of interest policy by {user}`Hasan-Mesbaul-Ali-Taher` in {pr}`4185`
|
||||
* Fix links to Pango website by {user}`ragibson` in {pr}`4217`
|
||||
* Replace poetry with uv in the README by {user}`xinoehp512` in {pr}`4226`
|
||||
* Improve docstring for `interpolate` method in `Mobject` class by {user}`irvanalhaq9` in {pr}`4149`
|
||||
* Add docstrings to `Line` and remove `None` handling for `path_arc` parameter by {user}`irvanalhaq9` in {pr}`4223`
|
||||
* Add docstring to :meth:`Mobject.family_members_with_points` by {user}`irvanalhaq9` in {pr}`4128`
|
||||
* Update incorrect docstring for :attr:`ManimConfig.gui_location` property by {user}`SAYAN02-DEV` in {pr}`4254`
|
||||
* Fix formatting of color space documentation by {user}`behackl` in {pr}`4274`
|
||||
* Enhance and Paraphrase Description of ManimCE in README.md by {user}`irvanalhaq9` in {pr}`4141`
|
||||
* docs: add explanation about the rate_func in the custom animation by {user}`pedropxoto` in {pr}`4278`
|
||||
* Fixed artifact in docstring of Animation by {user}`barollet` in {pr}`4283`
|
||||
* Rename update function `dot_position` to `update_label` in `.add_updater` example by {user}`irvanalhaq9` in {pr}`4196`
|
||||
* Fix Microsoft typo in `TexFontTemplateLibrary` scene in `example_scenes/advanced_tex_fonts.py` by {user}`alterdim` in {pr}`4305`
|
||||
* Improved readability, grammar, as well as added docstrings for consistency by {user}`NASAnerd05` in {pr}`4267`
|
||||
* Add docstrings for `ChangingDecimal` and `ChangeDecimalToValue` by {user}`haveheartt` in {pr}`4346`
|
||||
* Fix Sphinx exceptions when trying to build documentation via latex / as pdf by {user}`behackl` in {pr}`4370`
|
||||
* Added license information to documentation landing page by {user}`Nikil-D-Gr8` in {pr}`3986`
|
||||
* Set the default Python version to 3.13 in the uv installation guide by {user}`henrikmidtiby` in {pr}`4480`
|
||||
|
||||
|
||||
### Maintenance and Testing
|
||||
|
||||
* Change project management tool from poetry to uv by {user}`behackl` in {pr}`4138`
|
||||
* Re-add ffmpeg as dependency within Docker image by {user}`behackl` in {pr}`4150`
|
||||
* Add tests for Matrix, DecimalMatrix, IntegerMatrix by {user}`pdrzan` in {pr}`4279`
|
||||
* Add tests for polylabel utility by {user}`giolucasd` in {pr}`4269`
|
||||
* Add support for `pycodestyle W` rule in Ruff by {user}`KaiqueDultra` in {pr}`4276`
|
||||
* Fix files with few MyPy typing errors by {user}`henrikmidtiby` in {pr}`4263`
|
||||
* Explicitly mention all files that mypy should ignore in the `mypy.ini` configuration file by {user}`henrikmidtiby` in {pr}`4306`
|
||||
* Remove dead code from `scene.py` and `vector_space_scene.py` by {user}`henrikmidtiby` in {pr}`4310`
|
||||
* Add type annotations to `scene.py` and `vector_space_scene.py` by {user}`henrikmidtiby` in {pr}`4260`
|
||||
* Replace setup-texlive-action in CI workflow by {user}`behackl` in {pr}`4326`
|
||||
* Adding type annotations to polyhedra.py and matrix.py by {user}`henrikmidtiby` in {pr}`4322`
|
||||
* Handling typing errors in text/numbers.py by {user}`henrikmidtiby` in {pr}`4317`
|
||||
* Move `configure_pygui` into a `Scene` method and remove `manim.gui` by {user}`chopan050` in {pr}`4314`
|
||||
* Add typing annotations to svg_mobject.py by {user}`henrikmidtiby` in {pr}`4318`
|
||||
* Add type annotations to `mobject/svg/brace.py` and default to `label_constructor=Text` in `BraceText` by {user}`henrikmidtiby` in {pr}`4309`
|
||||
* Add classes `MethodWithArgs`, `SceneInteractContinue` and `SceneInteractRerun` inside new module `manim.data_structures` by {user}`chopan050` in {pr}`4315`
|
||||
* Fix typo in import of OpenGLCamera in `utils/hashing.py` by {user}`fmuenkel` in {pr}`4352`
|
||||
* Add type annotations to `manim/renderer/shader.py` by {user}`henrikmidtiby` in {pr}`4350`
|
||||
* Add type annotations to `tex_mobject.py` by {user}`henrikmidtiby` in {pr}`4355`
|
||||
* Add type annotations to `three_d_camera.py` by {user}`henrikmidtiby` in {pr}`4356`
|
||||
* Revert change of default value for tex_environment by {user}`henrikmidtiby` in {pr}`4358`
|
||||
* Add type hints to `scene_file_writer.py`, `section.py`, and `zoomed_scene.py` by {user}`fmuenkel` in {pr}`4133`
|
||||
* Add type annotations for most of `camera` and `mobject.graphing` by {user}`henrikmidtiby` in {pr}`4125`
|
||||
* Add `VectorNDLike` type aliases by {user}`chopan050` in {pr}`4068`
|
||||
* Add type annotations to `dot_cloud.py`, `vectorized_mobject_rendering.py` and `opengl_three_dimensions.py` by {user}`henrikmidtiby` in {pr}`4359`
|
||||
* Add type annotations to `indication.py` by {user}`henrikmidtiby` in {pr}`4367`
|
||||
* Add type annotations to `composition.py` by {user}`henrikmidtiby` in {pr}`4366`
|
||||
* Add type annotations to `growing.py` by {user}`henrikmidtiby` in {pr}`4368`
|
||||
* Add type annotations to `movement.py` by {user}`henrikmidtiby` in {pr}`4371`
|
||||
* Exclude check for cyclic imports by CodeQL by {user}`behackl` in {pr}`4384`
|
||||
* Refactor imports from `collections.abc`, `typing` and `typing_extensions` for Python 3.9 by {user}`chopan050` in {pr}`4353`
|
||||
* Add type annotations to `opengl_renderer_window.py` by {user}`fmuenkel` in {pr}`4363`
|
||||
* Rename `SceneFileWriter.save_final_image()` to `save_image()` by {user}`fmuenkel` in {pr}`4378`
|
||||
* Add type annotations to `text_mobject.py` by {user}`henrikmidtiby` in {pr}`4381`
|
||||
* Rename types like `RGBA_Array_Float` to `FloatRGBA` and add types like `FloatRGBA_Array` by {user}`chopan050` in {pr}`4386`
|
||||
* Add type annotations to `opengl_geometry.py` by {user}`henrikmidtiby` in {pr}`4396`
|
||||
* Add type annotations to `moving_camera.py` by {user}`henrikmidtiby` in {pr}`4397`
|
||||
* Add type annotations to `opengl_mobject.py` by {user}`RBerga06` in {pr}`4398`
|
||||
* Fix failing pre-commit tests by {user}`cclauss` in {pr}`4434`
|
||||
* Add type annotations to `cairo_renderer.py` by {user}`fmuenkel` in {pr}`4393`
|
||||
* Fix type errors and add typings for `Mobject.apply_function()`, its derivatives, and other utility functions by {user}`godalming123` in {pr}`4228`
|
||||
* Bump macOS image from deprecated macos-13 to macos-15-intel by {user}`chopan050` in {pr}`4481`
|
||||
* Prepare new release `v0.19.1` and bump minimum required Python version to 3.10 by {user}`behackl` in {pr}`4490`
|
||||
|
||||
|
||||
### Dependency Version Changes
|
||||
|
||||
* Bump typing extensions minimum version by {user}`JasonGrace2282` in {pr}`4121`
|
||||
* [pre-commit.ci] pre-commit autoupdate by {user}`pre-commit-ci`[bot] in {pr}`4122`
|
||||
* [pre-commit.ci] pre-commit autoupdate by {user}`pre-commit-ci`[bot] in {pr}`4140`
|
||||
* [pre-commit.ci] pre-commit autoupdate by {user}`pre-commit-ci`[bot] in {pr}`4148`
|
||||
* [pre-commit.ci] pre-commit autoupdate by {user}`pre-commit-ci`[bot] in {pr}`4181`
|
||||
* Bump astral-sh/setup-uv from 5 to 6 by {user}`dependabot`[bot] in {pr}`4234`
|
||||
* [pre-commit.ci] pre-commit autoupdate by {user}`pre-commit-ci`[bot] in {pr}`4204`
|
||||
* [pre-commit.ci] pre-commit autoupdate by {user}`pre-commit-ci`[bot] in {pr}`4391`
|
||||
* [pre-commit.ci] pre-commit autoupdate by {user}`pre-commit-ci`[bot] in {pr}`4405`
|
||||
* Bump actions/setup-python from 5 to 6 by {user}`dependabot`[bot] in {pr}`4433`
|
||||
* Bump actions/checkout from 4 to 5 by {user}`dependabot`[bot] in {pr}`4418`
|
||||
* [pre-commit.ci] pre-commit autoupdate by {user}`pre-commit-ci`[bot] in {pr}`4409`
|
||||
* [pre-commit.ci] pre-commit autoupdate by {user}`pre-commit-ci`[bot] in {pr}`4460`
|
||||
* [pre-commit.ci] pre-commit autoupdate by {user}`pre-commit-ci`[bot] in {pr}`4467`
|
||||
* Bump github/codeql-action from 3 to 4 by {user}`dependabot`[bot] in {pr}`4466`
|
||||
* Bump astral-sh/setup-uv from 6 to 7 by {user}`dependabot`[bot] in {pr}`4465`
|
||||
* Bump actions/upload-artifact from 4 to 5 by {user}`dependabot`[bot] in {pr}`4464`
|
||||
|
||||
|
||||
## New Contributors
|
||||
* {user}`BenKirkels` made their first contribution in {pr}`4144`
|
||||
* {user}`Hasan-Mesbaul-Ali-Taher` made their first contribution in {pr}`4185`
|
||||
* {user}`ragibson` made their first contribution in {pr}`4217`
|
||||
* {user}`thehugwizard` made their first contribution in {pr}`4202`
|
||||
* {user}`xinoehp512` made their first contribution in {pr}`4226`
|
||||
* {user}`SAYAN02-DEV` made their first contribution in {pr}`4254`
|
||||
* {user}`Akshat-Mishra-py` made their first contribution in {pr}`4251`
|
||||
* {user}`pdrzan` made their first contribution in {pr}`4279`
|
||||
* {user}`pedropxoto` made their first contribution in {pr}`4278`
|
||||
* {user}`giolucasd` made their first contribution in {pr}`4269`
|
||||
* {user}`KaiqueDultra` made their first contribution in {pr}`4276`
|
||||
* {user}`ishu9bansal` made their first contribution in {pr}`4291`
|
||||
* {user}`StevenH34` made their first contribution in {pr}`4213`
|
||||
* {user}`alterdim` made their first contribution in {pr}`4305`
|
||||
* {user}`mohiuddin-khan-shiam` made their first contribution in {pr}`4324`
|
||||
* {user}`elshorbagyx` made their first contribution in {pr}`4332`
|
||||
* {user}`NASAnerd05` made their first contribution in {pr}`4267`
|
||||
* {user}`ra1u` made their first contribution in {pr}`4003`
|
||||
* {user}`AbhilashaTandon` made their first contribution in {pr}`4099`
|
||||
* {user}`nubDotDev` made their first contribution in {pr}`4286`
|
||||
* {user}`haveheartt` made their first contribution in {pr}`4346`
|
||||
* {user}`xnov18` made their first contribution in {pr}`4375`
|
||||
* {user}`Nikil-D-Gr8` made their first contribution in {pr}`3986`
|
||||
* {user}`RBerga06` made their first contribution in {pr}`4398`
|
||||
* {user}`Nikhil172913832` made their first contribution in {pr}`4440`
|
||||
* {user}`SASHAKT1290` made their first contribution in {pr}`4452`
|
||||
* {user}`Brainsucker92` made their first contribution in {pr}`4469`
|
||||
* {user}`NightyStudios` made their first contribution in {pr}`4470`
|
||||
* {user}`Morkunas` made their first contribution in {pr}`4478`
|
||||
* {user}`GuiCT` made their first contribution in {pr}`4479`
|
||||
* {user}`godalming123` made their first contribution in {pr}`4228`
|
||||
* {user}`jakekinchen` made their first contribution in {pr}`4484`
|
||||
|
||||
**Full Changelog**: https://github.com/ManimCommunity/manim/compare/v0.19.0...v0.19.1
|
||||
41
docs/source/changelog/0.19.2-changelog.md
Normal file
41
docs/source/changelog/0.19.2-changelog.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
short-title: v0.19.2
|
||||
description: Changelog for Manim v0.19.2
|
||||
---
|
||||
|
||||
# v0.19.2
|
||||
|
||||
Date
|
||||
: January 17, 2026
|
||||
|
||||
|
||||
## What's Changed
|
||||
### Highlights 🌟
|
||||
* Add support for Python 3.14, bump minimum Python to 3.11 and av to 14.0.1 by {user}`behackl` in {pr}`4385`
|
||||
|
||||
### Bug Fixes 🐛
|
||||
* Fix argument passed to `get_hash_from_play_call` in hashing by {user}`judenimo` in {pr}`4524`
|
||||
* Fix incorrect `Circle.point_at_angle` calculation by {user}`Swarnlataaa` in {pr}`4438`
|
||||
|
||||
### Testing 🧪
|
||||
* Test on Apple Silicon ARM64 by {user}`cclauss` in {pr}`4496`
|
||||
|
||||
### Code Quality & Refactoring 🧹
|
||||
* Add ruff rules PERF for performance by {user}`cclauss` in {pr}`4492`
|
||||
* Remove deprecation warning from pytest "np.trapz" -> "np.trapezoid" by {user}`henrikmidtiby` in {pr}`4513`
|
||||
* Bump Python target versions of both mypy and ruff by {user}`behackl` in {pr}`4520`
|
||||
* Replace legacy numpy usage -- ruff rule NPY002 by {user}`cclauss` in {pr}`4516`
|
||||
* Add `.github/release.yml` for improved classifications in automatically generated changelogs by {user}`behackl` in {pr}`4526`
|
||||
* Check and bump lower version requirements for dependencies by {user}`henrikmidtiby` in {pr}`4529`
|
||||
|
||||
### Type Hints 📝
|
||||
* Add type annotations to `three_dimensions.py` by {user}`henrikmidtiby` in {pr}`4497`
|
||||
|
||||
### Other Changes
|
||||
* Prepare new release `v0.19.2` by {user}`behackl` in {pr}`4528`
|
||||
|
||||
## New Contributors
|
||||
* {user}`judenimo` made their first contribution in {pr}`4524`
|
||||
* {user}`Swarnlataaa` made their first contribution in {pr}`4438`
|
||||
|
||||
**Full Changelog**: https://github.com/ManimCommunity/manim/compare/v0.19.1...v0.19.2
|
||||
|
|
@ -58,7 +58,7 @@ extensions = [
|
|||
# Automatically generate stub pages when using the .. autosummary directive
|
||||
autosummary_generate = True
|
||||
|
||||
myst_enable_extensions = ["colon_fence", "amsmath"]
|
||||
myst_enable_extensions = ["colon_fence", "amsmath", "deflist"]
|
||||
|
||||
# redirects (for moved / deleted pages)
|
||||
redirects = {
|
||||
|
|
@ -161,7 +161,8 @@ latex_engine = "lualatex"
|
|||
# external links
|
||||
extlinks = {
|
||||
"issue": ("https://github.com/ManimCommunity/manim/issues/%s", "#%s"),
|
||||
"pr": ("https://github.com/ManimCommunity/manim/pull/%s", "#%s"),
|
||||
"pr": ("https://github.com/ManimCommunity/manim/pull/%s", "PR #%s"),
|
||||
"user": ("https://github.com/%s", "@%s"),
|
||||
}
|
||||
|
||||
# opengraph settings
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ or specific OpenGL classes like `OpenGLSurface`, but documentation for some of
|
|||
them is available in form of docstrings
|
||||
[in the source code](https://github.com/ManimCommunity/manim/tree/main/manim/mobject/opengl).
|
||||
|
||||
Furthermore, [this user guide by *aquabeam*](https://www.aquabeam.me/manim/opengl_guide/)
|
||||
Furthermore, [this user guide by *aquabeam*](https://web.archive.org/web/20250708135737/https://www.aquabeam.me/manim/opengl_guide/)
|
||||
can be helpful to get started using the OpenGL renderer.
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -298,6 +298,7 @@ class ManimConfig(MutableMapping):
|
|||
"save_sections",
|
||||
"save_last_frame",
|
||||
"scene_names",
|
||||
"seed",
|
||||
"show_in_file_browser",
|
||||
"tex_dir",
|
||||
"tex_template",
|
||||
|
|
@ -615,6 +616,7 @@ class ManimConfig(MutableMapping):
|
|||
# the next two must be set BEFORE digesting frame_width and frame_height
|
||||
"pixel_height",
|
||||
"pixel_width",
|
||||
"seed",
|
||||
"window_monitor",
|
||||
"zero_pad",
|
||||
]:
|
||||
|
|
@ -779,6 +781,7 @@ class ManimConfig(MutableMapping):
|
|||
"dry_run",
|
||||
"no_latex_cleanup",
|
||||
"preview_command",
|
||||
"seed",
|
||||
]:
|
||||
if hasattr(args, key):
|
||||
attr = getattr(args, key)
|
||||
|
|
@ -1789,6 +1792,17 @@ class ManimConfig(MutableMapping):
|
|||
def plugins(self, value: list[str]):
|
||||
self._d["plugins"] = value
|
||||
|
||||
@property
|
||||
def seed(self) -> int | None:
|
||||
"""Random seed for reproducibility. None means no seed is set."""
|
||||
return self._d["seed"]
|
||||
|
||||
@seed.setter
|
||||
def seed(self, value: int | None) -> None:
|
||||
if value is None:
|
||||
return
|
||||
self._set_pos_number("seed", value, False)
|
||||
|
||||
|
||||
# TODO: to be used in the future - see PR #620
|
||||
# https://github.com/ManimCommunity/manim/pull/620
|
||||
|
|
|
|||
|
|
@ -157,7 +157,17 @@ def turn_animation_into_updater(
|
|||
nonlocal total_time
|
||||
if total_time >= 0:
|
||||
run_time = animation.get_run_time()
|
||||
time_ratio = total_time / run_time
|
||||
|
||||
# handle zero/negative runtime safely
|
||||
if run_time <= 0:
|
||||
# instantly snap to final state once, then remove updater
|
||||
animation.interpolate(1)
|
||||
animation.update_mobjects(dt)
|
||||
animation.finish()
|
||||
m.remove_updater(update)
|
||||
return
|
||||
|
||||
time_ratio = animation.total_time / run_time
|
||||
if cycle:
|
||||
alpha = time_ratio % 1
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -145,4 +145,10 @@ global_options = option_group(
|
|||
help="The command used to preview the output file (for example vlc for video files)",
|
||||
default=None,
|
||||
),
|
||||
option(
|
||||
"--seed",
|
||||
type=int,
|
||||
help="Set the random seed to allow reproducibility.",
|
||||
default=None,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -146,12 +146,6 @@ class BackgroundRectangle(SurroundingRectangle):
|
|||
)
|
||||
return self
|
||||
|
||||
def get_fill_color(self) -> ManimColor:
|
||||
# The type of the color property is set to Any using the property decorator
|
||||
# vectorized_mobject.py#L571
|
||||
temp_color: ManimColor = self.color
|
||||
return temp_color
|
||||
|
||||
|
||||
class Cross(VGroup):
|
||||
"""Creates a cross.
|
||||
|
|
|
|||
|
|
@ -591,9 +591,7 @@ class GenericGraph(VMobject):
|
|||
self._labels = labels
|
||||
elif isinstance(labels, bool):
|
||||
if labels:
|
||||
self._labels = {
|
||||
v: MathTex(v, fill_color=label_fill_color) for v in vertices
|
||||
}
|
||||
self._labels = {v: MathTex(v, color=label_fill_color) for v in vertices}
|
||||
else:
|
||||
self._labels = {}
|
||||
|
||||
|
|
@ -700,7 +698,7 @@ class GenericGraph(VMobject):
|
|||
)
|
||||
|
||||
if label is True:
|
||||
label = MathTex(vertex, fill_color=label_fill_color)
|
||||
label = MathTex(vertex, color=label_fill_color)
|
||||
elif vertex in self._labels:
|
||||
label = self._labels[vertex]
|
||||
elif not isinstance(label, Mobject):
|
||||
|
|
|
|||
|
|
@ -3240,6 +3240,7 @@ class PolarPlane(Axes):
|
|||
}
|
||||
for i in a_values
|
||||
]
|
||||
a_tex = []
|
||||
if self.azimuth_units == "PI radians" or self.azimuth_units == "TAU radians":
|
||||
a_tex = [
|
||||
self.get_radian_label(
|
||||
|
|
|
|||
|
|
@ -1305,23 +1305,23 @@ class OpenGLMobject:
|
|||
alignments[i] = mapping[alignments[i]]
|
||||
return alignments
|
||||
|
||||
row_alignments = init_alignments(
|
||||
row_alignments_seq: Sequence[Vector3D] = init_alignments(
|
||||
row_alignments,
|
||||
rows,
|
||||
{"u": UP, "c": ORIGIN, "d": DOWN},
|
||||
"row",
|
||||
RIGHT,
|
||||
)
|
||||
col_alignments = init_alignments(
|
||||
col_alignments_seq: Sequence[Vector3D] = init_alignments(
|
||||
col_alignments,
|
||||
cols,
|
||||
{"l": LEFT, "c": ORIGIN, "r": RIGHT},
|
||||
"col",
|
||||
UP,
|
||||
)
|
||||
# Now row_alignment[r] + col_alignment[c] is the alignment in cell [r][c]
|
||||
# Now row_alignments_seq[r] + col_alignment_seq[c] is the alignment in cell [r][c]
|
||||
|
||||
mapper = {
|
||||
mapper: dict[str, Callable[[int, int], int]] = {
|
||||
"dr": lambda r, c: (rows - r - 1) + c * rows,
|
||||
"dl": lambda r, c: (rows - r - 1) + (cols - c - 1) * rows,
|
||||
"ur": lambda r, c: r + c * rows,
|
||||
|
|
@ -1332,10 +1332,11 @@ class OpenGLMobject:
|
|||
"lu": lambda r, c: r * cols + (cols - c - 1),
|
||||
}
|
||||
if flow_order not in mapper:
|
||||
valid_flow_orders = ",".join([f'"{key}"' for key in mapper])
|
||||
raise ValueError(
|
||||
'flow_order must be one of the following values: "dr", "rd", "ld" "dl", "ru", "ur", "lu", "ul".',
|
||||
f"flow_order must be one of the following values: {valid_flow_orders}.",
|
||||
)
|
||||
flow_order = mapper[flow_order]
|
||||
flow_order_func = mapper[flow_order]
|
||||
|
||||
# Reverse row_alignments and row_heights. Necessary since the
|
||||
# grid filling is handled bottom up for simplicity reasons.
|
||||
|
|
@ -1345,7 +1346,7 @@ class OpenGLMobject:
|
|||
maybe_list.reverse()
|
||||
return maybe_list
|
||||
|
||||
row_alignments = reverse(row_alignments)
|
||||
row_alignments_seq = reverse(row_alignments_seq)
|
||||
row_heights = reverse(row_heights)
|
||||
|
||||
placeholder = OpenGLMobject()
|
||||
|
|
@ -1354,7 +1355,7 @@ class OpenGLMobject:
|
|||
# properties of 0.
|
||||
|
||||
mobs.extend([placeholder] * (rows * cols - len(mobs)))
|
||||
grid = [[mobs[flow_order(r, c)] for c in range(cols)] for r in range(rows)]
|
||||
grid = [[mobs[flow_order_func(r, c)] for c in range(cols)] for r in range(rows)]
|
||||
|
||||
measured_heigths = [
|
||||
max(grid[r][c].height for c in range(cols)) for r in range(rows)
|
||||
|
|
@ -1370,18 +1371,19 @@ class OpenGLMobject:
|
|||
if len(sizes) != num:
|
||||
raise ValueError(f"{name} has a mismatching size.")
|
||||
return [
|
||||
sizes[i] if sizes[i] is not None else measures[i] for i in range(num)
|
||||
size if (size := sizes[i]) is not None else measures[i]
|
||||
for i in range(num)
|
||||
]
|
||||
|
||||
heights = init_sizes(row_heights, rows, measured_heigths, "row_heights")
|
||||
widths = init_sizes(col_widths, cols, measured_widths, "col_widths")
|
||||
|
||||
x, y = 0, 0
|
||||
x, y = 0.0, 0.0
|
||||
for r in range(rows):
|
||||
x = 0
|
||||
x = 0.0
|
||||
for c in range(cols):
|
||||
if grid[r][c] is not placeholder:
|
||||
alignment = row_alignments[r] + col_alignments[c]
|
||||
alignment = row_alignments_seq[r] + col_alignments_seq[c]
|
||||
line = Line(
|
||||
x * RIGHT + y * UP,
|
||||
(x + widths[c]) * RIGHT + (y + heights[r]) * UP,
|
||||
|
|
|
|||
|
|
@ -149,10 +149,10 @@ class Surface(VGroup):
|
|||
self.resolution = resolution
|
||||
self.surface_piece_config = surface_piece_config
|
||||
self.checkerboard_colors: list[ManimColor] | Literal[False]
|
||||
if checkerboard_colors:
|
||||
self.checkerboard_colors = [ManimColor(x) for x in checkerboard_colors]
|
||||
else:
|
||||
if checkerboard_colors is False:
|
||||
self.checkerboard_colors = checkerboard_colors
|
||||
else:
|
||||
self.checkerboard_colors = [ManimColor(i) for i in checkerboard_colors]
|
||||
self.should_make_jagged = should_make_jagged
|
||||
self.pre_function_handle_to_anchor_scale_factor = (
|
||||
pre_function_handle_to_anchor_scale_factor
|
||||
|
|
|
|||
|
|
@ -111,9 +111,10 @@ class Scene:
|
|||
self.quit_interaction = False
|
||||
|
||||
# Much nicer to work with deterministic scenes
|
||||
if self.random_seed is not None:
|
||||
random.seed(self.random_seed)
|
||||
np.random.default_rng(self.random_seed)
|
||||
if self.random_seed is None:
|
||||
self.random_seed = config.seed
|
||||
random.seed(self.random_seed)
|
||||
np.random.seed(self.random_seed) # noqa: NPY002 (only way to set seed globally)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__class__.__name__
|
||||
|
|
|
|||
214
manim/scene/zoomed_scene.py
Normal file
214
manim/scene/zoomed_scene.py
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
"""A scene supporting zooming in on a specified section.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. manim:: UseZoomedScene
|
||||
|
||||
class UseZoomedScene(ZoomedScene):
|
||||
def construct(self):
|
||||
dot = Dot().set_color(GREEN)
|
||||
self.add(dot)
|
||||
self.wait(1)
|
||||
self.activate_zooming(animate=False)
|
||||
self.wait(1)
|
||||
self.play(dot.animate.shift(LEFT))
|
||||
|
||||
.. manim:: ChangingZoomScale
|
||||
|
||||
class ChangingZoomScale(ZoomedScene):
|
||||
def __init__(self, **kwargs):
|
||||
ZoomedScene.__init__(
|
||||
self,
|
||||
zoom_factor=0.3,
|
||||
zoomed_display_height=1,
|
||||
zoomed_display_width=3,
|
||||
image_frame_stroke_width=20,
|
||||
zoomed_camera_config={
|
||||
"default_frame_stroke_width": 3,
|
||||
},
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def construct(self):
|
||||
dot = Dot().set_color(GREEN)
|
||||
sq = Circle(fill_opacity=1, radius=0.2).next_to(dot, RIGHT)
|
||||
self.add(dot, sq)
|
||||
self.wait(1)
|
||||
self.activate_zooming(animate=False)
|
||||
self.wait(1)
|
||||
self.play(dot.animate.shift(LEFT * 0.3))
|
||||
|
||||
self.play(self.zoomed_camera.frame.animate.scale(4))
|
||||
self.play(self.zoomed_camera.frame.animate.shift(0.5 * DOWN))
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = ["ZoomedScene"]
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from ..animation.transform import ApplyMethod
|
||||
from ..camera.camera import Camera
|
||||
from ..camera.moving_camera import MovingCamera
|
||||
from ..camera.multi_camera import MultiCamera
|
||||
from ..constants import *
|
||||
from ..mobject.types.image_mobject import ImageMobjectFromCamera
|
||||
from ..renderer.opengl_renderer import OpenGLCamera
|
||||
from ..scene.moving_camera_scene import MovingCameraScene
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from manim.typing import Point3DLike, Vector3D
|
||||
|
||||
# Note, any scenes from old videos using ZoomedScene will almost certainly
|
||||
# break, as it was restructured.
|
||||
|
||||
|
||||
class ZoomedScene(MovingCameraScene):
|
||||
"""This is a Scene with special configurations made for when
|
||||
a particular part of the scene must be zoomed in on and displayed
|
||||
separately.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
camera_class: type[Camera] = MultiCamera,
|
||||
zoomed_display_height: float = 3,
|
||||
zoomed_display_width: float = 3,
|
||||
zoomed_display_center: Point3DLike | None = None,
|
||||
zoomed_display_corner: Vector3D = UP + RIGHT,
|
||||
zoomed_display_corner_buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER,
|
||||
zoomed_camera_config: dict[str, Any] = {
|
||||
"default_frame_stroke_width": 2,
|
||||
"background_opacity": 1,
|
||||
},
|
||||
zoomed_camera_image_mobject_config: dict[str, Any] = {},
|
||||
zoomed_camera_frame_starting_position: Point3DLike = ORIGIN,
|
||||
zoom_factor: float = 0.15,
|
||||
image_frame_stroke_width: float = 3,
|
||||
zoom_activated: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
self.zoomed_display_height = zoomed_display_height
|
||||
self.zoomed_display_width = zoomed_display_width
|
||||
self.zoomed_display_center = zoomed_display_center
|
||||
self.zoomed_display_corner = zoomed_display_corner
|
||||
self.zoomed_display_corner_buff = zoomed_display_corner_buff
|
||||
self.zoomed_camera_config = zoomed_camera_config
|
||||
self.zoomed_camera_image_mobject_config = zoomed_camera_image_mobject_config
|
||||
self.zoomed_camera_frame_starting_position = (
|
||||
zoomed_camera_frame_starting_position
|
||||
)
|
||||
self.zoom_factor = zoom_factor
|
||||
self.image_frame_stroke_width = image_frame_stroke_width
|
||||
self.zoom_activated = zoom_activated
|
||||
super().__init__(camera_class=camera_class, **kwargs)
|
||||
|
||||
def setup(self) -> None:
|
||||
"""This method is used internally by Manim to
|
||||
setup the scene for proper use.
|
||||
"""
|
||||
super().setup()
|
||||
# Initialize camera and display
|
||||
zoomed_camera = MovingCamera(**self.zoomed_camera_config)
|
||||
zoomed_display = ImageMobjectFromCamera(
|
||||
zoomed_camera, **self.zoomed_camera_image_mobject_config
|
||||
)
|
||||
zoomed_display.add_display_frame()
|
||||
for mob in zoomed_camera.frame, zoomed_display:
|
||||
mob.stretch_to_fit_height(self.zoomed_display_height)
|
||||
mob.stretch_to_fit_width(self.zoomed_display_width)
|
||||
zoomed_camera.frame.scale(self.zoom_factor)
|
||||
|
||||
# Position camera and display
|
||||
zoomed_camera.frame.move_to(self.zoomed_camera_frame_starting_position)
|
||||
if self.zoomed_display_center is not None:
|
||||
zoomed_display.move_to(self.zoomed_display_center)
|
||||
else:
|
||||
zoomed_display.to_corner(
|
||||
self.zoomed_display_corner,
|
||||
buff=self.zoomed_display_corner_buff,
|
||||
)
|
||||
|
||||
self.zoomed_camera = zoomed_camera
|
||||
self.zoomed_display = zoomed_display
|
||||
|
||||
def activate_zooming(self, animate: bool = False) -> None:
|
||||
"""This method is used to activate the zooming for the zoomed_camera.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
animate
|
||||
Whether or not to animate the activation
|
||||
of the zoomed camera.
|
||||
"""
|
||||
self.zoom_activated = True
|
||||
self.renderer.camera.add_image_mobject_from_camera(self.zoomed_display) # type: ignore[union-attr]
|
||||
if animate:
|
||||
self.play(self.get_zoom_in_animation())
|
||||
self.play(self.get_zoomed_display_pop_out_animation())
|
||||
self.add_foreground_mobjects(
|
||||
self.zoomed_camera.frame,
|
||||
self.zoomed_display,
|
||||
)
|
||||
|
||||
def get_zoom_in_animation(self, run_time: float = 2, **kwargs: Any) -> ApplyMethod:
|
||||
"""Returns the animation of camera zooming in.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
run_time
|
||||
The run_time of the animation of the camera zooming in.
|
||||
**kwargs
|
||||
Any valid keyword arguments of ApplyMethod()
|
||||
|
||||
Returns
|
||||
-------
|
||||
ApplyMethod
|
||||
The animation of the camera zooming in.
|
||||
"""
|
||||
frame = self.zoomed_camera.frame
|
||||
if isinstance(self.camera, OpenGLCamera):
|
||||
full_frame_width, full_frame_height = self.camera.frame_shape
|
||||
else:
|
||||
full_frame_height = self.camera.frame_height
|
||||
full_frame_width = self.camera.frame_width
|
||||
frame.save_state()
|
||||
frame.stretch_to_fit_width(full_frame_width)
|
||||
frame.stretch_to_fit_height(full_frame_height)
|
||||
frame.center()
|
||||
frame.set_stroke(width=0)
|
||||
return ApplyMethod(frame.restore, run_time=run_time, **kwargs)
|
||||
|
||||
def get_zoomed_display_pop_out_animation(self, **kwargs: Any) -> ApplyMethod:
|
||||
"""This is the animation of the popping out of the mini-display that
|
||||
shows the content of the zoomed camera.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ApplyMethod
|
||||
The Animation of the Zoomed Display popping out.
|
||||
"""
|
||||
display = self.zoomed_display
|
||||
display.save_state()
|
||||
display.replace(self.zoomed_camera.frame, stretch=True)
|
||||
return ApplyMethod(display.restore)
|
||||
|
||||
def get_zoom_factor(self) -> float:
|
||||
"""Returns the Zoom factor of the Zoomed camera.
|
||||
|
||||
Defined as the ratio between the height of the zoomed camera and
|
||||
the height of the zoomed mini display.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
The zoom factor.
|
||||
"""
|
||||
zoom_factor: float = (
|
||||
self.zoomed_camera.frame.height / self.zoomed_display.height
|
||||
)
|
||||
return zoom_factor
|
||||
|
|
@ -1570,10 +1570,9 @@ class RandomColorGenerator:
|
|||
seed: int | None = None,
|
||||
sample_colors: list[ManimColor] | None = None,
|
||||
) -> None:
|
||||
self.choice = random.choice if seed is None else random.Random(seed).choice
|
||||
|
||||
from manim.utils.color.manim_colors import _all_manim_colors
|
||||
|
||||
self.choice = random.choice if seed is None else random.Random(seed).choice
|
||||
self.colors = _all_manim_colors if sample_colors is None else sample_colors
|
||||
|
||||
def next(self) -> ManimColor:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ __all__ = [
|
|||
|
||||
|
||||
def capture(
|
||||
command: str, cwd: StrOrBytesPath | None = None, command_input: str | None = None
|
||||
command: str | list[str],
|
||||
cwd: StrOrBytesPath | None = None,
|
||||
command_input: str | None = None,
|
||||
) -> tuple[str, str, int]:
|
||||
p = run(
|
||||
command,
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ def restructure_list_to_exclude_certain_family_members(
|
|||
to_remove = extract_mobject_family_members(to_remove)
|
||||
|
||||
def add_safe_mobjects_from_list(
|
||||
list_to_examine: list[Mobject], set_to_remove: set[Mobject]
|
||||
list_to_examine: Iterable[Mobject], set_to_remove: set[Mobject]
|
||||
) -> None:
|
||||
for mob in list_to_examine:
|
||||
if mob in set_to_remove:
|
||||
|
|
|
|||
|
|
@ -6,16 +6,19 @@ import re
|
|||
import sys
|
||||
import types
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Literal, overload
|
||||
|
||||
from manim import config, console, constants, logger
|
||||
from manim.constants import CHOOSE_NUMBER_MESSAGE, INVALID_NUMBER_MESSAGE
|
||||
from manim._config import config, console, logger
|
||||
from manim.constants import (
|
||||
CHOOSE_NUMBER_MESSAGE,
|
||||
INVALID_NUMBER_MESSAGE,
|
||||
NO_SCENE_MESSAGE,
|
||||
SCENE_NOT_FOUND_MESSAGE,
|
||||
)
|
||||
from manim.file_writer import FileWriter
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable, Sequence
|
||||
from pathlib import Path
|
||||
|
||||
from manim.scene.scene import Scene
|
||||
|
||||
__all__ = ["scene_classes_from_file"]
|
||||
|
|
@ -83,9 +86,9 @@ def get_scene_classes_from_module(module: types.ModuleType) -> list[type[Scene]]
|
|||
]
|
||||
|
||||
|
||||
def get_scenes_to_render(scene_classes: Sequence[type[Scene]]) -> Sequence[type[Scene]]:
|
||||
def get_scenes_to_render(scene_classes: list[type[Scene]]) -> list[type[Scene]]:
|
||||
if not scene_classes:
|
||||
logger.error(constants.NO_SCENE_MESSAGE)
|
||||
logger.error(NO_SCENE_MESSAGE)
|
||||
return []
|
||||
if config.write_all:
|
||||
return scene_classes
|
||||
|
|
@ -98,7 +101,7 @@ def get_scenes_to_render(scene_classes: Sequence[type[Scene]]) -> Sequence[type[
|
|||
result.append(scene_class)
|
||||
break
|
||||
else:
|
||||
logger.error(constants.SCENE_NOT_FOUND_MESSAGE.format(scene_name))
|
||||
logger.error(SCENE_NOT_FOUND_MESSAGE.format(scene_name))
|
||||
if result:
|
||||
return result
|
||||
if len(scene_classes) == 1:
|
||||
|
|
@ -107,9 +110,7 @@ def get_scenes_to_render(scene_classes: Sequence[type[Scene]]) -> Sequence[type[
|
|||
return prompt_user_for_choice(scene_classes)
|
||||
|
||||
|
||||
def prompt_user_for_choice(
|
||||
scene_classes: Iterable[type[Scene]],
|
||||
) -> Sequence[type[Scene]]:
|
||||
def prompt_user_for_choice(scene_classes: list[type[Scene]]) -> list[type[Scene]]:
|
||||
num_to_class = {}
|
||||
FileWriter.use_output_as_scene_name()
|
||||
for count, scene_class in enumerate(scene_classes, 1):
|
||||
|
|
@ -122,7 +123,7 @@ def prompt_user_for_choice(
|
|||
)
|
||||
|
||||
if user_input == "*":
|
||||
selected_scenes_classes = list(scene_classes)
|
||||
selected_scenes_classes = scene_classes
|
||||
else:
|
||||
selected_scenes_classes = [
|
||||
num_to_class[int(num_str)]
|
||||
|
|
|
|||
|
|
@ -373,9 +373,9 @@ def angle_between_vectors(v1: np.ndarray, v2: np.ndarray) -> float:
|
|||
|
||||
|
||||
def normalize(
|
||||
vect: npt.NDArray[float], fall_back: npt.NDArray[float] | None = None
|
||||
) -> npt.NDArray[float]:
|
||||
norm = get_norm(vect)
|
||||
vect: np.ndarray | tuple[float], fall_back: np.ndarray | None = None
|
||||
) -> np.ndarray:
|
||||
norm = np.linalg.norm(vect)
|
||||
if norm > 0:
|
||||
return np.array(vect) / norm
|
||||
elif fall_back is not None:
|
||||
|
|
|
|||
84
mypy.ini
84
mypy.ini
|
|
@ -64,30 +64,12 @@ ignore_errors = True
|
|||
[mypy-manim.animation.animation]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.animation.changing]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.animation.composition]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.animation.creation]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.animation.fading]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.animation.growing]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.animation.indication]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.animation.movement]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.animation.numbers]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.animation.specialized]
|
||||
ignore_errors = True
|
||||
|
||||
|
|
@ -100,9 +82,6 @@ ignore_errors = True
|
|||
[mypy-manim.animation.transform]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.animation.updaters.update]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.animation.updaters.mobject_update_utils]
|
||||
ignore_errors = True
|
||||
|
||||
|
|
@ -112,33 +91,15 @@ ignore_errors = True
|
|||
[mypy-manim.camera.mapping_camera]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.cli.checkhealth.commands]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.cli.default_group]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.mobject.frame]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.mobject.geometry.arc]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.mobject.geometry.boolean_ops]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.mobject.geometry.labeled]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.mobject.geometry.line]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.mobject.geometry.polygram]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.mobject.geometry.shape_matchers]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.mobject.graphing.coordinate_systems]
|
||||
ignore_errors = True
|
||||
|
||||
|
|
@ -151,18 +112,6 @@ ignore_errors = True
|
|||
[mypy-manim.mobject.mobject]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.mobject.text.tex_mobject]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.mobject.text.text_mobject]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.mobject.opengl.dot_cloud]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.mobject.opengl.shader]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.mobject.opengl.opengl_compatibility]
|
||||
ignore_errors = True
|
||||
|
||||
|
|
@ -193,39 +142,15 @@ ignore_errors = True
|
|||
[mypy-manim.mobject.vector_field]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.renderer.buffers.buffer]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.renderer.cairo_renderer]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.renderer.opengl_renderer]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.renderer.opengl_shader_program]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.renderer.shader_wrapper]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.scene.three_d_scene]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.utils.caching]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.utils.directories]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.utils.family_ops]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.utils.hashing]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.utils.testing.frames_comparison]
|
||||
ignore_errors = True
|
||||
|
||||
# Added temporarily due to current mypy failures
|
||||
[mypy-manim.camera.three_d_camera]
|
||||
ignore_errors = True
|
||||
|
|
@ -293,27 +218,18 @@ ignore_errors = True
|
|||
[mypy-manim.utils.commands]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.utils.debug]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.utils.images]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.utils.iterables]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.utils.module_ops]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.utils.opengl]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.utils.paths]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.utils.progressbar]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-manim.utils.space_ops]
|
||||
ignore_errors = True
|
||||
|
||||
|
|
|
|||
|
|
@ -78,11 +78,9 @@ jupyterlab = [
|
|||
[dependency-groups]
|
||||
dev = [
|
||||
"furo>=2024.8.6",
|
||||
"gitpython>=3.1.44",
|
||||
"matplotlib>=3.9.4",
|
||||
"myst-parser>=3.0.1",
|
||||
"pre-commit>=4.1.0",
|
||||
"pygithub>=2.5.0",
|
||||
"pytest>=8.3.4",
|
||||
"pytest-cov>=6.0.0",
|
||||
"pytest-xdist>=2.2,<3.0",
|
||||
|
|
@ -213,9 +211,6 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|||
"F403",
|
||||
]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
required-imports = ["from __future__ import annotations"]
|
||||
|
||||
[tool.ruff.lint.flake8-pytest-style]
|
||||
fixture-parentheses = false
|
||||
mark-parentheses = false
|
||||
|
|
|
|||
|
|
@ -1,316 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
"""Script to generate contributor and pull request lists.
|
||||
|
||||
This script generates contributor and pull request lists for release
|
||||
changelogs using Github v3 protocol. Use requires an authentication token in
|
||||
order to have sufficient bandwidth, you can get one following the directions at
|
||||
`<https://help.github.com/articles/creating-an-access-token-for-command-line-use/>_
|
||||
Don't add any scope, as the default is read access to public information. The
|
||||
token may be stored in an environment variable as you only get one chance to
|
||||
see it.
|
||||
|
||||
Usage::
|
||||
|
||||
$ ./scripts/dev_changelog.py [OPTIONS] TOKEN PRIOR TAG [ADDITIONAL]...
|
||||
|
||||
The output is utf8 rst.
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
- gitpython
|
||||
- pygithub
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
From a bash command line with $GITHUB environment variable as the GitHub token::
|
||||
|
||||
$ ./scripts/dev_changelog.py $GITHUB v0.3.0 v0.4.0
|
||||
|
||||
This would generate 0.4.0-changelog.rst file and place it automatically under
|
||||
docs/source/changelog/.
|
||||
|
||||
As another example, you may also run include PRs that have been excluded by
|
||||
providing a space separated list of ticket numbers after TAG::
|
||||
|
||||
$ ./scripts/dev_changelog.py $GITHUB v0.3.0 v0.4.0 1911 1234 1492 ...
|
||||
|
||||
|
||||
Note
|
||||
----
|
||||
|
||||
This script was taken from Numpy under the terms of BSD-3-Clause license.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import concurrent.futures
|
||||
import datetime
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from textwrap import dedent, indent
|
||||
|
||||
import cloup
|
||||
from git import Repo
|
||||
from github import Github
|
||||
from tqdm import tqdm
|
||||
|
||||
from manim.constants import CONTEXT_SETTINGS, EPILOG
|
||||
|
||||
this_repo = Repo(str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
PR_LABELS = {
|
||||
"breaking changes": "Breaking changes",
|
||||
"highlight": "Highlights",
|
||||
"pr:deprecation": "Deprecated classes and functions",
|
||||
"new feature": "New features",
|
||||
"enhancement": "Enhancements",
|
||||
"pr:bugfix": "Fixed bugs",
|
||||
"documentation": "Documentation-related changes",
|
||||
"testing": "Changes concerning the testing system",
|
||||
"infrastructure": "Changes to our development infrastructure",
|
||||
"maintenance": "Code quality improvements and similar refactors",
|
||||
"revert": "Changes that needed to be reverted again",
|
||||
"release": "New releases",
|
||||
"unlabeled": "Unclassified changes",
|
||||
}
|
||||
|
||||
SILENT_CONTRIBUTORS = [
|
||||
"dependabot[bot]",
|
||||
]
|
||||
|
||||
|
||||
def update_citation(version, date):
|
||||
current_directory = Path(__file__).parent
|
||||
parent_directory = current_directory.parent
|
||||
contents = (current_directory / "TEMPLATE.cff").read_text()
|
||||
contents = contents.replace("<version>", version)
|
||||
contents = contents.replace("<date_released>", date)
|
||||
with (parent_directory / "CITATION.cff").open("w", newline="\n") as f:
|
||||
f.write(contents)
|
||||
|
||||
|
||||
def process_pullrequests(lst, cur, github_repo, pr_nums):
|
||||
lst_commit = github_repo.get_commit(sha=this_repo.git.rev_list("-1", lst))
|
||||
lst_date = lst_commit.commit.author.date
|
||||
|
||||
authors = set()
|
||||
reviewers = set()
|
||||
pr_by_labels = defaultdict(list)
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
future_to_num = {
|
||||
executor.submit(github_repo.get_pull, num): num for num in pr_nums
|
||||
}
|
||||
for future in tqdm(
|
||||
concurrent.futures.as_completed(future_to_num), "Processing PRs"
|
||||
):
|
||||
pr = future.result()
|
||||
authors.add(pr.user)
|
||||
reviewers = reviewers.union(rev.user for rev in pr.get_reviews())
|
||||
pr_labels = [label.name for label in pr.labels]
|
||||
for label in PR_LABELS:
|
||||
if label in pr_labels:
|
||||
pr_by_labels[label].append(pr)
|
||||
break # ensure that PR is only added in one category
|
||||
else:
|
||||
pr_by_labels["unlabeled"].append(pr)
|
||||
|
||||
# identify first-time contributors:
|
||||
author_names = []
|
||||
for author in authors:
|
||||
name = author.name if author.name is not None else author.login
|
||||
if name in SILENT_CONTRIBUTORS:
|
||||
continue
|
||||
if github_repo.get_commits(author=author, until=lst_date).totalCount == 0:
|
||||
name += " +"
|
||||
author_names.append(name)
|
||||
|
||||
reviewer_names = []
|
||||
for reviewer in reviewers:
|
||||
name = reviewer.name if reviewer.name is not None else reviewer.login
|
||||
if name in SILENT_CONTRIBUTORS:
|
||||
continue
|
||||
reviewer_names.append(name)
|
||||
|
||||
# Sort items in pr_by_labels
|
||||
for i in pr_by_labels:
|
||||
pr_by_labels[i] = sorted(pr_by_labels[i], key=lambda pr: pr.number)
|
||||
|
||||
return {
|
||||
"authors": sorted(author_names),
|
||||
"reviewers": sorted(reviewer_names),
|
||||
"PRs": pr_by_labels,
|
||||
}
|
||||
|
||||
|
||||
def get_pr_nums(lst, cur):
|
||||
print("Getting PR Numbers:")
|
||||
prnums = []
|
||||
|
||||
# From regular merges
|
||||
merges = this_repo.git.log("--oneline", "--merges", f"{lst}..{cur}")
|
||||
issues = re.findall(r".*\(\#(\d+)\)", merges)
|
||||
prnums.extend(int(s) for s in issues)
|
||||
|
||||
# From fast forward squash-merges
|
||||
commits = this_repo.git.log(
|
||||
"--oneline",
|
||||
"--no-merges",
|
||||
"--first-parent",
|
||||
f"{lst}..{cur}",
|
||||
)
|
||||
split_commits = list(
|
||||
filter(
|
||||
lambda x: not any(
|
||||
["pre-commit autoupdate" in x, "New Crowdin updates" in x]
|
||||
),
|
||||
commits.split("\n"),
|
||||
),
|
||||
)
|
||||
commits = "\n".join(split_commits)
|
||||
issues = re.findall(r"^.*\(\#(\d+)\)$", commits, re.M)
|
||||
prnums.extend(int(s) for s in issues)
|
||||
|
||||
print(prnums)
|
||||
return prnums
|
||||
|
||||
|
||||
def get_summary(body):
|
||||
pattern = '<!--changelog-start-->([^"]*)<!--changelog-end-->'
|
||||
try:
|
||||
has_changelog_pattern = re.search(pattern, body)
|
||||
if has_changelog_pattern:
|
||||
return has_changelog_pattern.group()[22:-21].strip()
|
||||
except Exception:
|
||||
print(f"Error parsing body for changelog: {body}")
|
||||
|
||||
|
||||
@cloup.command(
|
||||
context_settings=CONTEXT_SETTINGS,
|
||||
epilog=EPILOG,
|
||||
)
|
||||
@cloup.argument("token")
|
||||
@cloup.argument("prior")
|
||||
@cloup.argument("tag")
|
||||
@cloup.argument(
|
||||
"additional",
|
||||
nargs=-1,
|
||||
required=False,
|
||||
type=int,
|
||||
)
|
||||
@cloup.option(
|
||||
"-o",
|
||||
"--outfile",
|
||||
type=str,
|
||||
help="Path and file name of the changelog output.",
|
||||
)
|
||||
def main(token, prior, tag, additional, outfile):
|
||||
"""Generate Changelog/List of contributors/PRs for release.
|
||||
|
||||
TOKEN is your GitHub Personal Access Token.
|
||||
|
||||
PRIOR is the tag/commit SHA of the previous release.
|
||||
|
||||
TAG is the tag of the new release.
|
||||
|
||||
ADDITIONAL includes additional PR(s) that have not been recognized automatically.
|
||||
"""
|
||||
lst_release, cur_release = prior, tag
|
||||
|
||||
github = Github(token)
|
||||
github_repo = github.get_repo("ManimCommunity/manim")
|
||||
|
||||
pr_nums = get_pr_nums(lst_release, cur_release)
|
||||
if additional:
|
||||
print(f"Adding {additional} to the mix!")
|
||||
pr_nums = pr_nums + list(additional)
|
||||
|
||||
# document authors
|
||||
contributions = process_pullrequests(lst_release, cur_release, github_repo, pr_nums)
|
||||
authors = contributions["authors"]
|
||||
reviewers = contributions["reviewers"]
|
||||
|
||||
# update citation file
|
||||
today = datetime.date.today()
|
||||
update_citation(tag, str(today))
|
||||
|
||||
if not outfile:
|
||||
outfile = (
|
||||
Path(__file__).resolve().parent.parent / "docs" / "source" / "changelog"
|
||||
)
|
||||
outfile = outfile / f"{tag[1:] if tag.startswith('v') else tag}-changelog.rst"
|
||||
else:
|
||||
outfile = Path(outfile).resolve()
|
||||
|
||||
with outfile.open("w", encoding="utf8", newline="\n") as f:
|
||||
f.write("*" * len(tag) + "\n")
|
||||
f.write(f"{tag}\n")
|
||||
f.write("*" * len(tag) + "\n\n")
|
||||
|
||||
f.write(f":Date: {today.strftime('%B %d, %Y')}\n\n")
|
||||
|
||||
heading = "Contributors"
|
||||
f.write(f"{heading}\n")
|
||||
f.write("=" * len(heading) + "\n\n")
|
||||
f.write(
|
||||
dedent(
|
||||
f"""\
|
||||
A total of {len(set(authors).union(set(reviewers)))} people contributed to this
|
||||
release. People with a '+' by their names authored a patch for the first
|
||||
time.\n
|
||||
""",
|
||||
),
|
||||
)
|
||||
|
||||
for author in authors:
|
||||
f.write(f"* {author}\n")
|
||||
|
||||
f.write("\n")
|
||||
f.write(
|
||||
dedent(
|
||||
"""
|
||||
The patches included in this release have been reviewed by
|
||||
the following contributors.\n
|
||||
""",
|
||||
),
|
||||
)
|
||||
|
||||
for reviewer in reviewers:
|
||||
f.write(f"* {reviewer}\n")
|
||||
|
||||
# document pull requests
|
||||
heading = "Pull requests merged"
|
||||
f.write("\n")
|
||||
f.write(heading + "\n")
|
||||
f.write("=" * len(heading) + "\n\n")
|
||||
f.write(
|
||||
f"A total of {len(pr_nums)} pull requests were merged for this release.\n\n",
|
||||
)
|
||||
|
||||
pr_by_labels = contributions["PRs"]
|
||||
for label in PR_LABELS:
|
||||
pr_of_label = pr_by_labels[label]
|
||||
|
||||
if pr_of_label:
|
||||
heading = PR_LABELS[label]
|
||||
f.write(f"{heading}\n")
|
||||
f.write("-" * len(heading) + "\n\n")
|
||||
|
||||
for PR in pr_by_labels[label]:
|
||||
num = PR.number
|
||||
title = PR.title
|
||||
label = PR.labels
|
||||
f.write(f"* :pr:`{num}`: {title}\n")
|
||||
overview = get_summary(PR.body)
|
||||
if overview:
|
||||
f.write(indent(f"{overview}\n\n", " "))
|
||||
else:
|
||||
f.write("\n\n")
|
||||
|
||||
print(f"Wrote changelog to: {outfile}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
540
scripts/release.py
Normal file
540
scripts/release.py
Normal file
|
|
@ -0,0 +1,540 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Release management tools for Manim.
|
||||
|
||||
This script provides commands for preparing and managing Manim releases:
|
||||
- Generate changelogs from GitHub's release notes API
|
||||
- Update CITATION.cff with new version information
|
||||
- Fetch existing release notes for documentation
|
||||
|
||||
Usage:
|
||||
# Generate changelog for an upcoming release
|
||||
uv run python scripts/release.py changelog --base v0.19.0 --version 0.20.0
|
||||
|
||||
# Also update CITATION.cff at the same time
|
||||
uv run python scripts/release.py changelog --base v0.19.0 --version 0.20.0 --update-citation
|
||||
|
||||
# Update only CITATION.cff
|
||||
uv run python scripts/release.py citation --version 0.20.0
|
||||
|
||||
# Fetch existing release changelogs for documentation
|
||||
uv run python scripts/release.py fetch-releases
|
||||
|
||||
Requirements:
|
||||
- gh CLI installed and authenticated
|
||||
- Python 3.11+
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import click
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
# =============================================================================
|
||||
# Constants
|
||||
# =============================================================================
|
||||
|
||||
REPO = "manimcommunity/manim"
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
REPO_ROOT = SCRIPT_DIR.parent
|
||||
CHANGELOG_DIR = REPO_ROOT / "docs" / "source" / "changelog"
|
||||
CITATION_TEMPLATE = SCRIPT_DIR / "TEMPLATE.cff"
|
||||
CITATION_FILE = REPO_ROOT / "CITATION.cff"
|
||||
|
||||
# Minimum version for fetching historical releases
|
||||
DEFAULT_MIN_VERSION = "0.18.0"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# GitHub CLI Helpers
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def run_gh(
|
||||
args: Sequence[str],
|
||||
*,
|
||||
check: bool = True,
|
||||
suppress_errors: bool = False,
|
||||
) -> subprocess.CompletedProcess[str]:
|
||||
"""
|
||||
Run a gh CLI command.
|
||||
|
||||
Args:
|
||||
args: Arguments to pass to gh
|
||||
check: If True, raise on non-zero exit
|
||||
suppress_errors: If True, don't print errors to stderr
|
||||
|
||||
Returns:
|
||||
CompletedProcess with stdout/stderr
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["gh", *args],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if (
|
||||
result.returncode != 0
|
||||
and not suppress_errors
|
||||
and "not found" not in result.stderr.lower()
|
||||
):
|
||||
click.echo(f"gh error: {result.stderr}", err=True)
|
||||
if check and result.returncode != 0:
|
||||
raise click.ClickException(f"gh command failed: gh {' '.join(args)}")
|
||||
return result
|
||||
|
||||
|
||||
def get_release_tags() -> list[str]:
|
||||
"""Get all published release tags from GitHub, newest first."""
|
||||
result = run_gh(
|
||||
["release", "list", "--repo", REPO, "--limit", "100", "--json", "tagName"],
|
||||
check=False,
|
||||
)
|
||||
if result.returncode != 0 or not result.stdout.strip():
|
||||
return []
|
||||
|
||||
import json
|
||||
|
||||
try:
|
||||
data = json.loads(result.stdout)
|
||||
return [item["tagName"] for item in data]
|
||||
except (json.JSONDecodeError, KeyError):
|
||||
return []
|
||||
|
||||
|
||||
def get_release_body(tag: str) -> str | None:
|
||||
"""Get the release body for a published release."""
|
||||
result = run_gh(
|
||||
["release", "view", tag, "--repo", REPO, "--json", "body", "--jq", ".body"],
|
||||
check=False,
|
||||
suppress_errors=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return None
|
||||
return result.stdout.strip() or None
|
||||
|
||||
|
||||
def get_release_date(tag: str) -> str | None:
|
||||
"""Get the release date formatted as 'Month DD, YYYY'."""
|
||||
result = run_gh(
|
||||
[
|
||||
"release",
|
||||
"view",
|
||||
tag,
|
||||
"--repo",
|
||||
REPO,
|
||||
"--json",
|
||||
"createdAt",
|
||||
"--jq",
|
||||
".createdAt",
|
||||
],
|
||||
check=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return None
|
||||
|
||||
date_str = result.stdout.strip().strip('"')
|
||||
try:
|
||||
dt = datetime.fromisoformat(date_str.replace("Z", "+00:00"))
|
||||
return dt.strftime("%B %d, %Y")
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def generate_release_notes(head_tag: str, base_tag: str) -> str:
|
||||
"""
|
||||
Generate release notes using GitHub's API.
|
||||
|
||||
This respects .github/release.yml for PR categorization.
|
||||
"""
|
||||
result = run_gh(
|
||||
[
|
||||
"api",
|
||||
f"repos/{REPO}/releases/generate-notes",
|
||||
"--field",
|
||||
f"tag_name={head_tag}",
|
||||
"--field",
|
||||
f"previous_tag_name={base_tag}",
|
||||
"--jq",
|
||||
".body",
|
||||
]
|
||||
)
|
||||
body = result.stdout.strip()
|
||||
if not body:
|
||||
raise click.ClickException("GitHub API returned empty release notes")
|
||||
return body
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Version Utilities
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def normalize_tag(tag: str) -> str:
|
||||
"""Ensure tag has 'v' prefix."""
|
||||
return tag if tag.startswith("v") else f"v{tag}"
|
||||
|
||||
|
||||
def version_from_tag(tag: str) -> str:
|
||||
"""Extract version from tag (e.g., 'v0.18.0' -> '0.18.0')."""
|
||||
return tag[1:] if tag.startswith("v") else tag
|
||||
|
||||
|
||||
def parse_version(version: str) -> tuple[int, ...]:
|
||||
"""Parse version string into comparable tuple."""
|
||||
# Handle post-releases like '0.18.0.post0'
|
||||
version = version.replace(".post", "-post")
|
||||
parts = []
|
||||
for part in version.replace("-", ".").split("."):
|
||||
try:
|
||||
parts.append(int(part))
|
||||
except ValueError:
|
||||
continue
|
||||
# Pad to at least 3 components
|
||||
while len(parts) < 3:
|
||||
parts.append(0)
|
||||
return tuple(parts)
|
||||
|
||||
|
||||
def version_gte(version: str, min_version: str) -> bool:
|
||||
"""Check if version >= min_version."""
|
||||
return parse_version(version) >= parse_version(min_version)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Markdown Conversion
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def convert_to_myst(body: str) -> str:
|
||||
"""
|
||||
Convert GitHub markdown to MyST format.
|
||||
|
||||
- PR URLs -> {pr}`NUMBER`
|
||||
- Issue URLs -> {issue}`NUMBER`
|
||||
- @mentions -> {user}`USERNAME`
|
||||
- Strips HTML comments
|
||||
- Ensures proper list spacing
|
||||
"""
|
||||
lines = body.split("\n")
|
||||
result = []
|
||||
prev_bullet = False
|
||||
|
||||
for line in lines:
|
||||
# Skip HTML comments
|
||||
if line.strip().startswith("<!--") and line.strip().endswith("-->"):
|
||||
continue
|
||||
|
||||
# Convert URLs to extlinks
|
||||
line = re.sub(
|
||||
r"https://github\.com/ManimCommunity/manim/pull/(\d+)",
|
||||
r"{pr}`\1`",
|
||||
line,
|
||||
)
|
||||
line = re.sub(
|
||||
r"https://github\.com/ManimCommunity/manim/issues/(\d+)",
|
||||
r"{issue}`\1`",
|
||||
line,
|
||||
)
|
||||
line = re.sub(r"@([a-zA-Z0-9_-]+)", r"{user}`\1`", line)
|
||||
|
||||
if line.startswith("**Full Changelog**:"):
|
||||
_, url = line.split(":", 1)
|
||||
url = url.strip().replace("vmain", "main")
|
||||
line = f"**Full Changelog**: [Compare view]({url})"
|
||||
|
||||
# Handle list spacing
|
||||
is_bullet = line.strip().startswith("*") and not line.strip().startswith("**")
|
||||
if prev_bullet and not is_bullet and line.strip():
|
||||
result.append("")
|
||||
|
||||
result.append(line)
|
||||
prev_bullet = is_bullet
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
def format_changelog(
|
||||
version: str,
|
||||
body: str,
|
||||
date: str | None = None,
|
||||
title: str | None = None,
|
||||
) -> str:
|
||||
"""Create changelog file content with MyST frontmatter."""
|
||||
title = title or f"v{version}"
|
||||
body = convert_to_myst(body)
|
||||
date_section = f"Date\n: {date}\n" if date else ""
|
||||
|
||||
return f"""---
|
||||
short-title: {title}
|
||||
description: Changelog for {title}
|
||||
---
|
||||
|
||||
# {title}
|
||||
|
||||
{date_section}
|
||||
{body}
|
||||
"""
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# File Operations
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def get_existing_versions() -> set[str]:
|
||||
"""Get versions that already have changelog files."""
|
||||
if not CHANGELOG_DIR.exists():
|
||||
return set()
|
||||
return {
|
||||
f.stem.replace("-changelog", "") for f in CHANGELOG_DIR.glob("*-changelog.*")
|
||||
}
|
||||
|
||||
|
||||
def save_changelog(version: str, content: str) -> Path:
|
||||
"""Save changelog and return filepath."""
|
||||
filepath = CHANGELOG_DIR / f"{version}-changelog.md"
|
||||
filepath.write_text(content)
|
||||
return filepath
|
||||
|
||||
|
||||
def update_citation(version: str, date: str | None = None) -> Path:
|
||||
"""Update CITATION.cff from template."""
|
||||
if not CITATION_TEMPLATE.exists():
|
||||
raise click.ClickException(f"Citation template not found: {CITATION_TEMPLATE}")
|
||||
|
||||
date = date or datetime.now().strftime("%Y-%m-%d")
|
||||
version_tag = normalize_tag(version)
|
||||
|
||||
content = CITATION_TEMPLATE.read_text()
|
||||
content = content.replace("<version>", version_tag)
|
||||
content = content.replace("<date_released>", date)
|
||||
|
||||
CITATION_FILE.write_text(content)
|
||||
return CITATION_FILE
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CLI Commands
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.option(
|
||||
"--dry-run", is_flag=True, help="Show what would be done without making changes"
|
||||
)
|
||||
@click.pass_context
|
||||
def cli(ctx: click.Context, dry_run: bool) -> None:
|
||||
"""Release management tools for Manim."""
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj["dry_run"] = dry_run
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--base", required=True, help="Base tag for comparison (e.g., v0.19.0)")
|
||||
@click.option(
|
||||
"--version", "version", required=True, help="New version number (e.g., 0.20.0)"
|
||||
)
|
||||
@click.option("--head", default="main", help="Head ref for comparison (default: main)")
|
||||
@click.option("--title", help="Custom changelog title (default: vX.Y.Z)")
|
||||
@click.option("--update-citation", is_flag=True, help="Also update CITATION.cff")
|
||||
@click.pass_context
|
||||
def changelog(
|
||||
ctx: click.Context,
|
||||
base: str,
|
||||
version: str,
|
||||
head: str,
|
||||
title: str | None,
|
||||
update_citation: bool,
|
||||
) -> None:
|
||||
"""Generate changelog for an upcoming release.
|
||||
|
||||
Uses GitHub's release notes API with .github/release.yml categorization.
|
||||
"""
|
||||
dry_run = ctx.obj["dry_run"]
|
||||
base_tag = normalize_tag(base)
|
||||
head_tag = normalize_tag(head) if head != "main" else normalize_tag(version)
|
||||
|
||||
click.echo(f"Generating changelog for v{version}...")
|
||||
click.echo(f" Comparing: {base_tag} → {head}")
|
||||
|
||||
body = generate_release_notes(head_tag, base_tag)
|
||||
date = datetime.now().strftime("%B %d, %Y")
|
||||
content = format_changelog(version, body, date=date, title=title)
|
||||
|
||||
if dry_run:
|
||||
click.echo()
|
||||
click.secho("[DRY RUN]", fg="yellow", bold=True)
|
||||
click.echo(f" Would save: {CHANGELOG_DIR / f'{version}-changelog.md'}")
|
||||
if update_citation:
|
||||
click.echo(f" Would update: {CITATION_FILE}")
|
||||
click.echo()
|
||||
click.echo("--- Generated changelog ---")
|
||||
click.echo(content)
|
||||
return
|
||||
|
||||
filepath = save_changelog(version, content)
|
||||
click.secho(f" ✓ Saved: {filepath}", fg="green")
|
||||
|
||||
if update_citation:
|
||||
citation_path = update_citation(version)
|
||||
click.secho(f" ✓ Updated: {citation_path}", fg="green")
|
||||
|
||||
click.echo()
|
||||
click.echo("Next steps:")
|
||||
click.echo(" • Review and edit the changelog as needed")
|
||||
click.echo(" • Update docs/source/changelog.rst to include the new file")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option(
|
||||
"--version", "version", required=True, help="Version number (e.g., 0.20.0)"
|
||||
)
|
||||
@click.option("--date", help="Release date as YYYY-MM-DD (default: today)")
|
||||
@click.pass_context
|
||||
def citation(ctx: click.Context, version: str, date: str | None) -> None:
|
||||
"""Update CITATION.cff for a release."""
|
||||
dry_run = ctx.obj["dry_run"]
|
||||
display_date = date or datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
if dry_run:
|
||||
click.secho("[DRY RUN]", fg="yellow", bold=True)
|
||||
click.echo(f" Would update: {CITATION_FILE}")
|
||||
click.echo(f" Version: v{version}")
|
||||
click.echo(f" Date: {display_date}")
|
||||
return
|
||||
|
||||
filepath = update_citation(version, date)
|
||||
click.secho(f"✓ Updated: {filepath}", fg="green")
|
||||
click.echo(f" Version: v{version}")
|
||||
click.echo(f" Date: {display_date}")
|
||||
|
||||
|
||||
@cli.command("fetch-releases")
|
||||
@click.option("--tag", help="Fetch a specific release tag")
|
||||
@click.option(
|
||||
"--min-version",
|
||||
default=DEFAULT_MIN_VERSION,
|
||||
help=f"Minimum version to fetch (default: {DEFAULT_MIN_VERSION})",
|
||||
)
|
||||
@click.option("--force", is_flag=True, help="Overwrite existing changelog files")
|
||||
@click.pass_context
|
||||
def fetch_releases(
|
||||
ctx: click.Context,
|
||||
tag: str | None,
|
||||
min_version: str,
|
||||
force: bool,
|
||||
) -> None:
|
||||
"""Fetch existing release changelogs from GitHub.
|
||||
|
||||
Converts GitHub release notes to documentation-ready MyST markdown.
|
||||
"""
|
||||
dry_run = ctx.obj["dry_run"]
|
||||
existing = get_existing_versions()
|
||||
|
||||
# Single tag mode
|
||||
if tag:
|
||||
tag = normalize_tag(tag)
|
||||
version = version_from_tag(tag)
|
||||
|
||||
if version in existing and not force:
|
||||
click.echo(
|
||||
f"Changelog for {version} already exists. Use --force to overwrite."
|
||||
)
|
||||
return
|
||||
|
||||
if dry_run:
|
||||
click.secho("[DRY RUN]", fg="yellow", bold=True)
|
||||
click.echo(f" Would fetch: {version}")
|
||||
return
|
||||
|
||||
_fetch_single_release(tag, version)
|
||||
return
|
||||
|
||||
# Batch mode
|
||||
click.echo(f"Existing versions: {', '.join(sorted(existing)) or '(none)'}")
|
||||
click.echo("Fetching release list...")
|
||||
|
||||
tags = get_release_tags()
|
||||
click.echo(f"Found {len(tags)} releases")
|
||||
|
||||
fetched = 0
|
||||
prev_tag = None
|
||||
|
||||
for tag in reversed(tags):
|
||||
version = version_from_tag(tag)
|
||||
|
||||
if not version_gte(version, min_version):
|
||||
prev_tag = tag
|
||||
continue
|
||||
|
||||
if version in existing and not force:
|
||||
click.echo(f" Skipping {version} (exists)")
|
||||
prev_tag = tag
|
||||
continue
|
||||
|
||||
if dry_run:
|
||||
click.echo(f" [DRY RUN] Would fetch {version}")
|
||||
fetched += 1
|
||||
else:
|
||||
if _fetch_single_release(tag, version, prev_tag):
|
||||
existing.add(version)
|
||||
fetched += 1
|
||||
|
||||
prev_tag = tag
|
||||
|
||||
click.echo()
|
||||
click.echo(f"Processed {fetched} changelog(s)")
|
||||
|
||||
if fetched > 0 and not dry_run:
|
||||
click.echo()
|
||||
click.echo("Next steps:")
|
||||
click.echo(" • Update docs/source/changelog.rst to include new files")
|
||||
|
||||
|
||||
def _fetch_single_release(tag: str, version: str, prev_tag: str | None = None) -> bool:
|
||||
"""Fetch and save a single release changelog."""
|
||||
click.echo(f" Fetching {version}...")
|
||||
|
||||
body = get_release_body(tag)
|
||||
|
||||
if not body and prev_tag:
|
||||
click.echo(f" No body, generating from {prev_tag}...")
|
||||
try:
|
||||
body = generate_release_notes(tag, prev_tag)
|
||||
except click.ClickException:
|
||||
body = None
|
||||
|
||||
if not body:
|
||||
click.secho(f" ✗ Could not get release notes for {tag}", fg="red", err=True)
|
||||
return False
|
||||
|
||||
date = get_release_date(tag)
|
||||
content = format_changelog(version, body, date=date)
|
||||
|
||||
filepath = save_changelog(version, content)
|
||||
click.secho(f" ✓ Saved: {filepath}", fg="green")
|
||||
return True
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Entry Point
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Entry point."""
|
||||
cli()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main() or 0)
|
||||
56
tests/module/animation/test_updaters.py
Normal file
56
tests/module/animation/test_updaters.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from manim import Circle, FadeIn
|
||||
from manim.animation.updaters.mobject_update_utils import turn_animation_into_updater
|
||||
|
||||
|
||||
def test_turn_animation_into_updater_zero_run_time():
|
||||
"""Test that turn_animation_into_updater handles zero run_time correctly."""
|
||||
# Create a simple mobject and animation
|
||||
mobject = Circle()
|
||||
animation = FadeIn(mobject, run_time=0)
|
||||
|
||||
# Track updater calls
|
||||
update_calls = []
|
||||
original_updaters = mobject.updaters.copy()
|
||||
|
||||
# Call turn_animation_into_updater
|
||||
result = turn_animation_into_updater(animation)
|
||||
|
||||
# Verify mobject is returned
|
||||
assert result is mobject
|
||||
|
||||
# Get the updater that was added
|
||||
assert len(mobject.updaters) == len(original_updaters) + 1
|
||||
updater = mobject.updaters[-1]
|
||||
|
||||
# Simulate calling the updater
|
||||
updater(mobject, dt=0.1)
|
||||
|
||||
# The updater should have finished and removed itself
|
||||
assert len(mobject.updaters) == len(original_updaters)
|
||||
assert updater not in mobject.updaters
|
||||
|
||||
# Animation should be in finished state
|
||||
assert animation.total_time >= 0
|
||||
|
||||
|
||||
def test_turn_animation_into_updater_positive_run_time_persists():
|
||||
"""Test that updater persists with positive run_time."""
|
||||
mobject = Circle()
|
||||
animation = FadeIn(mobject, run_time=1.0)
|
||||
|
||||
original_updaters = mobject.updaters.copy()
|
||||
|
||||
# Call turn_animation_into_updater
|
||||
result = turn_animation_into_updater(animation)
|
||||
|
||||
# Get the updater that was added
|
||||
updater = mobject.updaters[-1]
|
||||
|
||||
# Simulate calling the updater (partial progress)
|
||||
updater(mobject, dt=0.1)
|
||||
|
||||
# The updater should still be present (not finished)
|
||||
assert len(mobject.updaters) == len(original_updaters) + 1
|
||||
assert updater in mobject.updaters
|
||||
|
|
@ -7,6 +7,7 @@ import numpy as np
|
|||
from manim import (
|
||||
DEGREES,
|
||||
DOWN,
|
||||
GREEN,
|
||||
LEFT,
|
||||
ORIGIN,
|
||||
RIGHT,
|
||||
|
|
@ -152,6 +153,18 @@ def test_BackgroundRectangle(manim_caplog):
|
|||
)
|
||||
|
||||
|
||||
def test_BackgroundRectangle_color_access():
|
||||
"""Test that BackgroundRectangle color access works correctly.
|
||||
|
||||
Regression test for https://github.com/ManimCommunity/manim/issues/4419
|
||||
"""
|
||||
square = Square()
|
||||
bg_rect = BackgroundRectangle(square, color=GREEN)
|
||||
|
||||
# Should not cause infinite recursion
|
||||
assert bg_rect.color == GREEN
|
||||
|
||||
|
||||
def test_Square_side_length_reflets_correct_width_and_height():
|
||||
sq = Square(side_length=1).scale(3)
|
||||
assert sq.side_length == 3
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ def is_close(x, y):
|
|||
return abs(x - y) < 0.00001
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_mobject_dimensions_nested_mobjects():
|
||||
vg = VGroup()
|
||||
|
||||
|
|
|
|||
19
tests/module/mobject/test_table.py
Normal file
19
tests/module/mobject/test_table.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
"""Tests for Table and related mobjects."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from manim import Table
|
||||
from manim.utils.color import GREEN
|
||||
|
||||
|
||||
def test_highlighted_cell_color_access():
|
||||
"""Test that accessing the color of a highlighted cell doesn't cause infinite recursion.
|
||||
|
||||
Regression test for https://github.com/ManimCommunity/manim/issues/4419
|
||||
"""
|
||||
table = Table([["This", "is a"], ["simple", "table"]])
|
||||
rect = table.get_highlighted_cell((1, 1), color=GREEN)
|
||||
|
||||
# Should not raise RecursionError
|
||||
color = rect.color
|
||||
assert color == GREEN
|
||||
|
|
@ -4,7 +4,7 @@ import datetime
|
|||
|
||||
import pytest
|
||||
|
||||
from manim import Circle, FadeIn, Group, Manager, Scene, Square
|
||||
from manim import Circle, Dot, FadeIn, Group, Manager, Scene, Square
|
||||
from manim.animation.animation import Wait
|
||||
from manim.mobject.opengl.opengl_mobject import OpenGLMobject as Mobject
|
||||
|
||||
|
|
@ -108,3 +108,48 @@ def test_replace(dry_run):
|
|||
scene.replace(second, beta)
|
||||
assert_names(scene.mobjects, ["alpha", "group", "fourth"])
|
||||
assert_names(scene.mobjects[1], ["beta", "third"])
|
||||
|
||||
|
||||
def test_reproducible_scene(dry_run):
|
||||
import numpy as np
|
||||
|
||||
scene = Scene(random_seed=42)
|
||||
dots1 = []
|
||||
for _ in range(10):
|
||||
dot = Dot(np.random.uniform(-3, 3, size=3)) # noqa: NPY002
|
||||
dots1.append(dot)
|
||||
scene.add(*dots1)
|
||||
|
||||
scene2 = Scene(random_seed=42)
|
||||
dots2 = []
|
||||
for _ in range(5):
|
||||
dot = Dot(np.random.uniform(-3, 3, size=3)) # noqa: NPY002
|
||||
dots2.append(dot)
|
||||
scene2.add(*dots2)
|
||||
|
||||
for d1, d2 in zip(dots1, dots2, strict=False):
|
||||
np.testing.assert_allclose(d1.get_center(), d2.get_center())
|
||||
|
||||
|
||||
def test_random_color_reproducibility_with_seed(dry_run):
|
||||
from manim import random_color, tempconfig
|
||||
|
||||
with tempconfig({"seed": 123}):
|
||||
# First run: create scene (which seeds global random state) and generate colors
|
||||
scene1 = Scene()
|
||||
colors_first_run = [random_color() for _ in range(5)]
|
||||
|
||||
# Interrupt with a scene that has an explicit different seed
|
||||
scene_explicit = Scene(random_seed=999)
|
||||
colors_interrupted = [random_color() for _ in range(3)]
|
||||
|
||||
# Second run: create a new scene without explicit seed (should use config.seed)
|
||||
scene2 = Scene()
|
||||
colors_second_run = [random_color() for _ in range(5)]
|
||||
|
||||
# The colors from the first and second run should match
|
||||
# because both scenes were seeded with config.seed=123
|
||||
assert colors_first_run == colors_second_run
|
||||
|
||||
# The interrupted colors should be different (seeded with 999)
|
||||
assert colors_interrupted != colors_first_run[:3]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from manim import *
|
||||
from manim.utils.testing.frames_comparison import frames_comparison
|
||||
|
||||
|
|
@ -37,6 +39,7 @@ def test_line_graph(scene):
|
|||
scene.add(plane, first_line, second_line)
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@frames_comparison
|
||||
def test_plot_surface(scene):
|
||||
axes = ThreeDAxes(x_range=(-5, 5, 1), y_range=(-5, 5, 1), z_range=(-5, 5, 1))
|
||||
|
|
@ -57,6 +60,7 @@ def test_plot_surface(scene):
|
|||
scene.add(axes, trig_plane)
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@frames_comparison
|
||||
def test_plot_surface_colorscale(scene):
|
||||
axes = ThreeDAxes(x_range=(-3, 3, 1), y_range=(-3, 3, 1), z_range=(-5, 5, 1))
|
||||
|
|
@ -143,6 +147,7 @@ def test_number_plane_log(scene):
|
|||
scene.add(VGroup(plane1, plane2).arrange())
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@frames_comparison
|
||||
def test_gradient_line_graph_x_axis(scene):
|
||||
"""Test that using `colorscale` generates a line whose gradient matches the y-axis"""
|
||||
|
|
@ -158,6 +163,7 @@ def test_gradient_line_graph_x_axis(scene):
|
|||
scene.add(axes, curve)
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@frames_comparison
|
||||
def test_gradient_line_graph_y_axis(scene):
|
||||
"""Test that using `colorscale` generates a line whose gradient matches the y-axis"""
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import pytest
|
||||
|
||||
from manim.constants import LEFT
|
||||
from manim.mobject.graphing.probability import BarChart
|
||||
from manim.mobject.text.tex_mobject import MathTex
|
||||
|
|
@ -7,6 +9,7 @@ from manim.utils.testing.frames_comparison import frames_comparison
|
|||
__module_test__ = "probability"
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@frames_comparison
|
||||
def test_default_chart(scene):
|
||||
pull_req = [54, 23, 47, 48, 40, 64, 112, 87]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from manim import *
|
||||
from manim.utils.testing.frames_comparison import frames_comparison
|
||||
|
||||
|
|
@ -18,6 +20,7 @@ def test_Cube(scene: Scene) -> None:
|
|||
scene.add(Cube())
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@frames_comparison
|
||||
def test_Sphere(scene: Scene) -> None:
|
||||
scene.add(Sphere())
|
||||
|
|
@ -64,6 +67,7 @@ def test_Arrow3D(scene: Scene) -> None:
|
|||
scene.add(Arrow3D(resolution=16))
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@frames_comparison
|
||||
def test_Torus(scene: Scene) -> None:
|
||||
scene.add(Torus())
|
||||
|
|
|
|||
|
|
@ -165,3 +165,15 @@ class ElaborateSceneWithSections(Scene):
|
|||
self.next_section("fade out")
|
||||
self.play(FadeOut(square))
|
||||
self.wait()
|
||||
|
||||
|
||||
class SceneWithRandomness(Scene):
|
||||
def construct(self):
|
||||
dots = VGroup()
|
||||
for _ in range(10):
|
||||
dot = Dot(
|
||||
point=np.random.uniform(-3, 3, size=3), # noqa: NPY002
|
||||
)
|
||||
dots.add(dot)
|
||||
self.add(dots)
|
||||
self.wait()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
|
@ -713,6 +714,49 @@ def test_mov_can_be_set_as_output_format(
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_reproducible_animation(tmp_path: Path, manim_cfg_file, simple_scenes_path):
|
||||
scene_name = "SceneWithRandomness"
|
||||
command = [
|
||||
sys.executable,
|
||||
"-m",
|
||||
"manim",
|
||||
"-ql",
|
||||
"--media_dir",
|
||||
str(tmp_path),
|
||||
"--seed",
|
||||
"42",
|
||||
str(simple_scenes_path),
|
||||
scene_name,
|
||||
]
|
||||
out, err, exit_code = capture(command)
|
||||
assert exit_code == 0, err
|
||||
|
||||
first_path = tmp_path / "videos" / "simple_scenes" / "480p15" / f"{scene_name}.mp4"
|
||||
|
||||
command = [
|
||||
sys.executable,
|
||||
"-m",
|
||||
"manim",
|
||||
"-ql",
|
||||
"--media_dir",
|
||||
str(tmp_path),
|
||||
"--seed",
|
||||
"42",
|
||||
str(simple_scenes_path),
|
||||
scene_name,
|
||||
]
|
||||
out, err, exit_code = capture(command)
|
||||
assert exit_code == 0, err
|
||||
|
||||
second_path = first_path.with_name(scene_name).with_suffix(".mp4")
|
||||
|
||||
with open(first_path, "rb") as f1, open(second_path, "rb") as f2:
|
||||
first_data = f1.read()
|
||||
second_data = f2.read()
|
||||
assert first_data == second_data, "Videos with the same seed differ."
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@video_comparison(
|
||||
"InputFileViaCfg.json",
|
||||
|
|
|
|||
166
uv.lock
generated
166
uv.lock
generated
|
|
@ -1,5 +1,5 @@
|
|||
version = 1
|
||||
revision = 2
|
||||
revision = 3
|
||||
requires-python = ">=3.11"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.14'",
|
||||
|
|
@ -655,68 +655,6 @@ toml = [
|
|||
{ name = "tomli", marker = "python_full_version <= '3.11'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "46.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cycler"
|
||||
version = "0.12.1"
|
||||
|
|
@ -934,30 +872,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/f4/b2/50e9b292b5cac13e9e81272c7171301abc753a60460d21505b606e15cf21/furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f", size = 339262, upload-time = "2025-12-19T17:34:38.905Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gitdb"
|
||||
version = "4.0.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "smmap" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gitpython"
|
||||
version = "3.1.46"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "gitdb" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glcontext"
|
||||
version = "3.0.0"
|
||||
|
|
@ -1525,12 +1439,10 @@ jupyterlab = [
|
|||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "furo" },
|
||||
{ name = "gitpython" },
|
||||
{ name = "matplotlib" },
|
||||
{ name = "myst-parser" },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "psutil" },
|
||||
{ name = "pygithub" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "pytest-xdist" },
|
||||
|
|
@ -1585,12 +1497,10 @@ provides-extras = ["gui", "jupyterlab"]
|
|||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "furo", specifier = ">=2024.8.6" },
|
||||
{ name = "gitpython", specifier = ">=3.1.44" },
|
||||
{ name = "matplotlib", specifier = ">=3.9.4" },
|
||||
{ name = "myst-parser", specifier = ">=3.0.1" },
|
||||
{ name = "pre-commit", specifier = ">=4.1.0" },
|
||||
{ name = "psutil", specifier = ">=6.1.1" },
|
||||
{ name = "pygithub", specifier = ">=2.5.0" },
|
||||
{ name = "pytest", specifier = ">=8.3.4" },
|
||||
{ name = "pytest-cov", specifier = ">=6.0.0" },
|
||||
{ name = "pytest-xdist", specifier = ">=2.2,<3.0" },
|
||||
|
|
@ -2420,22 +2330,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327, upload-time = "2021-03-10T02:09:53.503Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygithub"
|
||||
version = "2.8.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyjwt", extra = ["crypto"] },
|
||||
{ name = "pynacl" },
|
||||
{ name = "requests" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c1/74/e560bdeffea72ecb26cff27f0fad548bbff5ecc51d6a155311ea7f9e4c4c/pygithub-2.8.1.tar.gz", hash = "sha256:341b7c78521cb07324ff670afd1baa2bf5c286f8d9fd302c1798ba594a5400c9", size = 2246994, upload-time = "2025-09-02T17:41:54.674Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/07/ba/7049ce39f653f6140aac4beb53a5aaf08b4407b6a3019aae394c1c5244ff/pygithub-2.8.1-py3-none-any.whl", hash = "sha256:23a0a5bca93baef082e03411bf0ce27204c32be8bfa7abc92fe4a3e132936df0", size = 432709, upload-time = "2025-09-02T17:41:52.947Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyglet"
|
||||
version = "2.1.12"
|
||||
|
|
@ -2512,55 +2406,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.10.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
crypto = [
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pynacl"
|
||||
version = "1.6.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/79/0e3c34dc3c4671f67d251c07aa8eb100916f250ee470df230b0ab89551b4/pynacl-1.6.2-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:622d7b07cc5c02c666795792931b50c91f3ce3c2649762efb1ef0d5684c81594", size = 390064, upload-time = "2026-01-01T17:31:57.264Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/1c/23a26e931736e13b16483795c8a6b2f641bf6a3d5238c22b070a5112722c/pynacl-1.6.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d071c6a9a4c94d79eb665db4ce5cedc537faf74f2355e4d502591d850d3913c0", size = 809370, upload-time = "2026-01-01T17:31:59.198Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/74/8d4b718f8a22aea9e8dcc8b95deb76d4aae380e2f5b570cc70b5fd0a852d/pynacl-1.6.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9", size = 1408304, upload-time = "2026-01-01T17:32:01.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/73/be4fdd3a6a87fe8a4553380c2b47fbd1f7f58292eb820902f5c8ac7de7b0/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04316d1fc625d860b6c162fff704eb8426b1a8bcd3abacea11142cbd99a6b574", size = 844871, upload-time = "2026-01-01T17:32:02.824Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/ad/6efc57ab75ee4422e96b5f2697d51bbcf6cdcc091e66310df91fbdc144a8/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44081faff368d6c5553ccf55322ef2819abb40e25afaec7e740f159f74813634", size = 1446356, upload-time = "2026-01-01T17:32:04.452Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b7/928ee9c4779caa0a915844311ab9fb5f99585621c5d6e4574538a17dca07/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:a9f9932d8d2811ce1a8ffa79dcbdf3970e7355b5c8eb0c1a881a57e7f7d96e88", size = 826814, upload-time = "2026-01-01T17:32:06.078Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/a9/1bdba746a2be20f8809fee75c10e3159d75864ef69c6b0dd168fc60e485d/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:bc4a36b28dd72fb4845e5d8f9760610588a96d5a51f01d84d8c6ff9849968c14", size = 1411742, upload-time = "2026-01-01T17:32:07.651Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/2f/5e7ea8d85f9f3ea5b6b87db1d8388daa3587eed181bdeb0306816fdbbe79/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bffb6d0f6becacb6526f8f42adfb5efb26337056ee0831fb9a7044d1a964444", size = 801714, upload-time = "2026-01-01T17:32:09.558Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/ea/43fe2f7eab5f200e40fb10d305bf6f87ea31b3bbc83443eac37cd34a9e1e/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fef529ef3ee487ad8113d287a593fa26f48ee3620d92ecc6f1d09ea38e0709b", size = 1372257, upload-time = "2026-01-01T17:32:11.026Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/54/c9ea116412788629b1347e415f72195c25eb2f3809b2d3e7b25f5c79f13a/pynacl-1.6.2-cp314-cp314t-win32.whl", hash = "sha256:a84bf1c20339d06dc0c85d9aea9637a24f718f375d861b2668b2f9f96fa51145", size = 231319, upload-time = "2026-01-01T17:32:12.46Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/04/64e9d76646abac2dccf904fccba352a86e7d172647557f35b9fe2a5ee4a1/pynacl-1.6.2-cp314-cp314t-win_amd64.whl", hash = "sha256:320ef68a41c87547c91a8b58903c9caa641ab01e8512ce291085b5fe2fcb7590", size = 244044, upload-time = "2026-01-01T17:32:13.781Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/33/7873dc161c6a06f43cda13dec67b6fe152cb2f982581151956fa5e5cdb47/pynacl-1.6.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2", size = 188740, upload-time = "2026-01-01T17:32:15.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458, upload-time = "2026-01-01T17:32:16.829Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020, upload-time = "2026-01-01T17:32:18.34Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174, upload-time = "2026-01-01T17:32:20.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085, upload-time = "2026-01-01T17:32:22.24Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614, upload-time = "2026-01-01T17:32:23.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251, upload-time = "2026-01-01T17:32:25.69Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859, upload-time = "2026-01-01T17:32:27.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926, upload-time = "2026-01-01T17:32:29.314Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101, upload-time = "2026-01-01T17:32:31.263Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421, upload-time = "2026-01-01T17:32:33.076Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754, upload-time = "2026-01-01T17:32:34.557Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-core"
|
||||
version = "12.1"
|
||||
|
|
@ -3167,15 +3012,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/2d/be/7daf7bf5ec6e4f245804842364222b1e857b42b2ca13192791e2b8cafc14/skia_pathops-0.9.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6c9ccc68d316371be3817eb20eaae4a7810d85f329276a7d7ca5a21f47fa6522", size = 1779252, upload-time = "2025-12-08T11:44:47.15Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smmap"
|
||||
version = "5.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snowballstemmer"
|
||||
version = "3.0.1"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue