plate-config-app/app/services/input_mappings_service.py

100 lines
3.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Сервис загрузки раскладок кнопок из .mapping файлов (TOML-формат).
Папка input_mappings/ лежит рядом с исполняемым файлом (или рядом с main.py
при разработке). Каждый .mapping файл описывает раскладку кнопок для
конкретного игрового проекта и содержит:
- [[buttons]] — список кнопок с полями:
- name (str) — подпись кнопки
- input_index (int) — номер цифрового входа (0..25)
- x (int) — столбец на холсте (0..20)
- y (int) — строка на холсте (-4..4, +Y = вверх)
"""
from __future__ import annotations
import sys
import tomllib
from dataclasses import dataclass, field
from pathlib import Path
_MAX_X = 20
_MIN_Y = -4
_MAX_Y = 4
@dataclass
class MappingButton:
"""Одна кнопка в раскладке."""
name: str
input_index: int
x: int
y: int
@dataclass
class InputMapping:
"""Раскладка кнопок для одного игрового проекта."""
project_name: str
buttons: list[MappingButton] = field(default_factory=list)
def _get_input_mappings_dir() -> Path:
"""Определить путь к папке input_mappings/.
При запуске из .exe (PyInstaller) — рядом с exe-файлом.
При запуске из исходников — рядом с корнем проекта.
"""
if getattr(sys, "frozen", False):
base = Path(sys.executable).parent
else:
base = Path(__file__).resolve().parent.parent.parent
return base / "input_mappings"
def load_input_mapping(path: Path) -> InputMapping | None:
"""Загрузить один .mapping файл. Возвращает None при ошибке."""
try:
data = tomllib.loads(path.read_text(encoding="utf-8"))
except Exception:
return None
buttons: list[MappingButton] = []
for entry in data.get("buttons", []):
name = entry.get("name", "")
input_index = entry.get("input_index")
x = entry.get("x", 0)
y = entry.get("y", 0)
if input_index is None:
continue
# Ограничиваем координаты
x = max(0, min(int(x), _MAX_X))
y = max(_MIN_Y, min(int(y), _MAX_Y))
buttons.append(MappingButton(
name=str(name),
input_index=int(input_index),
x=x,
y=y,
))
project_name = path.stem # имя файла без расширения
return InputMapping(project_name=project_name, buttons=buttons)
def load_all_input_mappings() -> list[InputMapping]:
"""Загрузить все .mapping файлы из папки input_mappings/."""
mappings_dir = _get_input_mappings_dir()
if not mappings_dir.is_dir():
return []
mappings: list[InputMapping] = []
for mapping_file in sorted(mappings_dir.glob("*.mapping")):
mapping = load_input_mapping(mapping_file)
if mapping is not None:
mappings.append(mapping)
return mappings
def get_input_mappings_dir() -> Path:
"""Публичный доступ к пути папки (для UI «Открыть папку»)."""
return _get_input_mappings_dir()