Can you make an animated video of dental bridge

Asking for Preferences
Running Command

mkdir -p /home/daytona/workspace/dental-bridge-video && ls /home/daytona/workspace/dental-bridge-video

Writing File

~/dental-bridge-video/dental_bridge_anim.py

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch, Arc, Wedge, FancyArrowPatch
from matplotlib.animation import FFMpegWriter
import matplotlib.animation as animation
from matplotlib.patches import PathPatch
from matplotlib.path import Path
import matplotlib.patheffects as pe

# ── colour palette ────────────────────────────────────────────────────────────
BG        = "#1a1a2e"
ENAMEL    = "#f0e6d3"
DENTIN    = "#e8d5a3"
PULP      = "#d4785a"
GUM_PINK  = "#e8a0a0"
GUM_DARK  = "#c06060"
CROWN_COL = "#d4af37"
CROWN_HL  = "#f5d060"
PONTIC    = "#b8860b"
ABUTMENT  = "#c0a030"
IMPLANT   = "#8899aa"
WHITE     = "#ffffff"
LIGHT     = "#ccddff"
ACCENT    = "#88ccff"
WARN      = "#ff9944"
GREEN     = "#55dd88"
TITLE_COL = "#ffd700"

FIG_W, FIG_H = 12, 7

# ─────────────────────────────────────────────────────────────────────────────
# HELPER DRAWING FUNCTIONS
# ─────────────────────────────────────────────────────────────────────────────

def clear_ax(ax):
    ax.cla()
    ax.set_facecolor(BG)
    ax.set_xlim(0, 10)
    ax.set_ylim(0, 7)
    ax.axis("off")


def add_bg_gradient(ax):
    """Subtle top-to-bottom gradient via a rectangle."""
    grad = plt.matplotlib.patches.Rectangle(
        (0, 0), 10, 7, linewidth=0,
        facecolor="#0d0d1e", zorder=0)
    ax.add_patch(grad)


def draw_title_bar(ax, title, subtitle=""):
    ax.add_patch(FancyBboxPatch((0.2, 6.1), 9.6, 0.75,
        boxstyle="round,pad=0.05", facecolor="#222244",
        edgecolor=TITLE_COL, linewidth=1.5, zorder=5))
    ax.text(5, 6.55, title, ha="center", va="center",
        fontsize=16, fontweight="bold", color=TITLE_COL, zorder=6)
    if subtitle:
        ax.text(5, 6.18, subtitle, ha="center", va="center",
            fontsize=9, color=LIGHT, zorder=6)


def draw_tooth(ax, cx, cy, w=0.7, h=1.2, color=ENAMEL,
               roots=2, label=None, alpha=1.0, crown_highlight=False):
    """Draw a stylised molar: crown + root(s)."""
    # crown body
    crown = FancyBboxPatch((cx - w/2, cy), w, h * 0.55,
        boxstyle="round,pad=0.04",
        facecolor=color, edgecolor="#888866",
        linewidth=1.2, alpha=alpha, zorder=3)
    ax.add_patch(crown)
    if crown_highlight:
        hl = FancyBboxPatch((cx - w/2 + 0.05, cy + h*0.55 - 0.15), w - 0.1, 0.12,
            boxstyle="round,pad=0.02",
            facecolor=CROWN_HL, edgecolor="none",
            alpha=0.5 * alpha, zorder=4)
        ax.add_patch(hl)
    # cusp bumps
    for bx in [cx - w*0.22, cx + w*0.22]:
        ax.add_patch(mpatches.Ellipse(
            (bx, cy + h*0.55 + 0.02), 0.18, 0.12,
            facecolor=color, edgecolor="#888866",
            linewidth=1, alpha=alpha, zorder=3))
    # roots
    root_xs = [cx - w*0.2, cx + w*0.2] if roots == 2 else [cx]
    for rx in root_xs:
        root_pts = [
            (rx - 0.1, cy),
            (rx - 0.08, cy - h * 0.45),
            (rx,       cy - h * 0.5),
            (rx + 0.08, cy - h * 0.45),
            (rx + 0.1,  cy),
        ]
        codes = [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.LINETO]
        ax.add_patch(PathPatch(Path(root_pts, codes),
            facecolor=DENTIN, edgecolor="#888866",
            linewidth=1, alpha=alpha, zorder=2))
    # dentin inner
    ax.add_patch(FancyBboxPatch((cx - w/2 + 0.08, cy + 0.04), w - 0.16, h*0.38,
        boxstyle="round,pad=0.03",
        facecolor=DENTIN, edgecolor="none",
        alpha=0.6 * alpha, zorder=4))
    if label:
        ax.text(cx, cy - h * 0.55, label, ha="center", va="top",
            fontsize=8, color=LIGHT, alpha=alpha)


def draw_gum(ax, x_left, x_right, y_top, y_bot=0.4, color=GUM_PINK,
             gap_cx=None, gap_w=0.0, alpha=1.0):
    """Draw a gum band, optionally with a gap (missing tooth area)."""
    if gap_cx is None or gap_w == 0:
        pts = [(x_left, y_top), (x_right, y_top),
               (x_right, y_bot), (x_left, y_bot)]
        codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO]
        ax.add_patch(PathPatch(
            Path(pts + [(x_left, y_top)], codes + [Path.CLOSEPOLY]),
            facecolor=color, edgecolor=GUM_DARK,
            linewidth=1.2, alpha=alpha, zorder=1))
    else:
        g0 = gap_cx - gap_w / 2
        g1 = gap_cx + gap_w / 2
        for xl, xr in [(x_left, g0), (g1, x_right)]:
            if xr > xl:
                pts = [(xl, y_top), (xr, y_top),
                       (xr, y_bot), (xl, y_bot)]
                codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO]
                ax.add_patch(PathPatch(
                    Path(pts + [(xl, y_top)], codes + [Path.CLOSEPOLY]),
                    facecolor=color, edgecolor=GUM_DARK,
                    linewidth=1.2, alpha=alpha, zorder=1))
        # socket area (darker)
        ax.add_patch(FancyBboxPatch(
            (g0, y_bot), gap_w, y_top - y_bot,
            boxstyle="square,pad=0",
            facecolor="#b06868", edgecolor=GUM_DARK,
            linewidth=1, alpha=0.7 * alpha, zorder=1))


def draw_crown(ax, cx, cy, w=0.76, h=0.7, color=CROWN_COL,
               label=None, alpha=1.0):
    """Gold dental crown."""
    body = FancyBboxPatch((cx - w/2, cy), w, h,
        boxstyle="round,pad=0.04",
        facecolor=color, edgecolor="#aa8800",
        linewidth=1.5, alpha=alpha, zorder=5)
    ax.add_patch(body)
    # highlight streak
    ax.add_patch(FancyBboxPatch(
        (cx - w/2 + 0.06, cy + h * 0.55), w - 0.12, 0.1,
        boxstyle="round,pad=0.02",
        facecolor=CROWN_HL, edgecolor="none",
        alpha=0.55 * alpha, zorder=6))
    # cusp bumps
    for bx in [cx - w*0.22, cx + w*0.22]:
        ax.add_patch(mpatches.Ellipse(
            (bx, cy + h + 0.02), 0.18, 0.10,
            facecolor=color, edgecolor="#aa8800",
            linewidth=1, alpha=alpha, zorder=5))
    if label:
        ax.text(cx, cy + h + 0.18, label, ha="center", va="bottom",
            fontsize=8, fontweight="bold", color=TITLE_COL, alpha=alpha,
            bbox=dict(fc="#111133", ec="none", pad=2))


def draw_bridge_bar(ax, x0, x1, y, h=0.12, color=CROWN_COL, alpha=1.0):
    """Horizontal bar connecting crowns in the bridge."""
    ax.add_patch(FancyBboxPatch(
        (x0, y), x1 - x0, h,
        boxstyle="round,pad=0.03",
        facecolor=color, edgecolor="#aa8800",
        linewidth=1.5, alpha=alpha, zorder=4))
    # shine line
    ax.add_patch(FancyBboxPatch(
        (x0 + 0.05, y + h * 0.6), x1 - x0 - 0.1, h * 0.2,
        boxstyle="round,pad=0.01",
        facecolor=CROWN_HL, edgecolor="none",
        alpha=0.45 * alpha, zorder=5))


def fade_text(ax, x, y, text, t, start=0, end=1, **kw):
    alpha = np.clip((t - start) / max(end - start, 0.01), 0, 1)
    if alpha > 0:
        ax.text(x, y, text, alpha=float(alpha), **kw)
    return alpha


def bullet(ax, x, y, text, color=LIGHT, fs=10):
    ax.text(x - 0.3, y, "•", ha="center", va="center",
        fontsize=fs, color=ACCENT)
    ax.text(x, y, text, ha="left", va="center",
        fontsize=fs, color=color)


# ─────────────────────────────────────────────────────────────────────────────
# SCENE RENDERERS  (t = 0.0 … 1.0 within the scene)
# ─────────────────────────────────────────────────────────────────────────────

def scene_title(ax, t):
    """Scene 0 – title card."""
    clear_ax(ax)
    add_bg_gradient(ax)

    # animated circle logo
    r = 0.6 + 0.1 * np.sin(t * 2 * np.pi)
    circle = plt.Circle((5, 4.2), r, color="#2244aa", zorder=2)
    ax.add_patch(circle)
    # tooth silhouette in circle
    draw_tooth(ax, 5, 3.85, w=0.55, h=0.8, color=WHITE, roots=2, alpha=min(t*3,1))

    # title
    a1 = min(t * 2, 1)
    ax.text(5, 5.4, "DENTAL BRIDGE", ha="center", va="center",
        fontsize=26, fontweight="bold", color=TITLE_COL,
        alpha=a1, zorder=8)
    ax.text(5, 4.95, "A Complete Visual Guide", ha="center", va="center",
        fontsize=13, color=LIGHT, alpha=a1, zorder=8)

    # sub-items fade in
    items = [
        (5, 2.2, "What is a dental bridge?"),
        (5, 1.75, "Types & components"),
        (5, 1.30, "Step-by-step procedure"),
    ]
    for i, (x, y, txt) in enumerate(items):
        a = max(0, min((t - 0.3 - i * 0.15) * 3, 1))
        ax.text(x, y, txt, ha="center", va="center",
            fontsize=10, color=ACCENT, alpha=a,
            bbox=dict(fc="#112244", ec=ACCENT, pad=4, alpha=a))

    ax.text(5, 0.5, "Educational Animation", ha="center", va="center",
        fontsize=8, color="#555577", alpha=min(t*2,1))


