Release build naming and arrow visibility controls

This commit is contained in:
Данил Ярош 2026-05-21 09:52:12 +03:00
parent 5895ccc29a
commit 070bb671c8
6 changed files with 125 additions and 6 deletions

View File

@ -1,11 +1,41 @@
# -*- mode: python ; coding: utf-8 -*-
import subprocess
from pathlib import Path
spec_dir = Path(SPECPATH)
def git_build_ref():
try:
tag = subprocess.check_output(
['git', 'describe', '--tags', '--exact-match', 'HEAD'],
cwd=spec_dir,
stderr=subprocess.DEVNULL,
text=True,
).strip()
if tag:
return tag
except (OSError, subprocess.CalledProcessError):
pass
try:
commit = subprocess.check_output(
['git', 'rev-parse', '--short', 'HEAD'],
cwd=spec_dir,
stderr=subprocess.DEVNULL,
text=True,
).strip()
if commit:
return commit
except (OSError, subprocess.CalledProcessError):
pass
return 'untagged'
app_name = f'LEDMapper-{git_build_ref()}'
a = Analysis(
[str(spec_dir / 'main.py')],
pathex=[str(spec_dir), str(spec_dir.parent)],
@ -34,7 +64,7 @@ exe = EXE(
a.binaries,
a.datas,
[],
name='LEDMapper',
name=app_name,
debug=False,
bootloader_ignore_signals=False,
strip=False,

View File

@ -100,6 +100,7 @@ python -m venv .venv
| Очистить выбор | `Esc` |
| Показать всю схему | `Ctrl+0` |
| Сетка 34 мм | `G` |
| Стрелки направления | `Ctrl+Shift+A` |
| Скрыть/показать боковую панель | `F9` |
| Новая линия | `Ctrl+L` |
| Автоблоки | `Ctrl+Shift+B` |

View File

@ -66,6 +66,7 @@ class LedCanvas(QGraphicsView):
self.items_by_id: dict[int, LedItem] = {}
self.grid_items: list[QGraphicsLineItem] = []
self.connection_items: list[QGraphicsPathItem] = []
self._direction_arrows_visible = True
self._point_radius = 4.5
self._brush_radius = 18.0
self._brush_zone_item: QGraphicsEllipseItem | None = None
@ -100,6 +101,15 @@ class LedCanvas(QGraphicsView):
self._grid_visible = visible
self._refresh_grid()
def set_direction_arrows_visible(self, visible: bool) -> None:
if self._direction_arrows_visible == visible:
return
self._direction_arrows_visible = visible
self.refresh_styles(redraw_connections=True)
def direction_arrows_visible(self) -> bool:
return self._direction_arrows_visible
def set_brush_enabled(self, enabled: bool) -> None:
self._brush_enabled = enabled
self._is_brush_stroke = False
@ -537,9 +547,11 @@ class LedCanvas(QGraphicsView):
ordered_items = [self.items_by_id[point_id] for point_id in block.point_ids if point_id in self.items_by_id]
for first, second in zip(ordered_items, ordered_items[1:]):
self._append_connection(inner_path, first.pos(), second.pos())
self._append_arrow_marker(inner_arrows, first.pos(), second.pos())
if self._direction_arrows_visible:
self._append_arrow_marker(inner_arrows, first.pos(), second.pos())
self._add_connection_path(inner_path, line.color, inner_width, inner_alpha)
self._add_connection_path(inner_arrows, line.color, arrow_width, arrow_alpha, z_value=2)
if self._direction_arrows_visible:
self._add_connection_path(inner_arrows, line.color, arrow_width, arrow_alpha, z_value=2)
previous_block_end = None
bridge_path = QPainterPath()
@ -551,11 +563,13 @@ class LedCanvas(QGraphicsView):
block_end = self.items_by_id.get(block.point_ids[-1])
if block_start is not None and previous_block_end is not None:
self._append_connection(bridge_path, previous_block_end.pos(), block_start.pos())
self._append_arrow_marker(bridge_arrows, previous_block_end.pos(), block_start.pos())
if self._direction_arrows_visible:
self._append_arrow_marker(bridge_arrows, previous_block_end.pos(), block_start.pos())
if block_end is not None:
previous_block_end = block_end
self._add_connection_path(bridge_path, line.color, bridge_width, bridge_alpha)
self._add_connection_path(bridge_arrows, line.color, arrow_width, bridge_alpha, z_value=2)
if self._direction_arrows_visible:
self._add_connection_path(bridge_arrows, line.color, arrow_width, bridge_alpha, z_value=2)
@staticmethod
def _append_connection(path: QPainterPath, start: QPointF, end: QPointF) -> None:

View File

@ -7,6 +7,7 @@ from PySide6.QtSvg import QSvgRenderer
LUCIDE_PATHS = {
"archive-restore": '<rect width="20" height="5" x="2" y="3" rx="1"/><path d="M4 8v11a2 2 0 0 0 2 2h2"/><path d="M20 8v11a2 2 0 0 1-2 2h-2"/><path d="m9 15 3-3 3 3"/><path d="M12 12v9"/>',
"arrow-right": '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>',
"brush": '<path d="m9.06 11.9 8.07-8.06a2.85 2.85 0 1 1 4.03 4.03l-8.06 8.08"/><path d="M7.07 14.94c-1.66 0-3 1.35-3 3.01 0 1.1-.89 2-2 2 1.4 1.4 3.67 1.4 5.07 0l2.94-2.94a2.93 2.93 0 0 0-4.14-4.14z"/>',
"chevron-right": '<path d="m9 18 6-6-6-6"/>',
"eraser": '<path d="m7 21-4.3-4.3c-.9-.9-.9-2.4 0-3.3L14 2l8 8-11 11Z"/><path d="M22 21H7"/><path d="m5 11 8 8"/>',

View File

@ -209,6 +209,16 @@ class MainWindow(QMainWindow):
add_action("fit_view", "Показать всё", self.canvas.fit_to_content, "eye", QKeySequence("Ctrl+0"))
grid_action = add_action("grid", "Сетка 34 мм", self.toggle_grid, "grid-2x2", QKeySequence("G"), "Показать или скрыть сетку 34 мм")
grid_action.setCheckable(True)
arrows_action = add_action(
"direction_arrows",
"Стрелки направления",
self.toggle_direction_arrows,
"arrow-right",
QKeySequence("Ctrl+Shift+A"),
"Показать или скрыть стрелки направления на линиях",
)
arrows_action.setCheckable(True)
arrows_action.setChecked(True)
add_action("toggle_sidebar", "Скрыть боковую панель", self.toggle_sidebar, "chevron-right", QKeySequence("F9"))
self._build_menu_bar()
self.addAction(self.delete_action)
@ -234,6 +244,7 @@ class MainWindow(QMainWindow):
view_menu = menu_bar.addMenu("Вид")
view_menu.addAction(self.actions["fit_view"])
view_menu.addAction(self.actions["grid"])
view_menu.addAction(self.actions["direction_arrows"])
window_menu = menu_bar.addMenu("Окно")
window_menu.addAction(self.actions["toggle_sidebar"])
@ -349,6 +360,11 @@ class MainWindow(QMainWindow):
else:
self.collapse_sidebar()
def toggle_direction_arrows(self) -> None:
action = self.actions.get("direction_arrows")
visible = True if action is None else action.isChecked()
self.canvas.set_direction_arrows_visible(visible)
def collapse_sidebar(self) -> None:
if self.sidebar_splitter is None or self.sidebar_stack is None or self._sidebar_collapsed:
return

View File

@ -82,6 +82,7 @@ def main() -> None:
test_grid_toggle_adds_grid_items,
test_canvas_connects_new_block_to_previous_non_empty_block,
test_canvas_draws_direction_arrows_on_connections,
test_canvas_can_toggle_direction_arrows,
test_canvas_rebuild_drops_deleted_selection_items,
test_canvas_refresh_does_not_accumulate_connection_items,
test_main_window_has_classic_menu_bar,
@ -90,6 +91,7 @@ def main() -> None:
test_undo_redo_roundtrip_for_line_creation,
test_main_window_actions_use_lucide_icons,
test_main_window_action_shortcuts_are_assigned,
test_main_window_direction_arrow_action_toggles_canvas,
test_lucide_icon_names_are_declared,
test_table_hides_empty_line_index_columns,
test_sidebar_collapses_and_expands,
@ -794,6 +796,37 @@ def test_canvas_draws_direction_arrows_on_connections() -> None:
assert sum(path.elementCount() for path in arrow_paths) >= 6
def test_canvas_can_toggle_direction_arrows() -> None:
if LedCanvas is None or QApplication is None:
return
app = QApplication.instance() or QApplication([])
with tempfile.TemporaryDirectory() as temp_dir:
path = Path(temp_dir) / "arrows-toggle.json"
points = [
{"id": 0, "x": 0.0, "y": 0.0, "row": 0, "col": 0},
{"id": 1, "x": 34.0, "y": 0.0, "row": 0, "col": 1},
{"id": 2, "x": 68.0, "y": 0.0, "row": 0, "col": 2},
]
path.write_text(json.dumps({"circles": points}), encoding="utf-8")
doc = MappingDocument()
doc.load(path)
line = doc.add_line()
doc.add_block(line.id, [0, 1, 2])
canvas = LedCanvas()
canvas.set_document(doc)
canvas.set_active_line(line.id)
assert any(item.zValue() == 2 for item in canvas.connection_items)
canvas.set_direction_arrows_visible(False)
assert canvas.direction_arrows_visible() is False
assert not any(item.zValue() == 2 for item in canvas.connection_items)
canvas.set_direction_arrows_visible(True)
assert canvas.direction_arrows_visible() is True
assert any(item.zValue() == 2 for item in canvas.connection_items)
def test_canvas_rebuild_drops_deleted_selection_items() -> None:
if LedCanvas is None or QApplication is None:
return
@ -855,7 +888,7 @@ def test_classic_menu_contains_expected_actions() -> None:
expected = {
"Файл": ["Открыть", "Сохранить", "Сохранить как", "Сгенерировать скрипт"],
"Правка": ["Отменить", "Повторить", "Удалить фиксацию", "Удалить все", "Очистить выбор"],
"Вид": ["Показать всё", "Сетка 34 мм"],
"Вид": ["Показать всё", "Сетка 34 мм", "Стрелки направления"],
"Окно": ["Скрыть боковую панель"],
"Инструменты": ["Новая линия", "Сгенерировать скрипт", "Автоблоки", "Кисть"],
}
@ -950,6 +983,7 @@ def test_main_window_actions_use_lucide_icons() -> None:
"clear_selection",
"fit_view",
"grid",
"direction_arrows",
"toggle_sidebar",
}
missing = [key for key in expected_icon_actions if window.actions[key].icon().isNull()]
@ -970,6 +1004,7 @@ def test_main_window_action_shortcuts_are_assigned() -> None:
"clear_selection": "Esc",
"fit_view": "Ctrl+0",
"grid": "G",
"direction_arrows": "Ctrl+Shift+A",
"toggle_sidebar": "F9",
"add_line": "Ctrl+L",
"auto_blocks": "Ctrl+Shift+B",
@ -981,9 +1016,31 @@ def test_main_window_action_shortcuts_are_assigned() -> None:
window.close()
def test_main_window_direction_arrow_action_toggles_canvas() -> None:
if MainWindow is None or QApplication is None:
return
app = QApplication.instance() or QApplication([])
window = MainWindow()
app.processEvents()
action = window.actions["direction_arrows"]
assert action.isChecked()
assert window.canvas.direction_arrows_visible() is True
action.trigger()
assert not action.isChecked()
assert window.canvas.direction_arrows_visible() is False
action.trigger()
assert action.isChecked()
assert window.canvas.direction_arrows_visible() is True
window.close()
def test_lucide_icon_names_are_declared() -> None:
expected_names = {
"archive-restore",
"arrow-right",
"chevron-right",
"eraser",
"eye",