added mesh surface arrows
This commit is contained in:
+72
-33
@@ -6,7 +6,7 @@ import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt5.QtCore import QSize, Qt, QTimer
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication,
|
||||
@@ -31,6 +31,7 @@ from .layout import (
|
||||
top_row_splitter_sizes,
|
||||
)
|
||||
from .styles import STYLE
|
||||
from .transport_icons import transport_icon
|
||||
from .widgets import (
|
||||
ArrowPanelWidget,
|
||||
ForceChartWidget,
|
||||
@@ -41,6 +42,15 @@ from .widgets import (
|
||||
)
|
||||
|
||||
|
||||
_PLAY_PAUSE_BUTTON_NAME = "btnPlayPause"
|
||||
_PLAY_PAUSE_ICON_PX = 18
|
||||
_PLAY_PAUSE_ICON_COLORS = {
|
||||
"run": "#4caf84",
|
||||
"starting": "#555555",
|
||||
"pause": "#ff9800",
|
||||
}
|
||||
|
||||
|
||||
class FingerDashboardWindow(QMainWindow):
|
||||
"""Single-window finger demo: 3D mesh | UV arrows | force charts."""
|
||||
|
||||
@@ -88,14 +98,14 @@ class FingerDashboardWindow(QMainWindow):
|
||||
bar = QHBoxLayout()
|
||||
bar.setSpacing(layout.toolbar_spacing_px)
|
||||
|
||||
self.btn_camera = QPushButton("摄像机")
|
||||
self.btn_camera = QPushButton("Sensor")
|
||||
self.btn_camera.clicked.connect(self._select_camera)
|
||||
bar.addWidget(self.btn_camera)
|
||||
self.camera_edit = QLineEdit("0")
|
||||
self.camera_edit.setPlaceholderText("摄像头编号 或 视频路径")
|
||||
self.camera_edit.setPlaceholderText("Camera ID or Video Path")
|
||||
bar.addWidget(self.camera_edit, 1)
|
||||
|
||||
self.btn_config = QPushButton("配置")
|
||||
self.btn_config = QPushButton("Config")
|
||||
self.btn_config.clicked.connect(self._select_config)
|
||||
bar.addWidget(self.btn_config)
|
||||
self.config_edit = QLineEdit("./config/dd02-ov.json")
|
||||
@@ -104,34 +114,38 @@ class FingerDashboardWindow(QMainWindow):
|
||||
self.cal_edit = QLineEdit("")
|
||||
bar.addWidget(self.cal_edit, 1)
|
||||
|
||||
self.btn_reset = QPushButton("重置")
|
||||
self.btn_reset = QPushButton("Reset")
|
||||
self.btn_reset.setEnabled(False)
|
||||
self.btn_reset.clicked.connect(self._on_reset)
|
||||
bar.addWidget(self.btn_reset)
|
||||
|
||||
self.btn_start = QPushButton("启动")
|
||||
self.btn_start.setObjectName("btnStart")
|
||||
self.btn_start.clicked.connect(self._on_start)
|
||||
bar.addWidget(self.btn_start)
|
||||
|
||||
self.btn_stop = QPushButton("暂停")
|
||||
self.btn_stop.setObjectName("btnStop")
|
||||
self.btn_stop.setEnabled(False)
|
||||
self.btn_stop.clicked.connect(self._on_stop)
|
||||
bar.addWidget(self.btn_stop)
|
||||
self.btn_play_pause = QPushButton()
|
||||
self.btn_play_pause.setObjectName(_PLAY_PAUSE_BUTTON_NAME)
|
||||
self.btn_play_pause.setFixedSize(36, 32)
|
||||
self.btn_play_pause.setIconSize(QSize(_PLAY_PAUSE_ICON_PX, _PLAY_PAUSE_ICON_PX))
|
||||
self.btn_play_pause.clicked.connect(self._on_play_pause_clicked)
|
||||
self._set_play_pause_button("run")
|
||||
bar.addWidget(self.btn_play_pause)
|
||||
|
||||
root_layout.addLayout(bar)
|
||||
|
||||
setup = layout.panel_setup
|
||||
self._panel_widgets: dict[str, QWidget] = {}
|
||||
|
||||
self.mesh_viewport = MeshViewportWidget(layout=layout)
|
||||
self.arrow_panel = ArrowPanelWidget(flow_threshold=layout.arrow_flow_threshold, layout=layout)
|
||||
arrows_slot = layout.slot("arrows")
|
||||
self.arrow_panel: ArrowPanelWidget | None = None
|
||||
if arrows_slot is not None and arrows_slot.enabled:
|
||||
self.arrow_panel = ArrowPanelWidget(
|
||||
flow_threshold=layout.arrow_flow_threshold,
|
||||
background=layout.arrow_background,
|
||||
layout=layout,
|
||||
)
|
||||
self.force_charts = ForceChartWidget(layout=layout, lowpass_cutoff_hz=self.config.lowpass_cutoff_hz)
|
||||
self.log_widget = make_log_widget(layout.log_config)
|
||||
|
||||
self._panel_widgets: dict[str, QWidget] = {}
|
||||
self._panel_widgets["mesh"] = self.mesh_viewport
|
||||
self._panel_widgets["arrows"] = self.arrow_panel
|
||||
if self.arrow_panel is not None:
|
||||
self._panel_widgets["arrows"] = self.arrow_panel
|
||||
self._panel_widgets["charts"] = self.force_charts.widget
|
||||
self._panel_widgets["log"] = self.log_widget
|
||||
|
||||
@@ -194,11 +208,40 @@ class FingerDashboardWindow(QMainWindow):
|
||||
sys.stdout = TeeStream(*streams)
|
||||
sys.stderr = sys.stdout
|
||||
|
||||
def _set_play_pause_button(self, state: str) -> None:
|
||||
"""``run`` = green play, ``starting`` = grey disabled play, ``pause`` = orange pause."""
|
||||
btn = self.btn_play_pause
|
||||
if state == "run":
|
||||
btn.setToolTip("Run")
|
||||
btn.setEnabled(True)
|
||||
elif state == "starting":
|
||||
btn.setToolTip("Starting…")
|
||||
btn.setEnabled(False)
|
||||
elif state == "pause":
|
||||
btn.setToolTip("Pause")
|
||||
btn.setEnabled(True)
|
||||
else:
|
||||
raise ValueError(f"unknown play/pause state: {state!r}")
|
||||
|
||||
kind = "pause" if state == "pause" else "play"
|
||||
btn.setText("")
|
||||
btn.setIcon(transport_icon(kind, _PLAY_PAUSE_ICON_COLORS[state]))
|
||||
btn.setProperty("playState", state)
|
||||
style = btn.style()
|
||||
style.unpolish(btn)
|
||||
style.polish(btn)
|
||||
btn.update()
|
||||
|
||||
def _on_play_pause_clicked(self) -> None:
|
||||
if self._running:
|
||||
self._on_stop()
|
||||
else:
|
||||
self._on_start()
|
||||
|
||||
def _on_start(self) -> None:
|
||||
if self._running:
|
||||
return
|
||||
self.btn_start.setText("启动中...")
|
||||
self.btn_start.setEnabled(False)
|
||||
self._set_play_pause_button("starting")
|
||||
QApplication.processEvents()
|
||||
|
||||
self.config.video = self.camera_edit.text().strip()
|
||||
@@ -214,8 +257,7 @@ class FingerDashboardWindow(QMainWindow):
|
||||
QTimer.singleShot(0, self._apply_panel_layout)
|
||||
except Exception as exc:
|
||||
self.mesh_viewport.close_renderer()
|
||||
self.btn_start.setText("启动")
|
||||
self.btn_start.setEnabled(True)
|
||||
self._set_play_pause_button("run")
|
||||
print(f"启动失败: {exc}")
|
||||
return
|
||||
|
||||
@@ -223,8 +265,7 @@ class FingerDashboardWindow(QMainWindow):
|
||||
self._frame_index = 0
|
||||
self._wall_fps = 0.0
|
||||
self.timer.start(self.layout.timer_interval_ms)
|
||||
self.btn_start.setText("启动")
|
||||
self.btn_stop.setEnabled(True)
|
||||
self._set_play_pause_button("pause")
|
||||
self.btn_reset.setEnabled(True)
|
||||
print(
|
||||
f"▶ 启动 — 摄像机={self.config.video}, 配置={self.config.config}, "
|
||||
@@ -237,17 +278,14 @@ class FingerDashboardWindow(QMainWindow):
|
||||
self._running = False
|
||||
self.timer.stop()
|
||||
self.log_handler.clear_status_line()
|
||||
self.btn_stop.setText("停止中...")
|
||||
self.btn_stop.setEnabled(False)
|
||||
self._set_play_pause_button("starting")
|
||||
QApplication.processEvents()
|
||||
|
||||
self.mesh_viewport.close_renderer()
|
||||
self.pipeline.close()
|
||||
|
||||
self.btn_start.setEnabled(True)
|
||||
self.btn_stop.setText("暂停")
|
||||
self._set_play_pause_button("run")
|
||||
self.btn_reset.setEnabled(False)
|
||||
print("⏸ 已暂停")
|
||||
|
||||
def _on_reset(self) -> None:
|
||||
if not self._running:
|
||||
@@ -278,9 +316,10 @@ class FingerDashboardWindow(QMainWindow):
|
||||
profile.record("mesh", mesh_ms)
|
||||
|
||||
arrows_ms = 0.0
|
||||
t0 = time.perf_counter()
|
||||
self.arrow_panel.update_frame(frame)
|
||||
arrows_ms = (time.perf_counter() - t0) * 1000.0
|
||||
if self.arrow_panel is not None:
|
||||
t0 = time.perf_counter()
|
||||
self.arrow_panel.update_frame(frame)
|
||||
arrows_ms = (time.perf_counter() - t0) * 1000.0
|
||||
if profile is not None:
|
||||
profile.record("arrows", arrows_ms)
|
||||
|
||||
|
||||
+47
-8
@@ -19,6 +19,8 @@ MeshLighting (below) controls the 3D mesh panel sun light:
|
||||
intensity — Filament sun strength (typical 30000–120000)
|
||||
ambient/diffuse — Windows viewer shading weights (0–1)
|
||||
|
||||
MESH_BACKGROUND (below) sets the 3D mesh panel clear color (RGB 0–255).
|
||||
|
||||
MESH_CONTACT_ARROW (below) controls the yellow 3D contact arrow on the mesh panel:
|
||||
reference_normal_force — Fn at which arrow reaches full size (log/power/linear curve)
|
||||
force_strength_mode — "log" | "power" | "linear"
|
||||
@@ -34,7 +36,7 @@ Performance (3D dashboard; edit DashboardLayout defaults):
|
||||
chart_update_stride — update force charts every N frames
|
||||
mesh_async_render — background mesh render (Linux only; Windows uses sync)
|
||||
|
||||
Edit TOP_ROW.slots to reorder or resize panels.
|
||||
Edit TOP_ROW.slots to reorder or resize panels; set ``enabled=False`` to hide a panel (e.g. arrows).
|
||||
Edit PANEL_SETUP to change top vs log proportions.
|
||||
Edit LOG_CONFIG for live status line and log panel appearance.
|
||||
"""
|
||||
@@ -46,6 +48,8 @@ from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
from viz.contact_arrow import ContactArrowStyle, DEFAULT_CONTACT_ARROW_STYLE
|
||||
from viz.flow_arrow import FlowArrowStyle
|
||||
from viz.render_2d import ArrowBackground, ArrowCanvasColors
|
||||
|
||||
PanelId = Literal["mesh", "arrows", "charts", "log"]
|
||||
|
||||
@@ -84,7 +88,7 @@ class PanelSetup:
|
||||
TOP_ROW = PanelRow(
|
||||
slots=(
|
||||
PanelSlot("mesh", "3D mesh", width_px=500, height_px=500),
|
||||
PanelSlot("arrows", "UV Unrolled", width_px=500, height_px=500),
|
||||
PanelSlot("arrows", "UV Unrolled", width_px=500, height_px=500, enabled=False),
|
||||
PanelSlot("charts", "Force charts", width_px=500, height_px=500),
|
||||
),
|
||||
)
|
||||
@@ -99,8 +103,8 @@ PANEL_SETUP = PanelSetup(
|
||||
)
|
||||
|
||||
# Open3D offscreen resolution for the mesh panel (independent of on-screen widget size).
|
||||
MESH_RENDER_WIDTH_PX = 256
|
||||
MESH_RENDER_HEIGHT_PX = 256
|
||||
MESH_RENDER_WIDTH_PX = 500
|
||||
MESH_RENDER_HEIGHT_PX = 500
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -123,13 +127,23 @@ class MeshLighting:
|
||||
|
||||
|
||||
MESH_LIGHTING = MeshLighting(
|
||||
azimuth_deg=-45.0,
|
||||
azimuth_deg=0,
|
||||
elevation_deg=0,
|
||||
intensity=75000.0,
|
||||
ambient=0.5,
|
||||
diffuse=0.5,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MeshBackground:
|
||||
"""3D mesh panel clear color — edit MESH_BACKGROUND or DashboardLayout.mesh_background."""
|
||||
|
||||
color: tuple[int, int, int] = (0, 0, 0)
|
||||
|
||||
|
||||
MESH_BACKGROUND = MeshBackground(color=(0, 0, 0))
|
||||
|
||||
# 3D mesh contact arrow — edit here for dashboard tuning (see ContactArrowStyle in viz/contact_arrow.py).
|
||||
MESH_CONTACT_ARROW = ContactArrowStyle(
|
||||
reference_normal_force=10000.0,
|
||||
@@ -143,6 +157,26 @@ MESH_CONTACT_ARROW = ContactArrowStyle(
|
||||
color=(1.0, 0.92, 0.10),
|
||||
)
|
||||
|
||||
# 3D mesh flow line arrows — tail_color/head_color are RGB 0–255 (see FlowArrowStyle).
|
||||
MESH_FLOW_ARROW = FlowArrowStyle(
|
||||
threshold=2.0,
|
||||
grid_spacing=20,
|
||||
arrow_scale=0.5,
|
||||
length_margin=1.0,
|
||||
tail_color=(40, 100, 220),
|
||||
head_color=(40, 100, 220),
|
||||
head_fraction=0.5,
|
||||
opacity=1,
|
||||
line_width=15,
|
||||
head_line_width=30.0,
|
||||
)
|
||||
|
||||
# UV arrow synthetic canvas — BGR tuples (outside valid_mask, inside valid_mask).
|
||||
ARROW_CANVAS_COLORS = ArrowCanvasColors(
|
||||
outside_bgr=(0, 0, 0),
|
||||
uv_bgr=(128, 0, 0),
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LogConfig:
|
||||
@@ -150,7 +184,7 @@ class LogConfig:
|
||||
|
||||
status_enabled: bool = True
|
||||
max_lines: int = 2000
|
||||
font_family: str = "Consolas"
|
||||
font_family: str = "Cascadia Mono"
|
||||
font_pt: int = 12
|
||||
show_flow: bool = True
|
||||
show_mask: bool = True
|
||||
@@ -191,7 +225,7 @@ class DashboardLayout:
|
||||
panel_setup: PanelSetup = PANEL_SETUP
|
||||
|
||||
window_title: str = "Orisys Finger Dashboard"
|
||||
window_width: int = 1500
|
||||
window_width: int = 1000
|
||||
window_height: int = 600
|
||||
|
||||
root_margin_px: int = 6
|
||||
@@ -203,15 +237,20 @@ class DashboardLayout:
|
||||
chart_title_font_pt: int = 11
|
||||
chart_axis_label_font_pt: int = 9
|
||||
chart_tick_font_pt: int = 9
|
||||
y_axis_min_span: float = 20000.0
|
||||
y_axis_min_span: float = 10000.0
|
||||
y_range_smooth_alpha: float = 0.5
|
||||
curve_color: str = "#00E5FF"
|
||||
|
||||
# UV arrow panel: min flow magnitude to draw an arrow (display only; not dd02-ov.json contact.flow_threshold)
|
||||
arrow_flow_threshold: float = 1.0
|
||||
# "unrolled" = sensor texture; "white" = solid canvas (tune colors via arrow_canvas_colors)
|
||||
arrow_background: ArrowBackground = "white"
|
||||
arrow_canvas_colors: ArrowCanvasColors = ARROW_CANVAS_COLORS
|
||||
|
||||
mesh_lighting: MeshLighting = MESH_LIGHTING
|
||||
mesh_background: MeshBackground = MESH_BACKGROUND
|
||||
mesh_contact_arrow: ContactArrowStyle = MESH_CONTACT_ARROW
|
||||
mesh_flow_arrow: FlowArrowStyle = MESH_FLOW_ARROW
|
||||
|
||||
log_config: LogConfig = LOG_CONFIG
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class MeshRenderWorker(QThread):
|
||||
self._renderer = None
|
||||
self._pending = None
|
||||
|
||||
def submit(self, *, flow, scalar_map, centroid, centroid_found, fnormal) -> None:
|
||||
def submit(self, *, flow, scalar_map, centroid, centroid_found, fnormal, valid_mask=None) -> None:
|
||||
with QMutexLocker(self._mutex):
|
||||
self._pending = {
|
||||
"flow": flow,
|
||||
@@ -36,6 +36,7 @@ class MeshRenderWorker(QThread):
|
||||
"centroid": centroid,
|
||||
"centroid_found": centroid_found,
|
||||
"fnormal": fnormal,
|
||||
"valid_mask": valid_mask,
|
||||
}
|
||||
self._wake = True
|
||||
if not self.isRunning():
|
||||
@@ -66,6 +67,7 @@ class MeshRenderWorker(QThread):
|
||||
centroid=job["centroid"],
|
||||
centroid_found=job["centroid_found"],
|
||||
fnormal=job["fnormal"],
|
||||
valid_mask=job["valid_mask"],
|
||||
)
|
||||
self.rgb_ready.emit(np.ascontiguousarray(rgb))
|
||||
|
||||
|
||||
+31
-22
@@ -1,5 +1,10 @@
|
||||
"""Qt stylesheet for the finger dashboard."""
|
||||
|
||||
UI_FONT_STACK = '"Segoe UI Variable", "Segoe UI", "Inter", "Microsoft YaHei UI", sans-serif'
|
||||
LOG_FONT_STACK = '"Cascadia Mono", "JetBrains Mono", "IBM Plex Mono", "Consolas", monospace'
|
||||
UI_FONT_PRIMARY = "Segoe UI"
|
||||
LOG_FONT_PRIMARY = "Cascadia Mono"
|
||||
|
||||
STYLE = """
|
||||
QMainWindow {
|
||||
background-color: #000000;
|
||||
@@ -17,7 +22,7 @@ QPushButton {
|
||||
border: 1px solid #333333;
|
||||
border-radius: 4px;
|
||||
padding: 6px 16px;
|
||||
font-family: "SimHei";
|
||||
font-family: __UI_FONT__;
|
||||
font-size: 13px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
@@ -25,33 +30,36 @@ QPushButton:hover {
|
||||
color: #cccccc;
|
||||
border: 1px solid #555555;
|
||||
}
|
||||
QPushButton#btnStart {
|
||||
QPushButton#btnPlayPause {
|
||||
background-color: #0d2818;
|
||||
color: #4caf84;
|
||||
border: 1px solid #1a4d30;
|
||||
padding: 4px 8px;
|
||||
font-size: 15px;
|
||||
}
|
||||
QPushButton#btnStart:hover {
|
||||
QPushButton#btnPlayPause:hover {
|
||||
background-color: #143d24;
|
||||
color: #6fd4a0;
|
||||
}
|
||||
QPushButton#btnStart:disabled {
|
||||
background-color: #0f0f0f;
|
||||
color: #333333;
|
||||
border: 1px solid #222222;
|
||||
QPushButton#btnPlayPause:disabled {
|
||||
background-color: #1a1a1a;
|
||||
color: #555555;
|
||||
border: 1px solid #333333;
|
||||
}
|
||||
QPushButton#btnStop {
|
||||
background-color: #2b1010;
|
||||
color: #e05555;
|
||||
border: 1px solid #4d1a1a;
|
||||
QPushButton#btnPlayPause[playState="pause"] {
|
||||
background-color: #3d2808;
|
||||
color: #ff9800;
|
||||
border: 1px solid #6d4400;
|
||||
}
|
||||
QPushButton#btnStop:hover {
|
||||
background-color: #3d1818;
|
||||
color: #f07070;
|
||||
QPushButton#btnPlayPause[playState="pause"]:hover {
|
||||
background-color: #523208;
|
||||
color: #ffb74d;
|
||||
border: 1px solid #8a5a00;
|
||||
}
|
||||
QPushButton#btnStop:disabled {
|
||||
background-color: #0f0f0f;
|
||||
color: #333333;
|
||||
border: 1px solid #222222;
|
||||
QPushButton#btnPlayPause[playState="pause"]:disabled {
|
||||
background-color: #1a1a1a;
|
||||
color: #555555;
|
||||
border: 1px solid #333333;
|
||||
}
|
||||
QLineEdit {
|
||||
background-color: #0d0d0d;
|
||||
@@ -59,6 +67,7 @@ QLineEdit {
|
||||
border: 1px solid #333333;
|
||||
border-radius: 4px;
|
||||
padding: 5px 10px;
|
||||
font-family: __UI_FONT__;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -69,11 +78,11 @@ GraphicsLayoutWidget {
|
||||
|
||||
QPlainTextEdit {
|
||||
background-color: #0b0b0b;
|
||||
color: #777777;
|
||||
color: #999999;
|
||||
border: 1px solid #262626;
|
||||
border-radius: 4px;
|
||||
font-family: "Cascadia Code", "Consolas", "Courier New", monospace;
|
||||
font-size: 13px;
|
||||
font-family: __LOG_FONT__;
|
||||
font-size: 12px;
|
||||
padding: 8px;
|
||||
selection-background-color: #1a3a4a;
|
||||
selection-color: #cccccc;
|
||||
@@ -111,4 +120,4 @@ QSplitter::handle:vertical {
|
||||
QSplitter::handle:horizontal {
|
||||
width: 1px;
|
||||
}
|
||||
"""
|
||||
""".replace("__UI_FONT__", UI_FONT_STACK).replace("__LOG_FONT__", LOG_FONT_STACK)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"""Monochrome play/pause icons for the dashboard toolbar (avoids Windows emoji glyphs)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from PyQt5.QtCore import QPointF, QRectF, Qt
|
||||
from PyQt5.QtGui import QColor, QIcon, QPainter, QPixmap, QPolygonF
|
||||
|
||||
TransportKind = Literal["play", "pause"]
|
||||
|
||||
_DEFAULT_ICON_PX = 18
|
||||
|
||||
|
||||
def transport_icon(kind: TransportKind, color: str, *, size: int = _DEFAULT_ICON_PX) -> QIcon:
|
||||
"""Build a flat play triangle or pause bars in ``color`` (#rrggbb)."""
|
||||
pixmap = QPixmap(size, size)
|
||||
pixmap.fill(Qt.transparent)
|
||||
|
||||
painter = QPainter(pixmap)
|
||||
painter.setRenderHint(QPainter.Antialiasing, True)
|
||||
painter.setBrush(QColor(color))
|
||||
painter.setPen(Qt.NoPen)
|
||||
|
||||
if kind == "play":
|
||||
w = size * 0.52
|
||||
h = size * 0.60
|
||||
x0 = (size - w) * 0.58
|
||||
y0 = (size - h) / 2.0
|
||||
painter.drawPolygon(
|
||||
QPolygonF(
|
||||
[
|
||||
QPointF(x0, y0),
|
||||
QPointF(x0, y0 + h),
|
||||
QPointF(x0 + w, y0 + h / 2.0),
|
||||
]
|
||||
)
|
||||
)
|
||||
else:
|
||||
bar_w = size * 0.17
|
||||
bar_h = size * 0.60
|
||||
gap = size * 0.14
|
||||
y0 = (size - bar_h) / 2.0
|
||||
x0 = (size - (2.0 * bar_w + gap)) / 2.0
|
||||
radius = max(1.0, size * 0.06)
|
||||
painter.drawRoundedRect(QRectF(x0, y0, bar_w, bar_h), radius, radius)
|
||||
painter.drawRoundedRect(QRectF(x0 + bar_w + gap, y0, bar_w, bar_h), radius, radius)
|
||||
|
||||
painter.end()
|
||||
return QIcon(pixmap)
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
import cv2
|
||||
|
||||
from pipeline.frame import FingerFrameView
|
||||
from viz.render_2d import render_uv_arrows
|
||||
from viz.render_2d import ArrowBackground, ArrowCanvasColors, render_uv_arrows
|
||||
|
||||
from ..layout import DashboardLayout, LAYOUT
|
||||
from .image_panel import ImagePanel
|
||||
@@ -19,6 +19,8 @@ class ArrowPanelWidget(ImagePanel):
|
||||
self,
|
||||
*,
|
||||
flow_threshold: float = 0.5,
|
||||
background: ArrowBackground = "unrolled",
|
||||
canvas_colors: ArrowCanvasColors | None = None,
|
||||
layout: DashboardLayout = LAYOUT,
|
||||
parent=None,
|
||||
):
|
||||
@@ -30,15 +32,18 @@ class ArrowPanelWidget(ImagePanel):
|
||||
parent=parent,
|
||||
)
|
||||
self.flow_threshold = flow_threshold
|
||||
self.background = background
|
||||
self.canvas_colors = canvas_colors if canvas_colors is not None else layout.arrow_canvas_colors
|
||||
|
||||
def set_flow_threshold(self, threshold: float) -> None:
|
||||
self.flow_threshold = threshold
|
||||
|
||||
def update_frame(self, frame: FingerFrameView) -> None:
|
||||
arrows = render_uv_arrows(
|
||||
frame.unrolled,
|
||||
frame.vnormal,
|
||||
frame,
|
||||
flow_threshold=self.flow_threshold,
|
||||
background=self.background,
|
||||
canvas_colors=self.canvas_colors,
|
||||
)
|
||||
arrows = cv2.rotate(arrows, cv2.ROTATE_180)
|
||||
self.set_source_pixmap(ndarray_to_pixmap(arrows, bgr=True))
|
||||
|
||||
@@ -7,6 +7,7 @@ from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWidgets import QSizePolicy
|
||||
|
||||
from gui.layout import DashboardLayout, LAYOUT
|
||||
from gui.styles import LOG_FONT_PRIMARY, UI_FONT_PRIMARY
|
||||
from pipeline.frame import FingerFrameView
|
||||
from viz.signal_filters import lowpass_filter
|
||||
|
||||
@@ -72,21 +73,21 @@ class ForceChartWidget:
|
||||
def _create_plots(self) -> None:
|
||||
pg = self._pg
|
||||
layout = self._layout
|
||||
titles = ["法向力", "切向力 X", "切向力 Y"]
|
||||
titles = ["Normal Deformation", "X Deformation", "Y Deformation"]
|
||||
self.plots = []
|
||||
self.curves = []
|
||||
self.dot_glows = []
|
||||
self.dot_cores = []
|
||||
|
||||
axis_pen = pg.mkPen(color=(38, 38, 38), width=1)
|
||||
label_font = QFont("SimHei", layout.chart_axis_label_font_pt)
|
||||
label_font = QFont(UI_FONT_PRIMARY, layout.chart_axis_label_font_pt)
|
||||
title_pt = layout.chart_title_font_pt
|
||||
|
||||
for i, title in enumerate(titles):
|
||||
plot = self.widget.addPlot(title=None)
|
||||
plot.setTitle(title, color="#999999", size=f"{title_pt}pt")
|
||||
plot.titleLabel.setFont(QFont("SimHei", title_pt))
|
||||
plot.setLabel("bottom", "时间", units="s")
|
||||
plot.titleLabel.setFont(QFont(UI_FONT_PRIMARY, title_pt))
|
||||
plot.setLabel("bottom", "Time", units="s")
|
||||
plot.getAxis("bottom").label.setFont(label_font)
|
||||
plot.showAxis("top", False)
|
||||
plot.showAxis("right", False)
|
||||
@@ -94,7 +95,7 @@ class ForceChartWidget:
|
||||
plot.getAxis("bottom").setPen(axis_pen)
|
||||
plot.showGrid(x=False, y=True, alpha=0.12)
|
||||
|
||||
tick_font = QFont("Consolas", layout.chart_tick_font_pt)
|
||||
tick_font = QFont(LOG_FONT_PRIMARY, layout.chart_tick_font_pt)
|
||||
tick_font.setStyleHint(QFont.Monospace)
|
||||
plot.getAxis("left").setTickFont(tick_font)
|
||||
plot.getAxis("bottom").setTickFont(tick_font)
|
||||
|
||||
@@ -65,7 +65,9 @@ class MeshViewportWidget(ImagePanel):
|
||||
world_to_cam=pipeline.world_to_cam,
|
||||
lookat=pipeline.mesh_lookat,
|
||||
mesh_lighting=self._layout.mesh_lighting,
|
||||
mesh_background=self._layout.mesh_background.color,
|
||||
contact_arrow_style=self._layout.mesh_contact_arrow,
|
||||
flow_arrow_style=self._layout.mesh_flow_arrow,
|
||||
)
|
||||
|
||||
def _stop_worker(self) -> None:
|
||||
@@ -93,6 +95,7 @@ class MeshViewportWidget(ImagePanel):
|
||||
centroid=frame.centroid,
|
||||
centroid_found=frame.centroid_found,
|
||||
fnormal=frame.fnormal,
|
||||
valid_mask=frame.valid_mask,
|
||||
)
|
||||
return 0.0
|
||||
|
||||
@@ -105,6 +108,7 @@ class MeshViewportWidget(ImagePanel):
|
||||
centroid=frame.centroid,
|
||||
centroid_found=frame.centroid_found,
|
||||
fnormal=frame.fnormal,
|
||||
valid_mask=frame.valid_mask,
|
||||
)
|
||||
self.set_source_pixmap(ndarray_to_pixmap(rgb, bgr=False))
|
||||
return (time.perf_counter() - t0) * 1000.0
|
||||
|
||||
Reference in New Issue
Block a user