summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Kolset <christian.kolset@gmail.com>2026-06-04 12:35:36 -0600
committerChristian Kolset <christian.kolset@gmail.com>2026-06-04 12:41:14 -0600
commit2815a90ffa7e734755884db675a9d5aa704c45a0 (patch)
treefa7251c99ec44813ecc2831a6e07b045f5d8e52a
parent864642a84d8e93cc4971c7915e39a60740cdd6fa (diff)
Restore Motion Capture toolbar button with multi-camera support
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 <noreply@anthropic.com>
-rw-r--r--plugins/motion_capture/plugin.py84
1 files changed, 82 insertions, 2 deletions
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)