handanim 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- handanim/__init__.py +96 -0
- handanim/animations/__init__.py +20 -0
- handanim/animations/camera.py +79 -0
- handanim/animations/color_transition.py +46 -0
- handanim/animations/fade.py +60 -0
- handanim/animations/rotate.py +31 -0
- handanim/animations/sketch.py +143 -0
- handanim/animations/translate.py +66 -0
- handanim/animations/zoom.py +52 -0
- handanim/core/__init__.py +16 -0
- handanim/core/animation.py +106 -0
- handanim/core/cache.py +90 -0
- handanim/core/draw_ops.py +571 -0
- handanim/core/drawable.py +205 -0
- handanim/core/scene.py +797 -0
- handanim/core/styles.py +103 -0
- handanim/core/tikz_renderer.py +290 -0
- handanim/core/utils.py +144 -0
- handanim/core/viewport.py +59 -0
- handanim/primitives/__init__.py +39 -0
- handanim/primitives/arrow.py +195 -0
- handanim/primitives/curves.py +131 -0
- handanim/primitives/ellipse.py +291 -0
- handanim/primitives/eraser.py +58 -0
- handanim/primitives/flowchart.py +382 -0
- handanim/primitives/hershey_constants.py +269 -0
- handanim/primitives/lines.py +164 -0
- handanim/primitives/math.py +379 -0
- handanim/primitives/polygons.py +202 -0
- handanim/primitives/raster_image.py +102 -0
- handanim/primitives/table.py +265 -0
- handanim/primitives/text.py +236 -0
- handanim/primitives/vector_svg.py +172 -0
- handanim/stylings/__init__.py +0 -0
- handanim/stylings/color.py +52 -0
- handanim/stylings/fillpatterns.py +209 -0
- handanim/stylings/fonts.py +40 -0
- handanim/stylings/strokes.py +88 -0
- handanim/stylings/utils.py +140 -0
- handanim-0.1.0.dist-info/LICENSE +21 -0
- handanim-0.1.0.dist-info/METADATA +243 -0
- handanim-0.1.0.dist-info/RECORD +47 -0
- handanim-0.1.0.dist-info/WHEEL +4 -0
- handanim_ai/__init__.py +0 -0
- handanim_ai/models.py +42 -0
- handanim_ai/prompts/big_picture.txt +44 -0
- handanim_ai/prompts/codegen.txt +116 -0
handanim/__init__.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from .core import (
|
|
2
|
+
AnimationEvent,
|
|
3
|
+
AnimationEventType,
|
|
4
|
+
CompositeAnimationEvent,
|
|
5
|
+
Drawable,
|
|
6
|
+
DrawableGroup,
|
|
7
|
+
FillStyle,
|
|
8
|
+
Scene,
|
|
9
|
+
SketchStyle,
|
|
10
|
+
StrokeStyle,
|
|
11
|
+
)
|
|
12
|
+
from .primitives import (
|
|
13
|
+
Arrow,
|
|
14
|
+
Circle,
|
|
15
|
+
Curve,
|
|
16
|
+
CurvedArrow,
|
|
17
|
+
Ellipse,
|
|
18
|
+
Eraser,
|
|
19
|
+
Flowchart,
|
|
20
|
+
FlowchartConnector,
|
|
21
|
+
FlowchartDiamond,
|
|
22
|
+
FlowchartNode,
|
|
23
|
+
Line,
|
|
24
|
+
LinearPath,
|
|
25
|
+
Math,
|
|
26
|
+
NGon,
|
|
27
|
+
Polygon,
|
|
28
|
+
Rectangle,
|
|
29
|
+
RoundedRectangle,
|
|
30
|
+
RoundedSquare,
|
|
31
|
+
Square,
|
|
32
|
+
Table,
|
|
33
|
+
TableRevealEvent,
|
|
34
|
+
Text,
|
|
35
|
+
VectorSVG,
|
|
36
|
+
)
|
|
37
|
+
from .animations import (
|
|
38
|
+
CameraAnimation,
|
|
39
|
+
ColorTransitionAnimation,
|
|
40
|
+
FadeInAnimation,
|
|
41
|
+
FadeOutAnimation,
|
|
42
|
+
RotateAnimation,
|
|
43
|
+
SketchAnimation,
|
|
44
|
+
TranslateFromAnimation,
|
|
45
|
+
TranslateToAnimation,
|
|
46
|
+
ZoomInAnimation,
|
|
47
|
+
ZoomOutAnimation,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
# Core
|
|
52
|
+
"Scene",
|
|
53
|
+
"StrokeStyle",
|
|
54
|
+
"SketchStyle",
|
|
55
|
+
"FillStyle",
|
|
56
|
+
"Drawable",
|
|
57
|
+
"DrawableGroup",
|
|
58
|
+
"AnimationEvent",
|
|
59
|
+
"AnimationEventType",
|
|
60
|
+
"CompositeAnimationEvent",
|
|
61
|
+
# Primitives
|
|
62
|
+
"Line",
|
|
63
|
+
"LinearPath",
|
|
64
|
+
"Arrow",
|
|
65
|
+
"CurvedArrow",
|
|
66
|
+
"Curve",
|
|
67
|
+
"Rectangle",
|
|
68
|
+
"RoundedRectangle",
|
|
69
|
+
"Square",
|
|
70
|
+
"RoundedSquare",
|
|
71
|
+
"Polygon",
|
|
72
|
+
"NGon",
|
|
73
|
+
"Ellipse",
|
|
74
|
+
"Circle",
|
|
75
|
+
"Text",
|
|
76
|
+
"Math",
|
|
77
|
+
"Eraser",
|
|
78
|
+
"VectorSVG",
|
|
79
|
+
"FlowchartNode",
|
|
80
|
+
"FlowchartDiamond",
|
|
81
|
+
"FlowchartConnector",
|
|
82
|
+
"Flowchart",
|
|
83
|
+
"Table",
|
|
84
|
+
"TableRevealEvent",
|
|
85
|
+
# Animations
|
|
86
|
+
"SketchAnimation",
|
|
87
|
+
"FadeInAnimation",
|
|
88
|
+
"FadeOutAnimation",
|
|
89
|
+
"ZoomInAnimation",
|
|
90
|
+
"ZoomOutAnimation",
|
|
91
|
+
"TranslateFromAnimation",
|
|
92
|
+
"TranslateToAnimation",
|
|
93
|
+
"RotateAnimation",
|
|
94
|
+
"ColorTransitionAnimation",
|
|
95
|
+
"CameraAnimation",
|
|
96
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .camera import CameraAnimation
|
|
2
|
+
from .color_transition import ColorTransitionAnimation
|
|
3
|
+
from .fade import FadeInAnimation, FadeOutAnimation
|
|
4
|
+
from .rotate import RotateAnimation
|
|
5
|
+
from .sketch import SketchAnimation
|
|
6
|
+
from .translate import TranslateFromAnimation, TranslateToAnimation
|
|
7
|
+
from .zoom import ZoomInAnimation, ZoomOutAnimation
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"SketchAnimation",
|
|
11
|
+
"FadeInAnimation",
|
|
12
|
+
"FadeOutAnimation",
|
|
13
|
+
"ZoomInAnimation",
|
|
14
|
+
"ZoomOutAnimation",
|
|
15
|
+
"TranslateFromAnimation",
|
|
16
|
+
"TranslateToAnimation",
|
|
17
|
+
"RotateAnimation",
|
|
18
|
+
"ColorTransitionAnimation",
|
|
19
|
+
"CameraAnimation",
|
|
20
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
|
|
2
|
+
from ..core.animation import AnimationEvent, AnimationEventType
|
|
3
|
+
from ..core.draw_ops import OpsSet
|
|
4
|
+
from ..core.viewport import Viewport
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CameraAnimation(AnimationEvent):
|
|
8
|
+
"""
|
|
9
|
+
Animates the Scene viewport (pan and/or zoom) over time.
|
|
10
|
+
|
|
11
|
+
Instead of touching any drawable's OpsSet, this animation changes the world
|
|
12
|
+
ranges that the Viewport maps to the screen — the "camera" moves, not the content.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
start_time: When the animation begins (seconds).
|
|
16
|
+
duration: Length of the animation (seconds).
|
|
17
|
+
easing_fun: Optional easing function applied to progress.
|
|
18
|
+
data: Dict with optional keys:
|
|
19
|
+
- "from_xrange" (tuple[float, float]): World x range at progress=0.
|
|
20
|
+
Defaults to wherever the camera currently is.
|
|
21
|
+
- "from_yrange" (tuple[float, float]): World y range at progress=0.
|
|
22
|
+
Defaults to wherever the camera currently is.
|
|
23
|
+
- "to_xrange" (tuple[float, float]): World x range at progress=1.
|
|
24
|
+
Defaults to from_xrange (no movement on x).
|
|
25
|
+
- "to_yrange" (tuple[float, float]): World y range at progress=1.
|
|
26
|
+
Defaults to from_yrange (no movement on y).
|
|
27
|
+
|
|
28
|
+
Usage::
|
|
29
|
+
|
|
30
|
+
scene.add_camera(CameraAnimation(
|
|
31
|
+
start_time=5, duration=3,
|
|
32
|
+
data={
|
|
33
|
+
"to_xrange": (400, 800),
|
|
34
|
+
"to_yrange": (200, 600),
|
|
35
|
+
}
|
|
36
|
+
))
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
|
|
40
|
+
super().__init__(AnimationEventType.MUTATION, start_time, duration, easing_fun, data)
|
|
41
|
+
|
|
42
|
+
def _apply(self, opsset: OpsSet, progress: float) -> OpsSet:
|
|
43
|
+
# CameraAnimation operates on the Viewport, not on OpsSets.
|
|
44
|
+
# Returning the opsset unchanged makes it safe if mistakenly used with scene.add().
|
|
45
|
+
return opsset
|
|
46
|
+
|
|
47
|
+
def apply_to_viewport(self, current: Viewport, progress: float) -> Viewport:
|
|
48
|
+
"""
|
|
49
|
+
Return a new Viewport interpolated toward the target world ranges.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
current: The viewport state immediately before this event (used as
|
|
53
|
+
the from_* default when not explicitly specified).
|
|
54
|
+
progress: Animation progress from 0.0 to 1.0.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
A new Viewport instance with interpolated world ranges.
|
|
58
|
+
"""
|
|
59
|
+
from_xrange: tuple[float, float] = self.data.get("from_xrange", current.world_xrange)
|
|
60
|
+
from_yrange: tuple[float, float] = self.data.get("from_yrange", current.world_yrange)
|
|
61
|
+
to_xrange: tuple[float, float] = self.data.get("to_xrange", from_xrange)
|
|
62
|
+
to_yrange: tuple[float, float] = self.data.get("to_yrange", from_yrange)
|
|
63
|
+
|
|
64
|
+
new_xrange = (
|
|
65
|
+
(1 - progress) * from_xrange[0] + progress * to_xrange[0],
|
|
66
|
+
(1 - progress) * from_xrange[1] + progress * to_xrange[1],
|
|
67
|
+
)
|
|
68
|
+
new_yrange = (
|
|
69
|
+
(1 - progress) * from_yrange[0] + progress * to_yrange[0],
|
|
70
|
+
(1 - progress) * from_yrange[1] + progress * to_yrange[1],
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return Viewport(
|
|
74
|
+
world_xrange=new_xrange,
|
|
75
|
+
world_yrange=new_yrange,
|
|
76
|
+
screen_width=current.screen_width,
|
|
77
|
+
screen_height=current.screen_height,
|
|
78
|
+
margin=current.margin,
|
|
79
|
+
)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
from ..core.animation import AnimationEvent, AnimationEventType
|
|
3
|
+
from ..core.draw_ops import Ops, OpsSet, OpsType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _lerp_color(
|
|
7
|
+
a: tuple[float, float, float],
|
|
8
|
+
b: tuple[float, float, float],
|
|
9
|
+
t: float,
|
|
10
|
+
) -> tuple[float, float, float]:
|
|
11
|
+
return tuple((1 - t) * ca + t * cb for ca, cb in zip(a, b, strict=False)) # type: ignore[return-value]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ColorTransitionAnimation(AnimationEvent):
|
|
15
|
+
"""
|
|
16
|
+
Interpolates every SET_PEN color in an OpsSet from `start_color` to `end_color`.
|
|
17
|
+
|
|
18
|
+
At progress=0 all strokes are rendered with `start_color`; at progress=1 they
|
|
19
|
+
use `end_color`. The fill color (if present) is interpolated identically.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
start_time: When the animation begins (seconds).
|
|
23
|
+
duration: Length of the animation (seconds).
|
|
24
|
+
easing_fun: Optional easing function.
|
|
25
|
+
data: Dict with keys:
|
|
26
|
+
- "start_color" (tuple[float,float,float]): RGB at progress 0. Required.
|
|
27
|
+
- "end_color" (tuple[float,float,float]): RGB at progress 1. Required.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
|
|
31
|
+
super().__init__(AnimationEventType.MUTATION, start_time, duration, easing_fun, data)
|
|
32
|
+
self.start_color: tuple[float, float, float] = self.data.get("start_color", (0.0, 0.0, 0.0))
|
|
33
|
+
self.end_color: tuple[float, float, float] = self.data.get("end_color", (1.0, 1.0, 1.0))
|
|
34
|
+
|
|
35
|
+
def _apply(self, opsset: OpsSet, progress: float) -> OpsSet:
|
|
36
|
+
current_color = _lerp_color(self.start_color, self.end_color, progress)
|
|
37
|
+
new_ops = []
|
|
38
|
+
for op in opsset.opsset:
|
|
39
|
+
if op.type == OpsType.SET_PEN:
|
|
40
|
+
pen_data = dict(op.data).copy()
|
|
41
|
+
if "color" in pen_data:
|
|
42
|
+
pen_data["color"] = current_color
|
|
43
|
+
new_ops.append(Ops(type=OpsType.SET_PEN, data=pen_data))
|
|
44
|
+
else:
|
|
45
|
+
new_ops.append(op)
|
|
46
|
+
return OpsSet(initial_set=new_ops)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from ..core.animation import AnimationEvent, AnimationEventType
|
|
2
|
+
from ..core.draw_ops import Ops, OpsSet, OpsType
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FadeInAnimation(AnimationEvent):
|
|
6
|
+
"""
|
|
7
|
+
A class representing a fade-in animation event that gradually increases the opacity of elements.
|
|
8
|
+
|
|
9
|
+
This animation applies a progressive opacity change from 0 to 1 over a specified duration,
|
|
10
|
+
creating a smooth fade-in effect for graphical elements.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
start_time (float, optional): The time at which the animation begins. Defaults to 0.
|
|
14
|
+
duration (float, optional): The total duration of the animation. Defaults to 0.
|
|
15
|
+
easing_fun (callable, optional): An optional function to modify the animation's progress curve. Defaults to None.
|
|
16
|
+
data (dict, optional): Additional data associated with the animation. Defaults to None.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
|
|
20
|
+
super().__init__(AnimationEventType.CREATION, start_time, duration, easing_fun, data)
|
|
21
|
+
|
|
22
|
+
def _opsset_apply(self, opsset: OpsSet, progress: float):
|
|
23
|
+
current_ops_list = []
|
|
24
|
+
for op in opsset.opsset:
|
|
25
|
+
if op.type == OpsType.SET_PEN:
|
|
26
|
+
modified_data = {k: progress if k == "opacity" else v for k, v in op.data.items()}
|
|
27
|
+
current_ops_list.append(Ops(type=OpsType.SET_PEN, data=modified_data, partial=op.partial, meta=op.meta))
|
|
28
|
+
elif op.type == OpsType.IMAGE and isinstance(op.data, dict):
|
|
29
|
+
modified_data = dict(op.data)
|
|
30
|
+
modified_data["opacity"] = progress
|
|
31
|
+
current_ops_list.append(Ops(type=OpsType.IMAGE, data=modified_data, partial=op.partial, meta=op.meta))
|
|
32
|
+
else:
|
|
33
|
+
current_ops_list.append(op)
|
|
34
|
+
new_opsset = OpsSet(initial_set=current_ops_list)
|
|
35
|
+
return new_opsset
|
|
36
|
+
|
|
37
|
+
def _apply(self, opsset: OpsSet, progress: float):
|
|
38
|
+
return self._opsset_apply(opsset, progress)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class FadeOutAnimation(FadeInAnimation):
|
|
42
|
+
"""
|
|
43
|
+
A class representing a fade-out animation event that gradually decreases the opacity of elements.
|
|
44
|
+
|
|
45
|
+
This animation applies a progressive opacity change from 1 to 0 over a specified duration,
|
|
46
|
+
creating a smooth fade-out effect for graphical elements.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
start_time (float, optional): The time at which the animation begins. Defaults to 0.
|
|
50
|
+
duration (float, optional): The total duration of the animation. Defaults to 0.
|
|
51
|
+
easing_fun (callable, optional): An optional function to modify the animation's progress curve. Defaults to None.
|
|
52
|
+
data (dict, optional): Additional data associated with the animation. Defaults to None.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
|
|
56
|
+
super().__init__(start_time, duration, easing_fun, data)
|
|
57
|
+
self.type = AnimationEventType.DELETION # override the type
|
|
58
|
+
|
|
59
|
+
def _apply(self, opsset, progress):
|
|
60
|
+
return super()._apply(opsset, 1 - progress)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
|
|
2
|
+
from ..core.animation import AnimationEvent, AnimationEventType
|
|
3
|
+
from ..core.draw_ops import OpsSet
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RotateAnimation(AnimationEvent):
|
|
7
|
+
"""
|
|
8
|
+
Animates a rotation of an OpsSet from 0 to `angle` degrees over the duration.
|
|
9
|
+
|
|
10
|
+
The rotation is applied around the center of gravity by default, or around
|
|
11
|
+
a fixed `center` point if provided in data.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
start_time: When the animation begins (seconds).
|
|
15
|
+
duration: Length of the animation (seconds).
|
|
16
|
+
easing_fun: Optional easing function.
|
|
17
|
+
data: Dict with optional keys:
|
|
18
|
+
- "angle" (float): Total rotation angle in degrees. Default 360.
|
|
19
|
+
- "center" (tuple[float, float]): Fixed pivot point. Default: center of gravity.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
|
|
23
|
+
super().__init__(AnimationEventType.MUTATION, start_time, duration, easing_fun, data)
|
|
24
|
+
self.angle = self.data.get("angle", 360)
|
|
25
|
+
self.center: tuple[float, float] | None = self.data.get("center", None)
|
|
26
|
+
|
|
27
|
+
def _apply(self, opsset: OpsSet, progress: float) -> OpsSet:
|
|
28
|
+
new_opsset = OpsSet(initial_set=opsset.opsset)
|
|
29
|
+
pivot = self.center if self.center is not None else new_opsset.get_center_of_gravity()
|
|
30
|
+
new_opsset.rotate(self.angle * progress, center_of_rotation=pivot)
|
|
31
|
+
return new_opsset
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..core.animation import AnimationEvent, AnimationEventType
|
|
4
|
+
from ..core.draw_ops import Ops, OpsSet, OpsType
|
|
5
|
+
from ..core.styles import FillStyle
|
|
6
|
+
from ..primitives.ellipse import GlowDot
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SketchAnimation(AnimationEvent):
|
|
10
|
+
"""
|
|
11
|
+
A class representing a sketch animation event that progressively renders drawing operations.
|
|
12
|
+
|
|
13
|
+
This animation supports partial rendering of drawing and fill operations with configurable timing
|
|
14
|
+
and optional glowing dot effects. It allows for smooth, incremental visualization of drawing sequences.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
wait_before_fill (float): Delay in seconds before starting fill animation, capped at half the total duration.
|
|
18
|
+
|
|
19
|
+
Methods:
|
|
20
|
+
get_partial_sketch: Calculates a partial OpsSet representing the current sketching progress.
|
|
21
|
+
apply: Applies the sketch animation to a given OpsSet at a specific progress point.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
|
|
25
|
+
super().__init__(AnimationEventType.CREATION, start_time, duration, easing_fun, data)
|
|
26
|
+
if data is None:
|
|
27
|
+
data = {}
|
|
28
|
+
self.wait_before_fill = data.get(
|
|
29
|
+
"wait_before_fill", 0
|
|
30
|
+
) # seconds to wait before starting fill animation, if any
|
|
31
|
+
self.wait_before_fill = min(self.wait_before_fill, self.duration / 2) # maximum wait is half of the duration
|
|
32
|
+
|
|
33
|
+
def get_partial_sketch(self, opsset: OpsSet, progress: float) -> OpsSet:
|
|
34
|
+
"""
|
|
35
|
+
Calculate a partial OpsSet representing the sketching progress of an operation set.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
OpsSet: The full drawn opsset on which to apply partial sketching
|
|
39
|
+
progress (float): The progress of sketching, ranging from 0.0 to 1.0.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
OpsSet: A new OpsSet containing the operations up to the specified progress point,
|
|
43
|
+
with the last operation potentially being partially completed.
|
|
44
|
+
"""
|
|
45
|
+
base_ops = opsset.opsset
|
|
46
|
+
draw_ops_count = 0 # number of opssets used for drawing
|
|
47
|
+
fill_ops_count = 0 # number of opssets used for filling
|
|
48
|
+
fill_mode = False
|
|
49
|
+
for op in base_ops:
|
|
50
|
+
if op.type not in Ops.SETUP_OPS_TYPES:
|
|
51
|
+
if fill_mode:
|
|
52
|
+
fill_ops_count += 1
|
|
53
|
+
else:
|
|
54
|
+
draw_ops_count += 1
|
|
55
|
+
elif op.type is OpsType.METADATA and op.data.get("drawing_mode") == "fill":
|
|
56
|
+
fill_mode = True
|
|
57
|
+
total_ops_count = draw_ops_count + fill_ops_count # total count of drawing ops
|
|
58
|
+
|
|
59
|
+
# calculate per ops how much seconds is assigned
|
|
60
|
+
per_op_time = (self.duration - self.wait_before_fill) / total_ops_count
|
|
61
|
+
|
|
62
|
+
# calculate the drawing and filling times
|
|
63
|
+
draw_end_time = per_op_time * draw_ops_count
|
|
64
|
+
fill_start_time = draw_end_time + self.wait_before_fill
|
|
65
|
+
|
|
66
|
+
# based on progress, find out the total number of ops to be drawn
|
|
67
|
+
if progress * self.duration <= draw_end_time:
|
|
68
|
+
# drawing is not completed yet
|
|
69
|
+
draw_progress = progress * self.duration / draw_end_time
|
|
70
|
+
n_active = int(draw_progress * draw_ops_count)
|
|
71
|
+
elif progress * self.duration <= fill_start_time:
|
|
72
|
+
# drawing is completed, but filling is not started yet
|
|
73
|
+
n_active = draw_ops_count
|
|
74
|
+
else:
|
|
75
|
+
# filling is in progress
|
|
76
|
+
fill_progress = (progress * self.duration - fill_start_time) / (self.duration - fill_start_time)
|
|
77
|
+
n_active = draw_ops_count + int(fill_progress * fill_ops_count)
|
|
78
|
+
|
|
79
|
+
# create a new opsset with the partial ops
|
|
80
|
+
counter = 0
|
|
81
|
+
last_op = None
|
|
82
|
+
new_opsset = OpsSet(initial_set=[]) # initially start with blank opsset
|
|
83
|
+
for op in base_ops:
|
|
84
|
+
if op.type not in Ops.SETUP_OPS_TYPES and counter < n_active:
|
|
85
|
+
new_opsset.add(op)
|
|
86
|
+
counter += 1
|
|
87
|
+
elif counter < n_active:
|
|
88
|
+
# other operations keep adding, but don't increase counter
|
|
89
|
+
new_opsset.add(op)
|
|
90
|
+
else:
|
|
91
|
+
last_op = op # the last operation for which it stopped
|
|
92
|
+
break
|
|
93
|
+
if last_op is not None and (progress * total_ops_count - n_active) > 0:
|
|
94
|
+
# need to calculate it partially
|
|
95
|
+
new_opsset.add(
|
|
96
|
+
Ops(
|
|
97
|
+
type=last_op.type,
|
|
98
|
+
data=last_op.data,
|
|
99
|
+
partial=progress * total_ops_count - n_active,
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
return new_opsset
|
|
103
|
+
|
|
104
|
+
def _apply(self, opsset: OpsSet, progress: float):
|
|
105
|
+
"""
|
|
106
|
+
Apply the sketch animation to an operation set with a given progress.
|
|
107
|
+
|
|
108
|
+
This method progressively renders the operations in the opsset based on the current animation progress,
|
|
109
|
+
with an optional glowing dot effect to highlight the current drawing point.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
opsset (OpsSet): The set of operations to animate
|
|
113
|
+
progress (float): Animation progress from 0.0 to 1.0
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
OpsSet: A new operation set with partially or fully rendered operations
|
|
117
|
+
"""
|
|
118
|
+
new_opsset = OpsSet(initial_set=[])
|
|
119
|
+
if progress > 0:
|
|
120
|
+
sketching_opssets = self.get_partial_sketch(opsset, progress)
|
|
121
|
+
new_opsset.extend(sketching_opssets)
|
|
122
|
+
# now we can optionally add a glowing dot for the sketching operation
|
|
123
|
+
if self.data.get("glowing_dot") and progress < 1:
|
|
124
|
+
# the glowing dot should disappear at the end of the sketch
|
|
125
|
+
glow_dot_data = self.data.get("glowing_dot")
|
|
126
|
+
if not isinstance(glow_dot_data, dict):
|
|
127
|
+
glow_dot_data = {}
|
|
128
|
+
# we need to draw glowing dot
|
|
129
|
+
cx, cy = sketching_opssets.get_current_point() # get the current point based on sketching
|
|
130
|
+
|
|
131
|
+
breathing_factor = 1 + 0.05 * np.sin(
|
|
132
|
+
2 * np.pi * progress * glow_dot_data.get("frequency", 5)
|
|
133
|
+
) # have a subtle breathing effect to increase or decrease glow
|
|
134
|
+
dot = GlowDot(
|
|
135
|
+
center=(cx, cy),
|
|
136
|
+
radius=glow_dot_data.get("radius", 5) * breathing_factor,
|
|
137
|
+
fill_style=FillStyle(color=glow_dot_data.get("color", (0.5, 0.5, 0.5))),
|
|
138
|
+
)
|
|
139
|
+
new_opsset.extend(dot.draw()) # add this dot at the end of current path
|
|
140
|
+
else:
|
|
141
|
+
# progress is 0, so nothing should be drawn
|
|
142
|
+
pass
|
|
143
|
+
return new_opsset
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from ..core.animation import AnimationEvent, AnimationEventType
|
|
2
|
+
from ..core.draw_ops import OpsSet
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TranslateToAnimation(AnimationEvent):
|
|
6
|
+
"""
|
|
7
|
+
A class representing a translate to a point animation event.
|
|
8
|
+
|
|
9
|
+
This animation translates an OpsSet from its current center of gravity to a specified point
|
|
10
|
+
over the course of the animation's duration, using an optional easing function.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
start_time (float, optional): The start time of the animation. Defaults to 0.
|
|
14
|
+
duration (float, optional): The duration of the animation. Defaults to 0.
|
|
15
|
+
easing_fun (callable, optional): An optional easing function to modify animation progress. Defaults to None.
|
|
16
|
+
data (dict, optional): A dictionary containing animation data, including the target 'point'. Defaults to None.
|
|
17
|
+
|
|
18
|
+
Methods:
|
|
19
|
+
_opsset_apply: Calculates and applies the translation of the OpsSet.
|
|
20
|
+
apply: Applies the translation to the given OpsSet at the specified progress.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
|
|
24
|
+
super().__init__(
|
|
25
|
+
AnimationEventType.MUTATION, start_time, duration, easing_fun, data
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def _opsset_apply(self, opsset: OpsSet, progress: float):
|
|
29
|
+
new_opsset = OpsSet(initial_set=opsset.opsset)
|
|
30
|
+
# calculate the center of gravity for the opsset
|
|
31
|
+
gravity_x, gravity_y = opsset.get_center_of_gravity()
|
|
32
|
+
point_x, point_y = self.data.get("point", (0, 0))
|
|
33
|
+
cur_x, cur_y = (
|
|
34
|
+
progress * point_x + (1 - progress) * gravity_x,
|
|
35
|
+
progress * point_y + (1 - progress) * gravity_y,
|
|
36
|
+
)
|
|
37
|
+
new_opsset.translate(cur_x - gravity_x, cur_y - gravity_y)
|
|
38
|
+
return new_opsset
|
|
39
|
+
|
|
40
|
+
def _apply(self, opsset: OpsSet, progress: float):
|
|
41
|
+
return self._opsset_apply(opsset, progress)
|
|
42
|
+
|
|
43
|
+
class TranslateFromAnimation(TranslateToAnimation):
|
|
44
|
+
"""
|
|
45
|
+
A class representing a translate from a point animation event.
|
|
46
|
+
|
|
47
|
+
This animation translates an OpsSet from a specified point to its current center of gravity
|
|
48
|
+
over the course of the animation's duration, using an optional easing function.
|
|
49
|
+
|
|
50
|
+
Inherits from TranslateToAnimation and reverses the progress to achieve the "from" translation effect.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
start_time (float, optional): The start time of the animation. Defaults to 0.
|
|
54
|
+
duration (float, optional): The duration of the animation. Defaults to 0.
|
|
55
|
+
easing_fun (callable, optional): An optional easing function to modify animation progress. Defaults to None.
|
|
56
|
+
data (dict, optional): A dictionary containing animation data, including the starting 'point'. Defaults to None.
|
|
57
|
+
|
|
58
|
+
Methods:
|
|
59
|
+
apply: Applies the translation from the specified point to the OpsSet's center of gravity.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, start_time=0, duration=0, easing_fun=None, data=None):
|
|
63
|
+
super().__init__(start_time, duration, easing_fun, data)
|
|
64
|
+
|
|
65
|
+
def _apply(self, opsset, progress):
|
|
66
|
+
return super()._apply(opsset, 1 - progress)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from ..core.animation import AnimationEvent, AnimationEventType
|
|
2
|
+
from ..core.draw_ops import OpsSet
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ZoomInAnimation(AnimationEvent):
|
|
6
|
+
"""
|
|
7
|
+
A class representing a zoom-in animation event that scales an OpsSet progressively.
|
|
8
|
+
|
|
9
|
+
This animation scales the operations set from its original size to a larger size
|
|
10
|
+
based on the provided progress value. It is typically used for creating or
|
|
11
|
+
expanding visual elements.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
start_time (int, optional): The start time of the animation. Defaults to 0.
|
|
15
|
+
duration (int, optional): The duration of the animation. Defaults to 0.
|
|
16
|
+
easing_fun (callable, optional): An optional easing function to modify the animation progress. Defaults to None.
|
|
17
|
+
data (Any, optional): Additional data associated with the animation. Defaults to None.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
|
|
21
|
+
super().__init__(AnimationEventType.CREATION, start_time, duration, easing_fun, data)
|
|
22
|
+
|
|
23
|
+
def _apply_opsset(self, opsset: OpsSet, progress: float):
|
|
24
|
+
new_opsset = OpsSet(initial_set=opsset.opsset)
|
|
25
|
+
new_opsset.scale(progress)
|
|
26
|
+
return new_opsset
|
|
27
|
+
|
|
28
|
+
def _apply(self, opsset, progress):
|
|
29
|
+
return self._apply_opsset(opsset, progress)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ZoomOutAnimation(ZoomInAnimation):
|
|
33
|
+
"""
|
|
34
|
+
A class representing a zoom-out animation event that scales an OpsSet progressively.
|
|
35
|
+
|
|
36
|
+
This animation scales the operations set from its current size to a smaller size
|
|
37
|
+
based on the provided progress value. It is typically used for shrinking or
|
|
38
|
+
removing visual elements.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
start_time (int, optional): The start time of the animation. Defaults to 0.
|
|
42
|
+
duration (int, optional): The duration of the animation. Defaults to 0.
|
|
43
|
+
easing_fun (callable, optional): An optional easing function to modify the animation progress. Defaults to None.
|
|
44
|
+
data (Any, optional): Additional data associated with the animation. Defaults to None.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, start_time=0.0, duration=0.0, easing_fun=None, data=None):
|
|
48
|
+
super().__init__(start_time, duration, easing_fun, data)
|
|
49
|
+
self.type = AnimationEventType.DELETION
|
|
50
|
+
|
|
51
|
+
def _apply(self, opsset, progress):
|
|
52
|
+
return super()._apply(opsset, 1 - progress)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .animation import AnimationEvent, AnimationEventType, CompositeAnimationEvent
|
|
2
|
+
from .drawable import Drawable, DrawableGroup
|
|
3
|
+
from .scene import Scene
|
|
4
|
+
from .styles import FillStyle, SketchStyle, StrokeStyle
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"AnimationEvent",
|
|
8
|
+
"AnimationEventType",
|
|
9
|
+
"Scene",
|
|
10
|
+
"StrokeStyle",
|
|
11
|
+
"SketchStyle",
|
|
12
|
+
"FillStyle",
|
|
13
|
+
"Drawable",
|
|
14
|
+
"DrawableGroup",
|
|
15
|
+
"CompositeAnimationEvent",
|
|
16
|
+
]
|