def scene_missing_tooth(ax, t):
    """Scene 1 – show a gap in the mouth."""
    clear_ax(ax)
    add_bg_gradient(ax)
    draw_title_bar(ax, "The Problem: Missing Tooth",
                   "A gap in your smile affects function and appearance")

    # gum
    draw_gum(ax, 0.5, 9.5, 2.0, y_bot=0.5,
             gap_cx=5.0, gap_w=1.2)

    # teeth on sides (fade in)
    a = min(t * 2, 1)
    positions = [1.2, 2.2, 3.2, 6.8, 7.8, 8.8]
    for px in positions:
        draw_tooth(ax, px, 2.05, w=0.75, h=1.1, color=ENAMEL, alpha=a)

    # gap highlight arrow + label
    ag = max(0, min((t - 0.4) * 3, 1))
    if ag > 0:
        ax.annotate("", xy=(5.0, 2.7), xytext=(5.0, 3.5),
            arrowprops=dict(arrowstyle="-|>", color=WARN,
                            lw=2, mutation_scale=18),
            alpha=ag, zorder=10)
        ax.text(5.0, 3.75, "Missing Tooth", ha="center", va="bottom",
            fontsize=12, fontweight="bold", color=WARN, alpha=ag,
            bbox=dict(fc="#331100", ec=WARN, pad=4))

    # consequences
    cons = [
        (2.5, 5.3, "Shifting of adjacent teeth"),
        (5.0, 5.3, "Bone loss in jaw"),
        (7.5, 5.3, "Difficulty chewing"),
    ]
    for i, (x, y, txt) in enumerate(cons):
        ac = max(0, min((t - 0.55 - i*0.1)*3, 1))
        ax.add_patch(FancyBboxPatch((x-1.1, y-0.22), 2.2, 0.45,
            boxstyle="round,pad=0.04",
            facecolor="#330011", edgecolor=WARN,
            linewidth=1, alpha=ac, zorder=5))
        ax.text(x, y, txt, ha="center", va="center",
            fontsize=9, color=WARN, alpha=ac, zorder=6)


def scene_what_is_bridge(ax, t):
    """Scene 2 – what is a dental bridge."""
    clear_ax(ax)
    add_bg_gradient(ax)
    draw_title_bar(ax, "What Is a Dental Bridge?",
                   "A fixed prosthetic device that literally 'bridges' a gap")

    a = min(t * 2.5, 1)

    # Draw simple arch / gap
    draw_gum(ax, 0.5, 9.5, 2.0, y_bot=0.5,
             gap_cx=5.0, gap_w=1.3)

    # Abutment teeth
    for px in [3.8, 6.2]:
        draw_tooth(ax, px, 2.05, w=0.78, h=1.1, color=ENAMEL, alpha=a)

    # Bridge components label
    ab = max(0, min((t - 0.3)*3, 1))
    # Bridge bar
    draw_bridge_bar(ax, 3.8 - 0.38, 6.2 + 0.38, 2.95, h=0.18,
                    color=CROWN_COL, alpha=ab)
    # Crowns on abutments
    draw_crown(ax, 3.8, 2.95, w=0.78, h=0.6,
               color=CROWN_COL, alpha=ab)
    draw_crown(ax, 6.2, 2.95, w=0.78, h=0.6,
               color=CROWN_COL, alpha=ab)
    # Pontic (middle)
    draw_crown(ax, 5.0, 2.95, w=0.78, h=0.6,
               color=PONTIC, alpha=ab)

    # Labels
    al = max(0, min((t - 0.55)*3, 1))
    label_data = [
        (3.8,  4.1, "Abutment Crown\n(anchor)", CROWN_COL),
        (5.0,  4.1, "Pontic\n(false tooth)", CROWN_HL),
        (6.2,  4.1, "Abutment Crown\n(anchor)", CROWN_COL),
    ]
    for lx, ly, lt, lc in label_data:
        ax.annotate("", xy=(lx, 3.75), xytext=(lx, ly - 0.1),
            arrowprops=dict(arrowstyle="-|>", color=lc,
                            lw=1.5, mutation_scale=12),
            alpha=al, zorder=10)
        ax.text(lx, ly, lt, ha="center", va="bottom",
            fontsize=8.5, color=lc, alpha=al,
            bbox=dict(fc="#111133", ec=lc, pad=3))

    # definition box
    ad = max(0, min((t - 0.7)*4, 1))
    ax.add_patch(FancyBboxPatch((0.6, 0.55), 8.8, 0.65,
        boxstyle="round,pad=0.05",
        facecolor="#112244", edgecolor=ACCENT,
        linewidth=1.5, alpha=ad, zorder=5))
    ax.text(5, 0.87,
        "A bridge consists of two crowns on neighbouring teeth (abutments) "
        "with a false tooth (pontic) in between.",
        ha="center", va="center",
        fontsize=9, color=WHITE, alpha=ad, zorder=6,
        wrap=True)


def scene_types(ax, t):
    """Scene 3 – types of dental bridges."""
    clear_ax(ax)
    add_bg_gradient(ax)
    draw_title_bar(ax, "Types of Dental Bridges")

    types = [
        (1.8,  "Traditional\nBridge",    "#d4af37", "Most common\n2 crowns + pontic"),
        (4.15, "Cantilever\nBridge",      "#7ec8e3", "One anchor\ntooth only"),
        (6.5,  "Maryland\nBridge",        "#a8e6a3", "Metal/ceramic\nwings, no crowns"),
        (8.85, "Implant-\nSupported",     "#f0a0a0", "Implants instead\nof crowns"),
    ]

    for i, (cx, name, col, desc) in enumerate(types):
        a = max(0, min((t - i * 0.15) * 3, 1))

        # card background
        ax.add_patch(FancyBboxPatch((cx - 1.1, 0.7), 2.2, 5.0,
            boxstyle="round,pad=0.08",
            facecolor="#181830", edgecolor=col,
            linewidth=1.5, alpha=a, zorder=3))

        # mini bridge diagram
        if i == 0:  # traditional
            draw_gum(ax, cx-0.9, cx+0.9, 2.2, y_bot=0.8, alpha=a)
            draw_tooth(ax, cx-0.55, 2.22, w=0.55, h=0.8, alpha=a)
            draw_tooth(ax, cx+0.55, 2.22, w=0.55, h=0.8, alpha=a)
            draw_bridge_bar(ax, cx-0.82, cx+0.82, 2.97, h=0.14,
                            color=col, alpha=a)
            draw_crown(ax, cx-0.55, 2.97, w=0.55, h=0.44, color=col, alpha=a)
            draw_crown(ax, cx,      2.97, w=0.55, h=0.44, color=col, alpha=a)
            draw_crown(ax, cx+0.55, 2.97, w=0.55, h=0.44, color=col, alpha=a)

        elif i == 1:  # cantilever
            draw_gum(ax, cx-0.9, cx+0.9, 2.2, y_bot=0.8,
                     gap_cx=cx+0.4, gap_w=0.85, alpha=a)
            draw_tooth(ax, cx-0.45, 2.22, w=0.55, h=0.8, alpha=a)
            draw_bridge_bar(ax, cx-0.72, cx+0.72, 2.97, h=0.14,
                            color=col, alpha=a)
            draw_crown(ax, cx-0.45, 2.97, w=0.55, h=0.44, color=col, alpha=a)
            draw_crown(ax, cx+0.3, 2.97, w=0.55, h=0.44,
                       color="#aabbcc", alpha=a)

        elif i == 2:  # maryland
            draw_gum(ax, cx-0.9, cx+0.9, 2.2, y_bot=0.8,
                     gap_cx=cx, gap_w=0.6, alpha=a)
            draw_tooth(ax, cx-0.55, 2.22, w=0.55, h=0.8, alpha=a)
            draw_tooth(ax, cx+0.55, 2.22, w=0.55, h=0.8, alpha=a)
            # wings
            ax.add_patch(FancyBboxPatch((cx-0.82, 2.70), 0.28, 0.36,
                boxstyle="round,pad=0.02",
                facecolor=col, edgecolor="#558855",
                linewidth=1, alpha=0.7*a, zorder=4))
            ax.add_patch(FancyBboxPatch((cx+0.54, 2.70), 0.28, 0.36,
                boxstyle="round,pad=0.02",
                facecolor=col, edgecolor="#558855",
                linewidth=1, alpha=0.7*a, zorder=4))
            draw_crown(ax, cx, 2.97, w=0.55, h=0.44,
                       color=col, alpha=a)

        else:  # implant
            draw_gum(ax, cx-0.9, cx+0.9, 2.2, y_bot=0.8,
                     gap_cx=cx, gap_w=1.2, alpha=a)
            draw_tooth(ax, cx-0.55, 2.22, w=0.55, h=0.8, alpha=a)
            draw_tooth(ax, cx+0.55, 2.22, w=0.55, h=0.8, alpha=a)
            # implant post
            ax.add_patch(FancyBboxPatch((cx-0.08, 1.2), 0.16, 1.0,
                boxstyle="round,pad=0.02",
                facecolor=IMPLANT, edgecolor="#556677",
                linewidth=1.5, alpha=a, zorder=4))
            ax.add_patch(mpatches.Ellipse((cx, 1.15), 0.2, 0.1,
                facecolor=IMPLANT, edgecolor="#556677",
                linewidth=1, alpha=a, zorder=4))
            draw_crown(ax, cx, 2.97, w=0.55, h=0.44,
                       color=col, alpha=a)

        # name
        ax.text(cx, 3.75, name, ha="center", va="bottom",
            fontsize=9, fontweight="bold", color=col, alpha=a,
            bbox=dict(fc="#111133", ec="none", pad=2))
        # desc
        ax.text(cx, 4.6, desc, ha="center", va="center",
            fontsize=7.5, color=LIGHT, alpha=a,
            multialignment="center")


