v 2.0
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 2.3 MiB |
Binary file not shown.
Binary file not shown.
@@ -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:
|
||||||
|
|||||||
@@ -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%
|
||||||
Reference in New Issue
Block a user