- window_capture.py: enumerate top-level windows (pygetwindow) and capture a specific one via PrintWindow PW_RENDERFULLCONTENT (works on non-focused windows). Linux falls back to mss region grab. - recognizer.py: replace MAE matcher with NCC over numpy vectors. Each rotatable slab generates 4 templates (0/90/180/270). Adds 248 artifact templates and an empty-cell heuristic (low mean/std-dev). Cells below confidence floor are tagged "unknown" — likely merged "?" boxes. - gui.py: new ScreenshotFrame with [게임 창 선택] button → window picker dialog → bbox crop → recognize → editable preview grid with per-cell CellEditor that handles slab / artifact / merged(?) / empty. Merged cells let user pick which two slabs got combined + a level. - artifacts.py + bundled _artifacts.json (248 entries from WhiteDog1004/sephiria) for matching and rendering. - renderer.py: factored CDN fetch into _fetch_image; added fetch_artifact_image(). - requirements.txt: + numpy, pygetwindow (Win), pywin32 (Win). - docker-build-cmd.sh: upgrade PyInstaller to 5.x inside cdrx container so numpy DLL manifest reads work. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
59 lines
1.7 KiB
Python
59 lines
1.7 KiB
Python
"""Artifact catalog parsed from WhiteDog1004/sephiria's artifacts.json.
|
|
|
|
Each entry exposes the fields we actually need for icon matching and
|
|
rendering: value (canonical key), Korean label, tier, and the CDN image URL.
|
|
The full effect text / description is kept opaque — the matcher only cares
|
|
about the image, and effects are not applied to the optimizer (slab effects
|
|
only, per existing design).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import dataclass
|
|
from importlib import resources
|
|
from typing import Dict, List, Optional
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Artifact:
|
|
value: str
|
|
ko_label: str
|
|
eng_label: str
|
|
tier: str # common / advanced / rare / legend / solid
|
|
image: str # CDN URL
|
|
level: int # max level (0 = unique / non-leveling)
|
|
|
|
|
|
def _load() -> List[Artifact]:
|
|
try:
|
|
text = resources.files(__package__).joinpath("_artifacts.json").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
except Exception:
|
|
return []
|
|
data = json.loads(text)
|
|
out: List[Artifact] = []
|
|
for row in data:
|
|
if row.get("disabled"):
|
|
continue
|
|
out.append(
|
|
Artifact(
|
|
value=row["value"],
|
|
ko_label=row.get("label_kor") or row["value"],
|
|
eng_label=row.get("label_eng") or row["value"],
|
|
tier=row.get("tier") or "common",
|
|
image=row.get("image") or "",
|
|
level=int(row.get("level") or 0),
|
|
)
|
|
)
|
|
return out
|
|
|
|
|
|
ARTIFACTS: List[Artifact] = _load()
|
|
ARTIFACTS_BY_VALUE: Dict[str, Artifact] = {a.value: a for a in ARTIFACTS}
|
|
|
|
|
|
def get(value: str) -> Optional[Artifact]:
|
|
return ARTIFACTS_BY_VALUE.get(value)
|