def scene_procedure(ax, t):
    """Scene 4 – step by step procedure (animated timeline)."""
    clear_ax(ax)
    add_bg_gradient(ax)
    draw_title_bar(ax, "The Procedure: Step by Step")

    steps = [
        (1.2,  4.8,  "#ff9944", "STEP 1",  "Examination\n& X-rays"),
        (3.1,  4.8,  "#ffcc44", "STEP 2",  "Tooth\nPreparation"),
        (5.0,  4.8,  "#88dd55", "STEP 3",  "Impressions\n& Temp Bridge"),
        (6.9,  4.8,  "#44bbff", "STEP 4",  "Lab Fabrication\n(1-2 weeks)"),
        (8.8,  4.8,  "#dd88ff", "STEP 5",  "Final Fitting\n& Cementation"),
    ]

    n_steps = len(steps)
    for i, (sx, sy, col, snum, sdesc) in enumerate(steps):
        progress = t * n_steps - i
        a = float(np.clip(progress * 2, 0, 1))

        # connector line
        if i < n_steps - 1:
            ax.plot([sx + 0.4, steps[i+1][0] - 0.4],
                    [sy, steps[i+1][1]],
                    color="#334466", linewidth=2, alpha=a, zorder=2)

        # circle node
        circle_a = float(np.clip(progress * 3, 0, 1))
        node = plt.Circle((sx, sy), 0.38, color=col,
                           alpha=circle_a, zorder=5)
        ax.add_patch(node)
        ax.text(sx, sy, str(i + 1), ha="center", va="center",
            fontsize=13, fontweight="bold", color="#111122",
            alpha=circle_a, zorder=6)

        label_a = float(np.clip((progress - 0.3) * 3, 0, 1))
        ax.text(sx, sy - 0.65, snum, ha="center", va="top",
            fontsize=7.5, fontweight="bold", color=col,
            alpha=label_a, zorder=6)
        ax.text(sx, sy - 1.0, sdesc, ha="center", va="top",
            fontsize=8, color=LIGHT, alpha=label_a,
            multialignment="center", zorder=6)

    # Bottom animated detail panel
    step_idx = min(int(t * n_steps), n_steps - 1)
    ap = float(np.clip(t * n_steps - step_idx, 0, 1))

    details = [
        "Dentist checks bite, gum health, and takes X-rays to plan treatment.",
        "Abutment teeth are shaped (reduced) under local anaesthesia to fit crowns.",
        "Dental putty impression taken; temporary bridge placed to protect teeth.",
        "A dental lab crafts the permanent porcelain-fused-to-metal bridge.",
        "Temporary bridge removed; permanent bridge checked for fit & cemented.",
    ]
    col = steps[step_idx][2]
    ax.add_patch(FancyBboxPatch((0.4, 0.5), 9.2, 1.0,
        boxstyle="round,pad=0.06",
        facecolor="#0f1f3a", edgecolor=col,
        linewidth=1.5, alpha=ap, zorder=5))
    ax.text(5, 1.0, details[step_idx],
        ha="center", va="center",
        fontsize=9.5, color=WHITE, alpha=ap, zorder=6,
        wrap=True)


def scene_anatomy(ax, t):
    """Scene 5 – labelled cross-section of a bridge."""
    clear_ax(ax)
    add_bg_gradient(ax)
    draw_title_bar(ax, "Bridge Anatomy: Cross-Section View",
                   "Understanding what sits above and below the gum line")

    a_base = min(t * 2, 1)

    # Jaw bone (bottom)
    ax.add_patch(FancyBboxPatch((0.8, 0.3), 8.4, 1.4,
        boxstyle="round,pad=0.08",
        facecolor="#c8a870", edgecolor="#a07840",
        linewidth=1.5, alpha=a_base, zorder=1))
    ax.text(5, 0.95, "Alveolar Bone (Jaw)", ha="center", va="center",
        fontsize=8.5, color="#5a3000", alpha=a_base, fontweight="bold")

    # Gum / gingiva layer
    ax.add_patch(FancyBboxPatch((0.8, 1.7), 8.4, 0.65,
        boxstyle="round,pad=0.03",
        facecolor=GUM_PINK, edgecolor=GUM_DARK,
        linewidth=1.5, alpha=a_base, zorder=2))
    ax.text(0.3, 2.02, "Gingiva\n(Gum)", ha="center", va="center",
        fontsize=7.5, color=GUM_DARK, alpha=a_base)

    # Abutment teeth bodies
    for px in [2.8, 7.2]:
        draw_tooth(ax, px, 2.35, w=0.85, h=1.3,
                   color=ENAMEL, roots=2, alpha=a_base)

    # Crowns
    ac = max(0, min((t - 0.3)*3, 1))
    draw_crown(ax, 2.8, 3.55, w=0.86, h=0.65, color=CROWN_COL, alpha=ac)
    draw_crown(ax, 7.2, 3.55, w=0.86, h=0.65, color=CROWN_COL, alpha=ac)

    # Pontic in middle
    draw_crown(ax, 5.0, 3.55, w=0.86, h=0.65, color=PONTIC, alpha=ac)

    # Bridge connector bar
    draw_bridge_bar(ax, 2.8 - 0.43, 7.2 + 0.43, 4.08, h=0.14,
                    color=CROWN_COL, alpha=ac)

    # Cement layer (thin line under each crown)
    for px in [2.8, 5.0, 7.2]:
        ax.add_patch(FancyBboxPatch((px - 0.4, 3.48), 0.8, 0.09,
            boxstyle="round,pad=0.01",
            facecolor="#f5f5dc", edgecolor="#cccc88",
            linewidth=0.8, alpha=0.7*ac, zorder=7))

    # Annotation arrows
    al = max(0, min((t - 0.55)*3, 1))
    annotations = [
        (2.8,  4.85, "Dental Crown\n(caps abutment)", CROWN_COL, 4.3),
        (5.0,  4.85, "Pontic\n(artificial tooth)", CROWN_HL, 4.3),
        (5.0,  5.75, "Connector\nBar", ACCENT, 4.25),
        (2.1,  2.0,  "Tooth\nStructure", "#ddddaa", 2.6),
        (2.1,  1.0,  "Root in\nBone Socket", "#c8a870", 1.4),
    ]
    for ax_x, ay, label, col, arrow_y in annotations:
        ax.annotate("", xy=(ax_x, arrow_y), xytext=(ax_x, ay - 0.15),
            arrowprops=dict(arrowstyle="-|>", color=col,
                            lw=1.4, mutation_scale=10),
            alpha=al, zorder=10)
        ax.text(ax_x, ay, label, ha="center", va="bottom",
            fontsize=7.5, color=col, alpha=al,
            bbox=dict(fc="#111133", ec=col, pad=2.5),
            multialignment="center", zorder=11)


def scene_care(ax, t):
    """Scene 6 – care and maintenance."""
    clear_ax(ax)
    add_bg_gradient(ax)
    draw_title_bar(ax, "Caring for Your Dental Bridge",
                   "With proper care, a bridge lasts 10-15 years")

    tips = [
        ("#44ccff", "Floss Daily",
         "Use floss threaders or interdental\nbrushes to clean under the pontic"),
        ("#88ee88", "Brush Twice Daily",
         "Use a soft-bristle brush and\nfluoride toothpaste"),
        ("#ffcc55", "Regular Check-ups",
         "Visit your dentist every 6 months\nfor cleaning and inspection"),
        ("#ff9988", "Avoid Hard Foods",
         "Do not bite ice, hard candy or\nnails — can crack the bridge"),
        ("#cc88ff", "Use Mouthwash",
         "Antibacterial rinse helps prevent\ngum disease around abutments"),
        ("#88ddff", "Longevity",
         "Average lifespan: 10-15 years\nImplant-supported: 20+ years"),
    ]

    cols_per_row = 3
    for i, (col, title, desc) in enumerate(tips):
        row = i // cols_per_row
        col_i = i % cols_per_row
        cx = 1.5 + col_i * 3.0
        cy = 4.7 - row * 2.6

        a = max(0, min((t - i * 0.1) * 3, 1))

        ax.add_patch(FancyBboxPatch((cx - 1.2, cy - 0.85), 2.4, 1.75,
            boxstyle="round,pad=0.06",
            facecolor="#111133", edgecolor=col,
            linewidth=1.5, alpha=a, zorder=3))

        # icon circle
        ax.add_patch(plt.Circle((cx, cy + 0.65), 0.3,
            color=col, alpha=0.25 * a, zorder=4))
        ax.text(cx, cy + 0.65, str(i + 1), ha="center", va="center",
            fontsize=11, fontweight="bold", color=col, alpha=a, zorder=5)

        ax.text(cx, cy + 0.2, title, ha="center", va="center",
            fontsize=9.5, fontweight="bold", color=col, alpha=a, zorder=5)
        ax.text(cx, cy - 0.35, desc, ha="center", va="center",
            fontsize=7.8, color=LIGHT, alpha=a, zorder=5,
            multialignment="center")


def scene_summary(ax, t):
    """Scene 7 – summary / outro."""
    clear_ax(ax)
    add_bg_gradient(ax)
    draw_title_bar(ax, "Summary: Dental Bridge at a Glance")

    # animated bridge
    ab = min(t * 3, 1)
    draw_gum(ax, 1.5, 8.5, 2.3, y_bot=0.4, alpha=ab)
    for px in [2.5, 4.0, 7.0, 8.0]:
        draw_tooth(ax, px, 2.32, w=0.78, h=1.1, alpha=ab)
    draw_bridge_bar(ax, 4.38, 6.62, 3.32, h=0.16, color=CROWN_COL, alpha=ab)
    draw_crown(ax, 4.0, 3.32, w=0.78, h=0.6, color=CROWN_COL, alpha=ab)
    draw_crown(ax, 5.5, 3.32, w=0.78, h=0.6, color=PONTIC,    alpha=ab)
    draw_crown(ax, 7.0, 3.32, w=0.78, h=0.6, color=CROWN_COL, alpha=ab)

    facts = [
        "Fixed prosthetic - does NOT need to be removed daily",
        "Restores chewing, speech, and smile aesthetics",
        "Prevents teeth shifting into the gap",
        "Procedure takes 2 appointments over 2-3 weeks",
        "Cost: varies; often partially covered by dental insurance",
    ]
    for i, fact in enumerate(facts):
        a = max(0, min((t - 0.3 - i * 0.1) * 3, 1))
        ax.text(1.2, 5.5 - i * 0.4, f"✓  {fact}",
            ha="left", va="center",
            fontsize=9, color=GREEN, alpha=a, zorder=6)

    # outro
    ao = max(0, min((t - 0.8) * 5, 1))
    ax.text(5, 0.65, "Consult your dentist to find out which bridge is right for you.",
        ha="center", va="center",
        fontsize=10, color=TITLE_COL, alpha=ao,
        bbox=dict(fc="#113300", ec=GREEN, pad=5))


# ─────────────────────────────────────────────────────────────────────────────
# BUILD ANIMATION
# ─────────────────────────────────────────────────────────────────────────────

