Can you make an animated video of dental bridge
mkdir -p /home/daytona/workspace/dental-bridge-video && ls /home/daytona/workspace/dental-bridge-video
~/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}")
cat -n /home/daytona/workspace/dental-bridge-video/dental_bridge_anim.py | head -5
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."
cd /home/daytona/workspace/dental-bridge-video && python dental_bridge_anim.py 2>&1
ffmpeg -version 2>&1 | head -3; python -c "import matplotlib; print(matplotlib.__version__)"
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"
cd /home/daytona/workspace/dental-bridge-video && python dental_bridge_anim.py 2>&1