v 2.0
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -37,13 +37,14 @@ try:
|
||||
QMainWindow,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
QSplitter,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
except Exception as exc:
|
||||
_QT_IMPORT_ERROR = exc
|
||||
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
|
||||
QFrame = QGraphicsOpacityEffect = QMainWindow = object
|
||||
|
||||
@@ -207,6 +208,18 @@ QPushButton#HoldButton {
|
||||
color: #d8d8d8;
|
||||
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):
|
||||
def __init__(self, title, value="--", sub=""):
|
||||
def __init__(self, title, value="--"):
|
||||
super().__init__()
|
||||
self.setObjectName("Panel")
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(10, 8, 10, 8)
|
||||
layout.setSpacing(3)
|
||||
layout.setSpacing(4)
|
||||
self.title_label = QLabel(title)
|
||||
self.title_label.setObjectName("MetricLabel")
|
||||
self.value_label = QLabel(value)
|
||||
self.value_label.setObjectName("MetricValue")
|
||||
self.sub_label = QLabel(sub)
|
||||
self.sub_label.setObjectName("Subtle")
|
||||
layout.addWidget(self.title_label)
|
||||
layout.addWidget(self.value_label)
|
||||
layout.addWidget(self.sub_label)
|
||||
|
||||
def set_value(self, value, sub=None):
|
||||
self.value_label.setText(value)
|
||||
if sub is not None:
|
||||
self.sub_label.setText(sub)
|
||||
|
||||
|
||||
class ImageBox(QFrame):
|
||||
@@ -326,8 +334,6 @@ class GripperDemo02Viewer(QMainWindow):
|
||||
title.setObjectName("Title")
|
||||
self.status_chip = QLabel("state=--")
|
||||
self.status_chip.setObjectName("StatusChip")
|
||||
self.action_chip = QLabel("action=--")
|
||||
self.action_chip.setObjectName("ActionChip")
|
||||
self.start_button = AnimatedButton("启动")
|
||||
self.start_button.setObjectName("StartButton")
|
||||
self.start_button.clicked.connect(self.start_control)
|
||||
@@ -350,7 +356,7 @@ class GripperDemo02Viewer(QMainWindow):
|
||||
self._set_button_state("idle")
|
||||
top.addWidget(title)
|
||||
top.addWidget(self.status_chip)
|
||||
top.addWidget(self.action_chip, 1)
|
||||
top.addStretch(1)
|
||||
top.addWidget(self.start_button)
|
||||
top.addWidget(self.stop_button)
|
||||
top.addWidget(self.restart_button)
|
||||
@@ -359,18 +365,29 @@ class GripperDemo02Viewer(QMainWindow):
|
||||
top.addWidget(self.manual_hold_button)
|
||||
outer.addLayout(top)
|
||||
|
||||
main = QGridLayout()
|
||||
main.setSpacing(8)
|
||||
outer.addLayout(main, 1)
|
||||
main_splitter = QSplitter(Qt.Horizontal)
|
||||
main_splitter.setChildrenCollapsible(False)
|
||||
outer.addWidget(main_splitter, 1)
|
||||
|
||||
flow_splitter = QSplitter(Qt.Vertical)
|
||||
flow_splitter.setChildrenCollapsible(False)
|
||||
self.left_flow = ImageBox("左侧光流")
|
||||
self.right_flow = ImageBox("右侧光流")
|
||||
main.addWidget(self.left_flow, 0, 0)
|
||||
main.addWidget(self.right_flow, 1, 0)
|
||||
flow_splitter.addWidget(self.left_flow)
|
||||
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)
|
||||
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.setSpacing(8)
|
||||
@@ -407,21 +424,6 @@ class GripperDemo02Viewer(QMainWindow):
|
||||
self.plot_widget.setMinimumHeight(240)
|
||||
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):
|
||||
self.plots = []
|
||||
self.curves = {}
|
||||
@@ -444,6 +446,7 @@ class GripperDemo02Viewer(QMainWindow):
|
||||
plot.showGrid(x=False, y=True, alpha=0.12)
|
||||
plot.setLabel("bottom", "时间", units="s")
|
||||
plot.setLabel("left", "力", units="N")
|
||||
plot.getAxis("bottom").setTickSpacing(major=1.0, minor=1.0)
|
||||
plot.setYRange(y_min, y_max, padding=0)
|
||||
plot.setLimits(yMin=y_min, yMax=y_max)
|
||||
plot.enableAutoRange(axis=pg.ViewBox.YAxis, enable=False)
|
||||
@@ -481,12 +484,11 @@ class GripperDemo02Viewer(QMainWindow):
|
||||
|
||||
def send_manual_command(self, command):
|
||||
if self.controller is None or self.control_thread is None or not self.control_thread.is_alive():
|
||||
self.info_label.setText("请先启动控制器")
|
||||
return
|
||||
try:
|
||||
self.controller.request_manual_command(command)
|
||||
except Exception as exc:
|
||||
self.info_label.setText(f"手动命令失败:{exc}")
|
||||
print(f"manual command failed: {exc}")
|
||||
|
||||
def toggle_manual_hold(self):
|
||||
command = "release" if self.manual_hold_button.text() == "释放" else "hold"
|
||||
@@ -499,10 +501,6 @@ class GripperDemo02Viewer(QMainWindow):
|
||||
self.start_time = time.time()
|
||||
self._set_button_state("restarting" if from_restart else "starting")
|
||||
self.controller_finished = False
|
||||
if from_restart:
|
||||
self.info_label.setText("重启中:正在重新连接夹爪与两个触觉传感器")
|
||||
else:
|
||||
self.info_label.setText("启动中:正在连接夹爪与两个触觉传感器")
|
||||
|
||||
self.controller = build_controller(
|
||||
self.argv,
|
||||
@@ -520,7 +518,6 @@ class GripperDemo02Viewer(QMainWindow):
|
||||
if self.controller is not None:
|
||||
self.controller.stop()
|
||||
self._set_button_state("stopping")
|
||||
self.info_label.setText("停止中:正在释放相机与夹爪")
|
||||
|
||||
def restart_control(self):
|
||||
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")
|
||||
if self.controller is not None:
|
||||
self.controller.stop()
|
||||
self.info_label.setText("重启中:正在停止当前控制线程")
|
||||
return
|
||||
self.restart_pending = False
|
||||
self.start_control(from_restart=True)
|
||||
@@ -561,54 +557,42 @@ class GripperDemo02Viewer(QMainWindow):
|
||||
self._set_button_state("running")
|
||||
|
||||
state = status.get("state", "--")
|
||||
action = status.get("action", "--")
|
||||
unit = status.get("normal_unit", "N")
|
||||
shear_unit = status.get("shear_unit", "N")
|
||||
self.status_chip.setText(f"state={state}")
|
||||
self.action_chip.setText(f"action={action}")
|
||||
self.manual_hold_button.setText(
|
||||
"释放" if bool(status.get("manual_hold_active")) else "保持"
|
||||
)
|
||||
|
||||
self.left_force.set_value(
|
||||
fmt(status.get("left_normal"), unit),
|
||||
f"raw FPS {self._sample_fps(status, 'left_sample')}",
|
||||
)
|
||||
self.right_force.set_value(
|
||||
fmt(status.get("right_normal"), unit),
|
||||
f"raw FPS {self._sample_fps(status, 'right_sample')}",
|
||||
)
|
||||
self.left_shear_box.set_value(
|
||||
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(
|
||||
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(
|
||||
str(status.get("force_pct", "--")),
|
||||
f"hold_pos={status.get('hold_pos')}",
|
||||
)
|
||||
self.target_force_box.set_value(
|
||||
str(status.get("adaptive_target_force", "--")),
|
||||
f"adaptive={int(bool(status.get('adaptive_grip_enabled')))}",
|
||||
)
|
||||
self.speed_box.set_value(
|
||||
str(status.get("speed_pct", "--")),
|
||||
"speed_pct",
|
||||
)
|
||||
self.trigger_box.set_value(
|
||||
f"{int(bool(status.get('trigger')))} / {int(bool(status.get('trigger_armed')))}",
|
||||
"trigger / armed",
|
||||
)
|
||||
self.contact_box.set_value(
|
||||
str(int(bool(status.get("grip_contact")))),
|
||||
self._contact_text(status),
|
||||
)
|
||||
self.release_box.set_value(
|
||||
str(int(bool(status.get("release_armed")))),
|
||||
"release_armed",
|
||||
)
|
||||
|
||||
left_sample = status.get("left_sample") or {}
|
||||
@@ -632,20 +616,6 @@ class GripperDemo02Viewer(QMainWindow):
|
||||
self.force_history = self.force_history[-PLOT_POINTS:]
|
||||
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):
|
||||
if not self.force_history:
|
||||
return
|
||||
@@ -662,8 +632,8 @@ class GripperDemo02Viewer(QMainWindow):
|
||||
for idx, key in enumerate(keys, start=1):
|
||||
self.curves[key].setData(x, data[:, idx])
|
||||
if len(x) >= 2:
|
||||
x_min = max(0.0, x[-1] - 6.0)
|
||||
x_max = max(6.0, x[-1])
|
||||
x_max = max(6.0, float(np.ceil(x[-1])))
|
||||
x_min = max(0.0, x_max - 6.0)
|
||||
for plot in self.plots:
|
||||
plot.setXRange(x_min, x_max, padding=0)
|
||||
|
||||
@@ -676,29 +646,9 @@ class GripperDemo02Viewer(QMainWindow):
|
||||
self.control_thread = None
|
||||
if self.restart_pending:
|
||||
self.restart_pending = False
|
||||
self.info_label.setText("重启中:正在重新启动")
|
||||
self.start_control(from_restart=True)
|
||||
return
|
||||
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):
|
||||
if event.key() == Qt.Key_Q:
|
||||
|
||||
Reference in New Issue
Block a user