Compare commits
1 Commits
main
...
backup/loc
| Author | SHA1 | Date | |
|---|---|---|---|
| 19264915b4 |
86
src/main.cpp
86
src/main.cpp
@ -11,8 +11,94 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <dbghelp.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
LONG WINAPI writeCrashDump(EXCEPTION_POINTERS *exceptionPointers)
|
||||||
|
{
|
||||||
|
static LONG handlingCrash = 0;
|
||||||
|
if (InterlockedCompareExchange(&handlingCrash, 1, 0) != 0)
|
||||||
|
return EXCEPTION_EXECUTE_HANDLER;
|
||||||
|
|
||||||
|
wchar_t executablePath[MAX_PATH] = {};
|
||||||
|
GetModuleFileNameW(nullptr, executablePath, MAX_PATH);
|
||||||
|
wchar_t *lastSeparator = wcsrchr(executablePath, L'\\');
|
||||||
|
if (lastSeparator)
|
||||||
|
*lastSeparator = L'\0';
|
||||||
|
|
||||||
|
wchar_t dumpDirectory[MAX_PATH] = {};
|
||||||
|
_snwprintf(dumpDirectory, MAX_PATH - 1, L"%ls\\crash_dumps", executablePath);
|
||||||
|
CreateDirectoryW(dumpDirectory, nullptr);
|
||||||
|
|
||||||
|
SYSTEMTIME time = {};
|
||||||
|
GetLocalTime(&time);
|
||||||
|
wchar_t baseName[96] = {};
|
||||||
|
_snwprintf(baseName, 95,
|
||||||
|
L"CmvrControlStation_%04u%02u%02u_%02u%02u%02u_%lu",
|
||||||
|
time.wYear, time.wMonth, time.wDay,
|
||||||
|
time.wHour, time.wMinute, time.wSecond,
|
||||||
|
GetCurrentProcessId());
|
||||||
|
|
||||||
|
wchar_t dumpPath[MAX_PATH] = {};
|
||||||
|
_snwprintf(dumpPath, MAX_PATH - 1, L"%ls\\%ls.dmp", dumpDirectory, baseName);
|
||||||
|
HANDLE dumpFile = CreateFileW(dumpPath, GENERIC_WRITE, FILE_SHARE_READ, nullptr,
|
||||||
|
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||||
|
if (dumpFile != INVALID_HANDLE_VALUE) {
|
||||||
|
MINIDUMP_EXCEPTION_INFORMATION exceptionInfo = {};
|
||||||
|
exceptionInfo.ThreadId = GetCurrentThreadId();
|
||||||
|
exceptionInfo.ExceptionPointers = exceptionPointers;
|
||||||
|
exceptionInfo.ClientPointers = FALSE;
|
||||||
|
const MINIDUMP_TYPE dumpType = static_cast<MINIDUMP_TYPE>(
|
||||||
|
MiniDumpWithDataSegs | MiniDumpWithHandleData |
|
||||||
|
MiniDumpWithThreadInfo | MiniDumpWithIndirectlyReferencedMemory);
|
||||||
|
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dumpFile,
|
||||||
|
dumpType, &exceptionInfo, nullptr, nullptr);
|
||||||
|
FlushFileBuffers(dumpFile);
|
||||||
|
CloseHandle(dumpFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t summaryPath[MAX_PATH] = {};
|
||||||
|
_snwprintf(summaryPath, MAX_PATH - 1, L"%ls\\%ls.txt", dumpDirectory, baseName);
|
||||||
|
HANDLE summaryFile = CreateFileW(summaryPath, GENERIC_WRITE, FILE_SHARE_READ, nullptr,
|
||||||
|
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||||
|
if (summaryFile != INVALID_HANDLE_VALUE) {
|
||||||
|
const DWORD exceptionCode = exceptionPointers && exceptionPointers->ExceptionRecord
|
||||||
|
? exceptionPointers->ExceptionRecord->ExceptionCode : 0;
|
||||||
|
const void *exceptionAddress = exceptionPointers && exceptionPointers->ExceptionRecord
|
||||||
|
? exceptionPointers->ExceptionRecord->ExceptionAddress : nullptr;
|
||||||
|
char summary[512] = {};
|
||||||
|
const int length = _snprintf(summary, sizeof(summary) - 1,
|
||||||
|
"Application: CmvrControlStation.exe\r\n"
|
||||||
|
"Timestamp: %04u-%02u-%02u %02u:%02u:%02u\r\n"
|
||||||
|
"Exception code: 0x%08lX\r\n"
|
||||||
|
"Exception address: %p\r\n"
|
||||||
|
"Process ID: %lu\r\nThread ID: %lu\r\n"
|
||||||
|
"Dump: %ls\r\n",
|
||||||
|
time.wYear, time.wMonth, time.wDay,
|
||||||
|
time.wHour, time.wMinute, time.wSecond,
|
||||||
|
exceptionCode, exceptionAddress,
|
||||||
|
GetCurrentProcessId(), GetCurrentThreadId(), dumpPath);
|
||||||
|
DWORD written = 0;
|
||||||
|
if (length > 0)
|
||||||
|
WriteFile(summaryFile, summary, static_cast<DWORD>(length), &written, nullptr);
|
||||||
|
FlushFileBuffers(summaryFile);
|
||||||
|
CloseHandle(summaryFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXCEPTION_EXECUTE_HANDLER;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
#endif
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
SetUnhandledExceptionFilter(writeCrashDump);
|
||||||
|
#endif
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
app.setApplicationName(QStringLiteral("CMVR Control Station"));
|
app.setApplicationName(QStringLiteral("CMVR Control Station"));
|
||||||
app.setOrganizationName(QStringLiteral("CMVR"));
|
app.setOrganizationName(QStringLiteral("CMVR"));
|
||||||
|
|||||||
@ -659,6 +659,7 @@ void MainWindow::updateTelemetry(const QVariantList &joints, const QVariantMap &
|
|||||||
byName.insert(name, joint);
|
byName.insert(name, joint);
|
||||||
jointPositions.insert(name, joint.value(QStringLiteral("position")).toDouble());
|
jointPositions.insert(name, joint.value(QStringLiteral("position")).toDouble());
|
||||||
}
|
}
|
||||||
|
m_latestArmJointPositions = jointPositions;
|
||||||
|
|
||||||
if (m_urdfViewer)
|
if (m_urdfViewer)
|
||||||
m_urdfViewer->setJointPositions(jointPositions);
|
m_urdfViewer->setJointPositions(jointPositions);
|
||||||
@ -689,10 +690,13 @@ void MainWindow::updateTelemetry(const QVariantList &joints, const QVariantMap &
|
|||||||
if (!nameItem)
|
if (!nameItem)
|
||||||
continue;
|
continue;
|
||||||
const QVariantMap joint = byName.value(nameItem->text());
|
const QVariantMap joint = byName.value(nameItem->text());
|
||||||
if (!joint.isEmpty())
|
if (!joint.isEmpty()) {
|
||||||
|
const double position = joint.value(QStringLiteral("position")).toDouble();
|
||||||
table->item(row, 1)->setText(QStringLiteral("%1°").arg(
|
table->item(row, 1)->setText(QStringLiteral("%1°").arg(
|
||||||
qRadiansToDegrees(joint.value(QStringLiteral("position")).toDouble()),
|
qRadiansToDegrees(position),
|
||||||
0, 'f', 2));
|
0, 'f', 2));
|
||||||
|
table->item(row, 2)->setText(QString::number(position, 'f', 6));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -967,6 +971,19 @@ void MainWindow::handleCommand(const QString &command, bool success, const QStri
|
|||||||
}
|
}
|
||||||
appendLog(success ? QStringLiteral("OK") : QStringLiteral("ERROR"),
|
appendLog(success ? QStringLiteral("OK") : QStringLiteral("ERROR"),
|
||||||
QStringLiteral("%1 · %2").arg(command, detail));
|
QStringLiteral("%1 · %2").arg(command, detail));
|
||||||
|
if (command == QStringLiteral("MoveJ") && m_jointPointMovePending) {
|
||||||
|
m_jointPointMovePending = false;
|
||||||
|
if (!success) {
|
||||||
|
stopJointPointLoop(QStringLiteral("MoveJ 执行失败:%1").arg(detail));
|
||||||
|
} else if (m_jointPointLoopActive) {
|
||||||
|
const int completedIndex = m_jointPointLoopCursor - 1;
|
||||||
|
const double waitSeconds = completedIndex >= 0
|
||||||
|
&& completedIndex < m_jointPointExecutionWaits.size()
|
||||||
|
? m_jointPointExecutionWaits.at(completedIndex) : 0.0;
|
||||||
|
const int delayMs = qRound(waitSeconds * 1000.0);
|
||||||
|
QTimer::singleShot(delayMs, this, &MainWindow::advanceJointPointLoop);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!success)
|
if (!success)
|
||||||
m_connectionText->setToolTip(detail);
|
m_connectionText->setToolTip(detail);
|
||||||
}
|
}
|
||||||
@ -1003,13 +1020,260 @@ void MainWindow::sendMoveJ()
|
|||||||
QVariantList positions;
|
QVariantList positions;
|
||||||
const bool leftArm = m_armSideCombo
|
const bool leftArm = m_armSideCombo
|
||||||
&& m_armSideCombo->currentData().toString() == QStringLiteral("left");
|
&& m_armSideCombo->currentData().toString() == QStringLiteral("left");
|
||||||
const int offset = leftArm ? 0 : 7;
|
const QList<QDoubleSpinBox *> &spins = leftArm
|
||||||
for (int i = 0; i < 7 && offset + i < m_targetSpins.size(); ++i)
|
? m_leftTargetSpins : m_rightTargetSpins;
|
||||||
positions.append(qDegreesToRadians(m_targetSpins.at(offset + i)->value()));
|
for (QDoubleSpinBox *spin : spins)
|
||||||
|
positions.append(qDegreesToRadians(spin->value()));
|
||||||
m_client->requestMoveJ(m_armIdEdit->text().trimmed(), positions,
|
m_client->requestMoveJ(m_armIdEdit->text().trimmed(), positions,
|
||||||
m_velocitySpin->value(), m_accelerationSpin->value());
|
m_velocitySpin->value(), m_accelerationSpin->value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::refreshRecordedJointPoints()
|
||||||
|
{
|
||||||
|
if (!m_jointPointTable)
|
||||||
|
return;
|
||||||
|
m_jointPointTable->setRowCount(m_recordedJointPoints.size());
|
||||||
|
for (int row = 0; row < m_recordedJointPoints.size(); ++row) {
|
||||||
|
const RecordedJointPoint &point = m_recordedJointPoints.at(row);
|
||||||
|
QStringList values;
|
||||||
|
for (double position : point.positions)
|
||||||
|
values.append(QString::number(position, 'f', 6));
|
||||||
|
m_jointPointTable->setItem(row, 0, new QTableWidgetItem(point.name));
|
||||||
|
m_jointPointTable->setItem(row, 1, new QTableWidgetItem(
|
||||||
|
point.side == QStringLiteral("left") ? QStringLiteral("左臂")
|
||||||
|
: QStringLiteral("右臂")));
|
||||||
|
m_jointPointTable->setItem(row, 2, new QTableWidgetItem(point.deviceId));
|
||||||
|
m_jointPointTable->setItem(row, 3, new QTableWidgetItem(
|
||||||
|
QString::number(point.positions.size())));
|
||||||
|
QTableWidgetItem *positionsItem = new QTableWidgetItem(values.join(QStringLiteral(", ")));
|
||||||
|
positionsItem->setToolTip(QStringLiteral("关节顺序:%1\n角度(rad):%2")
|
||||||
|
.arg(point.jointNames.join(QStringLiteral(", ")),
|
||||||
|
values.join(QStringLiteral(", "))));
|
||||||
|
m_jointPointTable->setItem(row, 4, positionsItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::refreshJointFlowTable()
|
||||||
|
{
|
||||||
|
if (!m_jointFlowTable)
|
||||||
|
return;
|
||||||
|
m_jointFlowTable->setRowCount(m_jointFlowSteps.size());
|
||||||
|
for (int row = 0; row < m_jointFlowSteps.size(); ++row) {
|
||||||
|
const JointFlowStep &step = m_jointFlowSteps.at(row);
|
||||||
|
m_jointFlowTable->setItem(row, 0, new QTableWidgetItem(QString::number(row + 1)));
|
||||||
|
m_jointFlowTable->setItem(row, 1, new QTableWidgetItem(step.pointName));
|
||||||
|
m_jointFlowTable->setItem(row, 2, new QTableWidgetItem(
|
||||||
|
QStringLiteral("%1 s").arg(step.waitSeconds, 0, 'f', 1)));
|
||||||
|
m_jointFlowTable->setItem(row, 3, new QTableWidgetItem(
|
||||||
|
step.loopGroup.isEmpty() ? QStringLiteral("—") : step.loopGroup));
|
||||||
|
m_jointFlowTable->setItem(row, 4, new QTableWidgetItem(
|
||||||
|
step.loopGroup.isEmpty() ? QStringLiteral("1")
|
||||||
|
: QString::number(step.loopCount)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::recordCurrentJointPoint()
|
||||||
|
{
|
||||||
|
const QString name = m_jointPointNameEdit
|
||||||
|
? m_jointPointNameEdit->text().trimmed() : QString();
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
QMessageBox::warning(this, QStringLiteral("记录关节点位"),
|
||||||
|
QStringLiteral("请输入动作名称。"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const RecordedJointPoint &point : m_recordedJointPoints) {
|
||||||
|
if (point.name.compare(name, Qt::CaseInsensitive) == 0) {
|
||||||
|
QMessageBox::warning(this, QStringLiteral("记录关节点位"),
|
||||||
|
QStringLiteral("动作名称“%1”已存在,请使用其他名称。")
|
||||||
|
.arg(name));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool leftArm = m_armSideCombo
|
||||||
|
&& m_armSideCombo->currentData().toString() == QStringLiteral("left");
|
||||||
|
const QStringList jointNames = leftArm ? m_leftArmJointNames : m_rightArmJointNames;
|
||||||
|
if (jointNames.isEmpty()) {
|
||||||
|
QMessageBox::warning(this, QStringLiteral("记录关节点位"),
|
||||||
|
QStringLiteral("当前 URDF 中没有该机械臂的可运动关节。"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RecordedJointPoint point;
|
||||||
|
point.name = name;
|
||||||
|
point.side = leftArm ? QStringLiteral("left") : QStringLiteral("right");
|
||||||
|
point.deviceId = m_armIdEdit->text().trimmed();
|
||||||
|
point.jointNames = jointNames;
|
||||||
|
QStringList missing;
|
||||||
|
for (const QString &jointName : jointNames) {
|
||||||
|
if (!m_latestArmJointPositions.contains(jointName)) {
|
||||||
|
missing.append(jointName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
point.positions.append(m_latestArmJointPositions.value(jointName));
|
||||||
|
}
|
||||||
|
if (!missing.isEmpty()) {
|
||||||
|
QMessageBox::warning(this, QStringLiteral("记录关节点位"),
|
||||||
|
QStringLiteral("尚未收到以下关节的实时角度:\n%1\n\n请先启动遥测。")
|
||||||
|
.arg(missing.join(QStringLiteral(", "))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (point.deviceId.isEmpty()) {
|
||||||
|
QMessageBox::warning(this, QStringLiteral("记录关节点位"),
|
||||||
|
QStringLiteral("当前 Device ID 为空。"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_recordedJointPoints.append(point);
|
||||||
|
refreshRecordedJointPoints();
|
||||||
|
QSettings settings;
|
||||||
|
saveJointPrograms(settings);
|
||||||
|
m_jointPointNameEdit->clear();
|
||||||
|
m_jointPointTable->selectRow(m_recordedJointPoints.size() - 1);
|
||||||
|
appendLog(QStringLiteral("OK"),
|
||||||
|
QStringLiteral("已记录动作“%1”,%2 个关节(rad)")
|
||||||
|
.arg(point.name).arg(point.positions.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::executeSelectedJointPoint()
|
||||||
|
{
|
||||||
|
if (m_jointPointLoopActive || m_jointPointMovePending)
|
||||||
|
return;
|
||||||
|
const QModelIndexList selected = m_jointPointTable
|
||||||
|
? m_jointPointTable->selectionModel()->selectedRows() : QModelIndexList();
|
||||||
|
if (selected.isEmpty()) {
|
||||||
|
QMessageBox::information(this, QStringLiteral("执行关节点位"),
|
||||||
|
QStringLiteral("请先选择一个已记录动作。"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const int row = selected.first().row();
|
||||||
|
if (row < 0 || row >= m_recordedJointPoints.size())
|
||||||
|
return;
|
||||||
|
const RecordedJointPoint &point = m_recordedJointPoints.at(row);
|
||||||
|
QVariantList positions;
|
||||||
|
for (double position : point.positions)
|
||||||
|
positions.append(position);
|
||||||
|
m_jointPointMovePending = true;
|
||||||
|
m_client->requestMoveJ(point.deviceId, positions,
|
||||||
|
m_velocitySpin->value(), m_accelerationSpin->value());
|
||||||
|
if (m_jointPointLoopStatus)
|
||||||
|
m_jointPointLoopStatus->setText(QStringLiteral("执行:%1").arg(point.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::startJointPointLoop()
|
||||||
|
{
|
||||||
|
if (m_jointPointLoopActive || m_jointPointMovePending)
|
||||||
|
return;
|
||||||
|
if (m_jointFlowSteps.isEmpty()) {
|
||||||
|
QMessageBox::information(this, QStringLiteral("执行自定义流程"),
|
||||||
|
QStringLiteral("请先向自定义流程中添加步骤。"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_jointPointLoopRows.clear();
|
||||||
|
m_jointPointExecutionWaits.clear();
|
||||||
|
const auto pointRow = [this](const QString &name) {
|
||||||
|
for (int row = 0; row < m_recordedJointPoints.size(); ++row) {
|
||||||
|
if (m_recordedJointPoints.at(row).name == name)
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
for (int i = 0; i < m_jointFlowSteps.size();) {
|
||||||
|
const JointFlowStep &step = m_jointFlowSteps.at(i);
|
||||||
|
if (step.loopGroup.isEmpty()) {
|
||||||
|
const int row = pointRow(step.pointName);
|
||||||
|
if (row >= 0) {
|
||||||
|
m_jointPointLoopRows.append(row);
|
||||||
|
m_jointPointExecutionWaits.append(step.waitSeconds);
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int end = i + 1;
|
||||||
|
while (end < m_jointFlowSteps.size()
|
||||||
|
&& m_jointFlowSteps.at(end).loopGroup == step.loopGroup)
|
||||||
|
++end;
|
||||||
|
for (int repeat = 0; repeat < step.loopCount; ++repeat) {
|
||||||
|
for (int index = i; index < end; ++index) {
|
||||||
|
const JointFlowStep &groupStep = m_jointFlowSteps.at(index);
|
||||||
|
const int row = pointRow(groupStep.pointName);
|
||||||
|
if (row >= 0) {
|
||||||
|
m_jointPointLoopRows.append(row);
|
||||||
|
m_jointPointExecutionWaits.append(groupStep.waitSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = end;
|
||||||
|
}
|
||||||
|
if (m_jointPointLoopRows.isEmpty()) {
|
||||||
|
QMessageBox::warning(this, QStringLiteral("执行自定义流程"),
|
||||||
|
QStringLiteral("流程引用的点位已不存在。"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_jointPointLoopCursor = 0;
|
||||||
|
m_jointPointLoopTarget = m_jointPointLoopRows.size();
|
||||||
|
m_jointPointLoopActive = true;
|
||||||
|
m_startJointPointLoopButton->setEnabled(false);
|
||||||
|
m_runJointPointButton->setEnabled(false);
|
||||||
|
m_deleteJointPointButton->setEnabled(false);
|
||||||
|
m_recordJointPointButton->setEnabled(false);
|
||||||
|
m_stopJointPointLoopButton->setEnabled(true);
|
||||||
|
m_jointPointTable->setEnabled(false);
|
||||||
|
advanceJointPointLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::advanceJointPointLoop()
|
||||||
|
{
|
||||||
|
if (!m_jointPointLoopActive || m_jointPointMovePending)
|
||||||
|
return;
|
||||||
|
if (m_jointPointLoopCursor >= m_jointPointLoopRows.size()) {
|
||||||
|
stopJointPointLoop(QStringLiteral("循环执行完成"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const int executionIndex = m_jointPointLoopCursor;
|
||||||
|
const int row = m_jointPointLoopRows.at(m_jointPointLoopCursor++);
|
||||||
|
if (row < 0 || row >= m_recordedJointPoints.size()) {
|
||||||
|
stopJointPointLoop(QStringLiteral("点位列表已发生变化"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const RecordedJointPoint &point = m_recordedJointPoints.at(row);
|
||||||
|
QVariantList positions;
|
||||||
|
for (double position : point.positions)
|
||||||
|
positions.append(position);
|
||||||
|
m_jointPointMovePending = true;
|
||||||
|
m_jointPointLoopStatus->setText(QStringLiteral("步骤 %1/%2 · %3")
|
||||||
|
.arg(executionIndex + 1).arg(m_jointPointLoopRows.size()).arg(point.name));
|
||||||
|
m_client->requestMoveJ(point.deviceId, positions,
|
||||||
|
m_velocitySpin->value(), m_accelerationSpin->value());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::stopJointPointLoop(const QString &reason)
|
||||||
|
{
|
||||||
|
const bool wasActive = m_jointPointLoopActive;
|
||||||
|
m_jointPointLoopActive = false;
|
||||||
|
m_jointPointMovePending = false;
|
||||||
|
m_jointPointLoopRows.clear();
|
||||||
|
m_jointPointExecutionWaits.clear();
|
||||||
|
if (m_startJointPointLoopButton)
|
||||||
|
m_startJointPointLoopButton->setEnabled(true);
|
||||||
|
if (m_runJointPointButton)
|
||||||
|
m_runJointPointButton->setEnabled(true);
|
||||||
|
if (m_deleteJointPointButton)
|
||||||
|
m_deleteJointPointButton->setEnabled(true);
|
||||||
|
if (m_recordJointPointButton)
|
||||||
|
m_recordJointPointButton->setEnabled(true);
|
||||||
|
if (m_stopJointPointLoopButton)
|
||||||
|
m_stopJointPointLoopButton->setEnabled(false);
|
||||||
|
if (m_jointPointTable)
|
||||||
|
m_jointPointTable->setEnabled(true);
|
||||||
|
if (m_jointPointLoopStatus)
|
||||||
|
m_jointPointLoopStatus->setText(reason.isEmpty() ? QStringLiteral("未运行") : reason);
|
||||||
|
if (wasActive && !reason.isEmpty())
|
||||||
|
appendLog(reason == QStringLiteral("循环执行完成") ? QStringLiteral("OK")
|
||||||
|
: QStringLiteral("WARN"),
|
||||||
|
reason);
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::sendMoveJPose()
|
void MainWindow::sendMoveJPose()
|
||||||
{
|
{
|
||||||
if (m_moveJPoseSpins.size() != 6 || !m_moveJPoseBaseLinkCombo)
|
if (m_moveJPoseSpins.size() != 6 || !m_moveJPoseBaseLinkCombo)
|
||||||
@ -1050,6 +1314,7 @@ void MainWindow::saveSnapshot()
|
|||||||
void MainWindow::loadSettings()
|
void MainWindow::loadSettings()
|
||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
loadJointPrograms(settings);
|
||||||
m_hostEdit->setText(settings.value(QStringLiteral("connection/host"), QStringLiteral("192.168.0.28")).toString());
|
m_hostEdit->setText(settings.value(QStringLiteral("connection/host"), QStringLiteral("192.168.0.28")).toString());
|
||||||
m_hostEdit->setCursorPosition(0);
|
m_hostEdit->setCursorPosition(0);
|
||||||
m_portSpin->setValue(settings.value(QStringLiteral("connection/port"), 50052).toInt());
|
m_portSpin->setValue(settings.value(QStringLiteral("connection/port"), 50052).toInt());
|
||||||
@ -1063,14 +1328,16 @@ void MainWindow::loadSettings()
|
|||||||
m_armSideCombo->setCurrentIndex(sideIndex);
|
m_armSideCombo->setCurrentIndex(sideIndex);
|
||||||
}
|
}
|
||||||
const QString defaultArmDeviceId = armSide == QStringLiteral("left")
|
const QString defaultArmDeviceId = armSide == QStringLiteral("left")
|
||||||
? QStringLiteral("eyou_left_arm") : QStringLiteral("eyou_right_arm");
|
? QStringLiteral("left_ethercat_arm") : QStringLiteral("right_ethercat_arm");
|
||||||
QString armDeviceId = settings.value(QStringLiteral("arm/device"),
|
QString armDeviceId = settings.value(QStringLiteral("arm/device"),
|
||||||
defaultArmDeviceId).toString().trimmed();
|
defaultArmDeviceId).toString().trimmed();
|
||||||
if (armDeviceId == QStringLiteral("left_arm")) {
|
if (armDeviceId == QStringLiteral("left_arm") ||
|
||||||
armDeviceId = QStringLiteral("eyou_left_arm");
|
armDeviceId == QStringLiteral("eyou_left_arm")) {
|
||||||
|
armDeviceId = QStringLiteral("left_ethercat_arm");
|
||||||
} else if (armDeviceId == QStringLiteral("right_arm") ||
|
} else if (armDeviceId == QStringLiteral("right_arm") ||
|
||||||
armDeviceId == QStringLiteral("eyou_arm")) {
|
armDeviceId == QStringLiteral("eyou_arm") ||
|
||||||
armDeviceId = QStringLiteral("eyou_right_arm");
|
armDeviceId == QStringLiteral("eyou_right_arm")) {
|
||||||
|
armDeviceId = QStringLiteral("right_ethercat_arm");
|
||||||
}
|
}
|
||||||
m_armIdEdit->setText(armDeviceId);
|
m_armIdEdit->setText(armDeviceId);
|
||||||
m_cameraIdEdit->setText(settings.value(QStringLiteral("camera/device"), QStringLiteral("camera")).toString());
|
m_cameraIdEdit->setText(settings.value(QStringLiteral("camera/device"), QStringLiteral("camera")).toString());
|
||||||
@ -1130,6 +1397,7 @@ void MainWindow::loadSettings()
|
|||||||
void MainWindow::saveSettings()
|
void MainWindow::saveSettings()
|
||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
saveJointPrograms(settings);
|
||||||
settings.setValue(QStringLiteral("connection/host"), m_hostEdit->text().trimmed());
|
settings.setValue(QStringLiteral("connection/host"), m_hostEdit->text().trimmed());
|
||||||
settings.setValue(QStringLiteral("connection/port"), m_portSpin->value());
|
settings.setValue(QStringLiteral("connection/port"), m_portSpin->value());
|
||||||
settings.setValue(QStringLiteral("connection/demo"), m_demoCheck->isChecked());
|
settings.setValue(QStringLiteral("connection/demo"), m_demoCheck->isChecked());
|
||||||
@ -1168,6 +1436,72 @@ void MainWindow::saveSettings()
|
|||||||
: certificatePath);
|
: certificatePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::loadJointPrograms(QSettings &settings)
|
||||||
|
{
|
||||||
|
m_recordedJointPoints.clear();
|
||||||
|
const int pointCount = settings.beginReadArray(QStringLiteral("arm/jointPoints"));
|
||||||
|
for (int i = 0; i < pointCount; ++i) {
|
||||||
|
settings.setArrayIndex(i);
|
||||||
|
RecordedJointPoint point;
|
||||||
|
point.name = settings.value(QStringLiteral("name")).toString();
|
||||||
|
point.side = settings.value(QStringLiteral("side")).toString();
|
||||||
|
point.deviceId = settings.value(QStringLiteral("deviceId")).toString();
|
||||||
|
point.jointNames = settings.value(QStringLiteral("jointNames")).toStringList();
|
||||||
|
const QVariantList storedPositions = settings.value(QStringLiteral("positions")).toList();
|
||||||
|
for (const QVariant &value : storedPositions)
|
||||||
|
point.positions.append(value.toDouble());
|
||||||
|
if (!point.name.isEmpty() && point.jointNames.size() == point.positions.size())
|
||||||
|
m_recordedJointPoints.append(point);
|
||||||
|
}
|
||||||
|
settings.endArray();
|
||||||
|
|
||||||
|
m_jointFlowSteps.clear();
|
||||||
|
const int flowCount = settings.beginReadArray(QStringLiteral("arm/jointFlow"));
|
||||||
|
for (int i = 0; i < flowCount; ++i) {
|
||||||
|
settings.setArrayIndex(i);
|
||||||
|
JointFlowStep step;
|
||||||
|
step.pointName = settings.value(QStringLiteral("pointName")).toString();
|
||||||
|
step.waitSeconds = settings.value(QStringLiteral("waitSeconds"), 1.0).toDouble();
|
||||||
|
step.loopGroup = settings.value(QStringLiteral("loopGroup")).toString();
|
||||||
|
step.loopCount = settings.value(QStringLiteral("loopCount"), 1).toInt();
|
||||||
|
if (!step.pointName.isEmpty())
|
||||||
|
m_jointFlowSteps.append(step);
|
||||||
|
}
|
||||||
|
settings.endArray();
|
||||||
|
refreshRecordedJointPoints();
|
||||||
|
refreshJointFlowTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::saveJointPrograms(QSettings &settings) const
|
||||||
|
{
|
||||||
|
settings.remove(QStringLiteral("arm/jointPoints"));
|
||||||
|
settings.beginWriteArray(QStringLiteral("arm/jointPoints"));
|
||||||
|
for (int i = 0; i < m_recordedJointPoints.size(); ++i) {
|
||||||
|
settings.setArrayIndex(i);
|
||||||
|
const RecordedJointPoint &point = m_recordedJointPoints.at(i);
|
||||||
|
QVariantList positions;
|
||||||
|
for (double value : point.positions)
|
||||||
|
positions.append(value);
|
||||||
|
settings.setValue(QStringLiteral("name"), point.name);
|
||||||
|
settings.setValue(QStringLiteral("side"), point.side);
|
||||||
|
settings.setValue(QStringLiteral("deviceId"), point.deviceId);
|
||||||
|
settings.setValue(QStringLiteral("jointNames"), point.jointNames);
|
||||||
|
settings.setValue(QStringLiteral("positions"), positions);
|
||||||
|
}
|
||||||
|
settings.endArray();
|
||||||
|
settings.remove(QStringLiteral("arm/jointFlow"));
|
||||||
|
settings.beginWriteArray(QStringLiteral("arm/jointFlow"));
|
||||||
|
for (int i = 0; i < m_jointFlowSteps.size(); ++i) {
|
||||||
|
settings.setArrayIndex(i);
|
||||||
|
const JointFlowStep &step = m_jointFlowSteps.at(i);
|
||||||
|
settings.setValue(QStringLiteral("pointName"), step.pointName);
|
||||||
|
settings.setValue(QStringLiteral("waitSeconds"), step.waitSeconds);
|
||||||
|
settings.setValue(QStringLiteral("loopGroup"), step.loopGroup);
|
||||||
|
settings.setValue(QStringLiteral("loopCount"), step.loopCount);
|
||||||
|
}
|
||||||
|
settings.endArray();
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::setControlsEnabled(bool enabled)
|
void MainWindow::setControlsEnabled(bool enabled)
|
||||||
{
|
{
|
||||||
for (QPushButton *button : m_serviceButtons) {
|
for (QPushButton *button : m_serviceButtons) {
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QVariantList>
|
#include <QVariantList>
|
||||||
#include <QVariantMap>
|
#include <QVariantMap>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
#include "videoframe.h"
|
#include "videoframe.h"
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ class QLineEdit;
|
|||||||
class QProgressBar;
|
class QProgressBar;
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
class QResizeEvent;
|
class QResizeEvent;
|
||||||
|
class QSettings;
|
||||||
class QSpinBox;
|
class QSpinBox;
|
||||||
class QStackedWidget;
|
class QStackedWidget;
|
||||||
class QTableWidget;
|
class QTableWidget;
|
||||||
@ -73,6 +75,13 @@ private slots:
|
|||||||
void appendQuicLog(const QString &level, const QString &message);
|
void appendQuicLog(const QString &level, const QString &message);
|
||||||
void sendMoveJ();
|
void sendMoveJ();
|
||||||
void sendMoveJPose();
|
void sendMoveJPose();
|
||||||
|
void recordCurrentJointPoint();
|
||||||
|
void executeSelectedJointPoint();
|
||||||
|
void startJointPointLoop();
|
||||||
|
void stopJointPointLoop(const QString &reason = QString());
|
||||||
|
void advanceJointPointLoop();
|
||||||
|
void refreshRecordedJointPoints();
|
||||||
|
void refreshJointFlowTable();
|
||||||
void saveSnapshot();
|
void saveSnapshot();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -89,6 +98,8 @@ private:
|
|||||||
QWidget *createDiagnosticsPage();
|
QWidget *createDiagnosticsPage();
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
void saveSettings();
|
void saveSettings();
|
||||||
|
void loadJointPrograms(QSettings &settings);
|
||||||
|
void saveJointPrograms(QSettings &settings) const;
|
||||||
void setControlsEnabled(bool enabled);
|
void setControlsEnabled(bool enabled);
|
||||||
void updateWindowChrome();
|
void updateWindowChrome();
|
||||||
void ensureArmModelLoaded();
|
void ensureArmModelLoaded();
|
||||||
@ -155,6 +166,10 @@ private:
|
|||||||
QTableWidget *m_motionTable = nullptr;
|
QTableWidget *m_motionTable = nullptr;
|
||||||
QTableWidget *m_leftMotionTable = nullptr;
|
QTableWidget *m_leftMotionTable = nullptr;
|
||||||
QList<QDoubleSpinBox *> m_targetSpins;
|
QList<QDoubleSpinBox *> m_targetSpins;
|
||||||
|
QList<QDoubleSpinBox *> m_leftTargetSpins;
|
||||||
|
QList<QDoubleSpinBox *> m_rightTargetSpins;
|
||||||
|
QStringList m_leftArmJointNames;
|
||||||
|
QStringList m_rightArmJointNames;
|
||||||
QStringList m_activeArmJointNames;
|
QStringList m_activeArmJointNames;
|
||||||
QTabWidget *m_motionCommandTabs = nullptr;
|
QTabWidget *m_motionCommandTabs = nullptr;
|
||||||
QList<QDoubleSpinBox *> m_moveJPoseSpins;
|
QList<QDoubleSpinBox *> m_moveJPoseSpins;
|
||||||
@ -180,6 +195,43 @@ private:
|
|||||||
QString m_teachSelectedJoint;
|
QString m_teachSelectedJoint;
|
||||||
QString m_pendingUrdfPath;
|
QString m_pendingUrdfPath;
|
||||||
bool m_armModelLoadScheduled = false;
|
bool m_armModelLoadScheduled = false;
|
||||||
|
struct RecordedJointPoint {
|
||||||
|
QString name;
|
||||||
|
QString side;
|
||||||
|
QString deviceId;
|
||||||
|
QStringList jointNames;
|
||||||
|
QVector<double> positions;
|
||||||
|
};
|
||||||
|
struct JointFlowStep {
|
||||||
|
QString pointName;
|
||||||
|
double waitSeconds = 1.0;
|
||||||
|
QString loopGroup;
|
||||||
|
int loopCount = 1;
|
||||||
|
};
|
||||||
|
QList<RecordedJointPoint> m_recordedJointPoints;
|
||||||
|
QList<JointFlowStep> m_jointFlowSteps;
|
||||||
|
QHash<QString, double> m_latestArmJointPositions;
|
||||||
|
QLineEdit *m_jointPointNameEdit = nullptr;
|
||||||
|
QTableWidget *m_jointPointTable = nullptr;
|
||||||
|
QLabel *m_jointPointDetailLabel = nullptr;
|
||||||
|
QSpinBox *m_jointPointLoopCount = nullptr;
|
||||||
|
QDoubleSpinBox *m_jointPointInterval = nullptr;
|
||||||
|
QLineEdit *m_jointFlowGroupEdit = nullptr;
|
||||||
|
QSpinBox *m_jointFlowGroupCount = nullptr;
|
||||||
|
QTableWidget *m_jointFlowTable = nullptr;
|
||||||
|
QPushButton *m_recordJointPointButton = nullptr;
|
||||||
|
QPushButton *m_runJointPointButton = nullptr;
|
||||||
|
QPushButton *m_deleteJointPointButton = nullptr;
|
||||||
|
QPushButton *m_startJointPointLoopButton = nullptr;
|
||||||
|
QPushButton *m_stopJointPointLoopButton = nullptr;
|
||||||
|
QLabel *m_jointPointLoopStatus = nullptr;
|
||||||
|
QVector<int> m_jointPointLoopRows;
|
||||||
|
QVector<double> m_jointPointExecutionWaits;
|
||||||
|
int m_jointPointLoopCursor = 0;
|
||||||
|
int m_jointPointLoopsCompleted = 0;
|
||||||
|
int m_jointPointLoopTarget = 0;
|
||||||
|
bool m_jointPointLoopActive = false;
|
||||||
|
bool m_jointPointMovePending = false;
|
||||||
|
|
||||||
QLineEdit *m_cameraIdEdit = nullptr;
|
QLineEdit *m_cameraIdEdit = nullptr;
|
||||||
VideoRenderWidget *m_videoRender = nullptr;
|
VideoRenderWidget *m_videoRender = nullptr;
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
#include <QSettings>
|
||||||
#include <QSignalBlocker>
|
#include <QSignalBlocker>
|
||||||
#include <QSlider>
|
#include <QSlider>
|
||||||
#include <QSpinBox>
|
#include <QSpinBox>
|
||||||
@ -143,11 +144,16 @@ QStringList armJointNames(bool leftArm)
|
|||||||
|
|
||||||
bool isArmJointForSide(const QString &jointName, bool leftArm)
|
bool isArmJointForSide(const QString &jointName, bool leftArm)
|
||||||
{
|
{
|
||||||
const QString upper = jointName.toUpper();
|
const QString lower = jointName.toLower();
|
||||||
return leftArm ? (upper.startsWith(QStringLiteral("L_"))
|
const QString sideWord = leftArm ? QStringLiteral("left") : QStringLiteral("right");
|
||||||
|| upper.contains(QStringLiteral("LEFT")))
|
const QString sidePrefix = leftArm ? QStringLiteral("l_") : QStringLiteral("r_");
|
||||||
: (upper.startsWith(QStringLiteral("R_"))
|
return lower.contains(sideWord + QStringLiteral("_arm"))
|
||||||
|| upper.contains(QStringLiteral("RIGHT")));
|
|| lower.contains(sideWord + QStringLiteral("arm"))
|
||||||
|
|| (lower.startsWith(sidePrefix)
|
||||||
|
&& (lower.contains(QStringLiteral("shoulder"))
|
||||||
|
|| lower.contains(QStringLiteral("elbow"))
|
||||||
|
|| lower.contains(QStringLiteral("wrist"))
|
||||||
|
|| lower.contains(QStringLiteral("arm"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
QWidget *withRpcExplorer(QWidget *commonPage, RobotClient *client,
|
QWidget *withRpcExplorer(QWidget *commonPage, RobotClient *client,
|
||||||
@ -247,19 +253,15 @@ QWidget *MainWindow::createArmPage()
|
|||||||
ui.commandPanel->setParent(ui.urdfTab);
|
ui.commandPanel->setParent(ui.urdfTab);
|
||||||
ui.urdfTabLayout->insertWidget(0, ui.commandPanel);
|
ui.urdfTabLayout->insertWidget(0, ui.commandPanel);
|
||||||
|
|
||||||
QSplitter *modelMotionWorkspace = new QSplitter(Qt::Vertical, ui.urdfTab);
|
// Qt3D preview is disabled in release builds. Keep the viewer alive and
|
||||||
modelMotionWorkspace->setObjectName(QStringLiteral("armModelMotionSplitter"));
|
// hidden so it can still parse the URDF and provide the joint list, while
|
||||||
modelMotionWorkspace->setChildrenCollapsible(false);
|
// giving the complete workspace height to the command/point-flow tables.
|
||||||
modelMotionWorkspace->setHandleWidth(8);
|
|
||||||
ui.urdfTabLayout->removeWidget(ui.urdfViewer);
|
ui.urdfTabLayout->removeWidget(ui.urdfViewer);
|
||||||
modelMotionWorkspace->addWidget(ui.urdfViewer);
|
ui.urdfViewer->hide();
|
||||||
modelMotionWorkspace->addWidget(ui.targetsPanel);
|
ui.targetsPanel->setMinimumHeight(0);
|
||||||
modelMotionWorkspace->setStretchFactor(0, 5);
|
ui.targetsPanel->setMaximumHeight(QWIDGETSIZE_MAX);
|
||||||
modelMotionWorkspace->setStretchFactor(1, 4);
|
ui.targetsPanel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||||
modelMotionWorkspace->setSizes({500, 350});
|
ui.urdfTabLayout->insertWidget(1, ui.targetsPanel, 1);
|
||||||
ui.targetsPanel->setMinimumHeight(360);
|
|
||||||
ui.targetsPanel->setMaximumHeight(440);
|
|
||||||
ui.urdfTabLayout->insertWidget(1, modelMotionWorkspace, 1);
|
|
||||||
ui.urdfTabLayout->setStretch(0, 0);
|
ui.urdfTabLayout->setStretch(0, 0);
|
||||||
ui.urdfTabLayout->setStretch(1, 1);
|
ui.urdfTabLayout->setStretch(1, 1);
|
||||||
|
|
||||||
@ -309,32 +311,251 @@ QWidget *MainWindow::createArmPage()
|
|||||||
m_motionCommandTabs->setObjectName(QStringLiteral("armMotionCommandTabs"));
|
m_motionCommandTabs->setObjectName(QStringLiteral("armMotionCommandTabs"));
|
||||||
m_motionCommandTabs->setDocumentMode(true);
|
m_motionCommandTabs->setDocumentMode(true);
|
||||||
QWidget *moveJPage = new QWidget(m_motionCommandTabs);
|
QWidget *moveJPage = new QWidget(m_motionCommandTabs);
|
||||||
QVBoxLayout *moveJLayout = new QVBoxLayout(moveJPage);
|
QGridLayout *moveJLayout = new QGridLayout(moveJPage);
|
||||||
moveJLayout->setContentsMargins(0, 0, 0, 0);
|
moveJLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
QSplitter *armTargetTables = new QSplitter(Qt::Horizontal, moveJPage);
|
moveJLayout->setHorizontalSpacing(12);
|
||||||
|
moveJLayout->setVerticalSpacing(10);
|
||||||
|
moveJLayout->setColumnStretch(0, 4);
|
||||||
|
moveJLayout->setColumnStretch(1, 6);
|
||||||
|
moveJLayout->setRowStretch(0, 1);
|
||||||
|
moveJLayout->setRowStretch(1, 1);
|
||||||
|
QSplitter *armTargetTables = new QSplitter(Qt::Vertical, moveJPage);
|
||||||
armTargetTables->setObjectName(QStringLiteral("armTargetTables"));
|
armTargetTables->setObjectName(QStringLiteral("armTargetTables"));
|
||||||
armTargetTables->setChildrenCollapsible(false);
|
armTargetTables->setChildrenCollapsible(false);
|
||||||
QWidget *leftTargets = new QWidget(armTargetTables);
|
QWidget *leftTargets = new QWidget(armTargetTables);
|
||||||
|
leftTargets->setObjectName(QStringLiteral("armTargetGroup"));
|
||||||
QVBoxLayout *leftTargetsLayout = new QVBoxLayout(leftTargets);
|
QVBoxLayout *leftTargetsLayout = new QVBoxLayout(leftTargets);
|
||||||
leftTargetsLayout->setContentsMargins(0, 4, 6, 0);
|
leftTargetsLayout->setContentsMargins(0, 4, 6, 0);
|
||||||
QLabel *leftTargetsTitle = new QLabel(QStringLiteral("左臂 · eyou_left_arm"), leftTargets);
|
QLabel *leftTargetsTitle = new QLabel(QStringLiteral("左臂 · left_ethercat_arm"), leftTargets);
|
||||||
leftTargetsTitle->setObjectName(QStringLiteral("eyebrow"));
|
leftTargetsTitle->setObjectName(QStringLiteral("eyebrow"));
|
||||||
m_leftMotionTable = new QTableWidget(7, 4, leftTargets);
|
m_leftMotionTable = new QTableWidget(7, 4, leftTargets);
|
||||||
m_leftMotionTable->setObjectName(QStringLiteral("leftMotionTable"));
|
m_leftMotionTable->setObjectName(QStringLiteral("leftMotionTable"));
|
||||||
leftTargetsLayout->addWidget(leftTargetsTitle);
|
leftTargetsLayout->addWidget(leftTargetsTitle);
|
||||||
leftTargetsLayout->addWidget(m_leftMotionTable);
|
leftTargetsLayout->addWidget(m_leftMotionTable);
|
||||||
QWidget *rightTargets = new QWidget(armTargetTables);
|
QWidget *rightTargets = new QWidget(armTargetTables);
|
||||||
|
rightTargets->setObjectName(QStringLiteral("armTargetGroup"));
|
||||||
QVBoxLayout *rightTargetsLayout = new QVBoxLayout(rightTargets);
|
QVBoxLayout *rightTargetsLayout = new QVBoxLayout(rightTargets);
|
||||||
rightTargetsLayout->setContentsMargins(6, 4, 0, 0);
|
rightTargetsLayout->setContentsMargins(6, 4, 0, 0);
|
||||||
QLabel *rightTargetsTitle = new QLabel(QStringLiteral("右臂 · eyou_right_arm"), rightTargets);
|
QLabel *rightTargetsTitle = new QLabel(QStringLiteral("右臂 · right_ethercat_arm"), rightTargets);
|
||||||
rightTargetsTitle->setObjectName(QStringLiteral("eyebrow"));
|
rightTargetsTitle->setObjectName(QStringLiteral("eyebrow"));
|
||||||
m_motionTable->setParent(rightTargets);
|
m_motionTable->setParent(rightTargets);
|
||||||
rightTargetsLayout->addWidget(rightTargetsTitle);
|
rightTargetsLayout->addWidget(rightTargetsTitle);
|
||||||
rightTargetsLayout->addWidget(m_motionTable);
|
rightTargetsLayout->addWidget(m_motionTable);
|
||||||
armTargetTables->addWidget(leftTargets);
|
armTargetTables->addWidget(leftTargets);
|
||||||
armTargetTables->addWidget(rightTargets);
|
armTargetTables->addWidget(rightTargets);
|
||||||
armTargetTables->setSizes({500, 500});
|
armTargetTables->setSizes({340, 340});
|
||||||
moveJLayout->addWidget(armTargetTables);
|
moveJLayout->addWidget(armTargetTables, 0, 0, 2, 1);
|
||||||
|
|
||||||
|
QFrame *pointPanel = new QFrame(moveJPage);
|
||||||
|
pointPanel->setObjectName(QStringLiteral("panelCard"));
|
||||||
|
QVBoxLayout *pointLayout = new QVBoxLayout(pointPanel);
|
||||||
|
pointLayout->setContentsMargins(12, 10, 12, 10);
|
||||||
|
pointLayout->setSpacing(8);
|
||||||
|
QHBoxLayout *pointHeader = new QHBoxLayout;
|
||||||
|
QLabel *pointTitle = new QLabel(QStringLiteral("关节点位记录与循环"), pointPanel);
|
||||||
|
pointTitle->setObjectName(QStringLiteral("panelTitle"));
|
||||||
|
m_jointPointNameEdit = new QLineEdit(pointPanel);
|
||||||
|
m_jointPointNameEdit->setPlaceholderText(QStringLiteral("动作名称,例如:抓取准备"));
|
||||||
|
m_jointPointNameEdit->setMinimumWidth(220);
|
||||||
|
m_recordJointPointButton = new QPushButton(QStringLiteral("记录当前角度"), pointPanel);
|
||||||
|
pointHeader->addWidget(pointTitle);
|
||||||
|
pointHeader->addStretch(1);
|
||||||
|
pointHeader->addWidget(m_jointPointNameEdit);
|
||||||
|
pointHeader->addWidget(m_recordJointPointButton);
|
||||||
|
pointLayout->addLayout(pointHeader);
|
||||||
|
|
||||||
|
m_jointPointTable = new QTableWidget(0, 5, pointPanel);
|
||||||
|
m_jointPointTable->setHorizontalHeaderLabels({
|
||||||
|
QStringLiteral("动作名称"), QStringLiteral("机械臂"), QStringLiteral("Device ID"),
|
||||||
|
QStringLiteral("关节数"), QStringLiteral("关节角度(rad)")});
|
||||||
|
m_jointPointTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
|
m_jointPointTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
m_jointPointTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||||
|
m_jointPointTable->verticalHeader()->setVisible(false);
|
||||||
|
m_jointPointTable->verticalHeader()->setDefaultSectionSize(28);
|
||||||
|
m_jointPointTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
||||||
|
m_jointPointTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
|
||||||
|
m_jointPointTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
|
||||||
|
m_jointPointTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
|
||||||
|
m_jointPointTable->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Stretch);
|
||||||
|
m_jointPointTable->setMinimumHeight(132);
|
||||||
|
pointLayout->addWidget(m_jointPointTable);
|
||||||
|
m_jointPointDetailLabel = new QLabel(QStringLiteral("选择点位后查看详细关节角度"), pointPanel);
|
||||||
|
m_jointPointDetailLabel->setObjectName(QStringLiteral("mutedLabel"));
|
||||||
|
m_jointPointDetailLabel->setWordWrap(true);
|
||||||
|
m_jointPointDetailLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
|
pointLayout->addWidget(m_jointPointDetailLabel);
|
||||||
|
|
||||||
|
QHBoxLayout *pointActions = new QHBoxLayout;
|
||||||
|
m_runJointPointButton = new QPushButton(QStringLiteral("执行选中点位"), pointPanel);
|
||||||
|
m_deleteJointPointButton = new QPushButton(QStringLiteral("删除选中"), pointPanel);
|
||||||
|
QLabel *loopCountLabel = new QLabel(QStringLiteral("循环次数"), pointPanel);
|
||||||
|
m_jointPointLoopCount = new QSpinBox(pointPanel);
|
||||||
|
m_jointPointLoopCount->setRange(1, 10000);
|
||||||
|
m_jointPointLoopCount->setValue(1);
|
||||||
|
m_startJointPointLoopButton = new QPushButton(QStringLiteral("执行自定义流程"), pointPanel);
|
||||||
|
m_stopJointPointLoopButton = new QPushButton(QStringLiteral("停止循环"), pointPanel);
|
||||||
|
m_stopJointPointLoopButton->setEnabled(false);
|
||||||
|
m_jointPointLoopStatus = new QLabel(QStringLiteral("未运行"), pointPanel);
|
||||||
|
m_jointPointLoopStatus->setObjectName(QStringLiteral("mutedLabel"));
|
||||||
|
pointActions->addWidget(m_runJointPointButton);
|
||||||
|
pointActions->addWidget(m_deleteJointPointButton);
|
||||||
|
pointActions->addStretch(1);
|
||||||
|
pointActions->addWidget(m_jointPointLoopStatus);
|
||||||
|
pointLayout->addLayout(pointActions);
|
||||||
|
moveJLayout->addWidget(pointPanel, 0, 1);
|
||||||
|
|
||||||
|
connect(m_recordJointPointButton, &QPushButton::clicked,
|
||||||
|
this, &MainWindow::recordCurrentJointPoint);
|
||||||
|
connect(m_runJointPointButton, &QPushButton::clicked,
|
||||||
|
this, &MainWindow::executeSelectedJointPoint);
|
||||||
|
connect(m_startJointPointLoopButton, &QPushButton::clicked,
|
||||||
|
this, &MainWindow::startJointPointLoop);
|
||||||
|
connect(m_stopJointPointLoopButton, &QPushButton::clicked, this, [this]() {
|
||||||
|
stopJointPointLoop(QStringLiteral("用户停止"));
|
||||||
|
m_client->requestArmStop(m_armIdEdit->text().trimmed());
|
||||||
|
});
|
||||||
|
connect(m_deleteJointPointButton, &QPushButton::clicked, this, [this]() {
|
||||||
|
if (m_jointPointLoopActive)
|
||||||
|
return;
|
||||||
|
QList<int> rows;
|
||||||
|
for (const QModelIndex &index : m_jointPointTable->selectionModel()->selectedRows())
|
||||||
|
rows.append(index.row());
|
||||||
|
std::sort(rows.begin(), rows.end(), std::greater<int>());
|
||||||
|
QStringList removedNames;
|
||||||
|
for (int row : rows) {
|
||||||
|
if (row >= 0 && row < m_recordedJointPoints.size()) {
|
||||||
|
removedNames.append(m_recordedJointPoints.at(row).name);
|
||||||
|
m_recordedJointPoints.removeAt(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = m_jointFlowSteps.size() - 1; i >= 0; --i) {
|
||||||
|
if (removedNames.contains(m_jointFlowSteps.at(i).pointName))
|
||||||
|
m_jointFlowSteps.removeAt(i);
|
||||||
|
}
|
||||||
|
refreshRecordedJointPoints();
|
||||||
|
refreshJointFlowTable();
|
||||||
|
QSettings settings;
|
||||||
|
saveJointPrograms(settings);
|
||||||
|
});
|
||||||
|
connect(m_jointPointTable, &QTableWidget::cellClicked, this,
|
||||||
|
[this](int row, int) {
|
||||||
|
if (row < 0 || row >= m_recordedJointPoints.size())
|
||||||
|
return;
|
||||||
|
const RecordedJointPoint &point = m_recordedJointPoints.at(row);
|
||||||
|
QStringList details;
|
||||||
|
for (int i = 0; i < point.jointNames.size() && i < point.positions.size(); ++i) {
|
||||||
|
details.append(QStringLiteral("%1 = %2 rad")
|
||||||
|
.arg(point.jointNames.at(i))
|
||||||
|
.arg(point.positions.at(i), 0, 'f', 6));
|
||||||
|
}
|
||||||
|
m_jointPointDetailLabel->setText(
|
||||||
|
QStringLiteral("%1 · %2 · %3\n%4")
|
||||||
|
.arg(point.name,
|
||||||
|
point.side == QStringLiteral("left") ? QStringLiteral("左臂")
|
||||||
|
: QStringLiteral("右臂"),
|
||||||
|
point.deviceId, details.join(QStringLiteral(" "))));
|
||||||
|
});
|
||||||
|
|
||||||
|
QFrame *flowPanel = new QFrame(moveJPage);
|
||||||
|
flowPanel->setObjectName(QStringLiteral("panelCard"));
|
||||||
|
QVBoxLayout *flowLayout = new QVBoxLayout(flowPanel);
|
||||||
|
flowLayout->setContentsMargins(12, 10, 12, 10);
|
||||||
|
flowLayout->setSpacing(8);
|
||||||
|
QHBoxLayout *flowBuilder = new QHBoxLayout;
|
||||||
|
QLabel *flowTitle = new QLabel(QStringLiteral("自定义执行流程"), flowPanel);
|
||||||
|
flowTitle->setObjectName(QStringLiteral("panelTitle"));
|
||||||
|
QLabel *intervalLabel = new QLabel(QStringLiteral("完成后等待"), flowPanel);
|
||||||
|
m_jointPointInterval = new QDoubleSpinBox(flowPanel);
|
||||||
|
m_jointPointInterval->setRange(0.0, 3600.0);
|
||||||
|
m_jointPointInterval->setDecimals(1);
|
||||||
|
m_jointPointInterval->setValue(1.0);
|
||||||
|
m_jointPointInterval->setSuffix(QStringLiteral(" s"));
|
||||||
|
m_jointFlowGroupEdit = new QLineEdit(flowPanel);
|
||||||
|
m_jointFlowGroupEdit->setPlaceholderText(QStringLiteral("循环组名(留空=执行一次)"));
|
||||||
|
m_jointFlowGroupEdit->setMaximumWidth(190);
|
||||||
|
m_jointFlowGroupCount = new QSpinBox(flowPanel);
|
||||||
|
m_jointFlowGroupCount->setRange(1, 10000);
|
||||||
|
m_jointFlowGroupCount->setValue(1);
|
||||||
|
QPushButton *addFlowStepButton = new QPushButton(QStringLiteral("添加选中点位到流程"), flowPanel);
|
||||||
|
flowBuilder->addWidget(flowTitle);
|
||||||
|
flowBuilder->addStretch(1);
|
||||||
|
flowBuilder->addWidget(intervalLabel);
|
||||||
|
flowBuilder->addWidget(m_jointPointInterval);
|
||||||
|
flowBuilder->addWidget(m_jointFlowGroupEdit);
|
||||||
|
flowBuilder->addWidget(new QLabel(QStringLiteral("组循环次数"), flowPanel));
|
||||||
|
flowBuilder->addWidget(m_jointFlowGroupCount);
|
||||||
|
flowBuilder->addWidget(addFlowStepButton);
|
||||||
|
flowLayout->addLayout(flowBuilder);
|
||||||
|
m_jointFlowTable = new QTableWidget(0, 5, flowPanel);
|
||||||
|
m_jointFlowTable->setHorizontalHeaderLabels({QStringLiteral("顺序"), QStringLiteral("动作点位"),
|
||||||
|
QStringLiteral("完成后等待"), QStringLiteral("循环组"), QStringLiteral("组循环次数")});
|
||||||
|
m_jointFlowTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
|
m_jointFlowTable->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
|
m_jointFlowTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||||
|
m_jointFlowTable->verticalHeader()->setVisible(false);
|
||||||
|
m_jointFlowTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||||
|
m_jointFlowTable->setMinimumHeight(120);
|
||||||
|
flowLayout->addWidget(m_jointFlowTable);
|
||||||
|
QHBoxLayout *flowActions = new QHBoxLayout;
|
||||||
|
QPushButton *flowUpButton = new QPushButton(QStringLiteral("上移"), flowPanel);
|
||||||
|
QPushButton *flowDownButton = new QPushButton(QStringLiteral("下移"), flowPanel);
|
||||||
|
QPushButton *flowDeleteButton = new QPushButton(QStringLiteral("删除步骤"), flowPanel);
|
||||||
|
flowActions->addWidget(flowUpButton);
|
||||||
|
flowActions->addWidget(flowDownButton);
|
||||||
|
flowActions->addWidget(flowDeleteButton);
|
||||||
|
flowActions->addStretch(1);
|
||||||
|
flowActions->addWidget(loopCountLabel);
|
||||||
|
loopCountLabel->setVisible(false);
|
||||||
|
m_jointPointLoopCount->setVisible(false);
|
||||||
|
flowActions->addWidget(m_startJointPointLoopButton);
|
||||||
|
flowActions->addWidget(m_stopJointPointLoopButton);
|
||||||
|
flowLayout->addLayout(flowActions);
|
||||||
|
moveJLayout->addWidget(flowPanel, 1, 1);
|
||||||
|
|
||||||
|
connect(addFlowStepButton, &QPushButton::clicked, this, [this]() {
|
||||||
|
const QModelIndexList rows = m_jointPointTable->selectionModel()->selectedRows();
|
||||||
|
if (rows.isEmpty())
|
||||||
|
return;
|
||||||
|
QList<int> sortedRows;
|
||||||
|
for (const QModelIndex &index : rows)
|
||||||
|
sortedRows.append(index.row());
|
||||||
|
std::sort(sortedRows.begin(), sortedRows.end());
|
||||||
|
for (int row : sortedRows) {
|
||||||
|
if (row < 0 || row >= m_recordedJointPoints.size())
|
||||||
|
continue;
|
||||||
|
JointFlowStep step;
|
||||||
|
step.pointName = m_recordedJointPoints.at(row).name;
|
||||||
|
step.waitSeconds = m_jointPointInterval->value();
|
||||||
|
step.loopGroup = m_jointFlowGroupEdit->text().trimmed();
|
||||||
|
step.loopCount = step.loopGroup.isEmpty() ? 1 : m_jointFlowGroupCount->value();
|
||||||
|
m_jointFlowSteps.append(step);
|
||||||
|
}
|
||||||
|
refreshJointFlowTable();
|
||||||
|
QSettings settings;
|
||||||
|
saveJointPrograms(settings);
|
||||||
|
});
|
||||||
|
connect(flowDeleteButton, &QPushButton::clicked, this, [this]() {
|
||||||
|
const int row = m_jointFlowTable->currentRow();
|
||||||
|
if (row >= 0 && row < m_jointFlowSteps.size())
|
||||||
|
m_jointFlowSteps.removeAt(row);
|
||||||
|
refreshJointFlowTable();
|
||||||
|
QSettings settings;
|
||||||
|
saveJointPrograms(settings);
|
||||||
|
});
|
||||||
|
const auto moveFlowStep = [this](int delta) {
|
||||||
|
const int row = m_jointFlowTable->currentRow();
|
||||||
|
const int target = row + delta;
|
||||||
|
if (row < 0 || target < 0 || target >= m_jointFlowSteps.size())
|
||||||
|
return;
|
||||||
|
m_jointFlowSteps.swap(row, target);
|
||||||
|
refreshJointFlowTable();
|
||||||
|
m_jointFlowTable->selectRow(target);
|
||||||
|
QSettings settings;
|
||||||
|
saveJointPrograms(settings);
|
||||||
|
};
|
||||||
|
connect(flowUpButton, &QPushButton::clicked, this, [moveFlowStep]() { moveFlowStep(-1); });
|
||||||
|
connect(flowDownButton, &QPushButton::clicked, this, [moveFlowStep]() { moveFlowStep(1); });
|
||||||
m_motionCommandTabs->addTab(moveJPage, QStringLiteral("MoveJ 关节角"));
|
m_motionCommandTabs->addTab(moveJPage, QStringLiteral("MoveJ 关节角"));
|
||||||
|
|
||||||
QWidget *moveLPage = new QWidget(m_motionCommandTabs);
|
QWidget *moveLPage = new QWidget(m_motionCommandTabs);
|
||||||
@ -480,9 +701,10 @@ QWidget *MainWindow::createArmPage()
|
|||||||
});
|
});
|
||||||
connect(ui.zeroButton, &QPushButton::clicked, this, [this]() {
|
connect(ui.zeroButton, &QPushButton::clicked, this, [this]() {
|
||||||
const bool leftArm = m_armSideCombo->currentData().toString() == QStringLiteral("left");
|
const bool leftArm = m_armSideCombo->currentData().toString() == QStringLiteral("left");
|
||||||
const int offset = leftArm ? 0 : 7;
|
const QList<QDoubleSpinBox *> &spins = leftArm
|
||||||
for (int i = 0; i < 7 && offset + i < m_targetSpins.size(); ++i)
|
? m_leftTargetSpins : m_rightTargetSpins;
|
||||||
m_targetSpins.at(offset + i)->setValue(0.0);
|
for (QDoubleSpinBox *spin : spins)
|
||||||
|
spin->setValue(0.0);
|
||||||
});
|
});
|
||||||
connect(m_sendMoveButton, &QPushButton::clicked, this, [this]() {
|
connect(m_sendMoveButton, &QPushButton::clicked, this, [this]() {
|
||||||
if (m_motionCommandTabs && m_motionCommandTabs->currentIndex() == 1)
|
if (m_motionCommandTabs && m_motionCommandTabs->currentIndex() == 1)
|
||||||
@ -528,18 +750,28 @@ QWidget *MainWindow::createArmPage()
|
|||||||
this, &MainWindow::appendLog);
|
this, &MainWindow::appendLog);
|
||||||
|
|
||||||
const auto configureMotionTable = [](QTableWidget *table) {
|
const auto configureMotionTable = [](QTableWidget *table) {
|
||||||
table->setHorizontalHeaderLabels({QStringLiteral("关节"), QStringLiteral("当前"),
|
table->setColumnCount(5);
|
||||||
QStringLiteral("目标角度"), QStringLiteral("目标弧度")});
|
table->setHorizontalHeaderLabels({QStringLiteral("关节"), QStringLiteral("当前角度"),
|
||||||
|
QStringLiteral("当前弧度"), QStringLiteral("目标角度"),
|
||||||
|
QStringLiteral("目标弧度")});
|
||||||
table->setAlternatingRowColors(true);
|
table->setAlternatingRowColors(true);
|
||||||
table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
table->setSelectionMode(QAbstractItemView::SingleSelection);
|
table->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
table->setShowGrid(false);
|
table->setShowGrid(false);
|
||||||
table->verticalHeader()->setVisible(false);
|
table->verticalHeader()->setVisible(false);
|
||||||
table->verticalHeader()->setDefaultSectionSize(32);
|
table->verticalHeader()->setDefaultSectionSize(29);
|
||||||
table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
|
table->horizontalHeader()->setFixedHeight(32);
|
||||||
table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
|
table->horizontalHeader()->setHighlightSections(false);
|
||||||
table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
|
for (int column = 0; column < 5; ++column)
|
||||||
table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch);
|
table->horizontalHeader()->setSectionResizeMode(column, QHeaderView::Fixed);
|
||||||
|
table->setColumnWidth(0, 112);
|
||||||
|
table->setColumnWidth(1, 76);
|
||||||
|
table->setColumnWidth(2, 92);
|
||||||
|
table->setColumnWidth(3, 118);
|
||||||
|
table->setColumnWidth(4, 96);
|
||||||
|
table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
|
table->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||||
|
table->setMinimumHeight(32 + 7 * 29 + 2);
|
||||||
};
|
};
|
||||||
configureMotionTable(m_leftMotionTable);
|
configureMotionTable(m_leftMotionTable);
|
||||||
configureMotionTable(m_motionTable);
|
configureMotionTable(m_motionTable);
|
||||||
@ -554,14 +786,21 @@ QWidget *MainWindow::createArmPage()
|
|||||||
m_jointTable->horizontalHeader()->setStretchLastSection(true);
|
m_jointTable->horizontalHeader()->setStretchLastSection(true);
|
||||||
|
|
||||||
m_targetSpins.clear();
|
m_targetSpins.clear();
|
||||||
const auto populateMotionTable = [this](QTableWidget *table, bool leftArm) {
|
m_leftArmJointNames = armJointNames(true);
|
||||||
const QStringList jointNames = armJointNames(leftArm);
|
m_rightArmJointNames = armJointNames(false);
|
||||||
|
const auto populateMotionTable = [this](QTableWidget *table, bool leftArm,
|
||||||
|
const QStringList &jointNames) {
|
||||||
|
QList<QDoubleSpinBox *> &sideSpins = leftArm
|
||||||
|
? m_leftTargetSpins : m_rightTargetSpins;
|
||||||
|
sideSpins.clear();
|
||||||
table->clearContents();
|
table->clearContents();
|
||||||
table->setRowCount(jointNames.size());
|
table->setRowCount(jointNames.size());
|
||||||
|
table->setMinimumHeight(32 + qMin(jointNames.size(), 10) * 29 + 2);
|
||||||
for (int row = 0; row < jointNames.size(); ++row) {
|
for (int row = 0; row < jointNames.size(); ++row) {
|
||||||
const QString jointName = jointNames.at(row);
|
const QString jointName = jointNames.at(row);
|
||||||
table->setItem(row, 0, new QTableWidgetItem(jointName));
|
table->setItem(row, 0, new QTableWidgetItem(jointName));
|
||||||
table->setItem(row, 1, new QTableWidgetItem(QStringLiteral("--")));
|
table->setItem(row, 1, new QTableWidgetItem(QStringLiteral("--")));
|
||||||
|
table->setItem(row, 2, new QTableWidgetItem(QStringLiteral("--")));
|
||||||
QDoubleSpinBox *target = new QDoubleSpinBox(table);
|
QDoubleSpinBox *target = new QDoubleSpinBox(table);
|
||||||
target->setRange(-180.0, 180.0);
|
target->setRange(-180.0, 180.0);
|
||||||
target->setDecimals(3);
|
target->setDecimals(3);
|
||||||
@ -570,14 +809,15 @@ QWidget *MainWindow::createArmPage()
|
|||||||
target->setFrame(false);
|
target->setFrame(false);
|
||||||
target->setEnabled(m_connected);
|
target->setEnabled(m_connected);
|
||||||
m_targetSpins.append(target);
|
m_targetSpins.append(target);
|
||||||
table->setCellWidget(row, 2, target);
|
sideSpins.append(target);
|
||||||
|
table->setCellWidget(row, 3, target);
|
||||||
QTableWidgetItem *radians = new QTableWidgetItem(QStringLiteral("0.000000"));
|
QTableWidgetItem *radians = new QTableWidgetItem(QStringLiteral("0.000000"));
|
||||||
radians->setFlags(radians->flags() & ~Qt::ItemIsEditable);
|
radians->setFlags(radians->flags() & ~Qt::ItemIsEditable);
|
||||||
table->setItem(row, 3, radians);
|
table->setItem(row, 4, radians);
|
||||||
connect(target, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
|
connect(target, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
|
||||||
[this, table, row, jointName](double degrees) {
|
[this, table, row, jointName](double degrees) {
|
||||||
if (row < table->rowCount() && table->item(row, 3))
|
if (row < table->rowCount() && table->item(row, 4))
|
||||||
table->item(row, 3)->setText(
|
table->item(row, 4)->setText(
|
||||||
QString::number(qDegreesToRadians(degrees), 'f', 6));
|
QString::number(qDegreesToRadians(degrees), 'f', 6));
|
||||||
if (m_teachModeButton->isChecked())
|
if (m_teachModeButton->isChecked())
|
||||||
m_urdfViewer->setTeachingJointPosition(
|
m_urdfViewer->setTeachingJointPosition(
|
||||||
@ -586,8 +826,8 @@ QWidget *MainWindow::createArmPage()
|
|||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
populateMotionTable(m_leftMotionTable, true);
|
populateMotionTable(m_leftMotionTable, true, m_leftArmJointNames);
|
||||||
populateMotionTable(m_motionTable, false);
|
populateMotionTable(m_motionTable, false, m_rightArmJointNames);
|
||||||
|
|
||||||
const QStringList allJointNames = armJointNames(true) + armJointNames(false);
|
const QStringList allJointNames = armJointNames(true) + armJointNames(false);
|
||||||
m_jointTable->clearContents();
|
m_jointTable->clearContents();
|
||||||
@ -600,7 +840,7 @@ QWidget *MainWindow::createArmPage()
|
|||||||
|
|
||||||
const auto refreshActiveArmControls = [this]() {
|
const auto refreshActiveArmControls = [this]() {
|
||||||
const bool leftArm = m_armSideCombo->currentData().toString() == QStringLiteral("left");
|
const bool leftArm = m_armSideCombo->currentData().toString() == QStringLiteral("left");
|
||||||
m_activeArmJointNames = armJointNames(leftArm);
|
m_activeArmJointNames = leftArm ? m_leftArmJointNames : m_rightArmJointNames;
|
||||||
QStringList zeroJointNames;
|
QStringList zeroJointNames;
|
||||||
const QStringList urdfNames = m_urdfViewer->jointNames();
|
const QStringList urdfNames = m_urdfViewer->jointNames();
|
||||||
for (const QString &name : urdfNames) {
|
for (const QString &name : urdfNames) {
|
||||||
@ -628,8 +868,8 @@ QWidget *MainWindow::createArmPage()
|
|||||||
if (telemetryRunning)
|
if (telemetryRunning)
|
||||||
m_client->setArmTelemetry(oldDeviceId, false);
|
m_client->setArmTelemetry(oldDeviceId, false);
|
||||||
const bool leftArm = m_armSideCombo->currentData().toString() == QStringLiteral("left");
|
const bool leftArm = m_armSideCombo->currentData().toString() == QStringLiteral("left");
|
||||||
m_armIdEdit->setText(leftArm ? QStringLiteral("eyou_left_arm")
|
m_armIdEdit->setText(leftArm ? QStringLiteral("left_ethercat_arm")
|
||||||
: QStringLiteral("eyou_right_arm"));
|
: QStringLiteral("right_ethercat_arm"));
|
||||||
m_latestArmPose.clear();
|
m_latestArmPose.clear();
|
||||||
m_poseValue->setText(QStringLiteral("X --\nY --\nZ --\n\nRX --\nRY --\nRZ --"));
|
m_poseValue->setText(QStringLiteral("X --\nY --\nZ --\n\nRX --\nRY --\nRZ --"));
|
||||||
poseSubtitle->setText(leftArm ? QStringLiteral("PELVIS_S → L_FINGER_TIP")
|
poseSubtitle->setText(leftArm ? QStringLiteral("PELVIS_S → L_FINGER_TIP")
|
||||||
@ -641,9 +881,40 @@ QWidget *MainWindow::createArmPage()
|
|||||||
m_client->setArmTelemetry(m_armIdEdit->text().trimmed(), true);
|
m_client->setArmTelemetry(m_armIdEdit->text().trimmed(), true);
|
||||||
});
|
});
|
||||||
connect(m_urdfViewer, &UrdfViewerWidget::modelLoaded, this,
|
connect(m_urdfViewer, &UrdfViewerWidget::modelLoaded, this,
|
||||||
[this, refreshActiveArmControls](const QString &, int, int) {
|
[this, refreshActiveArmControls, populateMotionTable](const QString &, int, int) {
|
||||||
|
const QStringList urdfNames = m_urdfViewer->jointNames();
|
||||||
|
QStringList leftNames;
|
||||||
|
QStringList rightNames;
|
||||||
|
for (const QString &name : urdfNames) {
|
||||||
|
if (isArmJointForSide(name, true))
|
||||||
|
leftNames.append(name);
|
||||||
|
else if (isArmJointForSide(name, false))
|
||||||
|
rightNames.append(name);
|
||||||
|
}
|
||||||
|
// A single-arm URDF often uses neutral names such as joint1..joint6.
|
||||||
|
// In that case bind all movable joints to the currently selected arm.
|
||||||
|
if (leftNames.isEmpty() && rightNames.isEmpty() && !urdfNames.isEmpty()) {
|
||||||
|
if (m_armSideCombo->currentData().toString() == QStringLiteral("left"))
|
||||||
|
leftNames = urdfNames;
|
||||||
|
else
|
||||||
|
rightNames = urdfNames;
|
||||||
|
}
|
||||||
|
m_leftArmJointNames = leftNames;
|
||||||
|
m_rightArmJointNames = rightNames;
|
||||||
|
m_targetSpins.clear();
|
||||||
|
populateMotionTable(m_leftMotionTable, true, m_leftArmJointNames);
|
||||||
|
populateMotionTable(m_motionTable, false, m_rightArmJointNames);
|
||||||
|
|
||||||
|
m_jointTable->clearContents();
|
||||||
|
m_jointTable->setRowCount(urdfNames.size());
|
||||||
|
for (int row = 0; row < urdfNames.size(); ++row) {
|
||||||
|
m_jointTable->setItem(row, 0, new QTableWidgetItem(urdfNames.at(row)));
|
||||||
|
for (int column = 1; column < 4; ++column)
|
||||||
|
m_jointTable->setItem(row, column,
|
||||||
|
new QTableWidgetItem(QStringLiteral("--")));
|
||||||
|
}
|
||||||
refreshActiveArmControls();
|
refreshActiveArmControls();
|
||||||
m_teachModeButton->setEnabled(!m_urdfViewer->jointNames().isEmpty());
|
m_teachModeButton->setEnabled(!urdfNames.isEmpty());
|
||||||
});
|
});
|
||||||
connect(m_teachModeButton, &QPushButton::toggled, this, [this](bool enabled) {
|
connect(m_teachModeButton, &QPushButton::toggled, this, [this](bool enabled) {
|
||||||
m_urdfViewer->setTeachingEnabled(enabled);
|
m_urdfViewer->setTeachingEnabled(enabled);
|
||||||
@ -683,15 +954,16 @@ QWidget *MainWindow::createArmPage()
|
|||||||
for (int side = 0; side < 2; ++side) {
|
for (int side = 0; side < 2; ++side) {
|
||||||
const bool leftArm = side == 0;
|
const bool leftArm = side == 0;
|
||||||
QTableWidget *table = leftArm ? m_leftMotionTable : m_motionTable;
|
QTableWidget *table = leftArm ? m_leftMotionTable : m_motionTable;
|
||||||
const QStringList names = armJointNames(leftArm);
|
const QStringList &names = leftArm ? m_leftArmJointNames : m_rightArmJointNames;
|
||||||
const int offset = leftArm ? 0 : 7;
|
const QList<QDoubleSpinBox *> &spins = leftArm
|
||||||
for (int row = 0; row < names.size() && offset + row < m_targetSpins.size(); ++row) {
|
? m_leftTargetSpins : m_rightTargetSpins;
|
||||||
|
for (int row = 0; row < names.size() && row < spins.size(); ++row) {
|
||||||
if (!positions.contains(names.at(row)))
|
if (!positions.contains(names.at(row)))
|
||||||
continue;
|
continue;
|
||||||
const double radians = positions.value(names.at(row));
|
const double radians = positions.value(names.at(row));
|
||||||
const QSignalBlocker blocker(m_targetSpins.at(offset + row));
|
const QSignalBlocker blocker(spins.at(row));
|
||||||
m_targetSpins.at(offset + row)->setValue(qRadiansToDegrees(radians));
|
spins.at(row)->setValue(qRadiansToDegrees(radians));
|
||||||
table->item(row, 3)->setText(QString::number(radians, 'f', 6));
|
table->item(row, 4)->setText(QString::number(radians, 'f', 6));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -705,7 +977,7 @@ QWidget *MainWindow::createArmPage()
|
|||||||
leftArm ? QStringLiteral("left") : QStringLiteral("right"));
|
leftArm ? QStringLiteral("left") : QStringLiteral("right"));
|
||||||
if (sideIndex >= 0 && sideIndex != m_armSideCombo->currentIndex())
|
if (sideIndex >= 0 && sideIndex != m_armSideCombo->currentIndex())
|
||||||
m_armSideCombo->setCurrentIndex(sideIndex);
|
m_armSideCombo->setCurrentIndex(sideIndex);
|
||||||
const QStringList names = armJointNames(leftArm);
|
const QStringList &names = leftArm ? m_leftArmJointNames : m_rightArmJointNames;
|
||||||
if (m_teachModeButton->isChecked() && row >= 0 && row < names.size())
|
if (m_teachModeButton->isChecked() && row >= 0 && row < names.size())
|
||||||
m_urdfViewer->selectTeachingJoint(names.at(row));
|
m_urdfViewer->selectTeachingJoint(names.at(row));
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2648,7 +2648,6 @@ private:
|
|||||||
|
|
||||||
void pollGrpc()
|
void pollGrpc()
|
||||||
{
|
{
|
||||||
++m_tick;
|
|
||||||
if (m_armPollingEnabled && !m_armDeviceId.isEmpty()) {
|
if (m_armPollingEnabled && !m_armDeviceId.isEmpty()) {
|
||||||
cmvr::api::JointRequest request;
|
cmvr::api::JointRequest request;
|
||||||
fillHeader(request.mutable_header(), m_armDeviceId);
|
fillHeader(request.mutable_header(), m_armDeviceId);
|
||||||
@ -2675,45 +2674,6 @@ private:
|
|||||||
emit logMessage(QStringLiteral("WARN"), QStringLiteral("ArmService 连续 10 次遥测请求失败"));
|
emit logMessage(QStringLiteral("WARN"), QStringLiteral("ArmService 连续 10 次遥测请求失败"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_tick % 5 == 0) {
|
|
||||||
cmvr::api::GetPose::Request poseRequest;
|
|
||||||
fillHeader(poseRequest.mutable_header(), m_armDeviceId);
|
|
||||||
poseRequest.set_base_link("PELVIS_S");
|
|
||||||
poseRequest.set_ee_link(
|
|
||||||
m_armDeviceId.contains(QStringLiteral("left"), Qt::CaseInsensitive)
|
|
||||||
? "L_FINGER_TIP" : "R_FINGER_TIP");
|
|
||||||
cmvr::api::GetPose::Response poseResponse;
|
|
||||||
grpc::ClientContext poseContext;
|
|
||||||
setDeadline(poseContext, 400);
|
|
||||||
const grpc::Status poseStatus = m_arm->getPose(&poseContext, poseRequest, &poseResponse);
|
|
||||||
if (poseStatus.ok() && poseResponse.header().success()) {
|
|
||||||
const auto &source = poseResponse.pose();
|
|
||||||
QVariantMap pose;
|
|
||||||
pose.insert(QStringLiteral("x"), source.x());
|
|
||||||
pose.insert(QStringLiteral("y"), source.y());
|
|
||||||
pose.insert(QStringLiteral("z"), source.z());
|
|
||||||
pose.insert(QStringLiteral("rx"), source.rx());
|
|
||||||
pose.insert(QStringLiteral("ry"), source.ry());
|
|
||||||
pose.insert(QStringLiteral("rz"), source.rz());
|
|
||||||
emit telemetryReceived(QVariantList(), pose);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_tick % 10 != 0)
|
|
||||||
return;
|
|
||||||
cmvr::api::GetSystemStatusCommand::Request statusRequest;
|
|
||||||
cmvr::api::GetSystemStatusCommand::Feedback statusResponse;
|
|
||||||
grpc::ClientContext statusContext;
|
|
||||||
setDeadline(statusContext, 500);
|
|
||||||
const grpc::Status rpcStatus = m_system->GetSystemStatus(&statusContext, statusRequest, &statusResponse);
|
|
||||||
if (rpcStatus.ok() && statusResponse.header().success()) {
|
|
||||||
const double memory = statusResponse.mem_total_mb() > 0
|
|
||||||
? statusResponse.mem_used_mb() * 100.0 / statusResponse.mem_total_mb() : 0.0;
|
|
||||||
const double disk = statusResponse.disk_total_gb() > 0
|
|
||||||
? statusResponse.disk_used_gb() * 100.0 / statusResponse.disk_total_gb() : 0.0;
|
|
||||||
emit systemStatusReceived(statusResponse.cpu_usage(), memory, disk,
|
|
||||||
statusResponse.device_list_size());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -44,6 +44,7 @@
|
|||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QProgressBar>
|
#include <QProgressBar>
|
||||||
|
#include <QPointer>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QQuaternion>
|
#include <QQuaternion>
|
||||||
#include <QShowEvent>
|
#include <QShowEvent>
|
||||||
@ -406,7 +407,29 @@ void UrdfViewerWidget::setLoading(bool loading, const QString &message)
|
|||||||
|
|
||||||
UrdfViewerWidget::~UrdfViewerWidget()
|
UrdfViewerWidget::~UrdfViewerWidget()
|
||||||
{
|
{
|
||||||
// Qt3DWindow's aspect engine owns and synchronously tears down the scene.
|
if (m_demoTimer)
|
||||||
|
m_demoTimer->stop();
|
||||||
|
|
||||||
|
// Qt 5.12/MinGW has a reproducible access violation in Qt53DRender.dll
|
||||||
|
// when an embedded Qt3DWindow is destroyed while its render aspect is
|
||||||
|
// winding down. Detach this process-lifetime viewer so normal QWidget
|
||||||
|
// destruction cannot enter the faulty Qt3D teardown path; the OS reclaims
|
||||||
|
// it immediately when the process exits.
|
||||||
|
if (m_viewContainer)
|
||||||
|
m_viewContainer->removeEventFilter(this);
|
||||||
|
if (m_view)
|
||||||
|
m_view->removeEventFilter(this);
|
||||||
|
if (m_viewContainer) {
|
||||||
|
m_layout->removeWidget(m_viewContainer);
|
||||||
|
m_viewContainer->hide();
|
||||||
|
m_viewContainer->setParent(nullptr);
|
||||||
|
}
|
||||||
|
m_viewContainer = nullptr;
|
||||||
|
m_view = nullptr;
|
||||||
|
m_camera = nullptr;
|
||||||
|
m_teachingRayCaster = nullptr;
|
||||||
|
m_modelRoot = nullptr;
|
||||||
|
m_rootEntity = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UrdfViewerWidget::showEvent(QShowEvent *event)
|
void UrdfViewerWidget::showEvent(QShowEvent *event)
|
||||||
@ -624,8 +647,7 @@ bool UrdfViewerWidget::loadUrdf(const QString &filePath)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true, QStringLiteral("正在初始化 Qt3D 场景…"));
|
setLoading(true, QStringLiteral("正在解析 URDF 关节数据…"));
|
||||||
ensureSceneInitialized();
|
|
||||||
m_statusLabel->setText(QStringLiteral("正在解析 %1…").arg(fileInfo.fileName()));
|
m_statusLabel->setText(QStringLiteral("正在解析 %1…").arg(fileInfo.fileName()));
|
||||||
|
|
||||||
QFile file(fileInfo.absoluteFilePath());
|
QFile file(fileInfo.absoluteFilePath());
|
||||||
@ -647,6 +669,60 @@ bool UrdfViewerWidget::loadUrdf(const QString &filePath)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Qt 5.12.4/MinGW's Qt3D renderer crashes in its backend entity
|
||||||
|
// synchronization code while a URDF component tree is being published.
|
||||||
|
// Parse the complete URDF for control tables without creating a 3D scene.
|
||||||
|
clearModel();
|
||||||
|
m_currentFile = fileInfo.absoluteFilePath();
|
||||||
|
m_urdfDirectory = fileInfo.absolutePath();
|
||||||
|
m_pathEdit->setText(fileInfo.fileName());
|
||||||
|
m_pathEdit->setToolTip(QDir::toNativeSeparators(m_currentFile));
|
||||||
|
m_reloadButton->setEnabled(true);
|
||||||
|
|
||||||
|
std::function<void(const urdf::LinkConstSharedPtr &)> collectJoints;
|
||||||
|
collectJoints = [this, &collectJoints](const urdf::LinkConstSharedPtr &link) {
|
||||||
|
if (!link)
|
||||||
|
return;
|
||||||
|
for (const urdf::JointSharedPtr &joint : link->child_joints) {
|
||||||
|
if (!joint)
|
||||||
|
continue;
|
||||||
|
if (joint->type == urdf::Joint::REVOLUTE
|
||||||
|
|| joint->type == urdf::Joint::CONTINUOUS
|
||||||
|
|| joint->type == urdf::Joint::PRISMATIC) {
|
||||||
|
const QString jointName = QString::fromStdString(joint->name);
|
||||||
|
m_jointOrder.append(jointName);
|
||||||
|
m_jointPositions.insert(jointName,
|
||||||
|
m_latestTelemetryPositions.value(jointName, 0.0));
|
||||||
|
}
|
||||||
|
urdf::LinkConstSharedPtr childLink;
|
||||||
|
for (const urdf::LinkSharedPtr &candidate : link->child_links) {
|
||||||
|
if (candidate && candidate->name == joint->child_link_name) {
|
||||||
|
childLink = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collectJoints(childLink);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
collectJoints(model->getRoot());
|
||||||
|
|
||||||
|
const int linkCount = static_cast<int>(model->links_.size());
|
||||||
|
const int jointCount = static_cast<int>(model->joints_.size());
|
||||||
|
m_modelSummary = QStringLiteral("%1 个 Link · %2 个 Joint · 仅关节数据")
|
||||||
|
.arg(linkCount).arg(jointCount);
|
||||||
|
m_statusLabel->setText(m_modelSummary);
|
||||||
|
m_loadingTitle->setText(QStringLiteral("3D 模型预览暂时关闭"));
|
||||||
|
m_loadingDetail->setText(QStringLiteral("URDF 已解析,关节表和控制功能可正常使用"));
|
||||||
|
m_loadingProgress->setVisible(false);
|
||||||
|
m_viewportPlaceholder->show();
|
||||||
|
emit logMessage(QStringLiteral("OK"),
|
||||||
|
QStringLiteral("URDF 已加载(关节数据模式):%1(%2 Link,%3 Joint)")
|
||||||
|
.arg(fileInfo.fileName()).arg(linkCount).arg(jointCount));
|
||||||
|
emit modelLoaded(m_currentFile, linkCount, jointCount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
clearModel();
|
clearModel();
|
||||||
m_currentFile = fileInfo.absoluteFilePath();
|
m_currentFile = fileInfo.absoluteFilePath();
|
||||||
m_urdfDirectory = fileInfo.absolutePath();
|
m_urdfDirectory = fileInfo.absolutePath();
|
||||||
@ -740,20 +816,24 @@ bool UrdfViewerWidget::loadUrdf(const QString &filePath)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Qt3DRender::QSceneLoader *loader = new Qt3DRender::QSceneLoader(geometryEntity);
|
Qt3DRender::QSceneLoader *loader = new Qt3DRender::QSceneLoader(geometryEntity);
|
||||||
|
const QPointer<Qt3DRender::QSceneLoader> guardedLoader(loader);
|
||||||
|
const QPointer<Qt3DCore::QEntity> guardedGeometry(geometryEntity);
|
||||||
connect(loader, &Qt3DRender::QSceneLoader::statusChanged, this,
|
connect(loader, &Qt3DRender::QSceneLoader::statusChanged, this,
|
||||||
[this, loader, source, geometryEntity, teachingJointName]
|
[this, guardedLoader, source, guardedGeometry, teachingJointName]
|
||||||
(Qt3DRender::QSceneLoader::Status status) {
|
(Qt3DRender::QSceneLoader::Status status) {
|
||||||
|
if (!guardedLoader || !guardedGeometry)
|
||||||
|
return;
|
||||||
if (status == Qt3DRender::QSceneLoader::Error) {
|
if (status == Qt3DRender::QSceneLoader::Error) {
|
||||||
emit logMessage(QStringLiteral("ERROR"),
|
emit logMessage(QStringLiteral("ERROR"),
|
||||||
QStringLiteral("Qt3D 无法加载网格:%1").arg(source));
|
QStringLiteral("Qt3D 无法加载网格:%1").arg(source));
|
||||||
} else if (status == Qt3DRender::QSceneLoader::Ready
|
} else if (status == Qt3DRender::QSceneLoader::Ready
|
||||||
&& !teachingJointName.isEmpty()) {
|
&& !teachingJointName.isEmpty()) {
|
||||||
const QStringList entityNames = loader->entityNames();
|
const QStringList entityNames = guardedLoader->entityNames();
|
||||||
for (const QString &entityName : entityNames) {
|
for (const QString &entityName : entityNames) {
|
||||||
Qt3DCore::QEntity *loadedEntity = loader->entity(entityName);
|
Qt3DCore::QEntity *loadedEntity = guardedLoader->entity(entityName);
|
||||||
if (!loadedEntity)
|
if (!loadedEntity)
|
||||||
continue;
|
continue;
|
||||||
if (loader->component(
|
if (guardedLoader->component(
|
||||||
entityName,
|
entityName,
|
||||||
Qt3DRender::QSceneLoader::GeometryRendererComponent)) {
|
Qt3DRender::QSceneLoader::GeometryRendererComponent)) {
|
||||||
attachTeachingPicker(loadedEntity, teachingJointName);
|
attachTeachingPicker(loadedEntity, teachingJointName);
|
||||||
@ -761,7 +841,7 @@ bool UrdfViewerWidget::loadUrdf(const QString &filePath)
|
|||||||
attachTeachingPickersRecursively(loadedEntity,
|
attachTeachingPickersRecursively(loadedEntity,
|
||||||
teachingJointName);
|
teachingJointName);
|
||||||
}
|
}
|
||||||
attachTeachingPickersRecursively(geometryEntity,
|
attachTeachingPickersRecursively(guardedGeometry.data(),
|
||||||
teachingJointName);
|
teachingJointName);
|
||||||
}
|
}
|
||||||
if (m_view)
|
if (m_view)
|
||||||
@ -1181,18 +1261,16 @@ QString UrdfViewerWidget::resolveMeshPath(const QString &uri) const
|
|||||||
|
|
||||||
void UrdfViewerWidget::scheduleViewAll()
|
void UrdfViewerWidget::scheduleViewAll()
|
||||||
{
|
{
|
||||||
|
// QCamera::viewEntity() starts a backend bounding-volume query. In Qt
|
||||||
|
// 5.12 on Windows that query can race the first scene synchronization and
|
||||||
|
// crash inside Qt53DRender.dll. The viewer already has a deterministic
|
||||||
|
// camera in resetView(), so only request frames after the scene settles.
|
||||||
QTimer::singleShot(120, this, [this]() {
|
QTimer::singleShot(120, this, [this]() {
|
||||||
if (m_camera && m_modelRoot) {
|
if (m_view && m_modelRoot)
|
||||||
m_camera->viewEntity(m_modelRoot);
|
|
||||||
if (m_view)
|
|
||||||
m_view->requestUpdate();
|
m_view->requestUpdate();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
QTimer::singleShot(700, this, [this]() {
|
QTimer::singleShot(700, this, [this]() {
|
||||||
if (m_camera && m_modelRoot) {
|
if (m_view && m_modelRoot)
|
||||||
m_camera->viewEntity(m_modelRoot);
|
|
||||||
if (m_view)
|
|
||||||
m_view->requestUpdate();
|
m_view->requestUpdate();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
48
styles.qss
48
styles.qss
@ -337,9 +337,51 @@ QTabWidget#armMotionCommandTabs QTabBar::tab:selected {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
QSplitter#armTargetTables::handle {
|
QSplitter#armTargetTables::handle {
|
||||||
background: #dfe5ec;
|
background: transparent;
|
||||||
width: 1px;
|
width: 12px;
|
||||||
margin: 26px 5px 4px 5px;
|
}
|
||||||
|
QWidget#armTargetGroup {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
QTableWidget#leftMotionTable,
|
||||||
|
QTableWidget#motionTable {
|
||||||
|
background: #ffffff;
|
||||||
|
alternate-background-color: #f8fafc;
|
||||||
|
border: 1px solid #dce3ea;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #26323d;
|
||||||
|
}
|
||||||
|
QTableWidget#leftMotionTable QHeaderView::section,
|
||||||
|
QTableWidget#motionTable QHeaderView::section {
|
||||||
|
background: #eef3f7;
|
||||||
|
color: #526171;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid #d7e0e8;
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
QTableWidget#leftMotionTable::item,
|
||||||
|
QTableWidget#motionTable::item {
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-bottom: 1px solid #edf1f4;
|
||||||
|
}
|
||||||
|
QTableWidget#leftMotionTable QDoubleSpinBox,
|
||||||
|
QTableWidget#motionTable QDoubleSpinBox {
|
||||||
|
min-height: 24px;
|
||||||
|
padding: 1px 7px;
|
||||||
|
background: #f5f8fb;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #1d5fa7;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
QTableWidget#leftMotionTable QDoubleSpinBox:hover,
|
||||||
|
QTableWidget#motionTable QDoubleSpinBox:hover,
|
||||||
|
QTableWidget#leftMotionTable QDoubleSpinBox:focus,
|
||||||
|
QTableWidget#motionTable QDoubleSpinBox:focus {
|
||||||
|
background: #ffffff;
|
||||||
|
border-color: #6aa7ea;
|
||||||
}
|
}
|
||||||
|
|
||||||
QFrame#urdfToolbar, QFrame#urdfStatusBar {
|
QFrame#urdfToolbar, QFrame#urdfStatusBar {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user