anki decks: add multiple-choice shapes variant; add real element photos to periodic table
shapes_mc (tools/anki-deck-visual.py) is the same shape images as "shapes" but multiple choice instead of type-the-name, matching the plain-text A/B/C/D pattern anki-deck-periodic.py's "category" deck already uses (no clickable UI, since that needs a desktop-only Anki add-on and breaks on AnkiDroid/AnkiMobile). periodic prehs/hs (tools/anki-deck-periodic.py) now show each element's real sample photo alongside every card, fetched once from Wikipedia's own MediaWiki pageimages API and cached under tools/periodic_images/ — the one part of this tooling that needs internet access at generation time; --no-images restores the old text-only cards. Elements 100 (Fermium) through 118 (Oganesson) are excluded, since none has ever existed in a photographable quantity — every fetch is otherwise per-element and non-fatal, with progress printed so failures are visible. This fetch logic could not be exercised against the real Wikipedia API from this sandbox (no route to en.wikipedia.org here) — --dry-run-tts now also substitutes a placeholder image so the pipeline is at least structurally tested end to end. Real-network behavior needs verifying on an actual run. Added tools/*.apkg, tools/media_*/, tools/periodic_images/, and tools/__pycache__/ to .gitignore — all generated/cached locally, never meant to be committed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014DyceEVVeQ33EeS6C1PDv5
This commit is contained in:
@@ -28,6 +28,12 @@ Decks:
|
||||
plus 5 quadrilateral types (image->name: square, rectangle,
|
||||
rhombus, trapezoid, parallelogram — each one's geometry is
|
||||
genuinely distinct, not just differently labeled)
|
||||
shapes_mc same shape images as "shapes", but as multiple choice
|
||||
(A/B/C/D shown as plain text below the image — not a
|
||||
clickable UI, since that needs a desktop-only Anki add-on
|
||||
and would break on AnkiDroid/AnkiMobile — same approach as
|
||||
tools/anki-deck-periodic.py's "category" deck), type the
|
||||
letter
|
||||
clocks analog clock faces, all 144 hour/5-min combinations,
|
||||
type the time as H:MM (the hour hand moves fractionally
|
||||
with the minutes, e.g. 6:30 sits halfway between 6 and 7 —
|
||||
@@ -39,6 +45,7 @@ Decks:
|
||||
|
||||
Usage (run with the venv from anki-deck-math.py's docstring activated):
|
||||
python3 anki-deck-visual.py --deck shapes
|
||||
python3 anki-deck-visual.py --deck shapes_mc
|
||||
python3 anki-deck-visual.py --deck clocks
|
||||
python3 anki-deck-visual.py --deck currency
|
||||
(add --voice en_US-amy-medium etc.; --model-path if a voice isn't found
|
||||
@@ -56,7 +63,8 @@ from PIL import Image, ImageDraw, ImageFont
|
||||
import subprocess
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--deck", required=True, choices=["shapes", "clocks", "currency"])
|
||||
parser.add_argument("--deck", required=True,
|
||||
choices=["shapes", "shapes_mc", "clocks", "currency"])
|
||||
parser.add_argument("--voice", default="en_US-lessac-medium")
|
||||
parser.add_argument("--model-path", default=None)
|
||||
parser.add_argument("--dry-run-tts", action="store_true")
|
||||
@@ -160,6 +168,8 @@ def build_model(deck_key):
|
||||
.card { font-family: Arial, sans-serif; font-size: 24px; text-align: center; }
|
||||
.imgwrap { margin: 10px auto; }
|
||||
.imgwrap img { max-width: 260px; max-height: 260px; }
|
||||
.mc-choices { display: inline-block; text-align: left; margin-top: 14px; font-size: 22px; }
|
||||
.mc-choices div { margin: 4px 0; }
|
||||
""",
|
||||
)
|
||||
|
||||
@@ -363,6 +373,48 @@ def gen_shapes():
|
||||
return deck, media_files, len(jobs)
|
||||
|
||||
|
||||
def gen_shapes_mc():
|
||||
"""Same shape images as gen_shapes(), but multiple choice instead of
|
||||
type-the-name — A/B/C/D distractors drawn from every other shape name
|
||||
in the pool (polygons and quadrilaterals share one distractor pool, so
|
||||
a polygon question can pull "square" as a wrong answer and vice versa)."""
|
||||
deck_key = "shapes_mc"
|
||||
model_id, model = build_model(deck_key)
|
||||
deck = build_deck(deck_key, "Shapes: Polygons & Quadrilaterals (multiple choice)")
|
||||
media_files = []
|
||||
|
||||
jobs = [("polygon", n, POLYGON_NAMES[n]) for n in range(3, 11)]
|
||||
jobs += [("quad", qname, qname) for qname in QUADRILATERALS]
|
||||
all_names = [name for _, _, name in jobs]
|
||||
random.seed(63)
|
||||
random.shuffle(jobs)
|
||||
|
||||
letters = ["A", "B", "C", "D"]
|
||||
for kind, val, name in jobs:
|
||||
if kind == "polygon":
|
||||
png_path = polygon_png(regular_polygon_points(val), f"mc_poly_{val}.png")
|
||||
tag = f"shape_mc_poly_{val}"
|
||||
else:
|
||||
png_path = polygon_png(QUADRILATERALS[val], f"mc_quad_{val}.png")
|
||||
tag = f"shape_mc_quad_{val}"
|
||||
media_files.append(png_path)
|
||||
|
||||
distractor_pool = [n for n in all_names if n != name]
|
||||
distractors = random.sample(distractor_pool, 3)
|
||||
choices = distractors + [name]
|
||||
random.shuffle(choices)
|
||||
correct_letter = letters[choices.index(name)]
|
||||
|
||||
choice_html = "".join(f"<div>{letter}) {choice}</div>"
|
||||
for letter, choice in zip(letters, choices))
|
||||
image_html = (f'<img src="{os.path.basename(png_path)}">'
|
||||
f'<div class="mc-choices">{choice_html}</div>')
|
||||
|
||||
add_note(deck, model, image_html, correct_letter,
|
||||
"What shape is this?", name, media_files, tag)
|
||||
return deck, media_files, len(jobs)
|
||||
|
||||
|
||||
def gen_clocks():
|
||||
deck_key = "clocks"
|
||||
model_id, model = build_model(deck_key)
|
||||
@@ -431,17 +483,15 @@ def gen_currency():
|
||||
return deck, media_files, len(combos)
|
||||
|
||||
|
||||
if args.deck == "shapes":
|
||||
deck_key = "shapes"
|
||||
elif args.deck == "clocks":
|
||||
deck_key = "clocks"
|
||||
else:
|
||||
deck_key = "currency"
|
||||
deck_key = args.deck
|
||||
|
||||
MEDIA_DIR = os.path.join(SCRATCH, f"media_{deck_key}_{args.voice}")
|
||||
os.makedirs(MEDIA_DIR, exist_ok=True)
|
||||
|
||||
GENERATORS = {"shapes": gen_shapes, "clocks": gen_clocks, "currency": gen_currency}
|
||||
GENERATORS = {
|
||||
"shapes": gen_shapes, "shapes_mc": gen_shapes_mc,
|
||||
"clocks": gen_clocks, "currency": gen_currency,
|
||||
}
|
||||
deck, media_files, count = GENERATORS[args.deck]()
|
||||
|
||||
package = genanki.Package(deck)
|
||||
|
||||
Reference in New Issue
Block a user