Add cookbook and tutorial showcase demos
tests/demo/: - cookbook_showcase.py: Interactive demo of cookbook recipes - tutorial_showcase.py: Visual walkthrough of tutorial content - tutorial_screenshots.py: Automated screenshot generation - new_features_showcase.py: Demo of modern API features - procgen_showcase.py: Procedural generation examples - simple_showcase.py: Minimal working examples Created during docs modernization to verify cookbook examples work. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
23afae69ad
commit
a1b692bb1f
6 changed files with 1734 additions and 0 deletions
495
tests/demo/cookbook_showcase.py
Normal file
495
tests/demo/cookbook_showcase.py
Normal file
|
|
@ -0,0 +1,495 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cookbook Screenshot Showcase - Visual examples for cookbook recipes!
|
||||
|
||||
Generates beautiful screenshots for cookbook pages.
|
||||
Run with: xvfb-run -a ./build/mcrogueface --headless --exec tests/demo/cookbook_showcase.py
|
||||
|
||||
In headless mode, automation.screenshot() is SYNCHRONOUS - no timer dance needed!
|
||||
"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Output directory - in the docs site images folder
|
||||
OUTPUT_DIR = "/opt/goblincorps/repos/mcrogueface.github.io/images/cookbook"
|
||||
|
||||
# Tile sprites from the labeled tileset
|
||||
TILES = {
|
||||
'player_knight': 84,
|
||||
'player_mage': 85,
|
||||
'player_rogue': 86,
|
||||
'player_warrior': 87,
|
||||
'enemy_slime': 108,
|
||||
'enemy_orc': 120,
|
||||
'enemy_skeleton': 123,
|
||||
'floor_stone': 42,
|
||||
'wall_stone': 30,
|
||||
'wall_brick': 14,
|
||||
'torch': 72,
|
||||
'chest_closed': 89,
|
||||
'item_potion': 113,
|
||||
}
|
||||
|
||||
|
||||
def screenshot_health_bar():
|
||||
"""Create a health bar showcase."""
|
||||
scene = mcrfpy.Scene("health_bar")
|
||||
|
||||
# Dark background
|
||||
bg = mcrfpy.Frame(pos=(0, 0), size=(800, 600))
|
||||
bg.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
scene.children.append(bg)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Health Bar Recipe", pos=(50, 30))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Nested frames for dynamic UI elements", pos=(50, 60))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Example health bars at different levels
|
||||
y_start = 120
|
||||
bar_configs = [
|
||||
("Player - Full Health", 100, 100, mcrfpy.Color(50, 200, 50)),
|
||||
("Player - Damaged", 65, 100, mcrfpy.Color(200, 200, 50)),
|
||||
("Player - Critical", 20, 100, mcrfpy.Color(200, 50, 50)),
|
||||
("Boss - 3/4 Health", 750, 1000, mcrfpy.Color(150, 50, 150)),
|
||||
]
|
||||
|
||||
for i, (label, current, maximum, color) in enumerate(bar_configs):
|
||||
y = y_start + i * 100
|
||||
|
||||
# Label
|
||||
lbl = mcrfpy.Caption(text=label, pos=(50, y))
|
||||
lbl.fill_color = mcrfpy.Color(220, 220, 220)
|
||||
lbl.font_size = 18
|
||||
scene.children.append(lbl)
|
||||
|
||||
# Background bar
|
||||
bar_bg = mcrfpy.Frame(pos=(50, y + 30), size=(400, 30))
|
||||
bar_bg.fill_color = mcrfpy.Color(40, 40, 50)
|
||||
bar_bg.outline = 2
|
||||
bar_bg.outline_color = mcrfpy.Color(80, 80, 100)
|
||||
scene.children.append(bar_bg)
|
||||
|
||||
# Fill bar (scaled to current/maximum)
|
||||
fill_width = int(400 * (current / maximum))
|
||||
bar_fill = mcrfpy.Frame(pos=(50, y + 30), size=(fill_width, 30))
|
||||
bar_fill.fill_color = color
|
||||
scene.children.append(bar_fill)
|
||||
|
||||
# Text overlay
|
||||
hp_text = mcrfpy.Caption(text=f"{current}/{maximum}", pos=(60, y + 35))
|
||||
hp_text.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
hp_text.font_size = 16
|
||||
scene.children.append(hp_text)
|
||||
|
||||
scene.activate()
|
||||
output_path = os.path.join(OUTPUT_DIR, "ui_health_bar.png")
|
||||
automation.screenshot(output_path)
|
||||
print(f" -> {output_path}")
|
||||
|
||||
|
||||
def screenshot_fog_of_war():
|
||||
"""Create a fog of war showcase."""
|
||||
scene = mcrfpy.Scene("fog_of_war")
|
||||
|
||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
grid = mcrfpy.Grid(
|
||||
pos=(50, 80),
|
||||
size=(700, 480),
|
||||
grid_size=(16, 12),
|
||||
texture=texture,
|
||||
zoom=2.8
|
||||
)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0) # Black for unknown areas
|
||||
scene.children.append(grid)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Fog of War Recipe", pos=(50, 20))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Visible, discovered, and unknown areas", pos=(50, 50))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Fill floor
|
||||
for y in range(12):
|
||||
for x in range(16):
|
||||
grid.at(x, y).tilesprite = TILES['floor_stone']
|
||||
|
||||
# Add walls
|
||||
for x in range(16):
|
||||
grid.at(x, 0).tilesprite = TILES['wall_stone']
|
||||
grid.at(x, 11).tilesprite = TILES['wall_stone']
|
||||
for y in range(12):
|
||||
grid.at(0, y).tilesprite = TILES['wall_stone']
|
||||
grid.at(15, y).tilesprite = TILES['wall_stone']
|
||||
|
||||
# Interior walls (to break LOS)
|
||||
for y in range(3, 8):
|
||||
grid.at(8, y).tilesprite = TILES['wall_brick']
|
||||
|
||||
# Player (mage with light)
|
||||
player = mcrfpy.Entity(grid_pos=(4, 6), texture=texture, sprite_index=TILES['player_mage'])
|
||||
grid.entities.append(player)
|
||||
|
||||
# Hidden enemies on the other side
|
||||
enemy1 = mcrfpy.Entity(grid_pos=(12, 4), texture=texture, sprite_index=TILES['enemy_orc'])
|
||||
grid.entities.append(enemy1)
|
||||
enemy2 = mcrfpy.Entity(grid_pos=(13, 8), texture=texture, sprite_index=TILES['enemy_skeleton'])
|
||||
grid.entities.append(enemy2)
|
||||
|
||||
# Torch in visible area
|
||||
torch = mcrfpy.Entity(grid_pos=(2, 3), texture=texture, sprite_index=TILES['torch'])
|
||||
grid.entities.append(torch)
|
||||
|
||||
grid.center = (4 * 16 + 8, 6 * 16 + 8)
|
||||
|
||||
scene.activate()
|
||||
output_path = os.path.join(OUTPUT_DIR, "grid_fog_of_war.png")
|
||||
automation.screenshot(output_path)
|
||||
print(f" -> {output_path}")
|
||||
|
||||
|
||||
def screenshot_combat_melee():
|
||||
"""Create a melee combat showcase."""
|
||||
scene = mcrfpy.Scene("combat_melee")
|
||||
|
||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
grid = mcrfpy.Grid(
|
||||
pos=(50, 80),
|
||||
size=(700, 480),
|
||||
grid_size=(12, 9),
|
||||
texture=texture,
|
||||
zoom=3.5
|
||||
)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
scene.children.append(grid)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Melee Combat Recipe", pos=(50, 20))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Bump-to-attack mechanics with damage calculation", pos=(50, 50))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Fill with dirt floor (battle arena feel)
|
||||
for y in range(9):
|
||||
for x in range(12):
|
||||
grid.at(x, y).tilesprite = 50 # dirt
|
||||
|
||||
# Brick walls
|
||||
for x in range(12):
|
||||
grid.at(x, 0).tilesprite = TILES['wall_brick']
|
||||
grid.at(x, 8).tilesprite = TILES['wall_brick']
|
||||
for y in range(9):
|
||||
grid.at(0, y).tilesprite = TILES['wall_brick']
|
||||
grid.at(11, y).tilesprite = TILES['wall_brick']
|
||||
|
||||
# Player knight engaging orc!
|
||||
player = mcrfpy.Entity(grid_pos=(4, 4), texture=texture, sprite_index=TILES['player_knight'])
|
||||
grid.entities.append(player)
|
||||
|
||||
enemy = mcrfpy.Entity(grid_pos=(6, 4), texture=texture, sprite_index=TILES['enemy_orc'])
|
||||
grid.entities.append(enemy)
|
||||
|
||||
# Fallen enemy (bones)
|
||||
bones = mcrfpy.Entity(grid_pos=(8, 6), texture=texture, sprite_index=75) # bones
|
||||
grid.entities.append(bones)
|
||||
|
||||
# Potion for healing
|
||||
potion = mcrfpy.Entity(grid_pos=(3, 2), texture=texture, sprite_index=TILES['item_potion'])
|
||||
grid.entities.append(potion)
|
||||
|
||||
grid.center = (5 * 16 + 8, 4 * 16 + 8)
|
||||
|
||||
# Combat log UI
|
||||
log_frame = mcrfpy.Frame(pos=(50, 520), size=(700, 60))
|
||||
log_frame.fill_color = mcrfpy.Color(30, 30, 40, 220)
|
||||
log_frame.outline = 1
|
||||
log_frame.outline_color = mcrfpy.Color(60, 60, 80)
|
||||
scene.children.append(log_frame)
|
||||
|
||||
msg1 = mcrfpy.Caption(text="You hit the Orc for 8 damage!", pos=(10, 10))
|
||||
msg1.fill_color = mcrfpy.Color(255, 200, 100)
|
||||
msg1.font_size = 14
|
||||
log_frame.children.append(msg1)
|
||||
|
||||
msg2 = mcrfpy.Caption(text="The Orc hits you for 4 damage!", pos=(10, 30))
|
||||
msg2.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
msg2.font_size = 14
|
||||
log_frame.children.append(msg2)
|
||||
|
||||
scene.activate()
|
||||
output_path = os.path.join(OUTPUT_DIR, "combat_melee.png")
|
||||
automation.screenshot(output_path)
|
||||
print(f" -> {output_path}")
|
||||
|
||||
|
||||
def screenshot_dungeon_generator():
|
||||
"""Create a dungeon generator showcase."""
|
||||
scene = mcrfpy.Scene("dungeon_gen")
|
||||
|
||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
grid = mcrfpy.Grid(
|
||||
pos=(50, 80),
|
||||
size=(700, 480),
|
||||
grid_size=(24, 16),
|
||||
texture=texture,
|
||||
zoom=2.0
|
||||
)
|
||||
grid.fill_color = mcrfpy.Color(10, 10, 15)
|
||||
scene.children.append(grid)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Dungeon Generator Recipe", pos=(50, 20))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Procedural rooms connected by corridors", pos=(50, 50))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Fill with walls
|
||||
for y in range(16):
|
||||
for x in range(24):
|
||||
grid.at(x, y).tilesprite = TILES['wall_stone']
|
||||
|
||||
# Carve rooms
|
||||
rooms = [
|
||||
(2, 2, 6, 5), # Room 1
|
||||
(10, 2, 7, 5), # Room 2
|
||||
(18, 3, 5, 4), # Room 3
|
||||
(2, 9, 5, 5), # Room 4
|
||||
(10, 10, 6, 5), # Room 5
|
||||
(18, 9, 5, 6), # Room 6
|
||||
]
|
||||
|
||||
for rx, ry, rw, rh in rooms:
|
||||
for y in range(ry, ry + rh):
|
||||
for x in range(rx, rx + rw):
|
||||
if x < 24 and y < 16:
|
||||
grid.at(x, y).tilesprite = TILES['floor_stone']
|
||||
|
||||
# Carve corridors (horizontal and vertical)
|
||||
# Room 1 to Room 2
|
||||
for x in range(7, 11):
|
||||
grid.at(x, 4).tilesprite = 50 # dirt corridor
|
||||
# Room 2 to Room 3
|
||||
for x in range(16, 19):
|
||||
grid.at(x, 4).tilesprite = 50
|
||||
# Room 1 to Room 4
|
||||
for y in range(6, 10):
|
||||
grid.at(4, y).tilesprite = 50
|
||||
# Room 2 to Room 5
|
||||
for y in range(6, 11):
|
||||
grid.at(13, y).tilesprite = 50
|
||||
# Room 3 to Room 6
|
||||
for y in range(6, 10):
|
||||
grid.at(20, y).tilesprite = 50
|
||||
# Room 5 to Room 6
|
||||
for x in range(15, 19):
|
||||
grid.at(x, 12).tilesprite = 50
|
||||
|
||||
# Add player in first room
|
||||
player = mcrfpy.Entity(grid_pos=(4, 4), texture=texture, sprite_index=TILES['player_knight'])
|
||||
grid.entities.append(player)
|
||||
|
||||
# Add decorations
|
||||
grid.entities.append(mcrfpy.Entity(grid_pos=(3, 3), texture=texture, sprite_index=TILES['torch']))
|
||||
grid.entities.append(mcrfpy.Entity(grid_pos=(12, 4), texture=texture, sprite_index=TILES['torch']))
|
||||
grid.entities.append(mcrfpy.Entity(grid_pos=(19, 11), texture=texture, sprite_index=TILES['chest_closed']))
|
||||
grid.entities.append(mcrfpy.Entity(grid_pos=(13, 12), texture=texture, sprite_index=TILES['enemy_slime']))
|
||||
grid.entities.append(mcrfpy.Entity(grid_pos=(20, 5), texture=texture, sprite_index=TILES['enemy_skeleton']))
|
||||
|
||||
grid.center = (12 * 16, 8 * 16)
|
||||
|
||||
scene.activate()
|
||||
output_path = os.path.join(OUTPUT_DIR, "grid_dungeon_generator.png")
|
||||
automation.screenshot(output_path)
|
||||
print(f" -> {output_path}")
|
||||
|
||||
|
||||
def screenshot_floating_text():
|
||||
"""Create a floating text/damage numbers showcase."""
|
||||
scene = mcrfpy.Scene("floating_text")
|
||||
|
||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
grid = mcrfpy.Grid(
|
||||
pos=(50, 100),
|
||||
size=(700, 420),
|
||||
grid_size=(12, 8),
|
||||
texture=texture,
|
||||
zoom=3.5
|
||||
)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
scene.children.append(grid)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Floating Text Recipe", pos=(50, 20))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Animated damage numbers and status messages", pos=(50, 50))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Fill floor
|
||||
for y in range(8):
|
||||
for x in range(12):
|
||||
grid.at(x, y).tilesprite = TILES['floor_stone']
|
||||
|
||||
# Walls
|
||||
for x in range(12):
|
||||
grid.at(x, 0).tilesprite = TILES['wall_stone']
|
||||
grid.at(x, 7).tilesprite = TILES['wall_stone']
|
||||
for y in range(8):
|
||||
grid.at(0, y).tilesprite = TILES['wall_stone']
|
||||
grid.at(11, y).tilesprite = TILES['wall_stone']
|
||||
|
||||
# Player and enemy in combat
|
||||
player = mcrfpy.Entity(grid_pos=(4, 4), texture=texture, sprite_index=TILES['player_warrior'])
|
||||
grid.entities.append(player)
|
||||
|
||||
enemy = mcrfpy.Entity(grid_pos=(7, 4), texture=texture, sprite_index=TILES['enemy_orc'])
|
||||
grid.entities.append(enemy)
|
||||
|
||||
grid.center = (5.5 * 16, 4 * 16)
|
||||
|
||||
# Floating damage numbers (as captions positioned over entities)
|
||||
# These would normally animate upward
|
||||
dmg1 = mcrfpy.Caption(text="-12", pos=(330, 240))
|
||||
dmg1.fill_color = mcrfpy.Color(255, 80, 80)
|
||||
dmg1.font_size = 24
|
||||
scene.children.append(dmg1)
|
||||
|
||||
dmg2 = mcrfpy.Caption(text="-5", pos=(500, 260))
|
||||
dmg2.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
dmg2.font_size = 20
|
||||
scene.children.append(dmg2)
|
||||
|
||||
crit = mcrfpy.Caption(text="CRITICAL!", pos=(280, 200))
|
||||
crit.fill_color = mcrfpy.Color(255, 200, 50)
|
||||
crit.font_size = 18
|
||||
scene.children.append(crit)
|
||||
|
||||
heal = mcrfpy.Caption(text="+8", pos=(320, 280))
|
||||
heal.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
heal.font_size = 20
|
||||
scene.children.append(heal)
|
||||
|
||||
scene.activate()
|
||||
output_path = os.path.join(OUTPUT_DIR, "effects_floating_text.png")
|
||||
automation.screenshot(output_path)
|
||||
print(f" -> {output_path}")
|
||||
|
||||
|
||||
def screenshot_message_log():
|
||||
"""Create a message log showcase."""
|
||||
scene = mcrfpy.Scene("message_log")
|
||||
|
||||
# Dark background
|
||||
bg = mcrfpy.Frame(pos=(0, 0), size=(800, 600))
|
||||
bg.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
scene.children.append(bg)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Message Log Recipe", pos=(50, 30))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
title.font_size = 28
|
||||
scene.children.append(title)
|
||||
|
||||
subtitle = mcrfpy.Caption(text="Scrollable combat and event messages", pos=(50, 60))
|
||||
subtitle.fill_color = mcrfpy.Color(180, 180, 200)
|
||||
subtitle.font_size = 16
|
||||
scene.children.append(subtitle)
|
||||
|
||||
# Message log frame
|
||||
log_frame = mcrfpy.Frame(pos=(50, 100), size=(700, 400))
|
||||
log_frame.fill_color = mcrfpy.Color(30, 30, 40)
|
||||
log_frame.outline = 2
|
||||
log_frame.outline_color = mcrfpy.Color(60, 60, 80)
|
||||
scene.children.append(log_frame)
|
||||
|
||||
# Sample messages with colors
|
||||
messages = [
|
||||
("Welcome to the dungeon!", mcrfpy.Color(200, 200, 255)),
|
||||
("You see a dark corridor ahead.", mcrfpy.Color(180, 180, 180)),
|
||||
("A goblin appears!", mcrfpy.Color(255, 200, 100)),
|
||||
("You hit the Goblin for 8 damage!", mcrfpy.Color(255, 255, 150)),
|
||||
("The Goblin hits you for 3 damage!", mcrfpy.Color(255, 100, 100)),
|
||||
("You hit the Goblin for 12 damage! Critical hit!", mcrfpy.Color(255, 200, 50)),
|
||||
("The Goblin dies!", mcrfpy.Color(150, 255, 150)),
|
||||
("You found a Healing Potion.", mcrfpy.Color(100, 200, 255)),
|
||||
("An Orc blocks your path!", mcrfpy.Color(255, 150, 100)),
|
||||
("You drink the Healing Potion. +15 HP", mcrfpy.Color(100, 255, 100)),
|
||||
("You hit the Orc for 6 damage!", mcrfpy.Color(255, 255, 150)),
|
||||
("The Orc hits you for 8 damage!", mcrfpy.Color(255, 100, 100)),
|
||||
]
|
||||
|
||||
for i, (msg, color) in enumerate(messages):
|
||||
caption = mcrfpy.Caption(text=msg, pos=(15, 15 + i * 30))
|
||||
caption.fill_color = color
|
||||
caption.font_size = 16
|
||||
log_frame.children.append(caption)
|
||||
|
||||
# Scroll indicator
|
||||
scroll = mcrfpy.Caption(text="▼ More messages below", pos=(580, 370))
|
||||
scroll.fill_color = mcrfpy.Color(100, 100, 120)
|
||||
scroll.font_size = 12
|
||||
log_frame.children.append(scroll)
|
||||
|
||||
scene.activate()
|
||||
output_path = os.path.join(OUTPUT_DIR, "ui_message_log.png")
|
||||
automation.screenshot(output_path)
|
||||
print(f" -> {output_path}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Generate all cookbook screenshots!"""
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
print("=== Cookbook Screenshot Showcase ===")
|
||||
print(f"Output: {OUTPUT_DIR}\n")
|
||||
|
||||
showcases = [
|
||||
('Health Bar UI', screenshot_health_bar),
|
||||
('Fog of War', screenshot_fog_of_war),
|
||||
('Melee Combat', screenshot_combat_melee),
|
||||
('Dungeon Generator', screenshot_dungeon_generator),
|
||||
('Floating Text', screenshot_floating_text),
|
||||
('Message Log', screenshot_message_log),
|
||||
]
|
||||
|
||||
for name, func in showcases:
|
||||
print(f"Generating {name}...")
|
||||
try:
|
||||
func()
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
print("\n=== All cookbook screenshots generated! ===")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue