{ "cells": [ { "cell_type": "markdown", "id": "substantial-impact", "metadata": {}, "source": [ "# Vítej!" ] }, { "cell_type": "markdown", "id": "first-armenia", "metadata": {}, "source": [ "Tento dokument obsahuje zdrojové kódy animací ke druhé sérii seriálu KSP. Před spouštěním opět nezapomeň Manim importovat spuštěním následujícího řádku!" ] }, { "cell_type": "code", "execution_count": null, "id": "e03b150c", "metadata": {}, "outputs": [], "source": [ "from manim import *" ] }, { "cell_type": "markdown", "id": "45897e9c", "metadata": {}, "source": [ "## Práce se skupinami objektů" ] }, { "cell_type": "code", "execution_count": null, "id": "wound-foundation", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh VGroupExample\n", "\n", "\n", "class VGroupExample(Scene):\n", " def construct(self):\n", " s1 = Square(color=RED)\n", " s2 = Square(color=GREEN)\n", " s3 = Square(color=BLUE)\n", "\n", " s1.next_to(s2, LEFT)\n", " s3.next_to(s2, RIGHT)\n", "\n", " self.play(Write(s1), Write(s2), Write(s3))\n", "\n", " group = VGroup(s1, s2, s3)\n", "\n", " # aplikace škálování na celou skupinu\n", " self.play(group.animate.scale(1.5).shift(UP))\n", "\n", " # na skupině můžeme indexovat\n", " self.play(group[1].animate.shift(DOWN * 2))\n", "\n", " # změna barvy se aplikuje na všechny objekty\n", " self.play(group.animate.set_color(WHITE))\n", " self.play(group.animate.set_fill(WHITE, 1))" ] }, { "cell_type": "code", "execution_count": null, "id": "659287fa", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh ArrangeExample\n", "\n", "from random import seed, uniform\n", "\n", "\n", "class ArrangeExample(Scene):\n", " def construct(self):\n", " seed(0xDEADBEEF)\n", "\n", " # používáme *, protože VGroup bere samotné objekty (viz minulý příklad)\n", " circles = VGroup(\n", " *[\n", " Circle(radius=0.1)\n", " .scale(uniform(0.5, 4))\n", " .shift(UP * uniform(-3, 3) + RIGHT * uniform(-5, 5))\n", " for _ in range(12)\n", " ]\n", " )\n", "\n", " self.play(FadeIn(circles))\n", "\n", " # uspořádání vedle sebe\n", " self.play(circles.animate.arrange())\n", "\n", " # různé odsazení a směry\n", " self.play(circles.animate.arrange(direction=DOWN, buff=0.1))\n", " self.play(circles.animate.arrange(buff=0.4))" ] }, { "cell_type": "code", "execution_count": null, "id": "ff1ab3fd", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh ArrangeInGridExample\n", "\n", "from random import seed, uniform\n", "\n", "\n", "class ArrangeInGridExample(Scene):\n", " def construct(self):\n", " seed(0xDEADBEEF)\n", "\n", " circles = VGroup(\n", " *[\n", " Circle(radius=0.1)\n", " .scale(uniform(0.5, 2))\n", " .shift(UP * uniform(-3, 3) + RIGHT * uniform(-5, 5))\n", " for _ in range(9 ** 2)\n", " ]\n", " )\n", "\n", " self.play(FadeIn(circles))\n", "\n", " # uspořádání do mřížky\n", " self.play(circles.animate.arrange_in_grid())\n", "\n", " # různé odsazení a počet řádků/sloupců\n", " self.play(circles.animate.arrange_in_grid(rows=5, buff=0))\n", " self.play(circles.animate.arrange_in_grid(cols=12, buff=0.3))" ] }, { "cell_type": "markdown", "id": "fc6ccd7e", "metadata": {}, "source": [ "## Přidávání a odebírání objektů" ] }, { "cell_type": "code", "execution_count": null, "id": "68f328dd", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh AddRemoveExample\n", "\n", "\n", "class AddRemoveExample(Scene):\n", " def construct(self):\n", " square = Square(fill_color=WHITE, fill_opacity=1)\n", " small_scale = 0.6\n", "\n", " triangle = Triangle(fill_opacity=1).scale(small_scale).move_to(square)\n", "\n", " self.play(Write(square))\n", "\n", " # přidání trojúhelníku pod čtverec\n", " self.bring_to_back(triangle)\n", " self.play(square.animate.shift(LEFT * 2))\n", "\n", " circle = Circle(fill_opacity=1).scale(small_scale).move_to(square)\n", "\n", " # přidání kruhu pod čtverec\n", " self.bring_to_back(circle)\n", " self.play(square.animate.shift(RIGHT * 2))\n", "\n", " square2 = (\n", " Square(stroke_color=GREEN, fill_color=GREEN, fill_opacity=1)\n", " .scale(small_scale)\n", " .move_to(square)\n", " )\n", " \n", " self.remove(triangle)\n", " \n", " # stejné jako self.add\n", " # přidání dopředu tu spíš nechceme, ale je dobré vidět co dělá\n", " self.bring_to_front(square2)\n", "\n", " self.play(square.animate.shift(RIGHT * 2))" ] }, { "cell_type": "markdown", "id": "07e6819f", "metadata": {}, "source": [ "## Překrývající-se animace" ] }, { "cell_type": "code", "execution_count": null, "id": "d63a1c09", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh AnimationGroupExample\n", "\n", "\n", "class AnimationGroupExample(Scene):\n", " def construct(self):\n", " c1 = Square(color=RED)\n", " c2 = Square(color=GREEN)\n", " c3 = Square(color=BLUE)\n", "\n", " VGroup(c1, c2, c3).arrange(buff=1)\n", "\n", " # každá další animace se spustí v polovině té předchozí (0.5)\n", " self.play(AnimationGroup(Write(c1), Write(c2), Write(c3), lag_ratio=0.5))\n", "\n", " # každá další animace se spustí v desetině té předchozí (0.1)\n", " self.play(AnimationGroup(FadeOut(c1), FadeOut(c2), FadeOut(c3), lag_ratio=0.1))\n", "\n", " # jedna z animací může být rovněž skupina, která může mít sama o sobě zpoždění\n", " self.play(\n", " AnimationGroup(\n", " AnimationGroup(Write(c1), Write(c2), lag_ratio=0.1),\n", " Write(c3),\n", " lag_ratio=0.5,\n", " )\n", " )\n", "\n", " # lag_ratio může být i záporné (animace se budou spouštět obráceně)\n", " self.play(AnimationGroup(FadeOut(c1), FadeOut(c2), FadeOut(c3), lag_ratio=-0.1))" ] }, { "cell_type": "code", "execution_count": null, "id": "4412a01b", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh VGroupLagRatioExample\n", "\n", "\n", "class VGroupLagRatioExample(Scene):\n", " def construct(self):\n", " squares = VGroup(Square(), Square(), Square()).arrange(buff=0.5).scale(1.5)\n", " \n", " # postupné vykreslení čtverců\n", " self.play(Write(squares))\n", "\n", " # FadeOut lag_ratio má nulové, animace se vykonají najednou\n", " self.play(FadeOut(squares))\n", "\n", " squares.set_color(BLUE)\n", " \n", " # lag_ratio můžeme manuálně nastavit tak, aby se čtverce vykreslily najednou \n", " self.play(Write(squares, lag_ratio=0))\n", "\n", " self.play(FadeOut(squares, lag_ratio=0.5))" ] }, { "cell_type": "markdown", "id": "1fa02f58", "metadata": {}, "source": [ "## Práce s pozorností" ] }, { "cell_type": "code", "execution_count": null, "id": "a3d83c22", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh AttentionExample\n", "\n", "\n", "class AttentionExample(Scene):\n", " def construct(self):\n", " c1 = Square()\n", "\n", " labels = [\n", " Tex(\"Flash\"),\n", " Tex(\"Indicate\"),\n", " Tex(\"Wiggle\"),\n", " Tex(\"FocusOn\"),\n", " Tex(\"Circumscribe\"),\n", " ]\n", "\n", " # labely posuneme dolů (pod čtverec)\n", " for label in labels:\n", " label.shift(DOWN * 1.5).scale(1.5)\n", "\n", " def switch_labels(i: int):\n", " \"\"\"Animace přeměny jednoho labelu na druhého.\"\"\"\n", " return AnimationGroup(\n", " FadeOut(labels[i], shift=UP * 0.7),\n", " FadeIn(labels[i + 1], shift=UP * 0.7),\n", " )\n", "\n", " self.play(Write(c1))\n", "\n", " self.play(FadeIn(labels[0], shift=UP * 0.5), c1.animate.shift(UP))\n", "\n", " # Flash\n", " self.play(Flash(c1, flash_radius=1.6, num_lines=20))\n", "\n", " # Indicate\n", " self.play(AnimationGroup(switch_labels(0), Indicate(c1), lag_ratio=0.7))\n", "\n", " # Wiggle\n", " self.play(AnimationGroup(switch_labels(1), Wiggle(c1), lag_ratio=0.7))\n", "\n", " # FocusOn\n", " self.play(AnimationGroup(switch_labels(2), FocusOn(c1), lag_ratio=0.7))\n", "\n", " # Circumscribe\n", " self.play(AnimationGroup(switch_labels(3), Circumscribe(c1), lag_ratio=0.7))" ] }, { "cell_type": "markdown", "id": "a9d9d8db", "metadata": {}, "source": [ "## Transformace" ] }, { "cell_type": "code", "execution_count": null, "id": "e2510a0d", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh BasicTransformExample\n", "\n", "\n", "class BasicTransformExample(Scene):\n", " def construct(self):\n", " c = Circle().scale(2)\n", " s = Square().scale(2)\n", "\n", " self.play(Write(c))\n", "\n", " self.play(Transform(c, s))" ] }, { "cell_type": "code", "execution_count": null, "id": "8561d8a8", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh BadTransformExample\n", "\n", "\n", "class BadTransformExample(Scene):\n", " def construct(self):\n", " good = [Circle(color=GREEN), Square(color=GREEN), Triangle(color=GREEN)]\n", " bad = [Circle(color=RED), Square(color=RED), Triangle(color=RED)]\n", "\n", " # uspořádání do mřížky - nahoře dobré, dole špatné\n", " VGroup(*(good + bad)).arrange_in_grid(rows=2, buff=1)\n", "\n", " self.play(Write(good[0]), Write(bad[0]))\n", "\n", " self.play(\n", " Transform(good[0], good[1]),\n", " Transform(bad[0], bad[1]),\n", " )\n", "\n", " self.play(\n", " Transform(good[0], good[2]),\n", " Transform(bad[1], bad[2]),\n", " )" ] }, { "cell_type": "code", "execution_count": null, "id": "0eda62fe", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh TransformMatchingShapesExample\n", "\n", "\n", "class TransformMatchingShapesExample(Scene):\n", " def construct(self):\n", " ksp_matching = Tex(\"KSP\").scale(5)\n", " ksp_full_matching = Tex(\"Korespondenční Seminář z Programování\")\n", "\n", " ksp_regular = ksp_matching.copy().set_color(BLUE)\n", " ksp_full_regular = ksp_full_matching.copy().set_color(BLUE)\n", "\n", " VGroup(ksp_matching, ksp_regular).arrange(direction=DOWN, buff=1)\n", " ksp_full_matching.move_to(ksp_matching)\n", " ksp_full_regular.move_to(ksp_regular)\n", "\n", " self.play(Write(ksp_matching), Write(ksp_regular))\n", "\n", " self.play(\n", " TransformMatchingShapes(ksp_matching, ksp_full_matching),\n", " Transform(ksp_regular, ksp_full_regular),\n", " )" ] }, { "cell_type": "markdown", "id": "310fdca9", "metadata": {}, "source": [ "## Updatery" ] }, { "cell_type": "code", "execution_count": null, "id": "84c90216", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh SimpleUpdaterExample\n", "\n", "\n", "class SimpleUpdaterExample(Scene):\n", " def construct(self):\n", " square = Square()\n", " square_label = Tex(\"A neat square.\").next_to(square, UP, buff=0.5)\n", "\n", " self.play(Write(square))\n", " self.play(FadeIn(square_label, shift=UP * 0.5))\n", "\n", " def label_updater(obj):\n", " \"\"\"Updater, který posune objekt nad čtverec.\n", "\n", " První parametr (obj) je vždy objekt, na který je updater přidaný.\"\"\"\n", " obj.next_to(square, UP, buff=0.5)\n", "\n", " # popisek čtverce chceme mít fixně nad čtvercem\n", " square_label.add_updater(label_updater)\n", "\n", " # vždy zůstává nad čtvercem\n", " self.play(square.animate.shift(LEFT * 3))\n", " self.play(square.animate.scale(1 / 2))\n", " self.play(square.animate.rotate(PI / 2).shift(RIGHT * 3 + DOWN * 0.5).scale(3))\n", "\n", " # odstranění updateru můžeme udělat přes remove_updater\n", " square_label.remove_updater(label_updater)\n", " self.play(square.animate.scale(1 / 3))\n", " self.play(square.animate.rotate(PI / 2))" ] }, { "cell_type": "code", "execution_count": null, "id": "f0a57559", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh BecomeUpdaterExample\n", "\n", "\n", "class BecomeUpdaterExample(Scene):\n", " def format_point(self, point):\n", " \"\"\"Formátování dané souřadnice na [x, y].\"\"\"\n", " return f\"[{point[0]:.2f}, {point[1]:.2f}]\"\n", "\n", " def construct(self):\n", " circle = Circle(color=WHITE)\n", "\n", " def circle_label_updater(obj):\n", " \"\"\"Updater pro label, který jej posouvá nad bod a nastavuje jeho text.\"\"\"\n", " obj.become(Tex(f\"p = {self.format_point(circle.get_center())}\"))\n", " obj.next_to(circle, UP, buff=0.35)\n", "\n", " self.play(Write(circle))\n", "\n", " circle_label = Tex()\n", "\n", " # používáme tu trochu trik k šetření kódu - updater voláme proto,\n", " # abychom nastavili popisek na základní hodnotu a pozici\n", " circle_label_updater(circle_label)\n", "\n", " self.play(FadeIn(circle_label, shift=UP * 0.3))\n", "\n", " circle_label.add_updater(circle_label_updater)\n", "\n", " # tato animace se bude pravděpodobně renderovat dlouho, protože\n", " # updater v každém snímku vytváří Tex objekt, což nějakou dobu trvá\n", " self.play(circle.animate.shift(RIGHT))\n", " self.play(circle.animate.shift(LEFT * 3 + UP))\n", " self.play(circle.animate.shift(DOWN * 2 + RIGHT * 2))\n", " self.play(circle.animate.shift(UP))" ] }, { "cell_type": "markdown", "id": "98daca2b", "metadata": {}, "source": [ "## Kostry úloh" ] }, { "cell_type": "markdown", "id": "8746ae11", "metadata": {}, "source": [ "### Trojúhelník [3b]" ] }, { "cell_type": "code", "execution_count": null, "id": "18fb5e35", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh Triangle\n", "\n", "\n", "class Triangle(Scene):\n", " def construct(self):\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "id": "e9471651", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh LineExample\n", "\n", "\n", "class LineExample(Scene):\n", " def construct(self):\n", " p1 = Dot()\n", " p2 = Dot()\n", " \n", " points = VGroup(p1, p2).arrange(buff=2.5)\n", " \n", " line = Line(start=p1.get_center(), end=p2.get_center())\n", " \n", " self.play(Write(p1), Write(p2))\n", " \n", " self.play(Write(line))" ] }, { "cell_type": "code", "execution_count": null, "id": "7bddd3e5", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh CircleFromPointsExample\n", "\n", "\n", "class CircleFromPointsExample(Scene):\n", " def construct(self):\n", " p1 = Dot().shift(LEFT + UP)\n", " p2 = Dot().shift(DOWN * 1.5)\n", " p3 = Dot().shift(RIGHT + UP)\n", " \n", " dots = VGroup(p1, p2, p3)\n", " \n", " # vytvoření kruhu ze tří bodů\n", " circle = Circle.from_three_points(p1.get_center(), p2.get_center(), p3.get_center(), color=WHITE)\n", " \n", " self.play(Write(dots), run_time=1.5)\n", " self.play(Write(circle))" ] }, { "cell_type": "markdown", "id": "4de93509", "metadata": {}, "source": [ "### Vlna [6b]" ] }, { "cell_type": "code", "execution_count": null, "id": "49bb1f67", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh Wave\n", "\n", "\n", "class Wave(Scene):\n", " def construct(self):\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "id": "f48d3810", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh ColorGradientExample\n", "\n", "\n", "class ColorGradientExample(Scene):\n", " def construct(self):\n", " rows = 6\n", " square_count = rows * 9\n", "\n", " colors = [\"#ef476f\", \"#ffd166\", \"#06d6a0\", \"#118ab2\"]\n", " squares = [\n", " Square(fill_color=WHITE, fill_opacity=1).scale(0.3)\n", " for _ in range(square_count)\n", " ]\n", "\n", " group = VGroup(*squares).arrange_in_grid(rows=rows)\n", "\n", " self.play(Write(group, lag_ratio=0.04))\n", "\n", " all_colors = color_gradient(colors, square_count)\n", "\n", " self.play(\n", " AnimationGroup(\n", " *[s.animate.set_color(all_colors[i]) for i, s in enumerate(squares)],\n", " lag_ratio=0.02,\n", " )\n", " )" ] }, { "cell_type": "markdown", "id": "159f8abb", "metadata": {}, "source": [ "### Hilbert [6b]" ] }, { "cell_type": "code", "execution_count": null, "id": "d95d2328", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh Hilbert\n", "\n", "\n", "class Path(VMobject):\n", " def __init__(self, points, *args, **kwargs):\n", " super().__init__(self, *args, **kwargs)\n", " self.set_points_as_corners(points)\n", "\n", "\n", "class Hilbert(Scene):\n", " def construct(self):\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "id": "1a1bfc52", "metadata": {}, "outputs": [], "source": [ "%%manim -v WARNING -qh PathExample\n", "\n", "\n", "class Path(VMobject):\n", " def __init__(self, points, *args, **kwargs):\n", " super().__init__(self, *args, **kwargs)\n", " self.set_points_as_corners(points)\n", " \n", " def get_important_points(self):\n", " \"\"\"Vrátí důležité body křivky.\"\"\"\n", " # drobné vysvětlení: Manim k vytváření úseček používá kvadratické Bézierovy křivky\n", " # - každá taková křivka má čtyři body -- dva krajní a dva řidicí\n", " # - path.points vrací *všechny* body, což po několika iteracích roste exponenciálně\n", " # \n", " # proto používáme funkce get_*_anchors, které vrací pouze krajní body\n", " # pro více detailů viz https://en.wikipedia.org/wiki/Bézier_curve\n", " return list(self.get_start_anchors()) + [self.get_end_anchors()[-1]]\n", "\n", "\n", "class PathExample(Scene):\n", " def construct(self):\n", " path = Path([LEFT + UP, LEFT + DOWN, RIGHT + UP, RIGHT + DOWN])\n", "\n", " self.play(Write(path))\n", "\n", " # opraveno po vydání seriálu, předtím jsme používali path.points]), vysvětlení viz výše\n", " path_points = VGroup(*[Dot().move_to(point) for point in path.get_important_points()])\n", " \n", " self.play(Write(path_points))\n", " \n", " path2 = path.copy()\n", " path3 = path.copy()\n", "\n", " self.play(\n", " path2.animate.next_to(path, LEFT, buff=1),\n", " path3.animate.next_to(path, RIGHT, buff=1),\n", " )\n", " \n", " # pozor, tohle není úplně intuitivní\n", " # LEFT flipne dolů, jelikož určuje osu, přes kterou se objekt přetočí\n", " self.play(\n", " path2.animate.flip(),\n", " path3.animate.flip(LEFT),\n", " )" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.1" } }, "nbformat": 4, "nbformat_minor": 5 }