{"id":"4QGQdn3SfJ","url":"https://pastebin.ca/4QGQdn3SfJ","raw_url":"https://raw.anybin.ca/4QGQdn3SfJ","visibility":"public","access":"public","created_at":1786959289440,"expires_at":null,"fetch_limit":null,"fetches_used":0,"reads_remaining":null,"size_bytes":49566,"syntax_hint":null,"title":null,"filename":"view_configGenerator.CPP","change_note":null,"cipher":null,"cipher_meta":null,"parent_id":null,"root_id":"4QGQdn3SfJ","version":1,"owner_id":"06G0Y46P106XKPNQDY7E0PJHTZ","recipient_id":null,"body":"#include \"view_configGenerator.h\"\n\n#include <QCheckBox>\n#include <QComboBox>\n#include <QFileDialog>\n#include <QFormLayout>\n#include <QGroupBox>\n#include <QHeaderView>\n#include <QLabel>\n#include <QLineEdit>\n#include <QMessageBox>\n#include <QPushButton>\n#include <QSpinBox>\n#include <QTableWidget>\n#include <QListWidget>\n#include <QTabWidget>\n#include <QJsonDocument>\n#include <QFile>\n\n#include \"e3d_configManager_equipment.h\"\n\nview_configGenerator::view_configGenerator(QWidget *parent)\n\t: QWidget(parent), currentData(new data_equipment)\n\t, ui(new Ui::e3d_configGenaraterClass())\n{\n\tui->setupUi(this);\n\n\tif (this->layout()) {\n\t\tdelete this->layout();\n\t}\n\tinitUI();\n}\n\nview_configGenerator::~view_configGenerator()\n{\n\tdelete currentData;\n\tdelete ui;\n}\n\nvoid view_configGenerator::initUI() {\n\tauto *mainLayout = new QVBoxLayout(this);\n\n\t// 1. 顶部文件操作栏\n\tauto *gb_file = new QGroupBox(tr(\"Config File Operations\"), this);\n\tauto *hl_file = new QHBoxLayout(gb_file);\n\tle_configPath = new QLineEdit(gb_file);\n\tpb_loadConfig = new QPushButton(tr(\"Load Config File\"), gb_file);\n\tpb_saveConfigToFile = new QPushButton(tr(\"Save Config to File\"), gb_file);\n\tpb_saveConfigToTemp = new QPushButton(tr(\"Save to Memory\"), gb_file);\n\n\thl_file->addWidget(le_configPath);\n\thl_file->addWidget(pb_loadConfig);\n\thl_file->addWidget(pb_saveConfigToFile);\n\thl_file->addWidget(pb_saveConfigToTemp);\n\tmainLayout->addWidget(gb_file);\n\n\t// 2. Tab 页面容器\n\tauto *tabWidget = new QTabWidget(this);\n\ttabWidget->addTab(createMotionControllerTab(), tr(\"Motion Controllers & I/O / Axes\"));\n\ttabWidget->addTab(createCommsTab(), tr(\"Comms Config\"));\n\ttabWidget->addTab(createHeightSensorTab(), tr(\"Height Sensor Config\"));\n\ttabWidget->addTab(createDispenserTab(), tr(\"Dispenser Config\"));\n\ttabWidget->addTab(createExternalDeviceTab(), tr(\"Global External Devices\"));\n\ttabWidget->addTab(createToolheadTab(), tr(\"Toolhead Config\"));\n\ttabWidget->addTab(createCommonTab(), tr(\"Common Config\"));\n\n\tmainLayout->addWidget(tabWidget);\n\n\t// 3. 信号槽绑定\n\tconnect(pb_loadConfig, &QPushButton::clicked, this, &view_configGenerator::onLoadConfigFromFile);\n\tconnect(pb_saveConfigToFile, &QPushButton::clicked, this, &view_configGenerator::onSaveConfigToFile);\n\tconnect(pb_saveConfigToTemp, &QPushButton::clicked, this, &view_configGenerator::onSaveTempClicked);\n\n\t// 初始化右侧面板状态（为空时不可编辑）\n\tupdateControllerRightPanel();\n\tupdateToolheadRightPanel();\n}\n\nvoid view_configGenerator::updateTableRows(QTableWidget *table, int targetCount) {\n\tint current = table->rowCount();\n\tif (targetCount > current) {\n\t\tfor (int i = current; i < targetCount; ++i) {\n\t\t\ttable->insertRow(i);\n\t\t}\n\t}\n\telse if (targetCount < current) {\n\t\tfor (int i = current - 1; i >= targetCount; --i) {\n\t\t\ttable->removeRow(i);\n\t\t}\n\t}\n}\n\n// ============================================================================\n// 1. 控制卡 Master-Detail 页面构建\n// ============================================================================\nQWidget* view_configGenerator::createMotionControllerTab() {\n\tauto *widget = new QWidget();\n\tauto *mainLayout = new QHBoxLayout(widget);\n\n\tauto *leftGroup = new QGroupBox(tr(\"Controller List\"), widget);\n\tauto *leftLayout = new QVBoxLayout(leftGroup);\n\n\tm_cardListWidget = new QListWidget(leftGroup);\n\n\tauto *btnLayout = new QHBoxLayout();\n\tauto *btnAddCard = new QPushButton(tr(\"Add Card\"), leftGroup);\n\tauto *btnDelCard = new QPushButton(tr(\"Delete Card\"), leftGroup);\n\tbtnLayout->addWidget(btnAddCard);\n\tbtnLayout->addWidget(btnDelCard);\n\n\tleftLayout->addWidget(m_cardListWidget);\n\tleftLayout->addLayout(btnLayout);\n\n\t// 使用成员变量管理右侧面板 GroupBox\n\tm_controllerRightGroup = new QGroupBox(tr(\"Controller Properties & Sub-Devices\"), widget);\n\tauto *rightLayout = new QVBoxLayout(m_controllerRightGroup);\n\n\tauto *formLayout = new QFormLayout();\n\tm_editCardName = new QLineEdit();\n\tm_comboBrand = new QComboBox();\n\tm_comboBrand->addItem(\"ZMOTION\",\"ZMOTION\");\n\tm_comboBrand->addItem(\"ACS\",\"ACS\");\n\n\tm_editIP = new QLineEdit(\"127.0.0.1\");\n\tm_spinPort = new QSpinBox();\n\tm_spinPort->setRange(1, 65535);\n\tm_spinPort->setValue(8080);\n\n\tm_editAllAxisHomePath = new QLineEdit(\"AllAxisHomePath\");\n\tm_showSub = new QCheckBox();\n\n\n\n\tformLayout->addRow(tr(\"Controller Name (userName):\"), m_editCardName);\n\tformLayout->addRow(tr(\"Brand:\"), m_comboBrand);\n\tformLayout->addRow(tr(\"IP Address:\"), m_editIP);\n\tformLayout->addRow(tr(\"Port:\"), m_spinPort);\n\tformLayout->addRow(tr(\"AllAxisHomePath:\"), m_editAllAxisHomePath);\n\tformLayout->addRow(tr(\"showSub:\"), m_showSub);\n\trightLayout->addLayout(formLayout);\n\n\tm_subConfigTab = new QTabWidget(m_controllerRightGroup);\n\n\tauto *axisTabWidget = new QWidget();\n\tauto *axisLayout = new QVBoxLayout(axisTabWidget);\n\tm_axisTable = new QTableWidget(0, 9);\n\tm_axisTable->setSelectionBehavior(QAbstractItemView::SelectRows);\n\tm_axisTable->setSelectionMode(QAbstractItemView::SingleSelection);\n\tm_axisTable->setHorizontalHeaderLabels({\n\t\ttr(\"Axis Name (userName)\"), tr(\"Axis No\"), tr(\"Left Limit\"), tr(\"Right Limit\"),\n\t\ttr(\"Acc\"), tr(\"Dec\"), tr(\"Fast Dec\"), tr(\"Enable Left Limit\"), tr(\"Enable Right Limit\")\n\t\t});\n\tm_axisTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);\n\n\tauto *axisBtnLayout = new QHBoxLayout();\n\tauto *btnAddAxis = new QPushButton(tr(\"Add Axis\"));\n\tauto *btnDelAxis = new QPushButton(tr(\"Delete Axis\"));\n\taxisBtnLayout->addWidget(btnAddAxis);\n\taxisBtnLayout->addWidget(btnDelAxis);\n\taxisBtnLayout->addStretch();\n\taxisLayout->addLayout(axisBtnLayout);\n\taxisLayout->addWidget(m_axisTable);\n\tm_subConfigTab->addTab(axisTabWidget, tr(\"Axes (AxisInfo)\"));\n\n\tauto createIOTab = [this](QTableWidget*& table, const QString& title, bool isDigital) {\n\t\tauto *w = new QWidget();\n\t\tauto *ly = new QVBoxLayout(w);\n\t\ttable = new QTableWidget(0, isDigital ? 3 : 2);\n\t\ttable->setSelectionBehavior(QAbstractItemView::SelectRows);\n\t\ttable->setSelectionMode(QAbstractItemView::SingleSelection);\n\n\t\tif (isDigital) {\n\t\t\ttable->setHorizontalHeaderLabels({ tr(\"IO Name (userName)\"), tr(\"Main IO (Port)\"), tr(\"Sub IO (Pin)\") });\n\t\t}\n\t\telse {\n\t\t\ttable->setHorizontalHeaderLabels({ tr(\"IO Name (userName)\"), tr(\"Address\") });\n\t\t}\n\t\ttable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);\n\n\t\tauto *bLy = new QHBoxLayout();\n\t\tauto *bAdd = new QPushButton(tr(\"Add IO\"));\n\t\tauto *bDel = new QPushButton(tr(\"Delete IO\"));\n\t\tbLy->addWidget(bAdd); bLy->addWidget(bDel); bLy->addStretch();\n\t\tly->addLayout(bLy); ly->addWidget(table);\n\n\t\tconnect(bAdd, &QPushButton::clicked, this, [this, table]() { onAddIO(table); });\n\t\tconnect(bDel, &QPushButton::clicked, this, [this, table]() { onDeleteIO(table); });\n\n\t\tm_subConfigTab->addTab(w, title);\n\t};\n\n\tcreateIOTab(m_dinTable, tr(\"Digital Input (DIN)\"), true);\n\tcreateIOTab(m_doutTable, tr(\"Digital Output (DOUT)\"), true);\n\tcreateIOTab(m_ainTable, tr(\"Analog Input (AIN)\"), false);\n\tcreateIOTab(m_aoutTable, tr(\"Analog Output (AOUT)\"), false);\n\n\trightLayout->addWidget(m_subConfigTab);\n\n\tmainLayout->addWidget(leftGroup, 1);\n\tmainLayout->addWidget(m_controllerRightGroup, 3);\n\n\tconnect(btnAddCard, &QPushButton::clicked, this, &view_configGenerator::onAddController);\n\tconnect(btnDelCard, &QPushButton::clicked, this, &view_configGenerator::onDeleteController);\n\tconnect(m_cardListWidget, &QListWidget::currentRowChanged, this, &view_configGenerator::onControllerSelected);\n\n\tconnect(btnAddAxis, &QPushButton::clicked, this, &view_configGenerator::onAddAxis);\n\tconnect(btnDelAxis, &QPushButton::clicked, this, &view_configGenerator::onDeleteAxis);\n\n\treturn widget;\n}\n\n// ============================================================================\n// 2. 通讯模块页构建 (Comms Config)\n// ============================================================================\nQWidget* view_configGenerator::createCommsTab() {\n\tauto *widget = new QWidget();\n\tauto *layout = new QVBoxLayout(widget);\n\n\tauto *hl_comms = new QHBoxLayout();\n\tauto *btnAdd = new QPushButton(tr(\"Add Comms Module\"), widget);\n\tauto *btnDel = new QPushButton(tr(\"Delete Selected\"), widget);\n\thl_comms->addWidget(btnAdd);\n\thl_comms->addWidget(btnDel);\n\n\thl_comms->addSpacing(20);\n\thl_comms->addWidget(new QLabel(tr(\"Comms Module Count:\")));\n\tsb_count_Comms = new QSpinBox();\n\tsb_count_Comms->setRange(0, 50);\n\thl_comms->addWidget(sb_count_Comms);\n\thl_comms->addStretch();\n\tlayout->addLayout(hl_comms);\n\n\ttw_Comms = new QTableWidget(0, 12);\n\ttw_Comms->setSelectionBehavior(QAbstractItemView::SelectRows);\n\ttw_Comms->setSelectionMode(QAbstractItemView::SingleSelection);\n\ttw_Comms->setHorizontalHeaderLabels({\n\t\ttr(\"Comms Name\"), tr(\"Type\"), tr(\"Port Name\"), tr(\"Baud Rate\"),\n\t\ttr(\"Parity\"), tr(\"Data Bits\"), tr(\"Stop Bits\"), tr(\"IP Address\"),\n\t\ttr(\"TCP Port\"), tr(\"Slave Addr\"), tr(\"Timeout(ms)\"), tr(\"Retry Times\")\n\t\t});\n\ttw_Comms->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);\n\tlayout->addWidget(tw_Comms);\n\n\tauto addCommsRow = [this](int r) {\n\t\ttw_Comms->setItem(r, 0, new QTableWidgetItem(QString(\"Comms_%1\").arg(r + 1)));\n\n\t\tauto *cbType = new QComboBox();\n\t\tcbType->addItems(toStringList<data_CommsConfig::CommsType>());\n\t\ttw_Comms->setCellWidget(r, 1, cbType);\n\n\t\ttw_Comms->setItem(r, 2, new QTableWidgetItem(\"COM1\"));\n\t\ttw_Comms->setItem(r, 3, new QTableWidgetItem(\"115200\"));\n\t\ttw_Comms->setItem(r, 4, new QTableWidgetItem(\"N\"));\n\t\ttw_Comms->setItem(r, 5, new QTableWidgetItem(\"8\"));\n\t\ttw_Comms->setItem(r, 6, new QTableWidgetItem(\"1\"));\n\n\t\ttw_Comms->setItem(r, 7, new QTableWidgetItem(\"192.168.1.100\"));\n\t\ttw_Comms->setItem(r, 8, new QTableWidgetItem(\"8080\"));\n\n\t\ttw_Comms->setItem(r, 9, new QTableWidgetItem(\"1\"));\n\t\ttw_Comms->setItem(r, 10, new QTableWidgetItem(\"1000\"));\n\t\ttw_Comms->setItem(r, 11, new QTableWidgetItem(\"2\"));\n\t};\n\n\tconnect(btnAdd, &QPushButton::clicked, [this, addCommsRow]() {\n\t\tint r = tw_Comms->rowCount();\n\t\ttw_Comms->insertRow(r);\n\t\taddCommsRow(r);\n\t\tsb_count_Comms->blockSignals(true);\n\t\tsb_count_Comms->setValue(tw_Comms->rowCount());\n\t\tsb_count_Comms->blockSignals(false);\n\t});\n\n\tconnect(btnDel, &QPushButton::clicked, [this]() {\n\t\tint r = tw_Comms->currentRow();\n\t\tif (r >= 0) {\n\t\t\ttw_Comms->removeRow(r);\n\t\t\tsb_count_Comms->blockSignals(true);\n\t\t\tsb_count_Comms->setValue(tw_Comms->rowCount());\n\t\t\tsb_count_Comms->blockSignals(false);\n\t\t}\n\t});\n\n\tconnect(sb_count_Comms, QOverload<int>::of(&QSpinBox::valueChanged), [this, addCommsRow](int val) {\n\t\tint oldRow = tw_Comms->rowCount();\n\t\tupdateTableRows(tw_Comms, val);\n\t\tfor (int i = oldRow; i < val; ++i) {\n\t\t\taddCommsRow(i);\n\t\t}\n\t});\n\n\treturn widget;\n}\n\n// ============================================================================\n// 3. 测距传感器配置页构建 (Height Sensor Config) - 新增 m_commsChannelName 列\n// ============================================================================\nQWidget* view_configGenerator::createHeightSensorTab() {\n\tauto *widget = new QWidget();\n\tauto *layout = new QVBoxLayout(widget);\n\n\tauto *hl = new QHBoxLayout();\n\tauto *btnAdd = new QPushButton(tr(\"Add Height Sensor\"), widget);\n\tauto *btnDel = new QPushButton(tr(\"Delete Selected\"), widget);\n\thl->addWidget(btnAdd);\n\thl->addWidget(btnDel);\n\n\thl->addSpacing(20);\n\thl->addWidget(new QLabel(tr(\"Height Sensor Count:\")));\n\tsb_count_HeightSensor = new QSpinBox();\n\tsb_count_HeightSensor->setRange(0, 20);\n\thl->addWidget(sb_count_HeightSensor);\n\thl->addStretch();\n\tlayout->addLayout(hl);\n\n\ttw_HeightSensor = new QTableWidget(0, 9);\n\ttw_HeightSensor->setSelectionBehavior(QAbstractItemView::SelectRows);\n\ttw_HeightSensor->setSelectionMode(QAbstractItemView::SingleSelection);\n\ttw_HeightSensor->setHorizontalHeaderLabels({\n\t\ttr(\"Sensor Name\"),\n\t\ttr(\"Comms Channel Name\"),\n\t\ttr(\"Comm Type\"),\n\t\ttr(\"MSR Addr (Hex/Dec)\"),\n\t\ttr(\"Status Addr (Hex/Dec)\"),\n\t\ttr(\"Same Dir with Axis\"),\n\t\ttr(\"Min Range\"),\n\t\ttr(\"Max Range\"),\n\t\ttr(\"Offset\")\n\t\t});\n\ttw_HeightSensor->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);\n\tlayout->addWidget(tw_HeightSensor);\n\n\tauto addSensorRow = [this](int r) {\n\t\ttw_HeightSensor->setItem(r, 0, new QTableWidgetItem(QString(\"HeightSensor_%1\").arg(r + 1)));\n\t\ttw_HeightSensor->setItem(r, 1, new QTableWidgetItem(\"COM_Channel_1\"));\n\n\t\tauto *cbComm = new QComboBox();\n\t\tcbComm->addItems(toStringList<data_HeightSensorConfig::HeightSensorType>());\n\t\ttw_HeightSensor->setCellWidget(r, 2, cbComm);\n\n\t\ttw_HeightSensor->setItem(r, 3, new QTableWidgetItem(\"0x6000\"));\n\t\ttw_HeightSensor->setItem(r, 4, new QTableWidgetItem(\"0x6001\"));\n\n\t\tauto *chk = new QCheckBox();\n\t\tauto *pContainer = new QWidget();\n\t\tauto *pLayout = new QHBoxLayout(pContainer);\n\t\tpLayout->addWidget(chk);\n\t\tpLayout->setAlignment(Qt::AlignCenter);\n\t\tpLayout->setContentsMargins(0, 0, 0, 0);\n\t\ttw_HeightSensor->setCellWidget(r, 5, pContainer);\n\n\t\ttw_HeightSensor->setItem(r, 6, new QTableWidgetItem(\"0.0\"));\n\t\ttw_HeightSensor->setItem(r, 7, new QTableWidgetItem(\"100.0\"));\n\t\ttw_HeightSensor->setItem(r, 8, new QTableWidgetItem(\"0.0\"));\n\t};\n\n\tconnect(btnAdd, &QPushButton::clicked, [this, addSensorRow]() {\n\t\tint r = tw_HeightSensor->rowCount();\n\t\ttw_HeightSensor->insertRow(r);\n\t\taddSensorRow(r);\n\t\tsb_count_HeightSensor->blockSignals(true);\n\t\tsb_count_HeightSensor->setValue(tw_HeightSensor->rowCount());\n\t\tsb_count_HeightSensor->blockSignals(false);\n\t});\n\n\tconnect(btnDel, &QPushButton::clicked, [this]() {\n\t\tint r = tw_HeightSensor->currentRow();\n\t\tif (r >= 0) {\n\t\t\ttw_HeightSensor->removeRow(r);\n\t\t\tsb_count_HeightSensor->blockSignals(true);\n\t\t\tsb_count_HeightSensor->setValue(tw_HeightSensor->rowCount());\n\t\t\tsb_count_HeightSensor->blockSignals(false);\n\t\t}\n\t});\n\n\tconnect(sb_count_HeightSensor, QOverload<int>::of(&QSpinBox::valueChanged), [this, addSensorRow](int val) {\n\t\tint oldRow = tw_HeightSensor->rowCount();\n\t\tupdateTableRows(tw_HeightSensor, val);\n\t\tfor (int i = oldRow; i < val; ++i) {\n\t\t\taddSensorRow(i);\n\t\t}\n\t});\n\n\treturn widget;\n}\n\n// ============================================================================\n// 4. 点胶机配置页构建 (Dispenser Config) - 新增 m_commsChannelName 列\n// ============================================================================\nQWidget* view_configGenerator::createDispenserTab() {\n\tauto *widget = new QWidget();\n\tauto *layout = new QVBoxLayout(widget);\n\n\tauto *hl = new QHBoxLayout();\n\tauto *btnAdd = new QPushButton(tr(\"Add Dispenser\"), widget);\n\tauto *btnDel = new QPushButton(tr(\"Delete Selected\"), widget);\n\thl->addWidget(btnAdd);\n\thl->addWidget(btnDel);\n\n\thl->addSpacing(20);\n\thl->addWidget(new QLabel(tr(\"Dispenser Count:\")));\n\tsb_count_Dispenser = new QSpinBox();\n\tsb_count_Dispenser->setRange(0, 20);\n\thl->addWidget(sb_count_Dispenser);\n\thl->addStretch();\n\tlayout->addLayout(hl);\n\n\ttw_Dispenser = new QTableWidget(0, 17);\n\ttw_Dispenser->setSelectionBehavior(QAbstractItemView::SelectRows);\n\ttw_Dispenser->setSelectionMode(QAbstractItemView::SingleSelection);\n\ttw_Dispenser->setHorizontalHeaderLabels({\n\t\ttr(\"Dispenser Name\"),\n\t\ttr(\"Comms Channel/Controller Name\"), tr(\"CommType\"), tr(\"Port\"), tr(\"BaudRate\"), tr(\"Parity\"), tr(\"DataBits\"), tr(\"StopBits\"),\n\t\ttr(\"Analog Addr\"), tr(\"Min Pressure\"), tr(\"Max Pressure\"),\n\t\ttr(\"Default Pressure\"), tr(\"Default Time(s)\"), tr(\"Switch IO\"), tr(\"Switch State\"),\n\t\ttr(\"MainIO OpenGas\"), tr(\"SubIO OpenGas\")\n\t\t});\n\ttw_Dispenser->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);\n\tlayout->addWidget(tw_Dispenser);\n\n\tauto addDispenserRow = [this](int r) {\n\n\t\tint i = 0;\n\t\ttw_Dispenser->setItem(r, i++, new QTableWidgetItem(\"Dispenser1\"));\n\t\ttw_Dispenser->setItem(r, i++, new QTableWidgetItem(\"ZMOTION_1\"));\n\n\t\tauto *cbComm = new QComboBox();\n\t\tcbComm->addItems(toStringList<data_DispenserConfig::DispenserType>());\n\t\ttw_Dispenser->setCellWidget(r, i++, cbComm);\n\n\t\ttw_Dispenser->setItem(r, i++, new QTableWidgetItem(\"COM1\"));\n\t\ttw_Dispenser->setItem(r, i++, new QTableWidgetItem(\"9600\"));\n\t\ttw_Dispenser->setItem(r, i++, new QTableWidgetItem(\"N\"));\n\t\ttw_Dispenser->setItem(r, i++, new QTableWidgetItem(\"8\"));\n\t\ttw_Dispenser->setItem(r, i++, new QTableWidgetItem(\"1\"));\n\n\t\ttw_Dispenser->setItem(r, i++, new QTableWidgetItem(\"0\"));\n\t\ttw_Dispenser->setItem(r, i++, new QTableWidgetItem(\"0.0\"));\n\t\ttw_Dispenser->setItem(r, i++, new QTableWidgetItem(\"100.0\"));\n\n\t\ttw_Dispenser->setItem(r, i++, new QTableWidgetItem(\"0.2\"));\n\t\ttw_Dispenser->setItem(r, i++, new QTableWidgetItem(\"0.5\"));\n\t\ttw_Dispenser->setItem(r, i++, new QTableWidgetItem(\"0\"));\n\n\t\tauto *chk = new QCheckBox();\n\t\tauto *pContainer = new QWidget();\n\t\tauto *pLayout = new QHBoxLayout(pContainer);\n\t\tpLayout->addWidget(chk);\n\t\tpLayout->setAlignment(Qt::AlignCenter);\n\t\tpLayout->setContentsMargins(0, 0, 0, 0);\n\t\ttw_Dispenser->setCellWidget(r, i++, pContainer);\n\n\t\ttw_Dispenser->setItem(r,i++, new QTableWidgetItem(\"0\"));\n\t\ttw_Dispenser->setItem(r,i++, new QTableWidgetItem(\"0\"));\n\t};\n\n\tconnect(btnAdd, &QPushButton::clicked, [this, addDispenserRow]() {\n\t\tint r = tw_Dispenser->rowCount();\n\t\ttw_Dispenser->insertRow(r);\n\t\taddDispenserRow(r);\n\t\tsb_count_Dispenser->blockSignals(true);\n\t\tsb_count_Dispenser->setValue(tw_Dispenser->rowCount());\n\t\tsb_count_Dispenser->blockSignals(false);\n\t});\n\n\tconnect(btnDel, &QPushButton::clicked, [this]() {\n\t\tint r = tw_Dispenser->currentRow();\n\t\tif (r >= 0) {\n\t\t\ttw_Dispenser->removeRow(r);\n\t\t\tsb_count_Dispenser->blockSignals(true);\n\t\t\tsb_count_Dispenser->setValue(tw_Dispenser->rowCount());\n\t\t\tsb_count_Dispenser->blockSignals(false);\n\t\t}\n\t});\n\n\tconnect(sb_count_Dispenser, QOverload<int>::of(&QSpinBox::valueChanged), [this, addDispenserRow](int val) {\n\t\tint oldRow = tw_Dispenser->rowCount();\n\t\tupdateTableRows(tw_Dispenser, val);\n\t\tfor (int i = oldRow; i < val; ++i) {\n\t\t\taddDispenserRow(i);\n\t\t}\n\t});\n\n\treturn widget;\n}\n\n// ============================================================================\n// 5. 全局外设配置页构建 (External Device Config) - 追加 m_commsChannelName 列\n// ============================================================================\nQWidget* view_configGenerator::createExternalDeviceTab() {\n\tauto *widget = new QWidget();\n\tauto *layout = new QVBoxLayout(widget);\n\n\tauto *hl = new QHBoxLayout();\n\tauto *btnAdd = new QPushButton(tr(\"Add External Device\"), widget);\n\tauto *btnDel = new QPushButton(tr(\"Delete Selected\"), widget);\n\thl->addWidget(btnAdd);\n\thl->addWidget(btnDel);\n\n\thl->addSpacing(20);\n\thl->addWidget(new QLabel(tr(\"External Device Count:\")));\n\tsb_count_ExternalDevice = new QSpinBox();\n\tsb_count_ExternalDevice->setRange(0, 50);\n\thl->addWidget(sb_count_ExternalDevice);\n\thl->addStretch();\n\tlayout->addLayout(hl);\n\n\ttw_ExternalDevice = new QTableWidget(0, 6);\n\ttw_ExternalDevice->setSelectionBehavior(QAbstractItemView::SelectRows);\n\ttw_ExternalDevice->setSelectionMode(QAbstractItemView::SingleSelection);\n\ttw_ExternalDevice->setHorizontalHeaderLabels({\n\t\ttr(\"Device Name (userName)\"),\n\t\ttr(\"Device Type\"),\n\t\ttr(\"Comms Channel Name\"),\n\t\ttr(\"Pos X\"),\n\t\ttr(\"Pos Y\"),\n\t\ttr(\"Pos Z\")\n\t\t});\n\ttw_ExternalDevice->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);\n\tlayout->addWidget(tw_ExternalDevice);\n\n\tauto addExtDevRow = [this](int r) {\n\t\ttw_ExternalDevice->setItem(r, 0, new QTableWidgetItem(QString(\"ExtDevice_%1\").arg(r + 1)));\n\t\ttw_ExternalDevice->setItem(r, 1, new QTableWidgetItem(\"HeightSensor\"));\n\t\ttw_ExternalDevice->setItem(r, 2, new QTableWidgetItem(\"Comms_Channel_1\"));\n\t\ttw_ExternalDevice->setItem(r, 3, new QTableWidgetItem(\"0.0\"));\n\t\ttw_ExternalDevice->setItem(r, 4, new QTableWidgetItem(\"0.0\"));\n\t\ttw_ExternalDevice->setItem(r, 5, new QTableWidgetItem(\"0.0\"));\n\t};\n\n\tconnect(btnAdd, &QPushButton::clicked, [this, addExtDevRow]() {\n\t\tint r = tw_ExternalDevice->rowCount();\n\t\ttw_ExternalDevice->insertRow(r);\n\t\taddExtDevRow(r);\n\t\tsb_count_ExternalDevice->blockSignals(true);\n\t\tsb_count_ExternalDevice->setValue(tw_ExternalDevice->rowCount());\n\t\tsb_count_ExternalDevice->blockSignals(false);\n\t});\n\n\tconnect(btnDel, &QPushButton::clicked, [this]() {\n\t\tint r = tw_ExternalDevice->currentRow();\n\t\tif (r >= 0) {\n\t\t\ttw_ExternalDevice->removeRow(r);\n\t\t\tsb_count_ExternalDevice->blockSignals(true);\n\t\t\tsb_count_ExternalDevice->setValue(tw_ExternalDevice->rowCount());\n\t\t\tsb_count_ExternalDevice->blockSignals(false);\n\t\t}\n\t});\n\n\tconnect(sb_count_ExternalDevice, QOverload<int>::of(&QSpinBox::valueChanged), [this, addExtDevRow](int val) {\n\t\tint oldRow = tw_ExternalDevice->rowCount();\n\t\tupdateTableRows(tw_ExternalDevice, val);\n\t\tfor (int i = oldRow; i < val; ++i) {\n\t\t\taddExtDevRow(i);\n\t\t}\n\t});\n\n\treturn widget;\n}\n\n// ============================================================================\n// 6. 工作头配置页构建 (Toolhead Config)\n// ============================================================================\nQWidget* view_configGenerator::createToolheadTab() {\n\tauto *widget = new QWidget();\n\tauto *mainLayout = new QHBoxLayout(widget);\n\n\tauto *leftGroup = new QGroupBox(tr(\"Toolhead List\"), widget);\n\tauto *leftLayout = new QVBoxLayout(leftGroup);\n\n\tm_toolheadListWidget = new QListWidget(leftGroup);\n\n\tauto *btnLayout = new QHBoxLayout();\n\tauto *btnAddToolhead = new QPushButton(tr(\"Add Toolhead\"), leftGroup);\n\tauto *btnDelToolhead = new QPushButton(tr(\"Delete Toolhead\"), leftGroup);\n\tbtnLayout->addWidget(btnAddToolhead);\n\tbtnLayout->addWidget(btnDelToolhead);\n\n\tleftLayout->addWidget(m_toolheadListWidget);\n\tleftLayout->addLayout(btnLayout);\n\n\tm_toolheadRightGroup = new QGroupBox(tr(\"Toolhead Properties & Mounted Devices\"), widget);\n\tauto *rightLayout = new QVBoxLayout(m_toolheadRightGroup);\n\n\tauto *formLayout = new QFormLayout();\n\tm_editToolheadName = new QLineEdit();\n\tm_chkIsVirtualToolhead = new QCheckBox(tr(\"Is Virtual Toolhead (Z-axis does not move)\"));\n\tm_editBindMotionGroup = new QLineEdit(\"XY_Stage_1\");\n\tm_editBindZAxis = new QLineEdit(\"Z_Axis_1\");\n\n\tformLayout->addRow(tr(\"Toolhead Name (userName):\"), m_editToolheadName);\n\tformLayout->addRow(tr(\"Virtual Flag:\"), m_chkIsVirtualToolhead);\n\tformLayout->addRow(tr(\"Bind Motion Group (XY Stage):\"), m_editBindMotionGroup);\n\tformLayout->addRow(tr(\"Bind Z Axis Name:\"), m_editBindZAxis);\n\trightLayout->addLayout(formLayout);\n\n\tauto *gbMountedDevs = new QGroupBox(tr(\"Mounted Devices List (mountedDevices)\"), m_toolheadRightGroup);\n\tauto *mountedLayout = new QVBoxLayout(gbMountedDevs);\n\n\tm_mountedDevicesTable = new QTableWidget(0, 3);\n\tm_mountedDevicesTable->setSelectionBehavior(QAbstractItemView::SelectRows);\n\tm_mountedDevicesTable->setSelectionMode(QAbstractItemView::SingleSelection);\n\tm_mountedDevicesTable->setHorizontalHeaderLabels({\n\t\ttr(\"Device Name (userName)\"),\n\t\ttr(\"Device Type\"),\n\t\ttr(\"Comms Channel Name\")\n\t\t});\n\tm_mountedDevicesTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);\n\n\tauto *mDevBtnLayout = new QHBoxLayout();\n\tauto *btnAddMDev = new QPushButton(tr(\"Add Mounted Device\"));\n\tauto *btnDelMDev = new QPushButton(tr(\"Delete Mounted Device\"));\n\tmDevBtnLayout->addWidget(btnAddMDev);\n\tmDevBtnLayout->addWidget(btnDelMDev);\n\tmDevBtnLayout->addStretch();\n\n\tmountedLayout->addLayout(mDevBtnLayout);\n\tmountedLayout->addWidget(m_mountedDevicesTable);\n\n\trightLayout->addWidget(gbMountedDevs);\n\n\tmainLayout->addWidget(leftGroup, 1);\n\tmainLayout->addWidget(m_toolheadRightGroup, 3);\n\n\tconnect(btnAddToolhead, &QPushButton::clicked, this, &view_configGenerator::onAddToolhead);\n\tconnect(btnDelToolhead, &QPushButton::clicked, this, &view_configGenerator::onDeleteToolhead);\n\tconnect(m_toolheadListWidget, &QListWidget::currentRowChanged, this, &view_configGenerator::onToolheadSelected);\n\n\tconnect(btnAddMDev, &QPushButton::clicked, this, &view_configGenerator::onAddMountedDevice);\n\tconnect(btnDelMDev, &QPushButton::clicked, this, &view_configGenerator::onDeleteMountedDevice);\n\n\treturn widget;\n}\n\n// ============================================================================\n// 7. 通用配置页构建 (Common Config)\n// ============================================================================\nQWidget* view_configGenerator::createCommonTab() {\n\tauto *widget = new QWidget();\n\tauto *layout = new QFormLayout(widget);\n\tle_softwareName = new QLineEdit(\"ConfigGeneratorApp\", widget);\n\tlayout->addRow(tr(\"Software Name:\"), le_softwareName);\n\treturn widget;\n}\n\n// ============================================================================\n// 控制卡 UI 交互槽实现\n// ============================================================================\n\nvoid view_configGenerator::onAddController() {\n\tsaveCurrentControllerFromUI();\n\n\tdata_motion_controller newCard;\n\tnewCard.m_userName = QString(\"Card_%1\").arg(m_controllerList.size() + 1);\n\tm_controllerList.append(newCard);\n\n\tm_cardListWidget->addItem(newCard.m_userName);\n\tm_cardListWidget->setCurrentRow(m_controllerList.size() - 1);\n}\n\nvoid view_configGenerator::onDeleteController() {\n\tint row = m_cardListWidget->currentRow();\n\tif (row < 0 || row >= m_controllerList.size()) return;\n\n\tm_controllerList.removeAt(row);\n\tdelete m_cardListWidget->takeItem(row);\n\n\tm_currentCardIndex = -1;\n\tif (!m_controllerList.isEmpty()) {\n\t\tint nextRow = qMin(row, m_controllerList.size() - 1);\n\t\tm_cardListWidget->setCurrentRow(nextRow);\n\t}\n\telse {\n\t\tupdateControllerRightPanel();\n\t}\n}\n\nvoid view_configGenerator::onControllerSelected(int currentRow) {\n\tif (currentRow == m_currentCardIndex) return;\n\n\tsaveCurrentControllerFromUI();\n\n\tm_currentCardIndex = currentRow;\n\tupdateControllerRightPanel();\n}\n\nvoid view_configGenerator::saveCurrentControllerFromUI() {\n\tif (m_currentCardIndex < 0 || m_currentCardIndex >= m_controllerList.size()) return;\n\n\tdata_motion_controller &card = m_controllerList[m_currentCardIndex];\n\tcard.m_userName = m_editCardName->text().trimmed();\n\tcard.motionControllerType = toEnum<data_motion_controller::MotionControllerType>(m_comboBrand->currentData().toString());\n\tcard.ipAddress = m_editIP->text().trimmed();\n\tcard.port = m_spinPort->value();\n\n\tcard.homePath_total = m_editAllAxisHomePath->text().trimmed();\n\tcard.b_showSub = m_showSub->isChecked();\n\n\tif (auto *item = m_cardListWidget->item(m_currentCardIndex)) {\n\t\titem->setText(card.m_userName);\n\t}\n\n\tcard.data_axisInfo.clear();\n\tfor (int r = 0; r < m_axisTable->rowCount(); ++r) {\n\t\tdata_AxisInfo axis;\n\t\taxis.m_userName = m_axisTable->item(r, 0) ? m_axisTable->item(r, 0)->text() : \"\";\n\t\taxis.axisNo = m_axisTable->item(r, 1) ? m_axisTable->item(r, 1)->text().toInt() : 0;\n\t\taxis.soft_leftLimit = m_axisTable->item(r, 2) ? m_axisTable->item(r, 2)->text().toDouble() : 0;\n\t\taxis.soft_rightLimit = m_axisTable->item(r, 3) ? m_axisTable->item(r, 3)->text().toDouble() : 0;\n\t\taxis.acc = m_axisTable->item(r, 4) ? m_axisTable->item(r, 4)->text().toDouble() : 500;\n\t\taxis.dec = m_axisTable->item(r, 5) ? m_axisTable->item(r, 5)->text().toDouble() : 500;\n\t\taxis.fastDec = m_axisTable->item(r, 6) ? m_axisTable->item(r, 6)->text().toDouble() : 500;\n\n\t\tif (auto *chkL = qobject_cast<QCheckBox*>(m_axisTable->cellWidget(r, 7)))\n\t\t\taxis.enable_leftSoftWareLimit = chkL->isChecked();\n\t\tif (auto *chkR = qobject_cast<QCheckBox*>(m_axisTable->cellWidget(r, 8)))\n\t\t\taxis.enable_rightSoftWareLimit = chkR->isChecked();\n\n\t\taxis.currentControllerName = card.m_userName;\n\n\t\tcard.data_axisInfo.append(axis);\n\t}\n\n\tauto saveDigitalIO = [&](QTableWidget* table, QList<data_DoInfo>& list) {\n\t\tlist.clear();\n\t\tfor (int r = 0; r < table->rowCount(); ++r) {\n\t\t\tdata_DoInfo io;\n\t\t\tio.m_userName = table->item(r, 0) ? table->item(r, 0)->text() : \"\";\n\t\t\tio.mainIO = table->item(r, 1) ? table->item(r, 1)->text().toInt() : 0;\n\t\t\tio.subIO = table->item(r, 2) ? table->item(r, 2)->text().toInt() : 0;\n\t\t\tio.currentControllerName = card.m_userName;\n\t\t\tio.showSub = card.b_showSub;\n\t\t\tlist.append(io);\n\t\t}\n\t};\n\n\tsaveDigitalIO(m_dinTable, card.data_Din);\n\tsaveDigitalIO(m_doutTable, card.data_Dout);\n\n\tauto saveAnalogIO = [&](QTableWidget* table, QList<data_AoInfo>& list) {\n\t\tlist.clear();\n\t\tfor (int r = 0; r < table->rowCount(); ++r) {\n\t\t\tdata_AoInfo io;\n\t\t\tio.m_userName = table->item(r, 0) ? table->item(r, 0)->text() : \"\";\n\t\t\tio.address = table->item(r, 1) ? table->item(r, 1)->text().toInt() : 0;\n\t\t\tio.currentControllerName = card.m_userName;\n\t\t\tio.showSub = card.b_showSub;\n\t\t\tlist.append(io);\n\t\t}\n\t};\n\n\tsaveAnalogIO(m_ainTable, card.data_Ain);\n\tsaveAnalogIO(m_aoutTable, card.data_Aout);\n}\n\nvoid view_configGenerator::clearControllerRightPanel() {\n\tm_editCardName->clear();\n\tm_editIP->clear();\n\tm_spinPort->setValue(8080);\n\tm_axisTable->setRowCount(0);\n\tm_dinTable->setRowCount(0);\n\tm_doutTable->setRowCount(0);\n\tm_ainTable->setRowCount(0);\n\tm_aoutTable->setRowCount(0);\n}\n\nvoid view_configGenerator::updateControllerRightPanel() {\n\tif (m_currentCardIndex < 0 || m_currentCardIndex >= m_controllerList.size()) {\n\t\tm_controllerRightGroup->setEnabled(false);\n\t\tclearControllerRightPanel();\n\t\treturn;\n\t}\n\n\tm_controllerRightGroup->setEnabled(true);\n\tconst data_motion_controller &card = m_controllerList[m_currentCardIndex];\n\n\tm_editCardName->setText(card.m_userName);\n\tint brandIdx = m_comboBrand->findData(toString(card.motionControllerType));\n\tm_comboBrand->setCurrentIndex(brandIdx >= 0 ? brandIdx : 0);\n\tm_editIP->setText(card.ipAddress);\n\tm_spinPort->setValue(card.port);\n\tm_editAllAxisHomePath->setText(card.homePath_total);\n\tm_showSub->setChecked(card.b_showSub);\n\n\n\tm_axisTable->setRowCount(0);\n\tfor (const auto& axis : card.data_axisInfo) {\n\t\tint r = m_axisTable->rowCount();\n\t\tm_axisTable->insertRow(r);\n\t\tm_axisTable->setItem(r, 0, new QTableWidgetItem(axis.m_userName));\n\t\tm_axisTable->setItem(r, 1, new QTableWidgetItem(QString::number(axis.axisNo)));\n\t\tm_axisTable->setItem(r, 2, new QTableWidgetItem(QString::number(axis.soft_leftLimit)));\n\t\tm_axisTable->setItem(r, 3, new QTableWidgetItem(QString::number(axis.soft_rightLimit)));\n\t\tm_axisTable->setItem(r, 4, new QTableWidgetItem(QString::number(axis.acc)));\n\t\tm_axisTable->setItem(r, 5, new QTableWidgetItem(QString::number(axis.dec)));\n\t\tm_axisTable->setItem(r, 6, new QTableWidgetItem(QString::number(axis.fastDec)));\n\n\t\tauto *chkL = new QCheckBox(); chkL->setChecked(axis.enable_leftSoftWareLimit);\n\t\tm_axisTable->setCellWidget(r, 7, chkL);\n\n\t\tauto *chkR = new QCheckBox(); chkR->setChecked(axis.enable_rightSoftWareLimit);\n\t\tm_axisTable->setCellWidget(r, 8, chkR);\n\t}\n\n\tauto fillDigitalIO = [](QTableWidget* table, const QList<data_DoInfo>& list) {\n\t\ttable->setRowCount(0);\n\t\tfor (const auto& io : list) {\n\t\t\tint r = table->rowCount();\n\t\t\ttable->insertRow(r);\n\t\t\ttable->setItem(r, 0, new QTableWidgetItem(io.m_userName));\n\t\t\ttable->setItem(r, 1, new QTableWidgetItem(QString::number(io.mainIO)));\n\t\t\ttable->setItem(r, 2, new QTableWidgetItem(QString::number(io.subIO)));\n\t\t}\n\t};\n\n\tfillDigitalIO(m_dinTable, card.data_Din);\n\tfillDigitalIO(m_doutTable, card.data_Dout);\n\n\tauto fillAnalogIO = [](QTableWidget* table, const QList<data_AoInfo>& list) {\n\t\ttable->setRowCount(0);\n\t\tfor (const auto& io : list) {\n\t\t\tint r = table->rowCount();\n\t\t\ttable->insertRow(r);\n\t\t\ttable->setItem(r, 0, new QTableWidgetItem(io.m_userName));\n\t\t\ttable->setItem(r, 1, new QTableWidgetItem(QString::number(io.address)));\n\t\t}\n\t};\n\n\tfillAnalogIO(m_ainTable, card.data_Ain);\n\tfillAnalogIO(m_aoutTable, card.data_Aout);\n}\n\nvoid view_configGenerator::onAddAxis() {\n\tint r = m_axisTable->rowCount();\n\tm_axisTable->insertRow(r);\n\tm_axisTable->setItem(r, 0, new QTableWidgetItem(QString(\"Axis_%1\").arg(r + 1)));\n\tm_axisTable->setItem(r, 1, new QTableWidgetItem(QString::number(r)));\n\tm_axisTable->setItem(r, 2, new QTableWidgetItem(\"-1000.0\"));\n\tm_axisTable->setItem(r, 3, new QTableWidgetItem(\"1000.0\"));\n\tm_axisTable->setItem(r, 4, new QTableWidgetItem(\"500.0\"));\n\tm_axisTable->setItem(r, 5, new QTableWidgetItem(\"500.0\"));\n\tm_axisTable->setItem(r, 6, new QTableWidgetItem(\"1000.0\"));\n\n\tm_axisTable->setCellWidget(r, 7, new QCheckBox());\n\tm_axisTable->setCellWidget(r, 8, new QCheckBox());\n}\n\nvoid view_configGenerator::onDeleteAxis() {\n\tint r = m_axisTable->currentRow();\n\tif (r >= 0) m_axisTable->removeRow(r);\n}\n\nvoid view_configGenerator::onAddIO(QTableWidget* table) {\n\tint r = table->rowCount();\n\ttable->insertRow(r);\n\ttable->setItem(r, 0, new QTableWidgetItem(QString(\"IO_%1\").arg(r + 1)));\n\ttable->setItem(r, 1, new QTableWidgetItem(\"0\"));\n\tif (table->columnCount() > 2) {\n\t\ttable->setItem(r, 2, new QTableWidgetItem(\"0\"));\n\t}\n}\n\nvoid view_configGenerator::onDeleteIO(QTableWidget* table) {\n\tint r = table->currentRow();\n\tif (r >= 0) table->removeRow(r);\n}\n\n// ============================================================================\n// 工作头 (Toolhead) Master-Detail UI 交互槽实现\n// ============================================================================\n\nvoid view_configGenerator::onAddToolhead() {\n\tsaveCurrentToolheadFromUI();\n\n\tdata_ToolheadConfig newToolhead;\n\tnewToolhead.m_userName = QString(\"Toolhead_%1\").arg(m_toolheadList.size() + 1);\n\tm_toolheadList.append(newToolhead);\n\n\tm_toolheadListWidget->addItem(newToolhead.m_userName);\n\tm_toolheadListWidget->setCurrentRow(m_toolheadList.size() - 1);\n}\n\nvoid view_configGenerator::onDeleteToolhead() {\n\tint row = m_toolheadListWidget->currentRow();\n\tif (row < 0 || row >= m_toolheadList.size()) return;\n\n\tm_toolheadList.removeAt(row);\n\tdelete m_toolheadListWidget->takeItem(row);\n\n\tm_currentToolheadIndex = -1;\n\tif (!m_toolheadList.isEmpty()) {\n\t\tint nextRow = qMin(row, m_toolheadList.size() - 1);\n\t\tm_toolheadListWidget->setCurrentRow(nextRow);\n\t}\n\telse {\n\t\tupdateToolheadRightPanel();\n\t}\n}\n\nvoid view_configGenerator::onToolheadSelected(int currentRow) {\n\tif (currentRow == m_currentToolheadIndex) return;\n\n\tsaveCurrentToolheadFromUI();\n\n\tm_currentToolheadIndex = currentRow;\n\tupdateToolheadRightPanel();\n}\n\nvoid view_configGenerator::saveCurrentToolheadFromUI() {\n\tif (m_currentToolheadIndex < 0 || m_currentToolheadIndex >= m_toolheadList.size()) return;\n\n\tdata_ToolheadConfig &th = m_toolheadList[m_currentToolheadIndex];\n\tth.m_userName = m_editToolheadName->text().trimmed();\n\tth.b_isVirtual = m_chkIsVirtualToolhead->isChecked();\n\tth.bindMotionGroup = m_editBindMotionGroup->text().trimmed();\n\tth.bindZAxisName = m_editBindZAxis->text().trimmed();\n\n\tif (auto *item = m_toolheadListWidget->item(m_currentToolheadIndex)) {\n\t\titem->setText(th.m_userName);\n\t}\n\n\tth.mountedDevices.clear();\n\tfor (int r = 0; r < m_mountedDevicesTable->rowCount(); ++r) {\n\t\tdata_externalDevice dev;\n\t\tdev.m_userName = m_mountedDevicesTable->item(r, 0) ? m_mountedDevicesTable->item(r, 0)->text() : \"\";\n\t\tdev.m_deviceType = toEnum<data_externalDevice::ExternalDeviceType>( m_mountedDevicesTable->item(r, 1) ? m_mountedDevicesTable->item(r, 1)->text() : \"\");\n\t\tdev.m_commsChannelName = m_mountedDevicesTable->item(r, 2) ? m_mountedDevicesTable->item(r, 2)->text() : \"\";\n\n\t\tth.mountedDevices.append(dev);\n\t}\n}\n\nvoid view_configGenerator::clearToolheadRightPanel() {\n\tm_editToolheadName->clear();\n\tm_chkIsVirtualToolhead->setChecked(false);\n\tm_editBindMotionGroup->clear();\n\tm_editBindZAxis->clear();\n\tm_mountedDevicesTable->setRowCount(0);\n}\n\nvoid view_configGenerator::updateToolheadRightPanel() {\n\tif (m_currentToolheadIndex < 0 || m_currentToolheadIndex >= m_toolheadList.size()) {\n\t\tm_toolheadRightGroup->setEnabled(false);\n\t\tclearToolheadRightPanel();\n\t\treturn;\n\t}\n\n\tm_toolheadRightGroup->setEnabled(true);\n\tconst data_ToolheadConfig &th = m_toolheadList[m_currentToolheadIndex];\n\n\tm_editToolheadName->setText(th.m_userName);\n\tm_chkIsVirtualToolhead->setChecked(th.b_isVirtual);\n\tm_editBindMotionGroup->setText(th.bindMotionGroup);\n\tm_editBindZAxis->setText(th.bindZAxisName);\n\n\tm_mountedDevicesTable->setRowCount(0);\n\tfor (const auto& dev : th.mountedDevices) {\n\t\tint r = m_mountedDevicesTable->rowCount();\n\t\tm_mountedDevicesTable->insertRow(r);\n\t\tm_mountedDevicesTable->setItem(r, 0, new QTableWidgetItem(dev.m_userName));\n\t\tm_mountedDevicesTable->setItem(r, 1, new QTableWidgetItem(toString( dev.m_deviceType)));\n\t\tm_mountedDevicesTable->setItem(r, 2, new QTableWidgetItem(dev.m_commsChannelName));\n\t}\n}\n\nvoid view_configGenerator::onAddMountedDevice() {\n\tint r = m_mountedDevicesTable->rowCount();\n\tm_mountedDevicesTable->insertRow(r);\n\tm_mountedDevicesTable->setItem(r, 0, new QTableWidgetItem(QString(\"MountedDev_%1\").arg(r + 1)));\n\tm_mountedDevicesTable->setItem(r, 1, new QTableWidgetItem(\"DispenserNozzle\"));\n\tm_mountedDevicesTable->setItem(r, 2, new QTableWidgetItem(\"Comms_Channel_1\"));\n}\n\nvoid view_configGenerator::onDeleteMountedDevice() {\n\tint r = m_mountedDevicesTable->currentRow();\n\tif (r >= 0) m_mountedDevicesTable->removeRow(r);\n}\n\n// ============================================================================\n// 数据同步与 JSON 序列化读写\n// ============================================================================\n\nQJsonObject view_configGenerator::saveToTempJson() {\n\tsaveCurrentControllerFromUI();\n\tsaveCurrentToolheadFromUI();\n\n\tdata_equipment config;\n\tconfig.deviceName = le_softwareName->text().trimmed();\n\n\t// 1. 读取 Motion Controller 列表\n\tconfig.data_motion_controller_configs = m_controllerList;\n\n\t// 2. 读取 通讯模块 列表\n\tfor (int i = 0; i < tw_Comms->rowCount(); ++i) {\n\t\tdata_CommsConfig comm;\n\t\tcomm.m_userName = tw_Comms->item(i, 0) ? tw_Comms->item(i, 0)->text() : \"\";\n\n\t\tif (auto *cbType = qobject_cast<QComboBox*>(tw_Comms->cellWidget(i, 1))) {\n\t\t\tcomm.commsType =toEnum<data_CommsConfig::CommsType>(cbType->currentText()) ;\n\t\t}\n\n\t\tcomm.portName = tw_Comms->item(i, 2) ? tw_Comms->item(i, 2)->text() : \"COM1\";\n\t\tcomm.baudRate = tw_Comms->item(i, 3) ? tw_Comms->item(i, 3)->text().toInt() : 115200;\n\n\t\tQString parityStr = tw_Comms->item(i, 4) ? tw_Comms->item(i, 4)->text() : \"N\";\n\t\tcomm.parity = parityStr.isEmpty() ? 'N' : parityStr.at(0).toLatin1();\n\n\t\tcomm.dataBits = tw_Comms->item(i, 5) ? tw_Comms->item(i, 5)->text().toInt() : 8;\n\t\tcomm.stopBits = tw_Comms->item(i, 6) ? tw_Comms->item(i, 6)->text().toInt() : 1;\n\n\t\tcomm.ip = tw_Comms->item(i, 7) ? tw_Comms->item(i, 7)->text() : \"192.168.1.100\";\n\t\tcomm.tcpPort = tw_Comms->item(i, 8) ? tw_Comms->item(i, 8)->text().toInt() : 8080;\n\n\t\tcomm.slaveAddress = tw_Comms->item(i, 9) ? tw_Comms->item(i, 9)->text().toInt() : 1;\n\t\tcomm.timeoutMs = tw_Comms->item(i, 10) ? tw_Comms->item(i, 10)->text().toInt() : 1000;\n\t\tcomm.retryTimes = tw_Comms->item(i, 11) ? tw_Comms->item(i, 11)->text().toInt() : 2;\n\n\t\tconfig.data_comms_configs.append(comm);\n\t}\n\n\t// 3. 读取 测距传感器 列表 (Height Sensor)\n\tfor (int i = 0; i < tw_HeightSensor->rowCount(); ++i) {\n\t\tdata_HeightSensorConfig sensor;\n\t\t\n\t\tsensor.m_userName = tw_HeightSensor->item(i, 0) ? tw_HeightSensor->item(i, 0)->text() : \"\";\n\t\tsensor.m_commsChannelName = tw_HeightSensor->item(i, 1) ? tw_HeightSensor->item(i, 1)->text() : \"\";\n\t\n\t\tsensor.m_deviceType =data_externalDevice::HEIGHT_SENSOR;\n\n\t\tif (auto *cbComm = qobject_cast<QComboBox*>(tw_HeightSensor->cellWidget(i, 2))) {\n\t\t\tsensor.commType = cbComm->currentText();\n\t\t}\n\n\t\tQString msrAddrStr = tw_HeightSensor->item(i, 3) ? tw_HeightSensor->item(i, 3)->text() : \"0x6000\";\n\t\tbool ok = false;\n\t\tsensor.msrAddress = msrAddrStr.toUInt(&ok, 0);\n\n\t\tQString statusAddrStr = tw_HeightSensor->item(i, 4) ? tw_HeightSensor->item(i, 4)->text() : \"0x6001\";\n\t\tsensor.statusAddress = statusAddrStr.toUInt(&ok, 0);\n\n\t\tif (auto *pContainer = tw_HeightSensor->cellWidget(i, 5)) {\n\t\t\tif (auto *chk = pContainer->findChild<QCheckBox*>()) {\n\t\t\t\tsensor.b_same_direction_with_axis = chk->isChecked();\n\t\t\t}\n\t\t}\n\n\t\tsensor.minRange = tw_HeightSensor->item(i, 6) ? tw_HeightSensor->item(i, 6)->text().toDouble() : 0.0;\n\t\tsensor.maxRange = tw_HeightSensor->item(i, 7) ? tw_HeightSensor->item(i, 7)->text().toDouble() : 0.0;\n\t\tsensor.offset = tw_HeightSensor->item(i, 8) ? tw_HeightSensor->item(i, 8)->text().toDouble() : 0.0;\n\n\t\tconfig.data_heightSensor_configs.append(sensor);\n\t}\n\n\t// 4. 读取 Dispenser 列表\n\tfor (int i = 0; i < tw_Dispenser->rowCount(); ++i) {\n\t\tdata_DispenserConfig disp;\n\t\tint j = 0;\n\n\t\tdisp.m_userName = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i, j)->text() : \"\";\n\t\tj++;\n\n\t\tdisp.m_commsChannelName = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i, j)->text() : \"\";\n\t\tj++;\n\n\t\tdisp.m_deviceType = data_externalDevice::ExternalDeviceType::DISPENSOR;\n\t\tif (auto *cbComm = qobject_cast<QComboBox*>(tw_Dispenser->cellWidget(i, j))) {\n\t\t\tdisp.commType = cbComm->currentText();\n\t\t}\n\t\tj++;\n\t\tdisp.port = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i, j)->text() : \"COM1\";\n\t\tj++;\n\t\tdisp.baudRate = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i, j)->text().toInt() : 9600;\n\t\tj++;\n\n\t\tQString parityStr = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i, j)->text() : \"N\";\n\t\tdisp.parity = parityStr.isEmpty() ? 'N' : parityStr.at(0).toLatin1();\n\t\tj++;\n\n\t\tdisp.dataBits = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i, j)->text().toInt() : 8;\n\t\tj++;\n\t\tdisp.stopBits = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i, j)->text().toInt() : 1;\n\t\tj++;\n\n\t\tdisp.analogAddress = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i, j)->text().toInt() : 0;\n\t\tj++;\n\t\tdisp.minPressure = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i, j)->text().toFloat() : 0.0f;\n\t\tj++;\n\t\tdisp.maxPressure = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i, j)->text().toFloat() : 100.0f;\n\t\tj++;\n\n\t\tdisp.defaultPressure = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i, j)->text().toDouble() : 0.2;\n\t\tj++;\n\t\tdisp.defaultDispenseTime = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i, j)->text().toDouble() : 0.5;\n\t\tj++;\n\t\tdisp.outputSwitchIO = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i, j)->text().toInt() : 0;\n\t\tj++;\n\n\t\tif (auto *pContainer = tw_Dispenser->cellWidget(i, j)) {\n\t\t\tif (auto *chk = pContainer->findChild<QCheckBox*>()) {\n\t\t\t\tdisp.state_outputSwitch = chk->isChecked();\n\t\t\t}\n\t\t}\n\t\tj++;\n\t\tdisp.mainIO_openGas = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i, j)->text().toInt() : 0;\n\t\tj++;\n\t\tdisp.subIO_openGas = tw_Dispenser->item(i, j) ? tw_Dispenser->item(i,j)->text().toInt() : 0;\n\t\tj++;\n\n\t\tconfig.data_dispenser_configs.append(disp);\n\t}\n\n\t// 5. 读取 External Devices 全局外设列表\n\tfor (int i = 0; i < tw_ExternalDevice->rowCount(); ++i) {\n\t\tdata_externalDevice dev;\n\t\tdev.m_userName = tw_ExternalDevice->item(i, 0) ? tw_ExternalDevice->item(i, 0)->text() : \"\";\n\t\tdev.m_deviceType =toEnum<data_externalDevice::ExternalDeviceType>( tw_ExternalDevice->item(i, 1) ? tw_ExternalDevice->item(i, 1)->text() : \"\");\n\t\tdev.m_commsChannelName = tw_ExternalDevice->item(i, 2) ? tw_ExternalDevice->item(i, 2)->text() : \"\";\n\n\t\tdev.pos_x = tw_ExternalDevice->item(i, 3) ? tw_ExternalDevice->item(i, 3)->text().toDouble() : 0.0;\n\t\tdev.pos_y = tw_ExternalDevice->item(i, 4) ? tw_ExternalDevice->item(i, 4)->text().toDouble() : 0.0;\n\t\tdev.pos_z = tw_ExternalDevice->item(i, 5) ? tw_ExternalDevice->item(i, 5)->text().toDouble() : 0.0;\n\n\t\tconfig.data_external_device_configs.append(dev);\n\t}\n\n\t// 6. 读取 Toolhead 工作头列表\n\tconfig.data_toolhead_configs = m_toolheadList;\n\n\t*currentData = config;\n\treturn config.toJson();\n}\n\nvoid view_configGenerator::loadFromTempJson(const QJsonObject& json) {\n\tdata_equipment config;\n\tconfig.fromJson(json);\n\n\t*currentData = config;\n\tle_softwareName->setText(config.deviceName);\n\n\t// 1. 加载 Motion Controller 数组\n\tm_controllerList = config.data_motion_controller_configs;\n\tm_cardListWidget->clear();\n\tfor (const auto& card : m_controllerList) {\n\t\tm_cardListWidget->addItem(card.m_userName);\n\t}\n\tm_currentCardIndex = -1;\n\tif (!m_controllerList.isEmpty()) {\n\t\tm_cardListWidget->setCurrentRow(0);\n\t}\n\telse {\n\t\tupdateControllerRightPanel();\n\t}\n\n\t// 2. 加载 通讯模块\n\tsb_count_Comms->setValue(config.data_comms_configs.size());\n\tfor (int i = 0; i < config.data_comms_configs.size(); ++i) {\n\t\tconst auto &comm = config.data_comms_configs[i];\n\n\t\ttw_Comms->setItem(i, 0, new QTableWidgetItem(comm.m_userName));\n\n\t\tif (auto *cbType = qobject_cast<QComboBox*>(tw_Comms->cellWidget(i, 1))) {\n\t\t\tcbType->setCurrentText(toString(comm.commsType) );\n\t\t}\n\n\t\ttw_Comms->setItem(i, 2, new QTableWidgetItem(comm.portName));\n\t\ttw_Comms->setItem(i, 3, new QTableWidgetItem(QString::number(comm.baudRate)));\n\t\ttw_Comms->setItem(i, 4, new QTableWidgetItem(QString(comm.parity)));\n\t\ttw_Comms->setItem(i, 5, new QTableWidgetItem(QString::number(comm.dataBits)));\n\t\ttw_Comms->setItem(i, 6, new QTableWidgetItem(QString::number(comm.stopBits)));\n\n\t\ttw_Comms->setItem(i, 7, new QTableWidgetItem(comm.ip));\n\t\ttw_Comms->setItem(i, 8, new QTableWidgetItem(QString::number(comm.tcpPort)));\n\n\t\ttw_Comms->setItem(i, 9, new QTableWidgetItem(QString::number(comm.slaveAddress)));\n\t\ttw_Comms->setItem(i, 10, new QTableWidgetItem(QString::number(comm.timeoutMs)));\n\t\ttw_Comms->setItem(i, 11, new QTableWidgetItem(QString::number(comm.retryTimes)));\n\t}\n\n\t// 3. 加载 测距传感器 (Height Sensor)\n\tsb_count_HeightSensor->setValue(config.data_heightSensor_configs.size());\n\tfor (int i = 0; i < config.data_heightSensor_configs.size(); ++i) {\n\t\tconst auto &sensor = config.data_heightSensor_configs[i];\n\n\t\ttw_HeightSensor->setItem(i, 0, new QTableWidgetItem(sensor.m_userName));\n\t\ttw_HeightSensor->setItem(i, 1, new QTableWidgetItem(sensor.m_commsChannelName));\n\n\t\tif (auto *cbComm = qobject_cast<QComboBox*>(tw_HeightSensor->cellWidget(i, 2))) {\n\t\t\tcbComm->setCurrentText(sensor.commType);\n\t\t}\n\n\t\ttw_HeightSensor->setItem(i, 3, new QTableWidgetItem(QString(\"0x%1\").arg(sensor.msrAddress, 4, 16, QChar('0'))));\n\t\ttw_HeightSensor->setItem(i, 4, new QTableWidgetItem(QString(\"0x%1\").arg(sensor.statusAddress, 4, 16, QChar('0'))));\n\n\t\tif (auto *pContainer = tw_HeightSensor->cellWidget(i, 5)) {\n\t\t\tif (auto *chk = pContainer->findChild<QCheckBox*>()) {\n\t\t\t\tchk->setChecked(sensor.b_same_direction_with_axis);\n\t\t\t}\n\t\t}\n\n\t\ttw_HeightSensor->setItem(i, 6, new QTableWidgetItem(QString::number(sensor.minRange)));\n\t\ttw_HeightSensor->setItem(i, 7, new QTableWidgetItem(QString::number(sensor.maxRange)));\n\t\ttw_HeightSensor->setItem(i, 8, new QTableWidgetItem(QString::number(sensor.offset)));\n\t}\n\n\t// 4. 加载 Dispenser\n\tsb_count_Dispenser->setValue(config.data_dispenser_configs.size());\n\tfor (int i = 0; i < config.data_dispenser_configs.size(); ++i) {\n\t\tconst auto &disp = config.data_dispenser_configs[i];\n\t\tint j = 0;\n\t\ttw_Dispenser->setItem(i, j++, new QTableWidgetItem(disp.m_userName));\n\t\ttw_Dispenser->setItem(i, j++, new QTableWidgetItem(disp.m_commsChannelName));\n\n\t\tif (auto *cbComm = qobject_cast<QComboBox*>(tw_Dispenser->cellWidget(i, j++))) {\n\t\t\tcbComm->setCurrentText(disp.commType);\n\t\t}\n\n\t\ttw_Dispenser->setItem(i,j++, new QTableWidgetItem(disp.port));\n\t\ttw_Dispenser->setItem(i,j++, new QTableWidgetItem(QString::number(disp.baudRate)));\n\t\ttw_Dispenser->setItem(i,j++, new QTableWidgetItem(QString(disp.parity)));\n\t\ttw_Dispenser->setItem(i,j++, new QTableWidgetItem(QString::number(disp.dataBits)));\n\t\ttw_Dispenser->setItem(i,j++, new QTableWidgetItem(QString::number(disp.stopBits)));\n\n\t\ttw_Dispenser->setItem(i,j++, new QTableWidgetItem(QString::number(disp.analogAddress)));\n\t\ttw_Dispenser->setItem(i,j++, new QTableWidgetItem(QString::number(disp.minPressure)));\n\t\ttw_Dispenser->setItem(i,j++, new QTableWidgetItem(QString::number(disp.maxPressure)));\n\n\t\ttw_Dispenser->setItem(i,j++, new QTableWidgetItem(QString::number(disp.defaultPressure)));\n\t\ttw_Dispenser->setItem(i,j++, new QTableWidgetItem(QString::number(disp.defaultDispenseTime)));\n\t\ttw_Dispenser->setItem(i,j++, new QTableWidgetItem(QString::number(disp.outputSwitchIO)));\n\n\t\tif (auto *pContainer = tw_Dispenser->cellWidget(i, j++)) {\n\t\t\tif (auto *chk = pContainer->findChild<QCheckBox*>()) {\n\t\t\t\tchk->setChecked(disp.state_outputSwitch);\n\t\t\t}\n\t\t}\n\n\t\ttw_Dispenser->setItem(i, j++, new QTableWidgetItem(QString::number(disp.mainIO_openGas)));\n\t\ttw_Dispenser->setItem(i, j++, new QTableWidgetItem(QString::number(disp.subIO_openGas)));\n\t}\n\n\t// 5. 加载 External Devices 全局外设\n\tsb_count_ExternalDevice->setValue(config.data_external_device_configs.size());\n\tfor (int i = 0; i < config.data_external_device_configs.size(); ++i) {\n\t\tconst auto &dev = config.data_external_device_configs[i];\n\n\t\ttw_ExternalDevice->setItem(i, 0, new QTableWidgetItem(dev.m_userName));\n\t\ttw_ExternalDevice->setItem(i, 1, new QTableWidgetItem(dev.m_deviceType));\n\t\ttw_ExternalDevice->setItem(i, 2, new QTableWidgetItem(dev.m_commsChannelName));\n\n\t\ttw_ExternalDevice->setItem(i, 3, new QTableWidgetItem(QString::number(dev.pos_x)));\n\t\ttw_ExternalDevice->setItem(i, 4, new QTableWidgetItem(QString::number(dev.pos_y)));\n\t\ttw_ExternalDevice->setItem(i, 5, new QTableWidgetItem(QString::number(dev.pos_z)));\n\t}\n\n\t// 6. 加载 Toolhead 工作头数组\n\tm_toolheadList = config.data_toolhead_configs;\n\tm_toolheadListWidget->clear();\n\tfor (const auto& th : m_toolheadList) {\n\t\tm_toolheadListWidget->addItem(th.m_userName);\n\t}\n\tm_currentToolheadIndex = -1;\n\tif (!m_toolheadList.isEmpty()) {\n\t\tm_toolheadListWidget->setCurrentRow(0);\n\t}\n\telse {\n\t\tupdateToolheadRightPanel();\n\t}\n}\n\n// 文件加载与保存槽函数\nvoid view_configGenerator::onLoadConfigFromFile() {\n\tQString path = QFileDialog::getOpenFileName(this, tr(\"Open Config File\"), \"\", tr(\"e3d Files (*.e3d *.json)\"));\n\tif (path.isEmpty()) return;\n\n\tQFile file(path);\n\tif (!file.open(QIODevice::ReadOnly)) {\n\t\tQMessageBox::critical(this, tr(\"Error\"), tr(\"Failed to open configuration file!\"));\n\t\treturn;\n\t}\n\n\tQJsonDocument doc = QJsonDocument::fromJson(file.readAll());\n\tfile.close();\n\n\tle_configPath->setText(path);\n\tloadFromTempJson(doc.object());\n\tQMessageBox::information(this, tr(\"Information\"), tr(\"Configuration file loaded successfully!\"));\n}\n\nvoid view_configGenerator::onSaveConfigToFile() {\n\tsaveToTempJson();\n\n\tif (!e3d_configManager_Equipment::Instance()->saveToFile_dialog(currentData)) {\n\t\treturn;\n\t}\n}\n\nvoid view_configGenerator::onSaveTempClicked() {\n\tsaveToTempJson();\n\tQMessageBox::information(this, tr(\"Information\"), tr(\"Configuration saved to temporary memory!\"));\n}"}