This commit is contained in:
cheng
2026-06-09 14:59:43 +08:00
parent 9a3a397e2c
commit c700f95fb0
5 changed files with 64 additions and 89 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

+39 -89
View File
@@ -37,13 +37,14 @@ try:
QMainWindow, QMainWindow,
QPushButton, QPushButton,
QSizePolicy, QSizePolicy,
QSplitter,
QVBoxLayout, QVBoxLayout,
QWidget, QWidget,
) )
except Exception as exc: except Exception as exc:
_QT_IMPORT_ERROR = exc _QT_IMPORT_ERROR = exc
Qt = QTimer = QPropertyAnimation = QEasingCurve = QFont = QImage = QPixmap = QApplication = None Qt = QTimer = QPropertyAnimation = QEasingCurve = QFont = QImage = QPixmap = QApplication = None
QGridLayout = QHBoxLayout = QLabel = QPushButton = QSizePolicy = None QGridLayout = QHBoxLayout = QLabel = QPushButton = QSizePolicy = QSplitter = None
QVBoxLayout = QWidget = None QVBoxLayout = QWidget = None
QFrame = QGraphicsOpacityEffect = QMainWindow = object QFrame = QGraphicsOpacityEffect = QMainWindow = object
@@ -207,6 +208,18 @@ QPushButton#HoldButton {
color: #d8d8d8; color: #d8d8d8;
border: 1px solid #363636; border: 1px solid #363636;
} }
QSplitter::handle {
background-color: #151515;
}
QSplitter::handle:hover {
background-color: #2a2a2a;
}
QSplitter::handle:horizontal {
width: 8px;
}
QSplitter::handle:vertical {
height: 8px;
}
""" """
@@ -241,26 +254,21 @@ else:
class MetricBox(QFrame): class MetricBox(QFrame):
def __init__(self, title, value="--", sub=""): def __init__(self, title, value="--"):
super().__init__() super().__init__()
self.setObjectName("Panel") self.setObjectName("Panel")
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setContentsMargins(10, 8, 10, 8) layout.setContentsMargins(10, 8, 10, 8)
layout.setSpacing(3) layout.setSpacing(4)
self.title_label = QLabel(title) self.title_label = QLabel(title)
self.title_label.setObjectName("MetricLabel") self.title_label.setObjectName("MetricLabel")
self.value_label = QLabel(value) self.value_label = QLabel(value)
self.value_label.setObjectName("MetricValue") self.value_label.setObjectName("MetricValue")
self.sub_label = QLabel(sub)
self.sub_label.setObjectName("Subtle")
layout.addWidget(self.title_label) layout.addWidget(self.title_label)
layout.addWidget(self.value_label) layout.addWidget(self.value_label)
layout.addWidget(self.sub_label)
def set_value(self, value, sub=None): def set_value(self, value, sub=None):
self.value_label.setText(value) self.value_label.setText(value)
if sub is not None:
self.sub_label.setText(sub)
class ImageBox(QFrame): class ImageBox(QFrame):
@@ -326,8 +334,6 @@ class GripperDemo02Viewer(QMainWindow):
title.setObjectName("Title") title.setObjectName("Title")
self.status_chip = QLabel("state=--") self.status_chip = QLabel("state=--")
self.status_chip.setObjectName("StatusChip") self.status_chip.setObjectName("StatusChip")
self.action_chip = QLabel("action=--")
self.action_chip.setObjectName("ActionChip")
self.start_button = AnimatedButton("启动") self.start_button = AnimatedButton("启动")
self.start_button.setObjectName("StartButton") self.start_button.setObjectName("StartButton")
self.start_button.clicked.connect(self.start_control) self.start_button.clicked.connect(self.start_control)
@@ -350,7 +356,7 @@ class GripperDemo02Viewer(QMainWindow):
self._set_button_state("idle") self._set_button_state("idle")
top.addWidget(title) top.addWidget(title)
top.addWidget(self.status_chip) top.addWidget(self.status_chip)
top.addWidget(self.action_chip, 1) top.addStretch(1)
top.addWidget(self.start_button) top.addWidget(self.start_button)
top.addWidget(self.stop_button) top.addWidget(self.stop_button)
top.addWidget(self.restart_button) top.addWidget(self.restart_button)
@@ -359,18 +365,29 @@ class GripperDemo02Viewer(QMainWindow):
top.addWidget(self.manual_hold_button) top.addWidget(self.manual_hold_button)
outer.addLayout(top) outer.addLayout(top)
main = QGridLayout() main_splitter = QSplitter(Qt.Horizontal)
main.setSpacing(8) main_splitter.setChildrenCollapsible(False)
outer.addLayout(main, 1) outer.addWidget(main_splitter, 1)
flow_splitter = QSplitter(Qt.Vertical)
flow_splitter.setChildrenCollapsible(False)
self.left_flow = ImageBox("左侧光流") self.left_flow = ImageBox("左侧光流")
self.right_flow = ImageBox("右侧光流") self.right_flow = ImageBox("右侧光流")
main.addWidget(self.left_flow, 0, 0) flow_splitter.addWidget(self.left_flow)
main.addWidget(self.right_flow, 1, 0) flow_splitter.addWidget(self.right_flow)
flow_splitter.setStretchFactor(0, 1)
flow_splitter.setStretchFactor(1, 1)
flow_splitter.setSizes([360, 360])
main_splitter.addWidget(flow_splitter)
side = QVBoxLayout() side_widget = QWidget()
side = QVBoxLayout(side_widget)
side.setContentsMargins(0, 0, 0, 0)
side.setSpacing(8) side.setSpacing(8)
main.addLayout(side, 0, 1, 2, 1) main_splitter.addWidget(side_widget)
main_splitter.setStretchFactor(0, 2)
main_splitter.setStretchFactor(1, 4)
main_splitter.setSizes([520, 980])
metrics_grid = QGridLayout() metrics_grid = QGridLayout()
metrics_grid.setSpacing(8) metrics_grid.setSpacing(8)
@@ -407,21 +424,6 @@ class GripperDemo02Viewer(QMainWindow):
self.plot_widget.setMinimumHeight(240) self.plot_widget.setMinimumHeight(240)
side.addWidget(self.plot_widget, 1) side.addWidget(self.plot_widget, 1)
self.info_box = QFrame()
self.info_box.setObjectName("Panel")
info_layout = QVBoxLayout(self.info_box)
info_layout.setContentsMargins(10, 8, 10, 8)
self.info_label = QLabel("等待启动")
self.info_label.setObjectName("SmallValue")
self.info_label.setWordWrap(True)
info_layout.addWidget(self.info_label)
side.addWidget(self.info_box)
main.setColumnStretch(0, 2)
main.setColumnStretch(1, 4)
main.setRowStretch(0, 1)
main.setRowStretch(1, 1)
def _configure_plot(self): def _configure_plot(self):
self.plots = [] self.plots = []
self.curves = {} self.curves = {}
@@ -444,6 +446,7 @@ class GripperDemo02Viewer(QMainWindow):
plot.showGrid(x=False, y=True, alpha=0.12) plot.showGrid(x=False, y=True, alpha=0.12)
plot.setLabel("bottom", "时间", units="s") plot.setLabel("bottom", "时间", units="s")
plot.setLabel("left", "", units="N") plot.setLabel("left", "", units="N")
plot.getAxis("bottom").setTickSpacing(major=1.0, minor=1.0)
plot.setYRange(y_min, y_max, padding=0) plot.setYRange(y_min, y_max, padding=0)
plot.setLimits(yMin=y_min, yMax=y_max) plot.setLimits(yMin=y_min, yMax=y_max)
plot.enableAutoRange(axis=pg.ViewBox.YAxis, enable=False) plot.enableAutoRange(axis=pg.ViewBox.YAxis, enable=False)
@@ -481,12 +484,11 @@ class GripperDemo02Viewer(QMainWindow):
def send_manual_command(self, command): def send_manual_command(self, command):
if self.controller is None or self.control_thread is None or not self.control_thread.is_alive(): if self.controller is None or self.control_thread is None or not self.control_thread.is_alive():
self.info_label.setText("请先启动控制器")
return return
try: try:
self.controller.request_manual_command(command) self.controller.request_manual_command(command)
except Exception as exc: except Exception as exc:
self.info_label.setText(f"手动命令失败:{exc}") print(f"manual command failed: {exc}")
def toggle_manual_hold(self): def toggle_manual_hold(self):
command = "release" if self.manual_hold_button.text() == "释放" else "hold" command = "release" if self.manual_hold_button.text() == "释放" else "hold"
@@ -499,10 +501,6 @@ class GripperDemo02Viewer(QMainWindow):
self.start_time = time.time() self.start_time = time.time()
self._set_button_state("restarting" if from_restart else "starting") self._set_button_state("restarting" if from_restart else "starting")
self.controller_finished = False self.controller_finished = False
if from_restart:
self.info_label.setText("重启中:正在重新连接夹爪与两个触觉传感器")
else:
self.info_label.setText("启动中:正在连接夹爪与两个触觉传感器")
self.controller = build_controller( self.controller = build_controller(
self.argv, self.argv,
@@ -520,7 +518,6 @@ class GripperDemo02Viewer(QMainWindow):
if self.controller is not None: if self.controller is not None:
self.controller.stop() self.controller.stop()
self._set_button_state("stopping") self._set_button_state("stopping")
self.info_label.setText("停止中:正在释放相机与夹爪")
def restart_control(self): def restart_control(self):
if self.control_thread is not None and self.control_thread.is_alive(): if self.control_thread is not None and self.control_thread.is_alive():
@@ -528,7 +525,6 @@ class GripperDemo02Viewer(QMainWindow):
self._set_button_state("restarting") self._set_button_state("restarting")
if self.controller is not None: if self.controller is not None:
self.controller.stop() self.controller.stop()
self.info_label.setText("重启中:正在停止当前控制线程")
return return
self.restart_pending = False self.restart_pending = False
self.start_control(from_restart=True) self.start_control(from_restart=True)
@@ -561,54 +557,42 @@ class GripperDemo02Viewer(QMainWindow):
self._set_button_state("running") self._set_button_state("running")
state = status.get("state", "--") state = status.get("state", "--")
action = status.get("action", "--")
unit = status.get("normal_unit", "N") unit = status.get("normal_unit", "N")
shear_unit = status.get("shear_unit", "N") shear_unit = status.get("shear_unit", "N")
self.status_chip.setText(f"state={state}") self.status_chip.setText(f"state={state}")
self.action_chip.setText(f"action={action}")
self.manual_hold_button.setText( self.manual_hold_button.setText(
"释放" if bool(status.get("manual_hold_active")) else "保持" "释放" if bool(status.get("manual_hold_active")) else "保持"
) )
self.left_force.set_value( self.left_force.set_value(
fmt(status.get("left_normal"), unit), fmt(status.get("left_normal"), unit),
f"raw FPS {self._sample_fps(status, 'left_sample')}",
) )
self.right_force.set_value( self.right_force.set_value(
fmt(status.get("right_normal"), unit), fmt(status.get("right_normal"), unit),
f"raw FPS {self._sample_fps(status, 'right_sample')}",
) )
self.left_shear_box.set_value( self.left_shear_box.set_value(
fmt(status.get("left_shear"), shear_unit), fmt(status.get("left_shear"), shear_unit),
f"x={fmt(status.get('left_shear_x'), shear_unit)} y={fmt(status.get('left_shear_y'), shear_unit)}",
) )
self.right_shear_box.set_value( self.right_shear_box.set_value(
fmt(status.get("right_shear"), shear_unit), fmt(status.get("right_shear"), shear_unit),
f"x={fmt(status.get('right_shear_x'), shear_unit)} y={fmt(status.get('right_shear_y'), shear_unit)}",
) )
self.force_pct_box.set_value( self.force_pct_box.set_value(
str(status.get("force_pct", "--")), str(status.get("force_pct", "--")),
f"hold_pos={status.get('hold_pos')}",
) )
self.target_force_box.set_value( self.target_force_box.set_value(
str(status.get("adaptive_target_force", "--")), str(status.get("adaptive_target_force", "--")),
f"adaptive={int(bool(status.get('adaptive_grip_enabled')))}",
) )
self.speed_box.set_value( self.speed_box.set_value(
str(status.get("speed_pct", "--")), str(status.get("speed_pct", "--")),
"speed_pct",
) )
self.trigger_box.set_value( self.trigger_box.set_value(
f"{int(bool(status.get('trigger')))} / {int(bool(status.get('trigger_armed')))}", f"{int(bool(status.get('trigger')))} / {int(bool(status.get('trigger_armed')))}",
"trigger / armed",
) )
self.contact_box.set_value( self.contact_box.set_value(
str(int(bool(status.get("grip_contact")))), str(int(bool(status.get("grip_contact")))),
self._contact_text(status),
) )
self.release_box.set_value( self.release_box.set_value(
str(int(bool(status.get("release_armed")))), str(int(bool(status.get("release_armed")))),
"release_armed",
) )
left_sample = status.get("left_sample") or {} left_sample = status.get("left_sample") or {}
@@ -632,20 +616,6 @@ class GripperDemo02Viewer(QMainWindow):
self.force_history = self.force_history[-PLOT_POINTS:] self.force_history = self.force_history[-PLOT_POINTS:]
self._update_plot() self._update_plot()
self.info_label.setText(
" | ".join(
[
f"min={fmt(status.get('min_normal'), unit)}",
f"max={fmt(status.get('max_normal'), unit)}",
f"L_shear={fmt(status.get('left_shear'), shear_unit)}",
f"R_shear={fmt(status.get('right_shear'), shear_unit)}",
f"target={status.get('adaptive_target_force', '--')}",
f"release_armed={int(bool(status.get('release_armed')))}",
f"flow L/R={self._flow_text(left_sample)} / {self._flow_text(right_sample)}",
]
)
)
def _update_plot(self): def _update_plot(self):
if not self.force_history: if not self.force_history:
return return
@@ -662,8 +632,8 @@ class GripperDemo02Viewer(QMainWindow):
for idx, key in enumerate(keys, start=1): for idx, key in enumerate(keys, start=1):
self.curves[key].setData(x, data[:, idx]) self.curves[key].setData(x, data[:, idx])
if len(x) >= 2: if len(x) >= 2:
x_min = max(0.0, x[-1] - 6.0) x_max = max(6.0, float(np.ceil(x[-1])))
x_max = max(6.0, x[-1]) x_min = max(0.0, x_max - 6.0)
for plot in self.plots: for plot in self.plots:
plot.setXRange(x_min, x_max, padding=0) plot.setXRange(x_min, x_max, padding=0)
@@ -676,29 +646,9 @@ class GripperDemo02Viewer(QMainWindow):
self.control_thread = None self.control_thread = None
if self.restart_pending: if self.restart_pending:
self.restart_pending = False self.restart_pending = False
self.info_label.setText("重启中:正在重新启动")
self.start_control(from_restart=True) self.start_control(from_restart=True)
return return
self._set_button_state("idle") self._set_button_state("idle")
if self.controller is not None:
self.info_label.setText("已停止")
def _sample_fps(self, status, key):
sample = status.get(key) or {}
fps = sample.get("fps")
if fps is None:
return "--"
return f"{fps:.1f}"
def _contact_text(self, status):
left = status.get("left_sample") or {}
right = status.get("right_sample") or {}
return f"L={int(bool(left.get('is_contact')))} R={int(bool(right.get('is_contact')))}"
def _flow_text(self, sample):
if not sample:
return "--"
return f"{sample.get('flow_mean', 0.0):.2f}/{sample.get('flow_max', 0.0):.2f}"
def keyPressEvent(self, event): def keyPressEvent(self, event):
if event.key() == Qt.Key_Q: if event.key() == Qt.Key_Q:
+25
View File
@@ -0,0 +1,25 @@
@echo off
setlocal
cd /d "%~dp0"
set "CONDA_ENV=py311"
call conda activate "%CONDA_ENV%"
if errorlevel 1 (
echo Failed to activate conda environment: %CONDA_ENV%
echo Please check that conda is initialized and the environment exists.
pause
exit /b 1
)
python -m gripper_control_02.visualizer %*
set EXIT_CODE=%ERRORLEVEL%
if not "%EXIT_CODE%"=="0" (
echo.
echo Gripper Demo 02 exited with code %EXIT_CODE%.
pause
)
exit /b %EXIT_CODE%