From 2815a90ffa7e734755884db675a9d5aa704c45a0 Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Thu, 4 Jun 2026 12:35:36 -0600 Subject: Restore Motion Capture toolbar button with multi-camera support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit get_toolbar_actions() returns a checkable 🎥 button. On click: - 0 camera devices → info message, button unchecks - 1 camera device → opens MotionCaptureWindow directly - N camera devices → QInputDialog picker, opens selected device's window Windows tracked per device_id; closing a window removes it from the map and unchecks the button when none remain. on_unload closes all open windows before deregistering the Camera panel. Co-Authored-By: Claude Sonnet 4.6 --- plugins/motion_capture/plugin.py | 84 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) (limited to 'plugins/motion_capture/plugin.py') diff --git a/plugins/motion_capture/plugin.py b/plugins/motion_capture/plugin.py index 6eb5508..0b44cb4 100644 --- a/plugins/motion_capture/plugin.py +++ b/plugins/motion_capture/plugin.py @@ -18,7 +18,9 @@ Install OpenCV for real camera support: from __future__ import annotations -from plugins.base_plugin import LabPlugin, PluginContext +from typing import List, Optional + +from plugins.base_plugin import LabPlugin, PluginAction, PluginContext class MotionCapturePlugin(LabPlugin): @@ -47,17 +49,95 @@ class MotionCapturePlugin(LabPlugin): # ── Lifecycle ───────────────────────────────────────────────────────── def on_load(self, context: PluginContext) -> None: - self._ctx = context + self._ctx = context + self._btn = None # toolbar button reference + self._windows: dict = {} # device_id → MotionCaptureWindow from ui.add_device_dialog import _PANELS from camera_panel import CameraPanel _PANELS["Camera"] = (CameraPanel, "cam") def on_unload(self) -> None: + for win in list(self._windows.values()): + win.close() + self._windows.clear() from ui.add_device_dialog import _PANELS _PANELS.pop("Camera", None) # ── Integration hooks ───────────────────────────────────────────────── + def get_toolbar_actions(self) -> list: + return [PluginAction( + label="Motion Capture", + icon="🎥", + tooltip="Open camera tracking view", + checkable=True, + callback=self._on_btn_toggled, + button_ref_callback=lambda btn: setattr(self, "_btn", btn), + )] + def get_filter_classes(self) -> dict: from filter import PixelToMMFilter return {"pixel_to_mm": PixelToMMFilter} + + # ── Internal ────────────────────────────────────────────────────────── + + def _camera_devices(self) -> list: + from device import CameraDevice + return [d for d in self._ctx.registry.all_instances() + if isinstance(d, CameraDevice)] + + def _on_btn_toggled(self, checked: bool) -> None: + from PyQt6.QtWidgets import QInputDialog, QMessageBox + if not checked: + return + + cameras = self._camera_devices() + + if not cameras: + QMessageBox.information( + None, "No Camera Devices", + "Add a Camera device first via Devices → Add Device.", + ) + if self._btn is not None: + self._btn.setChecked(False) + return + + if len(cameras) == 1: + self._open_window(cameras[0]) + else: + names = [f"{d.info.name} ({d.info.device_id})" for d in cameras] + choice, ok = QInputDialog.getItem( + None, "Select Camera", "Open tracking view for:", names, 0, False, + ) + if not ok: + if self._btn is not None: + self._btn.setChecked(False) + return + idx = names.index(choice) + self._open_window(cameras[idx]) + + def _open_window(self, device) -> None: + from window import MotionCaptureWindow + dev_id = device.info.device_id + if dev_id in self._windows: + win = self._windows[dev_id] + win.show(); win.raise_() + return + if device._tracker is None: + from PyQt6.QtWidgets import QMessageBox + QMessageBox.warning( + None, "Camera Not Connected", + f"{device.info.name} is not connected. Connect it first.", + ) + if self._btn is not None: + self._btn.setChecked(False) + return + win = MotionCaptureWindow(device._tracker) + win.closed.connect(lambda: self._close_window(dev_id)) + self._windows[dev_id] = win + win.show() + + def _close_window(self, dev_id: str) -> None: + self._windows.pop(dev_id, None) + if not self._windows and self._btn is not None: + self._btn.setChecked(False) -- cgit v1.2.3