Files
Frametop--frametop/models/ft-models
T
DeeJanuzandClaude Opus 5.5 d0a52f3a7c Model picker: choose hand and eye model versions in Input Settings
Catalogs list each kind's versions with notes, cost and benchmark numbers:
hands/models.json (Latest = v13b-p1, stock) and gaze/tracker/models.json
(classic, SteamVR, eyes-v0 as Testing). Installs read their branch's
catalog from GitHub, so a new version reaches them with a push; offline,
the copy in the install is used.

models/catalog.py (+ ft-models CLI) validates catalogs strictly, downloads
versions from the kind's fixed Hugging Face repo pinned to a commit, and
keeps only files matching their sha256. HANDS_MODEL and EYES_MODEL pick
latest (follows new versions), a version by name, or a builtin. ft-hands
and ft-eyes read them at start; Input Settings restarts hand tracking and
ft-gazed restarts ft-eyes when the setting changes. The services fetch
what the setting needs at start (HANDS_MODELS_UPDATE / EYES_MODELS_UPDATE
= 0 turn that off).

Input Settings: "Hand model" on the Hands page, "Eye tracker" on the Gaze
page (replaces the Own/SteamVR switch), tagged Recommended / Testing /
Older / Built in, with a benchmark table against the model in use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:18:07 -06:00

92 lines
4.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""ft-models: the hand and eye model catalogs (models/catalog.py) from a terminal.
ft-models list hands|eyes [--json] the versions, their notes, and what's in use
ft-models refresh hands|eyes fetch this install's branch's catalog from GitHub
ft-models fetch hands|eyes NAME download a version (every file checked against the catalog)
ft-models verify hands|eyes NAME check a downloaded version's files again
ft-models remove hands|eyes NAME delete a downloaded version
ft-models resolve hands|eyes [VALUE] what the setting (or VALUE) runs now, as JSON
ft-models auto hands|eyes what the services run at start: refresh at most every
15 minutes, fetch what the setting needs if it's missing
ft-models hashes hands|eyes STAGING VERSION
for a new catalog entry: the "files" of STAGING/VERSION and
STAGING's LICENSE and README.md, as uploaded (models/README.md)
Pick a model in Frametop Input Settings (Hands and Gaze pages), or set HANDS_MODEL / EYES_MODEL in
~/.config/frametop.conf: latest (follows new versions), a version's name, or a builtin (stock for
hands; classic for eyes, and GAZE_TRACKER=steam for SteamVR's).
"""
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import catalog # noqa: E402
def main(argv):
if len(argv) < 2 or argv[0] not in ("list", "refresh", "fetch", "verify", "remove", "resolve", "auto", "hashes") \
or argv[1] not in catalog.SPECS:
print(__doc__.strip(), file=sys.stderr)
return 2
cmd, kind, rest = argv[0], argv[1], argv[2:]
try:
if cmd == "list":
v = catalog.view(kind)
if "--json" in rest:
print(json.dumps(v, indent=1))
return 0
print(f"{kind} models ({v['source']}); setting {v['setting']!r} -> {v['resolved']['why']}")
for m in v["models"]:
mark = "*" if m["name"] == v["resolved"]["name"] else " "
latest = " (latest)" if m["name"] == v["latest"] else ""
state = "builtin" if m["builtin"] else "downloaded" if m["installed"] else \
"needs a newer Frametop" if not m["usable"] else "not downloaded"
print(f"{mark} {m['name']:<16} {m['status']:<12} {state:<15} {m['title']}{latest}")
if m["summary"]:
print(f" {'':<16} {m['summary']}")
return 0
if cmd == "refresh":
ok, msg = catalog.refresh(kind)
print(f"{kind} catalog: {msg}")
return 0 if ok else 1
if cmd == "resolve":
print(json.dumps(catalog.resolve(kind, rest[0] if rest else None)))
return 0
if cmd == "auto":
print(catalog.auto(kind))
return 0
if cmd == "hashes" and len(rest) == 2:
staging, version = Path(rest[0]), rest[1]
spec = catalog.SPECS[kind]
files = {p.name: catalog._sha(p) for p in sorted((staging / version).iterdir()) if p.is_file()}
files.update({n: catalog._sha(staging / n) for n in sorted(spec["top"]) if (staging / n).is_file()})
if set(files) - spec["files"] - spec["top"] or spec["required"] - set(files):
print(f"ft-models: a {kind} version has {sorted(spec['files'])} (needs {sorted(spec['required'])}),"
f" plus {sorted(spec['top'])} at the top; found {sorted(files)}", file=sys.stderr)
return 1
print(json.dumps({"files": files}, indent=1))
return 0
if not rest:
print(f"ft-models {cmd} {kind} NAME", file=sys.stderr)
return 2
name = rest[0]
if cmd == "fetch":
d = catalog.fetch(kind, name, progress=lambda s: print(s, flush=True))
print(f"{kind} model {name} in {d}")
elif cmd == "verify":
bad = catalog.verify(kind, name)
print(f"{kind} model {name}: " + ("all files match" if not bad else "doesn't match: " + ", ".join(bad)))
return 1 if bad else 0
elif cmd == "remove":
print(f"{kind} model {name}: " + ("removed" if catalog.remove(kind, name) else "wasn't downloaded"))
except (catalog.CatalogError, OSError, ValueError) as e:
print(f"ft-models: {e}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))