SCENES = [
    (scene_title,         90,  "Title"),
    (scene_missing_tooth, 90,  "Missing Tooth"),
    (scene_what_is_bridge,100, "What is a Bridge"),
    (scene_types,         110, "Types"),
    (scene_procedure,     120, "Procedure"),
    (scene_anatomy,       110, "Anatomy"),
    (scene_care,          110, "Care"),
    (scene_summary,       90,  "Summary"),
]

TOTAL_FRAMES = sum(f for _, f, _ in SCENES)

fig, ax = plt.subplots(figsize=(FIG_W, FIG_H), dpi=120)
fig.patch.set_facecolor(BG)
plt.subplots_adjust(left=0, right=1, top=1, bottom=0)

def make_frame(frame_idx):
    cumulative = 0
    for renderer, n_frames, name in SCENES:
        if frame_idx < cumulative + n_frames:
            local = frame_idx - cumulative
            t = local / max(n_frames - 1, 1)
            renderer(ax, t)
            # scene label bottom-right
            ax.text(9.8, 0.15, name, ha="right", va="bottom",
                fontsize=6, color="#444466")
            return
        cumulative += n_frames

def animate(i):
    make_frame(i)

FPS = 24
writer = FFMpegWriter(fps=FPS, bitrate=2000,
    extra_args=["-vcodec", "libx264", "-pix_fmt", "yuv420p"])

out_path = "/home/daytona/workspace/dental-bridge-video/dental_bridge.mp4"

