update csv export and added fixed mesh axis drawing

This commit is contained in:
ykcheungab
2026-07-15 14:25:03 +08:00
parent 3d1509df9d
commit 87ac152907
11 changed files with 578 additions and 35 deletions
+30 -21
View File
@@ -22,12 +22,13 @@ class TeeStream:
class WidgetLogHandler:
"""Mirror print output into a QPlainTextEdit with a live status line at the bottom."""
"""Mirror print output into a QPlainTextEdit with pinned status lines at the bottom."""
def __init__(self, widget: QPlainTextEdit):
self.widget = widget
self.buf = ""
self._has_status_line = False
self._status_text: str | None = None
self._status_block_count = 0
def write(self, text: str):
self.buf += text
@@ -42,34 +43,42 @@ class WidgetLogHandler:
self.buf = ""
def set_status_line(self, text: str) -> None:
"""Update the pinned bottom line (FPS / forces) without scrolling the log."""
"""Update pinned bottom status (supports multi-line) without scrolling away."""
text = text.rstrip("\n")
if self._has_status_line:
cursor = QTextCursor(self.widget.document().lastBlock())
cursor.select(QTextCursor.BlockUnderCursor)
cursor.insertText(text)
else:
self.widget.appendPlainText(text)
self._has_status_line = True
self._remove_status_blocks()
self.widget.appendPlainText(text)
self._status_text = text
self._status_block_count = max(1, text.count("\n") + 1)
scrollbar = self.widget.verticalScrollBar()
scrollbar.setValue(scrollbar.maximum())
def clear_status_line(self) -> None:
if not self._has_status_line:
self._remove_status_blocks()
self._status_text = None
self._status_block_count = 0
def _remove_status_blocks(self) -> None:
if self._status_block_count <= 0:
return
cursor = QTextCursor(self.widget.document().lastBlock())
cursor.select(QTextCursor.BlockUnderCursor)
doc = self.widget.document()
end = QTextCursor(doc)
end.movePosition(QTextCursor.End)
start = QTextCursor(doc)
start.movePosition(QTextCursor.End)
for _ in range(self._status_block_count):
start.movePosition(QTextCursor.StartOfBlock)
if start.position() > 0:
start.movePosition(QTextCursor.Left)
cursor = QTextCursor(doc)
cursor.setPosition(start.position())
cursor.setPosition(end.position(), QTextCursor.KeepAnchor)
cursor.removeSelectedText()
self._has_status_line = False
self._status_block_count = 0
def _append_log_line(self, line: str) -> None:
status = None
if self._has_status_line:
status = self.widget.document().lastBlock().text()
cursor = QTextCursor(self.widget.document().lastBlock())
cursor.select(QTextCursor.BlockUnderCursor)
cursor.removeSelectedText()
self._has_status_line = False
status = self._status_text
if status is not None:
self.clear_status_line()
self.widget.appendPlainText(line)
if status is not None:
self.set_status_line(status)
+1
View File
@@ -120,6 +120,7 @@ class MeshViewportWidget(ImagePanel):
mesh_background=self._layout.mesh_background.color,
contact_arrow_style=self._layout.mesh_contact_arrow,
flow_arrow_style=self._layout.mesh_flow_arrow,
axis_overlay=self._layout.mesh_axis_overlay,
)
self._renderer.flow_arrow.set_enabled(self._mesh_state.flow_arrows_enabled)
+70 -5
View File
@@ -41,6 +41,8 @@ class SettingsPanelWidget(QWidget):
browse_video_requested = pyqtSignal()
browse_config_requested = pyqtSignal()
browse_finger_assets_requested = pyqtSignal()
browse_csv_dir_requested = pyqtSignal()
csv_record_toggled = pyqtSignal(bool)
def __init__(
self,
@@ -57,8 +59,8 @@ class SettingsPanelWidget(QWidget):
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
root = QVBoxLayout(self)
root.setContentsMargins(10, 10, 10, 10)
root.setSpacing(10)
root.setContentsMargins(8, 8, 8, 8)
root.setSpacing(6)
title = QLabel(layout.settings_panel_title)
title.setObjectName("settingsTitle")
@@ -125,10 +127,10 @@ class SettingsPanelWidget(QWidget):
config_row.addWidget(self.btn_browse_config)
root.addLayout(config_row)
# Optional calibration path is kept for future re-enablement but hidden for now.
self.cal_edit = QLineEdit("")
self.cal_edit.setPlaceholderText("Optional calibration path")
root.addWidget(self._caption("Calibration"))
root.addWidget(self.cal_edit)
self.cal_edit.hide()
root.addWidget(self._caption("Finger assets"))
finger_assets_row = QHBoxLayout()
@@ -143,7 +145,28 @@ class SettingsPanelWidget(QWidget):
finger_assets_row.addWidget(self.btn_browse_finger_assets)
root.addLayout(finger_assets_row)
root.addSpacing(8)
root.addWidget(self._caption("CSV save directory"))
csv_dir_row = QHBoxLayout()
csv_dir_row.setSpacing(6)
self.csv_dir_edit = QLineEdit("./outputs/csv")
self.csv_dir_edit.setPlaceholderText("Directory for CSV recordings")
csv_dir_row.addWidget(self.csv_dir_edit, 1)
self.btn_browse_csv_dir = QPushButton("…")
self.btn_browse_csv_dir.setFixedWidth(32)
self.btn_browse_csv_dir.setToolTip("Browse CSV save directory")
self.btn_browse_csv_dir.clicked.connect(self.browse_csv_dir_requested.emit)
csv_dir_row.addWidget(self.btn_browse_csv_dir)
root.addLayout(csv_dir_row)
self.btn_csv_record = QPushButton("Record CSV")
self.btn_csv_record.setToolTip("Start writing contact/force CSV")
self.btn_csv_record.setEnabled(False)
self.btn_csv_record.clicked.connect(self._on_csv_toggle_clicked)
root.addWidget(self.btn_csv_record)
self._csv_recording = False
self._pipeline_running = False
root.addSpacing(4)
rotate_row = QHBoxLayout()
rotate_row.setSpacing(6)
self.free_rotate_check = QCheckBox("Free rotate 3D view")
@@ -211,6 +234,45 @@ class SettingsPanelWidget(QWidget):
self.finger_assets_edit.setEnabled(enabled)
self.btn_browse_finger_assets.setEnabled(enabled)
def set_csv_save_dir(self, value: str) -> None:
self.csv_dir_edit.setText(value)
def set_csv_controls_for_pipeline(self, running: bool) -> None:
"""Update CSV controls from pipeline start/stop."""
self._pipeline_running = bool(running)
if not self._pipeline_running:
self._csv_recording = False
self._refresh_csv_controls()
def set_csv_recording(self, recording: bool) -> None:
"""Update CSV controls after a successful start/stop of recording."""
self._csv_recording = bool(recording) and self._pipeline_running
self._refresh_csv_controls()
def _refresh_csv_controls(self) -> None:
self.btn_csv_record.setEnabled(self._pipeline_running)
if self._csv_recording:
self.btn_csv_record.setText("Stop CSV")
self.btn_csv_record.setToolTip("Stop CSV recording")
self.btn_csv_record.setProperty("recording", True)
else:
self.btn_csv_record.setText("Record CSV")
self.btn_csv_record.setToolTip("Start writing contact/force CSV")
self.btn_csv_record.setProperty("recording", False)
style = self.btn_csv_record.style()
style.unpolish(self.btn_csv_record)
style.polish(self.btn_csv_record)
self.btn_csv_record.update()
dir_enabled = not self._csv_recording
self.csv_dir_edit.setEnabled(dir_enabled)
self.btn_browse_csv_dir.setEnabled(dir_enabled)
def _on_csv_toggle_clicked(self) -> None:
if not self._pipeline_running:
return
self.csv_record_toggled.emit(not self._csv_recording)
def _emit_arrow_background_changed(self, _index: int) -> None:
value = self.arrow_background_combo.currentData()
if value:
@@ -239,3 +301,6 @@ class SettingsPanelWidget(QWidget):
def finger_export_dir(self) -> str:
return self.finger_assets_edit.text().strip()
def csv_save_dir(self) -> str:
return self.csv_dir_edit.text().strip()