From 973c02a043e38cff3d5e5a54332cb8ab646c2c8c Mon Sep 17 00:00:00 2001 From: qyhhh Date: Thu, 30 Jul 2026 15:55:33 +0800 Subject: [PATCH] dasd --- .gitignore | 1 + README.md | 2 ++ agent.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ main.py | 46 +++++++++++++++++++++++++++++++++++----------- 4 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 agent.md diff --git a/.gitignore b/.gitignore index 4dee5c7..8b9633e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ __pycache__/ .venv/ .build-venv/ venv/ +client/ *.egg-info/ dist/ build/ diff --git a/README.md b/README.md index 91067eb..5c6a891 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ python main.py static/test.mp4 1. **文件 → 打开视频** 2. 在首帧依次点击 **O**(圆心)、**M**(初始尾端参考) 3. 拖动滑块到目标帧,点击 **M2**(当前尾端),右侧显示角度 +4. 在画面上按 **A** / **D** 可按“跳帧数”向前 / 向后跳转 ### 测量模式 @@ -34,6 +35,7 @@ python main.py static/test.mp4 3. 逐帧点击 M2,每点完一帧自动进下一帧 4. 点击 **「停止测量」** 或跑完全部帧后自动停止 5. **「导出测量数据 (CSV)」** 保存角度 + 每帧标注截图 +6. 测量中按 **A** / **D** 按“跳帧数”跳转,按 **→** 跳过当前帧并记录 0 ### 方向矫正 diff --git a/agent.md b/agent.md new file mode 100644 index 0000000..29846eb --- /dev/null +++ b/agent.md @@ -0,0 +1,51 @@ +# Actuator Agent Guide + +## Project + +- Python/PyQt6 desktop tool for reading actuator angles from video frames. +- Application entry point: `main.py`. +- Core modules: `actuator/angle_calc.py`, `actuator/video_reader.py`, and `actuator/automation.py`. + +## Environment + +- Use the project-local `uv` virtual environment: `client/`. +- Python version: 3.10. +- Install or refresh dependencies: + +```bash +uv pip install --python client/bin/python -r requirements.txt +``` + +## Run And Verify + +```bash +client/bin/python main.py +``` + +For Wayland: + +```bash +QT_QPA_PLATFORM=xcb client/bin/python main.py +``` + +Smoke-test GUI initialization without a display: + +```bash +QT_QPA_PLATFORM=offscreen timeout 5s client/bin/python main.py +``` + +An exit status caused by `timeout` is expected when the GUI initializes and remains open. + +## Frame Navigation + +- Set the "跳帧数" control to choose a frame-jump step (1-10000). +- `A`: move backward by the selected step. +- `D`: move forward by the selected step. +- During measurement, `A`/`D` only navigate and do not create measurements for skipped frames. +- During measurement, `Right` skips the current frame and records angle `0`; if M2 is selected, it records the measurement and advances one frame. + +## Change Guidelines + +- Keep GUI text in Chinese to match the existing application. +- Run `client/bin/python -m py_compile main.py actuator/*.py` after Python changes. +- Do not commit the local `client/` environment; it is ignored by Git. diff --git a/main.py b/main.py index 801de67..561af41 100644 --- a/main.py +++ b/main.py @@ -24,7 +24,7 @@ from PyQt6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QSlider, QFileDialog, QGroupBox, QDoubleSpinBox, QFormLayout, QMessageBox, QSplitter, QCheckBox, - QSizePolicy, + QSizePolicy, QSpinBox, ) from actuator import VideoReader, signed_angle, curvature_from_three_points @@ -55,6 +55,7 @@ class FrameView(QLabel): self.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setStyleSheet("background: #1a1a1a; border: 1px solid #333;") self.setMouseTracking(True) + self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.setAcceptDrops(True) self._frame: Optional[np.ndarray] = None @@ -375,10 +376,19 @@ class MainWindow(QMainWindow): self.slider.setMaximum(0) self.slider.valueChanged.connect(self._on_slider) nl.addWidget(self.slider) + step_row = QHBoxLayout() + step_row.addWidget(QLabel("跳帧数:")) + self.spin_frame_step = QSpinBox() + self.spin_frame_step.setRange(1, 10000) + self.spin_frame_step.setValue(1) + self.spin_frame_step.setSuffix(" 帧") + self.spin_frame_step.setToolTip("A/D 快捷键和导航按钮的跳转步长") + step_row.addWidget(self.spin_frame_step) + nl.addLayout(step_row) bn = QHBoxLayout() - self.btn_prev = QPushButton("◀") + self.btn_prev = QPushButton("A ◀") self.btn_prev.clicked.connect(self._prev_frame) - self.btn_next = QPushButton("▶") + self.btn_next = QPushButton("D ▶") self.btn_next.clicked.connect(self._next_frame) bn.addWidget(self.btn_prev) bn.addWidget(self.btn_next) @@ -486,13 +496,23 @@ class MainWindow(QMainWindow): self._show_frame(value) def _prev_frame(self) -> None: - if self._video and not self._measuring: - self._show_frame(max(0, self._current_frame_idx - 1)) + self._jump_frames(-self.spin_frame_step.value()) def _next_frame(self) -> None: - if self._video and not self._measuring: - self._show_frame(min(self._video.frame_count - 1, - self._current_frame_idx + 1)) + self._jump_frames(self.spin_frame_step.value()) + + def _jump_frames(self, offset: int) -> None: + """按偏移量跳转,测量时不为未测帧创建记录。""" + if self._video is None: + return + target = max(0, min(self._video.frame_count - 1, + self._current_frame_idx + offset)) + if target == self._current_frame_idx: + return + if self._measuring: + self._go_to_frame(target) + else: + self._show_frame(target) # ══════════════ 测量模式 ═════════════════════════════ @@ -527,6 +547,12 @@ class MainWindow(QMainWindow): self._update_display() def _on_key_pressed(self, key: int) -> None: + if key == Qt.Key.Key_A: + self._prev_frame() + return + if key == Qt.Key.Key_D: + self._next_frame() + return if not self._measuring: return if key == Qt.Key.Key_Right: @@ -535,9 +561,7 @@ class MainWindow(QMainWindow): else: self._record_skip() elif key == Qt.Key.Key_Left: - if self._current_frame_idx > 0: - self._current_frame_idx -= 1 - self._show_frame(self._current_frame_idx) + self._jump_frames(-1) def _record_skip(self) -> None: """跳过当前帧,记录 0"""