print(f"Rendering {TOTAL_FRAMES} frames at {FPS} fps …")
anim = animation.FuncAnimation(fig, animate,
    frames=TOTAL_FRAMES, interval=1000 // FPS)
anim.save(out_path, writer=writer)
plt.close(fig)
print(f"Saved: {out_path}")
Running Command

cat -n /home/daytona/workspace/dental-bridge-video/dental_bridge_anim.py | head -5

Running Command

cat > /home/daytona/workspace/dental-bridge-video/dental_bridge_anim.py << 'PYEOF' import numpy as np import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyBboxPatch, Arc, Wedge from matplotlib.animation import FFMpegWriter import matplotlib.animation as animation from matplotlib.patches import PathPatch from matplotlib.path import Path BG = "#1a1a2e" ENAMEL = "#f0e6d3" DENTIN = "#e8d5a3" GUM_PINK = "#e8a0a0" GUM_DARK = "#c06060" CROWN_COL = "#d4af37" CROWN_HL = "#f5d060" PONTIC = "#b8860b" IMPLANT = "#8899aa" WHITE = "#ffffff" LIGHT = "#ccddff" ACCENT = "#88ccff" WARN = "#ff9944" GREEN = "#55dd88" TITLE_COL = "#ffd700" def clear_ax(ax): ax.cla() ax.set_facecolor(BG) ax.set_xlim(0, 10) ax.set_ylim(0, 7) ax.axis("off") def draw_title_bar(ax, title, subtitle=""): ax.add_patch(FancyBboxPatch((0.2, 6.1), 9.6, 0.75, boxstyle="round,pad=0.05", facecolor="#222244", edgecolor=TITLE_COL, linewidth=1.5, zorder=5)) ax.text(5, 6.55, title, ha="center", va="center", fontsize=16, fontweight="bold", color=TITLE_COL, zorder=6) if subtitle: ax.text(5, 6.18, subtitle, ha="center", va="center", fontsize=9, color=LIGHT, zorder=6) def draw_tooth(ax, cx, cy, w=0.7, h=1.2, color=ENAMEL, roots=2, label=None, alpha=1.0): crown = FancyBboxPatch((cx - w/2, cy), w, h * 0.55, boxstyle="round,pad=0.04", facecolor=color, edgecolor="#888866", linewidth=1.2, alpha=alpha, zorder=3) ax.add_patch(crown) for bx in [cx - w*0.22, cx + w*0.22]: ax.add_patch(mpatches.Ellipse((bx, cy + h*0.55 + 0.02), 0.18, 0.12, facecolor=color, edgecolor="#888866", linewidth=1, alpha=alpha, zorder=3)) root_xs = [cx - w*0.2, cx + w*0.2] if roots == 2 else [cx] for rx in root_xs: root_pts = [(rx-0.1,cy),(rx-0.08,cy-h*0.45),(rx,cy-h*0.5),(rx+0.08,cy-h*0.45),(rx+0.1,cy)] codes = [Path.MOVETO,Path.CURVE4,Path.CURVE4,Path.CURVE4,Path.LINETO] ax.add_patch(PathPatch(Path(root_pts,codes), facecolor=DENTIN, edgecolor="#888866", linewidth=1, alpha=alpha, zorder=2)) ax.add_patch(FancyBboxPatch((cx-w/2+0.08, cy+0.04), w-0.16, h*0.38, boxstyle="round,pad=0.03", facecolor=DENTIN, edgecolor="none", alpha=0.6*alpha, zorder=4)) if label: ax.text(cx, cy-h*0.55, label, ha="center", va="top", fontsize=8, color=LIGHT, alpha=alpha) def draw_gum(ax, x_left, x_right, y_top, y_bot=0.4, color=GUM_PINK, gap_cx=None, gap_w=0.0, alpha=1.0): if gap_cx is None or gap_w == 0: pts = [(x_left,y_top),(x_right,y_top),(x_right,y_bot),(x_left,y_bot),(x_left,y_top)] codes = [Path.MOVETO,Path.LINETO,Path.LINETO,Path.LINETO,Path.CLOSEPOLY] ax.add_patch(PathPatch(Path(pts,codes), facecolor=color, edgecolor=GUM_DARK, linewidth=1.2, alpha=alpha, zorder=1)) else: g0, g1 = gap_cx - gap_w/2, gap_cx + gap_w/2 for xl, xr in [(x_left, g0), (g1, x_right)]: if xr > xl: pts = [(xl,y_top),(xr,y_top),(xr,y_bot),(xl,y_bot),(xl,y_top)] codes = [Path.MOVETO,Path.LINETO,Path.LINETO,Path.LINETO,Path.CLOSEPOLY] ax.add_patch(PathPatch(Path(pts,codes), facecolor=color, edgecolor=GUM_DARK, linewidth=1.2, alpha=alpha, zorder=1)) ax.add_patch(FancyBboxPatch((g0,y_bot), gap_w, y_top-y_bot, boxstyle="square,pad=0", facecolor="#b06868", edgecolor=GUM_DARK, linewidth=1, alpha=0.7*alpha, zorder=1)) def draw_crown(ax, cx, cy, w=0.76, h=0.7, color=CROWN_COL, label=None, alpha=1.0): ax.add_patch(FancyBboxPatch((cx-w/2,cy), w, h, boxstyle="round,pad=0.04", facecolor=color, edgecolor="#aa8800", linewidth=1.5, alpha=alpha, zorder=5)) ax.add_patch(FancyBboxPatch((cx-w/2+0.06,cy+h*0.55), w-0.12, 0.1, boxstyle="round,pad=0.02", facecolor=CROWN_HL, edgecolor="none", alpha=0.55*alpha, zorder=6)) for bx in [cx-w*0.22, cx+w*0.22]: ax.add_patch(mpatches.Ellipse((bx,cy+h+0.02), 0.18, 0.10, facecolor=color, edgecolor="#aa8800", linewidth=1, alpha=alpha, zorder=5)) if label: ax.text(cx, cy+h+0.18, label, ha="center", va="bottom", fontsize=8, fontweight="bold", color=TITLE_COL, alpha=alpha, bbox=dict(fc="#111133", ec="none", pad=2)) def draw_bridge_bar(ax, x0, x1, y, h=0.12, color=CROWN_COL, alpha=1.0): ax.add_patch(FancyBboxPatch((x0,y), x1-x0, h, boxstyle="round,pad=0.03", facecolor=color, edgecolor="#aa8800", linewidth=1.5, alpha=alpha, zorder=4)) ax.add_patch(FancyBboxPatch((x0+0.05,y+h*0.6), x1-x0-0.1, h*0.2, boxstyle="round,pad=0.01", facecolor=CROWN_HL, edgecolor="none", alpha=0.45*alpha, zorder=5)) # ── SCENES ──────────────────────────────────────────────────────────────────── def scene_title(ax, t): clear_ax(ax) r = 0.6 + 0.08*np.sin(t*2*np.pi) ax.add_patch(plt.Circle((5,4.1), r, color="#2244aa", zorder=2)) draw_tooth(ax, 5, 3.75, w=0.55, h=0.8, color=WHITE, roots=2, alpha=min(t*3,1)) a1 = min(t*2, 1) ax.text(5, 5.35, "DENTAL BRIDGE", ha="center", va="center", fontsize=26, fontweight="bold", color=TITLE_COL, alpha=a1, zorder=8) ax.text(5, 4.9, "A Complete Visual Guide", ha="center", va="center", fontsize=13, color=LIGHT, alpha=a1, zorder=8) items = ["What is a dental bridge?", "Types & components", "Step-by-step procedure"] for i, txt in enumerate(items): a = max(0, min((t-0.3-i*0.15)*3, 1)) ax.text(5, 2.2-i*0.45, txt, ha="center", va="center", fontsize=10, color=ACCENT, alpha=a, bbox=dict(fc="#112244", ec=ACCENT, pad=4, alpha=a)) ax.text(5, 0.5, "Educational Animation", ha="center", va="center", fontsize=8, color="#555577", alpha=min(t*2,1)) def scene_missing_tooth(ax, t): clear_ax(ax) draw_title_bar(ax, "The Problem: Missing Tooth", "A gap in your smile affects function and appearance") draw_gum(ax, 0.5, 9.5, 2.0, y_bot=0.5, gap_cx=5.0, gap_w=1.2) a = min(t*2, 1) for px in [1.2, 2.2, 3.2, 6.8, 7.8, 8.8]: draw_tooth(ax, px, 2.05, w=0.75, h=1.1, alpha=a) ag = max(0, min((t-0.4)*3, 1)) if ag > 0: ax.annotate("", xy=(5.0,2.6), xytext=(5.0,3.5), arrowprops=dict(arrowstyle="-|>", color=WARN, lw=2, mutation_scale=18), alpha=ag, zorder=10) ax.text(5.0, 3.75, "Missing Tooth", ha="center", va="bottom", fontsize=12, fontweight="bold", color=WARN, alpha=ag, bbox=dict(fc="#331100", ec=WARN, pad=4)) cons = [(2.5, 5.3, "Shifting adjacent teeth"), (5.0, 5.3, "Bone loss in jaw"), (7.5, 5.3, "Difficulty chewing")] for i, (x, y, txt) in enumerate(cons): ac = max(0, min((t-0.55-i*0.1)*3, 1)) ax.add_patch(FancyBboxPatch((x-1.1,y-0.22), 2.2, 0.45, boxstyle="round,pad=0.04", facecolor="#330011", edgecolor=WARN, linewidth=1, alpha=ac, zorder=5)) ax.text(x, y, txt, ha="center", va="center", fontsize=9, color=WARN, alpha=ac, zorder=6) def scene_what_is_bridge(ax, t): clear_ax(ax) draw_title_bar(ax, "What Is a Dental Bridge?", "A fixed prosthetic that literally 'bridges' a gap") a = min(t*2.5, 1) draw_gum(ax, 0.5, 9.5, 2.0, y_bot=0.5, gap_cx=5.0, gap_w=1.3) for px in [3.8, 6.2]: draw_tooth(ax, px, 2.05, w=0.78, h=1.1, alpha=a) ab = max(0, min((t-0.3)*3, 1)) draw_bridge_bar(ax, 3.42, 6.58, 2.95, h=0.18, color=CROWN_COL, alpha=ab) draw_crown(ax, 3.8, 2.95, w=0.78, h=0.6, color=CROWN_COL, alpha=ab) draw_crown(ax, 5.0, 2.95, w=0.78, h=0.6, color=PONTIC, alpha=ab) draw_crown(ax, 6.2, 2.95, w=0.78, h=0.6, color=CROWN_COL, alpha=ab) al = max(0, min((t-0.55)*3, 1)) label_data = [(3.8,4.2,"Abutment Crown\n(anchor)",CROWN_COL),(5.0,4.2,"Pontic\n(false tooth)",CROWN_HL),(6.2,4.2,"Abutment Crown\n(anchor)",CROWN_COL)] for lx, ly, lt, lc in label_data: ax.annotate("", xy=(lx,3.75), xytext=(lx,ly-0.1), arrowprops=dict(arrowstyle="-|>", color=lc, lw=1.5, mutation_scale=12), alpha=al, zorder=10) ax.text(lx, ly, lt, ha="center", va="bottom", fontsize=8.5, color=lc, alpha=al, bbox=dict(fc="#111133", ec=lc, pad=3), multialignment="center") ad = max(0, min((t-0.7)*4, 1)) ax.add_patch(FancyBboxPatch((0.6,0.55), 8.8, 0.65, boxstyle="round,pad=0.05", facecolor="#112244", edgecolor=ACCENT, linewidth=1.5, alpha=ad, zorder=5)) ax.text(5, 0.87, "Two crowns on neighbouring teeth (abutments) with a false tooth (pontic) between them.", ha="center", va="center", fontsize=9, color=WHITE, alpha=ad, zorder=6) def scene_types(ax, t): clear_ax(ax) draw_title_bar(ax, "Types of Dental Bridges") types = [ (1.8, "Traditional\nBridge", "#d4af37", "Most common\n2 crowns + pontic"), (4.15, "Cantilever\nBridge", "#7ec8e3", "One anchor\ntooth only"), (6.5, "Maryland\nBridge", "#a8e6a3", "Metal wings,\nno crowns"), (8.85, "Implant-\nSupported", "#f0a0a0", "Implants instead\nof crowns"), ] for i, (cx, name, col, desc) in enumerate(types): a = max(0, min((t-i*0.15)*3, 1)) ax.add_patch(FancyBboxPatch((cx-1.1,0.7), 2.2, 5.0, boxstyle="round,pad=0.08", facecolor="#181830", edgecolor=col, linewidth=1.5, alpha=a, zorder=3)) if i == 0: draw_gum(ax, cx-0.9, cx+0.9, 2.2, y_bot=0.8, alpha=a) draw_tooth(ax, cx-0.55, 2.22, w=0.55, h=0.8, alpha=a) draw_tooth(ax, cx+0.55, 2.22, w=0.55, h=0.8, alpha=a) draw_bridge_bar(ax, cx-0.82, cx+0.82, 2.97, h=0.14, color=col, alpha=a) for px2 in [cx-0.55, cx, cx+0.55]: draw_crown(ax, px2, 2.97, w=0.55, h=0.44, color=col, alpha=a) elif i == 1: draw_gum(ax, cx-0.9, cx+0.9, 2.2, y_bot=0.8, gap_cx=cx+0.4, gap_w=0.85, alpha=a) draw_tooth(ax, cx-0.45, 2.22, w=0.55, h=0.8, alpha=a) draw_bridge_bar(ax, cx-0.72, cx+0.72, 2.97, h=0.14, color=col, alpha=a) draw_crown(ax, cx-0.45, 2.97, w=0.55, h=0.44, color=col, alpha=a) draw_crown(ax, cx+0.3, 2.97, w=0.55, h=0.44, color="#aabbcc", alpha=a) elif i == 2: draw_gum(ax, cx-0.9, cx+0.9, 2.2, y_bot=0.8, gap_cx=cx, gap_w=0.6, alpha=a) draw_tooth(ax, cx-0.55, 2.22, w=0.55, h=0.8, alpha=a) draw_tooth(ax, cx+0.55, 2.22, w=0.55, h=0.8, alpha=a) ax.add_patch(FancyBboxPatch((cx-0.82,2.70), 0.28, 0.36, boxstyle="round,pad=0.02", facecolor=col, edgecolor="#558855", linewidth=1, alpha=0.7*a, zorder=4)) ax.add_patch(FancyBboxPatch((cx+0.54,2.70), 0.28, 0.36, boxstyle="round,pad=0.02", facecolor=col, edgecolor="#558855", linewidth=1, alpha=0.7*a, zorder=4)) draw_crown(ax, cx, 2.97, w=0.55, h=0.44, color=col, alpha=a) else: draw_gum(ax, cx-0.9, cx+0.9, 2.2, y_bot=0.8, gap_cx=cx, gap_w=1.2, alpha=a) draw_tooth(ax, cx-0.55, 2.22, w=0.55, h=0.8, alpha=a) draw_tooth(ax, cx+0.55, 2.22, w=0.55, h=0.8, alpha=a) ax.add_patch(FancyBboxPatch((cx-0.08,1.2), 0.16, 1.0, boxstyle="round,pad=0.02", facecolor=IMPLANT, edgecolor="#556677", linewidth=1.5, alpha=a, zorder=4)) ax.add_patch(mpatches.Ellipse((cx,1.15), 0.2, 0.1, facecolor=IMPLANT, edgecolor="#556677", linewidth=1, alpha=a, zorder=4)) draw_crown(ax, cx, 2.97, w=0.55, h=0.44, color=col, alpha=a) ax.text(cx, 3.75, name, ha="center", va="bottom", fontsize=9, fontweight="bold", color=col, alpha=a, bbox=dict(fc="#111133", ec="none", pad=2), multialignment="center") ax.text(cx, 4.55, desc, ha="center", va="center", fontsize=7.5, color=LIGHT, alpha=a, multialignment="center") def scene_procedure(ax, t): clear_ax(ax) draw_title_bar(ax, "The Procedure: Step by Step") steps = [ (1.2, 4.8, "#ff9944", "STEP 1", "Examination\n& X-rays"), (3.1, 4.8, "#ffcc44", "STEP 2", "Tooth\nPreparation"), (5.0, 4.8, "#88dd55", "STEP 3", "Impressions\n& Temp Bridge"), (6.9, 4.8, "#44bbff", "STEP 4", "Lab Fabrication\n(1-2 weeks)"), (8.8, 4.8, "#dd88ff", "STEP 5", "Final Fitting\n& Cementation"), ] n = len(steps) for i, (sx, sy, col, snum, sdesc) in enumerate(steps): progress = t*n - i a = float(np.clip(progress*2, 0, 1)) if i < n-1: ax.plot([sx+0.4, steps[i+1][0]-0.4],[sy,steps[i+1][1]], color="#334466", linewidth=2, alpha=a, zorder=2) ca = float(np.clip(progress*3, 0, 1)) ax.add_patch(plt.Circle((sx,sy), 0.38, color=col, alpha=ca, zorder=5)) ax.text(sx, sy, str(i+1), ha="center", va="center", fontsize=13, fontweight="bold", color="#111122", alpha=ca, zorder=6) la = float(np.clip((progress-0.3)*3, 0, 1)) ax.text(sx, sy-0.65, snum, ha="center", va="top", fontsize=7.5, fontweight="bold", color=col, alpha=la, zorder=6) ax.text(sx, sy-1.0, sdesc, ha="center", va="top", fontsize=8, color=LIGHT, alpha=la, multialignment="center", zorder=6) idx = min(int(t*n), n-1) ap = float(np.clip(t*n-idx, 0, 1)) details = [ "Dentist checks bite, gum health, and takes X-rays to plan treatment.", "Abutment teeth are shaped (reduced) under local anaesthesia to fit crowns.", "Dental putty impression taken; temporary bridge placed to protect teeth.", "A dental lab crafts the permanent porcelain-fused-to-metal bridge.", "Temporary bridge removed; permanent bridge checked for fit & cemented.", ] col = steps[idx][2] ax.add_patch(FancyBboxPatch((0.4,0.5), 9.2, 1.0, boxstyle="round,pad=0.06", facecolor="#0f1f3a", edgecolor=col, linewidth=1.5, alpha=ap, zorder=5)) ax.text(5, 1.0, details[idx], ha="center", va="center", fontsize=9.5, color=WHITE, alpha=ap, zorder=6) def scene_anatomy(ax, t): clear_ax(ax) draw_title_bar(ax, "Bridge Anatomy: Cross-Section", "What sits above and below the gum line") a = min(t*2, 1) ax.add_patch(FancyBboxPatch((0.8,0.3), 8.4, 1.4, boxstyle="round,pad=0.08", facecolor="#c8a870", edgecolor="#a07840", linewidth=1.5, alpha=a, zorder=1)) ax.text(5, 0.95, "Alveolar Bone (Jaw)", ha="center", va="center", fontsize=8.5, color="#5a3000", alpha=a, fontweight="bold") ax.add_patch(FancyBboxPatch((0.8,1.7), 8.4, 0.65, boxstyle="round,pad=0.03", facecolor=GUM_PINK, edgecolor=GUM_DARK, linewidth=1.5, alpha=a, zorder=2)) ax.text(0.3, 2.02, "Gingiva\n(Gum)", ha="center", va="center", fontsize=7.5, color=GUM_DARK, alpha=a) for px in [2.8, 7.2]: draw_tooth(ax, px, 2.35, w=0.85, h=1.3, roots=2, alpha=a) ac = max(0, min((t-0.3)*3, 1)) draw_crown(ax, 2.8, 3.55, w=0.86, h=0.65, color=CROWN_COL, alpha=ac) draw_crown(ax, 7.2, 3.55, w=0.86, h=0.65, color=CROWN_COL, alpha=ac) draw_crown(ax, 5.0, 3.55, w=0.86, h=0.65, color=PONTIC, alpha=ac) draw_bridge_bar(ax, 2.42, 7.58, 4.08, h=0.14, color=CROWN_COL, alpha=ac) for px in [2.8, 5.0, 7.2]: ax.add_patch(FancyBboxPatch((px-0.4,3.48), 0.8, 0.09, boxstyle="round,pad=0.01", facecolor="#f5f5dc", edgecolor="#cccc88", linewidth=0.8, alpha=0.7*ac, zorder=7)) al = max(0, min((t-0.55)*3, 1)) annots = [ (2.8, 4.85, "Dental Crown\n(caps abutment)", CROWN_COL, 4.3), (5.0, 4.85, "Pontic\n(false tooth)", CROWN_HL, 4.3), (5.0, 5.7, "Connector Bar", ACCENT, 4.25), (2.1, 2.1, "Tooth Structure", "#ddddaa", 2.65), (2.1, 1.0, "Root in Bone", "#c8a870", 1.4), ] for ax_x, ay, label, col, arrow_y in annots: ax.annotate("", xy=(ax_x,arrow_y), xytext=(ax_x,ay-0.15), arrowprops=dict(arrowstyle="-|>", color=col, lw=1.4, mutation_scale=10), alpha=al, zorder=10) ax.text(ax_x, ay, label, ha="center", va="bottom", fontsize=7.5, color=col, alpha=al, bbox=dict(fc="#111133", ec=col, pad=2.5), multialignment="center", zorder=11) def scene_care(ax, t): clear_ax(ax) draw_title_bar(ax, "Caring for Your Dental Bridge", "With proper care, a bridge lasts 10-15 years") tips = [ ("#44ccff", "Floss Daily", "Use floss threaders under\nthe pontic daily"), ("#88ee88", "Brush Twice Daily", "Soft brush + fluoride\ntoothpaste"), ("#ffcc55", "Regular Check-ups", "Visit dentist every\n6 months"), ("#ff9988", "Avoid Hard Foods", "No ice, hard candy,\nor nail-biting"), ("#cc88ff", "Use Mouthwash", "Antibacterial rinse\nprotects gums"), ("#88ddff", "Longevity", "Traditional: 10-15 yrs\nImplant: 20+ yrs"), ] for i, (col, title, desc) in enumerate(tips): row, col_i = i//3, i%3 cx = 1.5 + col_i*3.0 cy = 4.8 - row*2.7 a = max(0, min((t-i*0.1)*3, 1)) ax.add_patch(FancyBboxPatch((cx-1.2,cy-0.85), 2.4, 1.75, boxstyle="round,pad=0.06", facecolor="#111133", edgecolor=col, linewidth=1.5, alpha=a, zorder=3)) ax.add_patch(plt.Circle((cx,cy+0.65), 0.28, color=col, alpha=0.3*a, zorder=4)) ax.text(cx, cy+0.65, str(i+1), ha="center", va="center", fontsize=11, fontweight="bold", color=col, alpha=a, zorder=5) ax.text(cx, cy+0.22, title, ha="center", va="center", fontsize=9.5, fontweight="bold", color=col, alpha=a, zorder=5) ax.text(cx, cy-0.35, desc, ha="center", va="center", fontsize=7.8, color=LIGHT, alpha=a, zorder=5, multialignment="center") def scene_summary(ax, t): clear_ax(ax) draw_title_bar(ax, "Summary: Dental Bridge at a Glance") ab = min(t*3, 1) draw_gum(ax, 1.5, 8.5, 2.3, y_bot=0.4, alpha=ab) for px in [2.5, 4.0, 7.0, 8.0]: draw_tooth(ax, px, 2.32, w=0.78, h=1.1, alpha=ab) draw_bridge_bar(ax, 4.38, 6.62, 3.32, h=0.16, color=CROWN_COL, alpha=ab) draw_crown(ax, 4.0, 3.32, w=0.78, h=0.6, color=CROWN_COL, alpha=ab) draw_crown(ax, 5.5, 3.32, w=0.78, h=0.6, color=PONTIC, alpha=ab) draw_crown(ax, 7.0, 3.32, w=0.78, h=0.6, color=CROWN_COL, alpha=ab) facts = [ "Fixed prosthetic - does NOT need to be removed daily", "Restores chewing, speech, and smile aesthetics", "Prevents adjacent teeth from shifting into the gap", "Procedure: 2 appointments over 2-3 weeks", "Average cost varies; often partially covered by dental insurance", ] for i, fact in enumerate(facts): a = max(0, min((t-0.3-i*0.1)*3, 1)) ax.text(1.2, 5.55-i*0.42, f"\u2713 {fact}", ha="left", va="center", fontsize=9, color=GREEN, alpha=a, zorder=6) ao = max(0, min((t-0.8)*5, 1)) ax.text(5, 0.65, "Consult your dentist to find out which bridge is right for you.", ha="center", va="center", fontsize=10, color=TITLE_COL, alpha=ao, bbox=dict(fc="#113300", ec=GREEN, pad=5)) # ── MAIN ───────────────────────────────────────────────────────────────────── SCENES = [ (scene_title, 90, "Title"), (scene_missing_tooth, 90, "Missing Tooth"), (scene_what_is_bridge, 100, "What is a Bridge"), (scene_types, 110, "Types"), (scene_procedure, 120, "Procedure"), (scene_anatomy, 110, "Anatomy"), (scene_care, 110, "Care"), (scene_summary, 90, "Summary"), ] TOTAL_FRAMES = sum(f for _, f, _ in SCENES) FPS = 24 fig, ax = plt.subplots(figsize=(12, 7), dpi=120) fig.patch.set_facecolor(BG) plt.subplots_adjust(left=0, right=1, top=1, bottom=0) def make_frame(fi): cumulative = 0 for renderer, n_frames, name in SCENES: if fi < cumulative + n_frames: local = fi - cumulative t = local / max(n_frames-1, 1) renderer(ax, t) ax.text(9.8, 0.15, name, ha="right", va="bottom", fontsize=6, color="#444466") return cumulative += n_frames writer = FFMpegWriter(fps=FPS, bitrate=2000, extra_args=["-vcodec","libx264","-pix_fmt","yuv420p"]) out = "/home/daytona/workspace/dental-bridge-video/dental_bridge.mp4" print(f"Rendering {TOTAL_FRAMES} frames …") anim = animation.FuncAnimation(fig, make_frame, frames=TOTAL_FRAMES, interval=1000//FPS) anim.save(out, writer=writer) plt.close(fig) print(f"Done -> {out}") PYEOF echo "Script written."

Running Command

cd /home/daytona/workspace/dental-bridge-video && python dental_bridge_anim.py 2>&1

Running Command

ffmpeg -version 2>&1 | head -3; python -c "import matplotlib; print(matplotlib.__version__)"

Running Command

cat > /home/daytona/workspace/dental-bridge-video/dental_bridge_anim.py << 'PYEOF' import numpy as np import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.patches import FancyBboxPatch from matplotlib.animation import FFMpegWriter import matplotlib.animation as animation from matplotlib.patches import PathPatch from matplotlib.path import Path import subprocess, os, tempfile, shutil BG = "#1a1a2e" ENAMEL = "#f0e6d3" DENTIN = "#e8d5a3" GUM_PINK = "#e8a0a0" GUM_DARK = "#c06060" CROWN_COL = "#d4af37" CROWN_HL = "#f5d060" PONTIC = "#b8860b" IMPLANT = "#8899aa" WHITE = "#ffffff" LIGHT = "#ccddff" ACCENT = "#88ccff" WARN = "#ff9944" GREEN = "#55dd88" TITLE_COL = "#ffd700" def clear_ax(ax): ax.cla() ax.set_facecolor(BG) ax.set_xlim(0, 10) ax.set_ylim(0, 7) ax.axis("off") def draw_title_bar(ax, title, subtitle=""): ax.add_patch(FancyBboxPatch((0.2,6.1),9.6,0.75, boxstyle="round,pad=0.05",facecolor="#222244",edgecolor=TITLE_COL,linewidth=1.5,zorder=5)) ax.text(5,6.55,title,ha="center",va="center",fontsize=16,fontweight="bold",color=TITLE_COL,zorder=6) if subtitle: ax.text(5,6.18,subtitle,ha="center",va="center",fontsize=9,color=LIGHT,zorder=6) def draw_tooth(ax,cx,cy,w=0.7,h=1.2,color=ENAMEL,roots=2,label=None,alpha=1.0): ax.add_patch(FancyBboxPatch((cx-w/2,cy),w,h*0.55, boxstyle="round,pad=0.04",facecolor=color,edgecolor="#888866",linewidth=1.2,alpha=alpha,zorder=3)) for bx in [cx-w*0.22,cx+w*0.22]: ax.add_patch(mpatches.Ellipse((bx,cy+h*0.55+0.02),0.18,0.12, facecolor=color,edgecolor="#888866",linewidth=1,alpha=alpha,zorder=3)) rxs=[cx-w*0.2,cx+w*0.2] if roots==2 else [cx] for rx in rxs: rp=[(rx-0.1,cy),(rx-0.08,cy-h*0.45),(rx,cy-h*0.5),(rx+0.08,cy-h*0.45),(rx+0.1,cy)] codes=[Path.MOVETO,Path.CURVE4,Path.CURVE4,Path.CURVE4,Path.LINETO] ax.add_patch(PathPatch(Path(rp,codes),facecolor=DENTIN,edgecolor="#888866",linewidth=1,alpha=alpha,zorder=2)) ax.add_patch(FancyBboxPatch((cx-w/2+0.08,cy+0.04),w-0.16,h*0.38, boxstyle="round,pad=0.03",facecolor=DENTIN,edgecolor="none",alpha=0.6*alpha,zorder=4)) if label: ax.text(cx,cy-h*0.55,label,ha="center",va="top",fontsize=8,color=LIGHT,alpha=alpha) def draw_gum(ax,x0,x1,yt,yb=0.4,color=GUM_PINK,gap_cx=None,gap_w=0.0,alpha=1.0): if gap_cx is None or gap_w==0: ax.add_patch(PathPatch(Path([(x0,yt),(x1,yt),(x1,yb),(x0,yb),(x0,yt)], [Path.MOVETO,Path.LINETO,Path.LINETO,Path.LINETO,Path.CLOSEPOLY]), facecolor=color,edgecolor=GUM_DARK,linewidth=1.2,alpha=alpha,zorder=1)) else: g0,g1=gap_cx-gap_w/2,gap_cx+gap_w/2 for xl,xr in [(x0,g0),(g1,x1)]: if xr>xl: ax.add_patch(PathPatch(Path([(xl,yt),(xr,yt),(xr,yb),(xl,yb),(xl,yt)], [Path.MOVETO,Path.LINETO,Path.LINETO,Path.LINETO,Path.CLOSEPOLY]), facecolor=color,edgecolor=GUM_DARK,linewidth=1.2,alpha=alpha,zorder=1)) ax.add_patch(FancyBboxPatch((g0,yb),gap_w,yt-yb, boxstyle="square,pad=0",facecolor="#b06868",edgecolor=GUM_DARK,linewidth=1,alpha=0.7*alpha,zorder=1)) def draw_crown(ax,cx,cy,w=0.76,h=0.7,color=CROWN_COL,alpha=1.0): ax.add_patch(FancyBboxPatch((cx-w/2,cy),w,h, boxstyle="round,pad=0.04",facecolor=color,edgecolor="#aa8800",linewidth=1.5,alpha=alpha,zorder=5)) ax.add_patch(FancyBboxPatch((cx-w/2+0.06,cy+h*0.55),w-0.12,0.1, boxstyle="round,pad=0.02",facecolor=CROWN_HL,edgecolor="none",alpha=0.55*alpha,zorder=6)) for bx in [cx-w*0.22,cx+w*0.22]: ax.add_patch(mpatches.Ellipse((bx,cy+h+0.02),0.18,0.10, facecolor=color,edgecolor="#aa8800",linewidth=1,alpha=alpha,zorder=5)) def draw_bar(ax,x0,x1,y,h=0.12,color=CROWN_COL,alpha=1.0): ax.add_patch(FancyBboxPatch((x0,y),x1-x0,h, boxstyle="round,pad=0.03",facecolor=color,edgecolor="#aa8800",linewidth=1.5,alpha=alpha,zorder=4)) # ───────── SCENES ───────────────────────────────────────────────────────────── def scene_title(ax,t): clear_ax(ax) ax.add_patch(plt.Circle((5,4.1),0.6+0.08*np.sin(t*6.28),color="#2244aa",zorder=2)) draw_tooth(ax,5,3.75,w=0.55,h=0.8,color=WHITE,alpha=min(t*3,1)) a1=min(t*2,1) ax.text(5,5.35,"DENTAL BRIDGE",ha="center",va="center", fontsize=26,fontweight="bold",color=TITLE_COL,alpha=a1,zorder=8) ax.text(5,4.9,"A Complete Visual Guide",ha="center",va="center", fontsize=13,color=LIGHT,alpha=a1,zorder=8) for i,txt in enumerate(["What is a dental bridge?","Types & components","Step-by-step procedure"]): a=max(0,min((t-0.3-i*0.15)*3,1)) ax.text(5,2.25-i*0.45,txt,ha="center",va="center",fontsize=10,color=ACCENT,alpha=a, bbox=dict(fc="#112244",ec=ACCENT,pad=4,alpha=a)) ax.text(5,0.5,"Educational Animation",ha="center",va="center",fontsize=8,color="#555577",alpha=min(t*2,1)) def scene_missing(ax,t): clear_ax(ax) draw_title_bar(ax,"The Problem: Missing Tooth","A gap affects function, aesthetics, and bone health") draw_gum(ax,0.5,9.5,2.0,yb=0.5,gap_cx=5.0,gap_w=1.2) a=min(t*2,1) for px in [1.2,2.2,3.2,6.8,7.8,8.8]: draw_tooth(ax,px,2.05,w=0.75,h=1.1,alpha=a) ag=max(0,min((t-0.4)*3,1)) if ag>0: ax.annotate("",xy=(5.0,2.6),xytext=(5.0,3.5), arrowprops=dict(arrowstyle="-|>",color=WARN,lw=2,mutation_scale=18),alpha=ag,zorder=10) ax.text(5.0,3.75,"Missing Tooth",ha="center",va="bottom", fontsize=12,fontweight="bold",color=WARN,alpha=ag, bbox=dict(fc="#331100",ec=WARN,pad=4)) for i,(x,y,txt) in enumerate([(2.5,5.3,"Shifting teeth"),(5.0,5.3,"Bone loss"),(7.5,5.3,"Chewing difficulty")]): ac=max(0,min((t-0.55-i*0.1)*3,1)) ax.add_patch(FancyBboxPatch((x-1.05,y-0.22),2.1,0.45, boxstyle="round,pad=0.04",facecolor="#330011",edgecolor=WARN,linewidth=1,alpha=ac,zorder=5)) ax.text(x,y,txt,ha="center",va="center",fontsize=9,color=WARN,alpha=ac,zorder=6) def scene_what(ax,t): clear_ax(ax) draw_title_bar(ax,"What Is a Dental Bridge?","A fixed prosthetic that bridges the gap") a=min(t*2.5,1) draw_gum(ax,0.5,9.5,2.0,yb=0.5,gap_cx=5.0,gap_w=1.3) for px in [3.8,6.2]: draw_tooth(ax,px,2.05,w=0.78,h=1.1,alpha=a) ab=max(0,min((t-0.3)*3,1)) draw_bar(ax,3.42,6.58,2.95,h=0.18,color=CROWN_COL,alpha=ab) draw_crown(ax,3.8,2.95,w=0.78,h=0.6,color=CROWN_COL,alpha=ab) draw_crown(ax,5.0,2.95,w=0.78,h=0.6,color=PONTIC,alpha=ab) draw_crown(ax,6.2,2.95,w=0.78,h=0.6,color=CROWN_COL,alpha=ab) al=max(0,min((t-0.55)*3,1)) for lx,ly,lt,lc in [(3.8,4.2,"Abutment Crown",CROWN_COL),(5.0,4.2,"Pontic (false tooth)",CROWN_HL),(6.2,4.2,"Abutment Crown",CROWN_COL)]: ax.annotate("",xy=(lx,3.75),xytext=(lx,ly-0.1), arrowprops=dict(arrowstyle="-|>",color=lc,lw=1.5,mutation_scale=12),alpha=al,zorder=10) ax.text(lx,ly,lt,ha="center",va="bottom",fontsize=8.5,color=lc,alpha=al, bbox=dict(fc="#111133",ec=lc,pad=3)) ad=max(0,min((t-0.7)*4,1)) ax.add_patch(FancyBboxPatch((0.6,0.55),8.8,0.65, boxstyle="round,pad=0.05",facecolor="#112244",edgecolor=ACCENT,linewidth=1.5,alpha=ad,zorder=5)) ax.text(5,0.87,"Two crowns on anchor teeth + artificial tooth (pontic) = dental bridge", ha="center",va="center",fontsize=9,color=WHITE,alpha=ad,zorder=6) def scene_types(ax,t): clear_ax(ax) draw_title_bar(ax,"Types of Dental Bridges") types=[(1.8,"Traditional","#d4af37","2 crowns + pontic"),(4.15,"Cantilever","#7ec8e3","One anchor only"), (6.5,"Maryland","#a8e6a3","Metal wings"),(8.85,"Implant-\nSupported","#f0a0a0","Implants anchoring")] for i,(cx,name,col,desc) in enumerate(types): a=max(0,min((t-i*0.15)*3,1)) ax.add_patch(FancyBboxPatch((cx-1.1,0.7),2.2,5.0, boxstyle="round,pad=0.08",facecolor="#181830",edgecolor=col,linewidth=1.5,alpha=a,zorder=3)) if i==0: draw_gum(ax,cx-0.9,cx+0.9,2.2,yb=0.8,alpha=a) draw_tooth(ax,cx-0.55,2.22,w=0.55,h=0.8,alpha=a) draw_tooth(ax,cx+0.55,2.22,w=0.55,h=0.8,alpha=a) draw_bar(ax,cx-0.82,cx+0.82,2.97,h=0.14,color=col,alpha=a) for px2 in [cx-0.55,cx,cx+0.55]: draw_crown(ax,px2,2.97,w=0.55,h=0.44,color=col,alpha=a) elif i==1: draw_gum(ax,cx-0.9,cx+0.9,2.2,yb=0.8,gap_cx=cx+0.4,gap_w=0.85,alpha=a) draw_tooth(ax,cx-0.45,2.22,w=0.55,h=0.8,alpha=a) draw_bar(ax,cx-0.72,cx+0.72,2.97,h=0.14,color=col,alpha=a) draw_crown(ax,cx-0.45,2.97,w=0.55,h=0.44,color=col,alpha=a) draw_crown(ax,cx+0.3,2.97,w=0.55,h=0.44,color="#aabbcc",alpha=a) elif i==2: draw_gum(ax,cx-0.9,cx+0.9,2.2,yb=0.8,gap_cx=cx,gap_w=0.6,alpha=a) draw_tooth(ax,cx-0.55,2.22,w=0.55,h=0.8,alpha=a) draw_tooth(ax,cx+0.55,2.22,w=0.55,h=0.8,alpha=a) for wx in [cx-0.82,cx+0.54]: ax.add_patch(FancyBboxPatch((wx,2.70),0.28,0.36,boxstyle="round,pad=0.02", facecolor=col,edgecolor="#558855",linewidth=1,alpha=0.7*a,zorder=4)) draw_crown(ax,cx,2.97,w=0.55,h=0.44,color=col,alpha=a) else: draw_gum(ax,cx-0.9,cx+0.9,2.2,yb=0.8,gap_cx=cx,gap_w=1.2,alpha=a) draw_tooth(ax,cx-0.55,2.22,w=0.55,h=0.8,alpha=a) draw_tooth(ax,cx+0.55,2.22,w=0.55,h=0.8,alpha=a) ax.add_patch(FancyBboxPatch((cx-0.08,1.2),0.16,1.0, boxstyle="round,pad=0.02",facecolor=IMPLANT,edgecolor="#556677",linewidth=1.5,alpha=a,zorder=4)) draw_crown(ax,cx,2.97,w=0.55,h=0.44,color=col,alpha=a) ax.text(cx,3.75,name,ha="center",va="bottom",fontsize=9,fontweight="bold",color=col,alpha=a, bbox=dict(fc="#111133",ec="none",pad=2),multialignment="center") ax.text(cx,4.55,desc,ha="center",va="center",fontsize=7.5,color=LIGHT,alpha=a,multialignment="center") def scene_procedure(ax,t): clear_ax(ax) draw_title_bar(ax,"The Procedure: Step by Step") steps=[(1.2,4.8,"#ff9944","STEP 1","Examination\n& X-rays"), (3.1,4.8,"#ffcc44","STEP 2","Tooth\nPreparation"), (5.0,4.8,"#88dd55","STEP 3","Impressions\n& Temp Bridge"), (6.9,4.8,"#44bbff","STEP 4","Lab Fabrication"), (8.8,4.8,"#dd88ff","STEP 5","Final Fitting")] n=len(steps) for i,(sx,sy,col,snum,sdesc) in enumerate(steps): prog=t*n-i a=float(np.clip(prog*2,0,1)) if i<n-1: ax.plot([sx+0.4,steps[i+1][0]-0.4],[sy,sy],color="#334466",linewidth=2,alpha=a,zorder=2) ca=float(np.clip(prog*3,0,1)) ax.add_patch(plt.Circle((sx,sy),0.38,color=col,alpha=ca,zorder=5)) ax.text(sx,sy,str(i+1),ha="center",va="center",fontsize=13,fontweight="bold",color="#111122",alpha=ca,zorder=6) la=float(np.clip((prog-0.3)*3,0,1)) ax.text(sx,sy-0.65,snum,ha="center",va="top",fontsize=7.5,fontweight="bold",color=col,alpha=la,zorder=6) ax.text(sx,sy-1.0,sdesc,ha="center",va="top",fontsize=8,color=LIGHT,alpha=la,multialignment="center",zorder=6) idx=min(int(t*n),n-1) ap=float(np.clip(t*n-idx,0,1)) details=["Dentist checks bite, gums and takes X-rays to plan the bridge.", "Abutment teeth reshaped under local anaesthesia to receive crowns.", "Putty impression taken; temporary bridge fitted for protection.", "Dental lab hand-crafts the permanent porcelain bridge (1-2 weeks).", "Temporary removed; permanent bridge checked for fit and cemented."] col=steps[idx][2] ax.add_patch(FancyBboxPatch((0.4,0.5),9.2,1.0, boxstyle="round,pad=0.06",facecolor="#0f1f3a",edgecolor=col,linewidth=1.5,alpha=ap,zorder=5)) ax.text(5,1.0,details[idx],ha="center",va="center",fontsize=9.5,color=WHITE,alpha=ap,zorder=6) def scene_anatomy(ax,t): clear_ax(ax) draw_title_bar(ax,"Bridge Anatomy: Cross-Section","What sits above and below the gum line") a=min(t*2,1) ax.add_patch(FancyBboxPatch((0.8,0.3),8.4,1.4, boxstyle="round,pad=0.08",facecolor="#c8a870",edgecolor="#a07840",linewidth=1.5,alpha=a,zorder=1)) ax.text(5,0.95,"Alveolar Bone (Jaw)",ha="center",va="center",fontsize=8.5,color="#5a3000",alpha=a,fontweight="bold") ax.add_patch(FancyBboxPatch((0.8,1.7),8.4,0.65, boxstyle="round,pad=0.03",facecolor=GUM_PINK,edgecolor=GUM_DARK,linewidth=1.5,alpha=a,zorder=2)) for px in [2.8,7.2]: draw_tooth(ax,px,2.35,w=0.85,h=1.3,roots=2,alpha=a) ac=max(0,min((t-0.3)*3,1)) draw_crown(ax,2.8,3.55,w=0.86,h=0.65,color=CROWN_COL,alpha=ac) draw_crown(ax,7.2,3.55,w=0.86,h=0.65,color=CROWN_COL,alpha=ac) draw_crown(ax,5.0,3.55,w=0.86,h=0.65,color=PONTIC,alpha=ac) draw_bar(ax,2.42,7.58,4.08,h=0.14,color=CROWN_COL,alpha=ac) for px in [2.8,5.0,7.2]: ax.add_patch(FancyBboxPatch((px-0.4,3.48),0.8,0.09, boxstyle="round,pad=0.01",facecolor="#f5f5dc",edgecolor="#cccc88",linewidth=0.8,alpha=0.7*ac,zorder=7)) al=max(0,min((t-0.55)*3,1)) for ax_x,ay,label,col,arrow_y in [ (2.8,4.85,"Crown (abutment)",CROWN_COL,4.3),(5.0,4.85,"Pontic",CROWN_HL,4.3), (5.0,5.65,"Connector Bar",ACCENT,4.25),(2.1,2.1,"Tooth structure","#ddddaa",2.65),(2.1,1.0,"Root in bone","#c8a870",1.4)]: ax.annotate("",xy=(ax_x,arrow_y),xytext=(ax_x,ay-0.15), arrowprops=dict(arrowstyle="-|>",color=col,lw=1.4,mutation_scale=10),alpha=al,zorder=10) ax.text(ax_x,ay,label,ha="center",va="bottom",fontsize=7.5,color=col,alpha=al, bbox=dict(fc="#111133",ec=col,pad=2.5),zorder=11) def scene_care(ax,t): clear_ax(ax) draw_title_bar(ax,"Caring for Your Dental Bridge","With proper care, a bridge lasts 10-15 years") tips=[("#44ccff","Floss Daily","Use threaders under pontic"), ("#88ee88","Brush Twice Daily","Soft brush + fluoride"), ("#ffcc55","Regular Check-ups","Every 6 months"), ("#ff9988","Avoid Hard Foods","No ice or hard candy"), ("#cc88ff","Use Mouthwash","Antibacterial rinse"), ("#88ddff","Longevity","10-15 yrs / 20+ with implants")] for i,(col,title,desc) in enumerate(tips): row,ci=i//3,i%3 cx=1.5+ci*3.0; cy=4.8-row*2.7 a=max(0,min((t-i*0.1)*3,1)) ax.add_patch(FancyBboxPatch((cx-1.2,cy-0.85),2.4,1.75, boxstyle="round,pad=0.06",facecolor="#111133",edgecolor=col,linewidth=1.5,alpha=a,zorder=3)) ax.add_patch(plt.Circle((cx,cy+0.65),0.28,color=col,alpha=0.3*a,zorder=4)) ax.text(cx,cy+0.65,str(i+1),ha="center",va="center",fontsize=11,fontweight="bold",color=col,alpha=a,zorder=5) ax.text(cx,cy+0.22,title,ha="center",va="center",fontsize=9.5,fontweight="bold",color=col,alpha=a,zorder=5) ax.text(cx,cy-0.35,desc,ha="center",va="center",fontsize=7.8,color=LIGHT,alpha=a,zorder=5,multialignment="center") def scene_summary(ax,t): clear_ax(ax) draw_title_bar(ax,"Summary: Dental Bridge at a Glance") ab=min(t*3,1) draw_gum(ax,1.5,8.5,2.3,yb=0.4,alpha=ab) for px in [2.5,4.0,7.0,8.0]: draw_tooth(ax,px,2.32,w=0.78,h=1.1,alpha=ab) draw_bar(ax,4.38,6.62,3.32,h=0.16,color=CROWN_COL,alpha=ab) draw_crown(ax,4.0,3.32,w=0.78,h=0.6,color=CROWN_COL,alpha=ab) draw_crown(ax,5.5,3.32,w=0.78,h=0.6,color=PONTIC,alpha=ab) draw_crown(ax,7.0,3.32,w=0.78,h=0.6,color=CROWN_COL,alpha=ab) facts=["Fixed prosthetic - does NOT need removal daily", "Restores chewing, speech, and smile aesthetics", "Prevents adjacent teeth from drifting into the gap", "2 appointments over 2-3 weeks", "Lifespan 10-15 years with proper care"] for i,fact in enumerate(facts): a=max(0,min((t-0.3-i*0.1)*3,1)) ax.text(1.2,5.55-i*0.42,f"\u2713 {fact}",ha="left",va="center",fontsize=9,color=GREEN,alpha=a,zorder=6) ao=max(0,min((t-0.8)*5,1)) ax.text(5,0.65,"Consult your dentist to find out which bridge is right for you.", ha="center",va="center",fontsize=10,color=TITLE_COL,alpha=ao, bbox=dict(fc="#113300",ec=GREEN,pad=5)) # ── BUILD ───────────────────────────────────────────────────────────────────── SCENES=[ (scene_title, 90, "Title"), (scene_missing, 90, "Missing Tooth"), (scene_what, 100, "What is a Bridge"), (scene_types, 110, "Types"), (scene_procedure,120,"Procedure"), (scene_anatomy,110, "Anatomy"), (scene_care, 110, "Care"), (scene_summary, 90, "Summary"), ] TOTAL=sum(f for _,f,_ in SCENES) FPS=24 fig,ax=plt.subplots(figsize=(12,7),dpi=120) fig.patch.set_facecolor(BG) plt.subplots_adjust(left=0,right=1,top=1,bottom=0) FRAME_DIR="/home/daytona/workspace/dental-bridge-video/frames" os.makedirs(FRAME_DIR,exist_ok=True) # Remove old frames for f in os.listdir(FRAME_DIR): os.remove(os.path.join(FRAME_DIR,f)) def render_frame(fi): cumulative=0 for renderer,n_frames,name in SCENES: if fi<cumulative+n_frames: local=fi-cumulative t=local/max(n_frames-1,1) renderer(ax,t) ax.text(9.8,0.15,name,ha="right",va="bottom",fontsize=6,color="#444466") return cumulative+=n_frames print(f"Rendering {TOTAL} frames …") for fi in range(TOTAL): render_frame(fi) fig.savefig(f"{FRAME_DIR}/frame_{fi:04d}.png", dpi=120, facecolor=BG) if fi%50==0: print(f" {fi}/{TOTAL}") plt.close(fig) print("Frames done, encoding with ffmpeg …") OUT="/home/daytona/workspace/dental-bridge-video/dental_bridge.mp4" cmd=["ffmpeg","-y","-framerate",str(FPS), "-i",f"{FRAME_DIR}/frame_%04d.png", "-vcodec","libx264","-pix_fmt","yuv420p", "-crf","22","-preset","fast",OUT] result=subprocess.run(cmd,capture_output=True,text=True) if result.returncode!=0: print("FFMPEG stderr:", result.stderr[-2000:]) else: print(f"Done -> {OUT}") PYEOF echo "Script written OK"

Running Command

cd /home/daytona/workspace/dental-bridge-video && python dental_bridge_anim.py 2>&1

This is a shared conversation. Sign in to Orris to start your own chat.