Skip to content

Spin, step 2: Spinning

In step 1 the sheet turned once, back to rest. In this step it spins twice about the vertical axis and lands facing you again.

3D chapters used: Rotation, Animating in 3D.

Update the module

Replace myspin.py with this version:

myspin.py
"""MySpin, tutorial step 2: the sheet spins.

The text spins twice about the vertical axis and ends facing the viewer. Later steps build it up into the Spin
effect.
"""

from __future__ import annotations

from dataclasses import dataclass

from terminaltexteffects import easing
from terminaltexteffects.engine.base_config import BaseConfig
from terminaltexteffects.engine.base_effect import BaseEffect, BaseEffectIterator
from terminaltexteffects.utils import argutils


def get_effect_resources() -> tuple[str, type[BaseEffect], type[BaseConfig]]:
    """Return the command name, effect class, and configuration class TTE registers."""
    return "myspin", MySpin, MySpinConfig


@dataclass
class MySpinConfig(BaseConfig):
    """Configuration for the MySpin effect. It has no options yet."""

    parser_spec = argutils.ParserSpec(
        name="myspin",
        help="The text tumbles in from the distance as a flat sheet, spins, and settles into place.",
        description="myspin | The text tumbles in from the distance as a flat sheet, spins, and settles into place.",
        epilog="Example: tte myspin",
    )


class MySpinIterator(BaseEffectIterator[MySpinConfig]):
    """Produce the frames of the MySpin effect."""

    def __init__(self, effect: MySpin) -> None:
        """Queue the text plane's spin."""
        super().__init__(effect)
        self.world = self.terminal.world
        self.build()

    def build(self) -> None:
        """Show every visible character and queue the spin."""
        world = self.world
        world.camera.set_perspective(0)
        characters = [character for character in self.terminal.get_characters() if character.input_symbol != " "]
        for character in characters:
            self.terminal.set_character_visibility(character, is_visible=True)
            self.active_characters.add(character)
        # The text plane is the body every character starts on, so spinning it spins the text.
        self.sheet = world.text_plane
        self.sheet.spin("y", turns=2, frames=240, ease=easing.in_out_sine)

    def __next__(self) -> str:
        """Move the text plane one frame; the characters on it move with it."""
        if self.world.animating or self.active_characters:
            self.update()
            return self.frame
        raise StopIteration


class MySpin(BaseEffect[MySpinConfig]):
    """The text tumbles in from the distance as a flat sheet, spins, and settles into place."""

    @property
    def _config_cls(self) -> type[MySpinConfig]:
        return MySpinConfig

    @property
    def _iterator_cls(self) -> type[MySpinIterator]:
        return MySpinIterator

What changed

The rotate and turn_to_rest calls are replaced with one call:

self.sheet.spin("y", turns=2, frames=240, ease=easing.in_out_sine)

spin turns the body about an axis by whole or partial turns, spread over frames. Two whole turns end where they started, so the text lands at rest. in_out_sine starts slowly, speeds up and slows down again.

A spin turns about the world's axis, not the body's own, because the text plane's parent is the world's root. That doesn't matter yet, because the sheet isn't tilted. It will in step 5.

Run it

tte --input-file concept_input.txt --canvas-width 80 --canvas-height 24 --anchor-text c myspin

MySpin after step 2: the text squeezes to a line and widens again, twice

Halfway through each turn the sheet faces away, and you read the text mirrored. Step 4 deals with the back side.

Next step

Step 3 adds perspective, and starts the sheet far behind the canvas.