| SourceCode/Bond/Servo/CCjPage2.cpp | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| SourceCode/Bond/Servo/CCjPage2.h | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| SourceCode/Bond/Servo/CMaster.cpp | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
SourceCode/Bond/Servo/CCjPage2.cpp
@@ -14,6 +14,64 @@ IDC_BUTTON_PORT4_PROCESS_START }; namespace { constexpr int kPortCount = 4; constexpr int kSlotCount = 8; int NormalizeMaterial(int material) { return (material == CCarrierSlotGrid::MAT_G2) ? CCarrierSlotGrid::MAT_G2 : CCarrierSlotGrid::MAT_G1; } void EnsureWarpDefaults(PJWarp& warp) { bool hasSelectedPort = false; for (int p = 0; p < kPortCount; ++p) { if (warp.selectedPorts[p]) { hasSelectedPort = true; } for (int s = 0; s < kSlotCount; ++s) { warp.materialSlots[p][s] = NormalizeMaterial(warp.materialSlots[p][s]); } } for (int s = 0; s < kSlotCount; ++s) { warp.material[s] = NormalizeMaterial(warp.material[s]); } // Migrate legacy single-port data into multi-port fields. if (!hasSelectedPort && 0 <= warp.port && warp.port < kPortCount) { warp.selectedPorts[warp.port] = TRUE; for (int s = 0; s < kSlotCount; ++s) { warp.checkSlots[warp.port][s] = warp.checkSlot[s]; warp.materialSlots[warp.port][s] = NormalizeMaterial(warp.material[s]); } } int firstSelectedPort = -1; for (int p = 0; p < kPortCount; ++p) { if (warp.selectedPorts[p]) { firstSelectedPort = p; break; } } warp.port = firstSelectedPort; if (firstSelectedPort >= 0) { for (int s = 0; s < kSlotCount; ++s) { warp.checkSlot[s] = warp.checkSlots[firstSelectedPort][s]; warp.material[s] = NormalizeMaterial(warp.materialSlots[firstSelectedPort][s]); } } else { for (int s = 0; s < kSlotCount; ++s) { warp.checkSlot[s] = FALSE; warp.material[s] = CCarrierSlotGrid::MAT_G1; } } } void BuildCassetteCtrlMaps(SERVO::CLoadPort* pPort, short (&jobExistence)[12], short& slotProcess) { slotProcess = 0; @@ -185,9 +243,10 @@ int CCjPage2::OnApply() { //SERVO::CProcessJob* if (m_pContext == nullptr) return -1; PJWarp* pPjWarp = (PJWarp*)m_pContext; EnsureWarpDefaults(*pPjWarp); SERVO::CProcessJob* pProcessJob = (SERVO::CProcessJob*)pPjWarp->pj; // 更新名称 @@ -208,7 +267,6 @@ return -1; } pProcessJob->setId(std::string(szBuffer)); // 更新配方 @@ -223,27 +281,45 @@ #else std::string recipe(strRecipe.GetString()); #endif pProcessJob->setRecipe(SERVO::RecipeMethod::NoTuning, recipe); } // 更新Port int port = -1; static int ids[] = { IDC_RADIO1, IDC_RADIO2, IDC_RADIO3, IDC_RADIO4 }; for (int i = 0; i < 4; i++) { int state = ((CButton*)GetDlgItem(ids[i]))->GetCheck(); if (state == BST_CHECKED) port = i; } pPjWarp->port = port; if (pPjWarp->port != -1) { for (int i = 0; i < 8; i++) { pPjWarp->checkSlot[i] = m_grid.GetSlotChecked(pPjWarp->port, i); pPjWarp->material[i] = m_grid.GetSlotMaterialType(pPjWarp->port, i); int firstSelectedPort = -1; for (int p = 0; p < kPortCount; ++p) { BOOL selected = (((CButton*)GetDlgItem(ids[p]))->GetCheck() == BST_CHECKED) ? TRUE : FALSE; pPjWarp->selectedPorts[p] = selected; if (selected && firstSelectedPort < 0) { firstSelectedPort = p; } for (int s = 0; s < kSlotCount; ++s) { if (selected) { pPjWarp->checkSlots[p][s] = m_grid.GetSlotChecked(p, s); pPjWarp->materialSlots[p][s] = NormalizeMaterial(m_grid.GetSlotMaterialType(p, s)); } else { pPjWarp->checkSlots[p][s] = FALSE; pPjWarp->materialSlots[p][s] = CCarrierSlotGrid::MAT_G1; } } } // Keep legacy single-port fields in sync for compatibility. pPjWarp->port = firstSelectedPort; if (firstSelectedPort >= 0) { for (int s = 0; s < kSlotCount; ++s) { pPjWarp->checkSlot[s] = pPjWarp->checkSlots[firstSelectedPort][s]; pPjWarp->material[s] = NormalizeMaterial(pPjWarp->materialSlots[firstSelectedPort][s]); } } else { for (int s = 0; s < kSlotCount; ++s) { pPjWarp->checkSlot[s] = FALSE; pPjWarp->material[s] = CCarrierSlotGrid::MAT_G1; } } ContentChanged(1); return 0; @@ -255,6 +331,13 @@ m_bContentChangedLock = TRUE; PJWarp* pPjWarp = (PJWarp*)m_pContext; EnsureWarpDefaults(*pPjWarp); for (auto& item : m_pjWarps) { EnsureWarpDefaults(item); } CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_RECIPE); pComboBox->ResetContent(); std::vector<std::string> vecRecipe = RecipeManager::getInstance().getAllPPID(); @@ -262,82 +345,47 @@ pComboBox->AddString(CString(recipe.c_str())); } // ComboBox PJWarp* pPjWarp = (PJWarp*)m_pContext; SERVO::CProcessJob* pProcessJob = (SERVO::CProcessJob*)pPjWarp->pj; SetDlgItemText(IDC_EDIT_PJ_ID, pProcessJob->id().c_str()); int idx = pComboBox->FindStringExact(-1, pProcessJob->recipeSpec().c_str()); if (idx != CB_ERR) pComboBox->SetCurSel(idx); // 4个checkbox static int ids[] = { IDC_RADIO1, IDC_RADIO2, IDC_RADIO3, IDC_RADIO4}; static char* pszUsed[] = { "Port1(已占用)", "Port2(已占用)", "Port3(已占用)", "Port4(已占用)" }; static char* pszUnUsed[] = { "Port1(可用)", "Port2(可用)", "Port3(可用)", "Port4(可用)" }; int portIndex = -1; bool enable[] = {true, true, true, true}; bool checked[] = { false, false, false, false }; for (auto item : m_pjWarps) { if (0 <= item.port && item.port <= 4 && item.pj != ((PJWarp*)m_pContext)->pj) { enable[item.port] = false; } } if (0 <= ((PJWarp*)m_pContext)->port && ((PJWarp*)m_pContext)->port <= 3) { checked[((PJWarp*)m_pContext)->port] = true; portIndex = ((PJWarp*)m_pContext)->port; m_nSelRadioId = ids[((PJWarp*)m_pContext)->port]; } for (int i = 0; i < 4; i++) { CButton* pButton = (CButton*)GetDlgItem(ids[i]); pButton->SetCheck(checked[i] ? BST_CHECKED : BST_UNCHECKED); pButton->SetWindowText(enable[i] ? pszUnUsed[i] : pszUsed[i]); pButton->EnableWindow(enable[i]); m_grid.SetPortAllocated(i, !checked[i], _T("")); GetDlgItem(btnID[i])->EnableWindow(checked[i]); } // 读取出真实数据 // 读取真实数据 auto& master = theApp.m_model.getMaster(); int EQID[] = {EQ_ID_LOADPORT1, EQ_ID_LOADPORT2, EQ_ID_LOADPORT3, EQ_ID_LOADPORT4}; for (int p = 0; p < 4; p++) { for (int p = 0; p < kPortCount; p++) { SERVO::CLoadPort* pPort = (SERVO::CLoadPort*)master.getEquipment(EQID[p]); m_grid.SetPortInfo(p, (std::string("Port ") + std::to_string(p+1)).c_str(), pPort->getCassetteId().c_str() ); for (int i = 0; i < SLOT_MAX; ++i) { SERVO::CSlot* pSlot = pPort->getSlot(i); if (!pSlot) { continue; } pPort->getCassetteId().c_str()); // 设置 Panel ID for (int s = 0; s < SLOT_MAX; ++s) { SERVO::CSlot* pSlot = pPort->getSlot(s); if (!pSlot) continue; SERVO::CGlass* pGlass = dynamic_cast<SERVO::CGlass*>(pSlot->getContext()); SERVO::CJobDataS* pJobDataS = pGlass->getJobDataS(); SERVO::CJobDataS* pJobDataS = (pGlass != nullptr) ? pGlass->getJobDataS() : nullptr; if (pGlass != nullptr && pJobDataS != nullptr) { m_grid.SetSlotGlass(p, i, TRUE, pGlass->getID().c_str(), m_pjWarps[p].material[i]); const int mat = (s < kSlotCount) ? NormalizeMaterial(pPjWarp->materialSlots[p][s]) : CCarrierSlotGrid::MAT_G1; m_grid.SetSlotGlass(p, s, TRUE, pGlass->getID().c_str(), mat); } else { m_grid.SetSlotGlass(p, i, FALSE, nullptr, CCarrierSlotGrid::MAT_G1); m_grid.SetSlotGlass(p, s, FALSE, nullptr, CCarrierSlotGrid::MAT_G1); } } } // 设置勾选数据 if (portIndex != -1) { for (int i = 0; i < 8; i++) { m_grid.SetSlotChecked(portIndex, i, ((PJWarp*)m_pContext)->checkSlot[i]); static int ids[] = { IDC_RADIO1, IDC_RADIO2, IDC_RADIO3, IDC_RADIO4 }; for (int p = 0; p < kPortCount; ++p) { ((CButton*)GetDlgItem(ids[p]))->SetCheck(pPjWarp->selectedPorts[p] ? BST_CHECKED : BST_UNCHECKED); for (int s = 0; s < kSlotCount; ++s) { m_grid.SetSlotChecked(p, s, pPjWarp->selectedPorts[p] ? pPjWarp->checkSlots[p][s] : FALSE); } } RefreshPortLocksAndButtons(); m_bContentChangedLock = FALSE; } @@ -351,87 +399,108 @@ ContentChanged(0); } void CCjPage2::RefreshPortLocksAndButtons() { if (m_pContext == nullptr) return; PJWarp* pCurrent = (PJWarp*)m_pContext; EnsureWarpDefaults(*pCurrent); bool usedByOthers[kPortCount] = { false, false, false, false }; for (auto& item : m_pjWarps) { if (item.pj == pCurrent->pj) { continue; } EnsureWarpDefaults(item); for (int p = 0; p < kPortCount; ++p) { if (item.selectedPorts[p]) { usedByOthers[p] = true; } } } static int ids[] = { IDC_RADIO1, IDC_RADIO2, IDC_RADIO3, IDC_RADIO4 }; static const char* pszUsed[] = { "Port1(已占用)", "Port2(已占用)", "Port3(已占用)", "Port4(已占用)" }; static const char* pszUnUsed[] = { "Port1(可用)", "Port2(可用)", "Port3(可用)", "Port4(可用)" }; for (int p = 0; p < kPortCount; ++p) { CButton* pButton = (CButton*)GetDlgItem(ids[p]); BOOL checked = (pButton->GetCheck() == BST_CHECKED) ? TRUE : FALSE; const bool enable = !usedByOthers[p] || checked; pButton->EnableWindow(enable ? TRUE : FALSE); pButton->SetWindowText((enable || checked) ? pszUnUsed[p] : pszUsed[p]); if (!enable && !checked) { pButton->SetCheck(BST_UNCHECKED); } checked = (pButton->GetCheck() == BST_CHECKED) ? TRUE : FALSE; pCurrent->selectedPorts[p] = checked; m_grid.SetPortAllocated(p, !checked, _T("")); GetDlgItem(btnID[p])->EnableWindow(checked); } // Keep one material type across all selected ports. int syncMat = CCarrierSlotGrid::MAT_G1; bool hasSyncMat = false; for (int p = 0; p < kPortCount && !hasSyncMat; ++p) { if (!pCurrent->selectedPorts[p]) continue; for (int s = 0; s < kSlotCount; ++s) { if (m_grid.GetSlotChecked(p, s)) { syncMat = m_grid.GetSlotMaterialType(p, s); hasSyncMat = true; break; } } } if (!hasSyncMat) { for (int p = 0; p < kPortCount; ++p) { if (!pCurrent->selectedPorts[p]) continue; syncMat = m_grid.GetSlotMaterialType(p, 0); hasSyncMat = true; break; } } SyncMaterialAcrossSelectedPorts(syncMat); EnsureWarpDefaults(*pCurrent); } void CCjPage2::SyncMaterialAcrossSelectedPorts(int material) { if (m_pContext == nullptr) return; const int mat = NormalizeMaterial(material); PJWarp* pCurrent = (PJWarp*)m_pContext; for (int p = 0; p < kPortCount; ++p) { if (!pCurrent->selectedPorts[p]) continue; for (int s = 0; s < kSlotCount; ++s) { m_grid.SetSlotMaterialType(p, s, mat, FALSE); } } } void CCjPage2::OnBnClickedRadio1() { BOOL lock[] = {TRUE, TRUE, TRUE, TRUE}; if (IDC_RADIO1 == m_nSelRadioId) { CheckRadioButton(IDC_RADIO1, IDC_RADIO4, 0); m_nSelRadioId = 0; } else { CheckRadioButton(IDC_RADIO1, IDC_RADIO4, IDC_RADIO1); m_nSelRadioId = IDC_RADIO1; lock[0] = FALSE; } for (int i = 0; i < 4; i++) { m_grid.SetPortAllocated(i, lock[i], _T("")); GetDlgItem(btnID[i])->EnableWindow(!lock[i]); } RefreshPortLocksAndButtons(); ContentChanged(0); } void CCjPage2::OnBnClickedRadio2() { BOOL lock[] = { TRUE, TRUE, TRUE, TRUE }; if (IDC_RADIO2 == m_nSelRadioId) { CheckRadioButton(IDC_RADIO1, IDC_RADIO4, 0); m_nSelRadioId = 0; } else { CheckRadioButton(IDC_RADIO1, IDC_RADIO4, IDC_RADIO2); m_nSelRadioId = IDC_RADIO2; lock[1] = FALSE; } for (int i = 0; i < 4; i++) { m_grid.SetPortAllocated(i, lock[i], _T("")); GetDlgItem(btnID[i])->EnableWindow(!lock[i]); } RefreshPortLocksAndButtons(); ContentChanged(0); } void CCjPage2::OnBnClickedRadio3() { BOOL lock[] = { TRUE, TRUE, TRUE, TRUE }; if (IDC_RADIO3 == m_nSelRadioId) { CheckRadioButton(IDC_RADIO1, IDC_RADIO4, 0); m_nSelRadioId = 0; } else { CheckRadioButton(IDC_RADIO1, IDC_RADIO4, IDC_RADIO3); m_nSelRadioId = IDC_RADIO3; lock[2] = FALSE; } for (int i = 0; i < 4; i++) { m_grid.SetPortAllocated(i, lock[i], _T("")); GetDlgItem(btnID[i])->EnableWindow(!lock[i]); } RefreshPortLocksAndButtons(); ContentChanged(0); } void CCjPage2::OnBnClickedRadio4() { BOOL lock[] = { TRUE, TRUE, TRUE, TRUE }; if (IDC_RADIO4 == m_nSelRadioId) { CheckRadioButton(IDC_RADIO1, IDC_RADIO4, 0); m_nSelRadioId = 0; } else { CheckRadioButton(IDC_RADIO1, IDC_RADIO4, IDC_RADIO4); m_nSelRadioId = IDC_RADIO4; lock[3] = FALSE; } for (int i = 0; i < 4; i++) { m_grid.SetPortAllocated(i, lock[i], _T("")); GetDlgItem(btnID[i])->EnableWindow(!lock[i]); } RefreshPortLocksAndButtons(); ContentChanged(0); } @@ -462,22 +531,10 @@ void CCjPage2::OnGridMatChanged(NMHDR* pNMHDR, LRESULT* pResult) { auto* nm = reinterpret_cast<CSG_MAT_CHANGE*>(pNMHDR); const int port = nm->port; const int slot = nm->slot; const int mat = nm->material; // 1/2 // 例如:即刻刷新右侧预览/记录日志等 SyncMaterialAcrossSelectedPorts(mat); ContentChanged(0); /* if (m_pContext != nullptr) { PJWarp* pjWarp = (PJWarp*)m_pContext; for (int i = 0; i < 8; i++) { pjWarp->checkSlot[i] = m_grid.GetSlotChecked(port, i); pjWarp->material[i] = m_grid.GetSlotMaterialType(port, i); } } */ *pResult = 0; } SourceCode/Bond/Servo/CCjPage2.h
@@ -25,6 +25,8 @@ private: void UpdatePjData(); void RefreshPortLocksAndButtons(); void SyncMaterialAcrossSelectedPorts(int material); private: CCarrierSlotGrid m_grid; SourceCode/Bond/Servo/CMaster.cpp
@@ -707,6 +707,91 @@ continue; } // 生产模式固定映射:Port1/3 -> G1,Port2/4 -> G2。 auto isProductionPortTypeMatch = [&](int portIndex, MaterialsType type) -> bool { if (m_schedulingMode != SchedulingMode::Production) return true; const bool isG1Port = (portIndex == 0 || portIndex == 2); if (type == MaterialsType::G1) return isG1Port; if (type == MaterialsType::G2) return !isG1Port; return true; }; // 生产模式:根据线上未配对玻璃反推下一片应来自哪个端口(Port1<->2, Port3<->4)。 auto getPreferredPortForType = [&](MaterialsType targetType) -> int { if (m_schedulingMode != SchedulingMode::Production) return -1; auto preferG1ByG2 = [&](CGlass* pG2) -> int { if (pG2 == nullptr || pG2->getType() != MaterialsType::G2 || pG2->getBuddy() != nullptr) return -1; int originPort = -1, originSlot = -1; pG2->getOrginPort(originPort, originSlot); if (originPort == 1) return 0; // Port2 -> Port1 if (originPort == 3) return 2; // Port4 -> Port3 return -1; }; auto preferG2ByG1 = [&](CGlass* pG1) -> int { if (pG1 == nullptr || pG1->getType() != MaterialsType::G1 || pG1->getBuddy() != nullptr) return -1; int originPort = -1, originSlot = -1; pG1->getOrginPort(originPort, originSlot); if (originPort == 0) return 1; // Port1 -> Port2 if (originPort == 2) return 3; // Port3 -> Port4 return -1; }; if (targetType == MaterialsType::G1) { int p = preferG1ByG2(pBonder1->getGlassFromSlot(1)); if (p >= 0) return p; p = preferG1ByG2(pBonder2->getGlassFromSlot(1)); if (p >= 0) return p; p = preferG1ByG2(pFliper->getGlassFromSlot(1)); if (p >= 0) return p; p = preferG1ByG2(pAligner->getGlassFromSlot(1)); if (p >= 0) return p; } else if (targetType == MaterialsType::G2) { int p = preferG2ByG1(pBonder1->getGlassFromSlot(2)); if (p >= 0) return p; p = preferG2ByG1(pBonder2->getGlassFromSlot(2)); if (p >= 0) return p; p = preferG2ByG1(pVacuumBake->getGlassFromSlot(1)); if (p >= 0) return p; p = preferG2ByG1(pVacuumBake->getGlassFromSlot(2)); if (p >= 0) return p; p = preferG2ByG1(pAligner->getGlassFromSlot(1)); if (p >= 0) return p; } return -1; }; // Job 模式下要求 Bonder 内的 G1/G2 来自同一个 ProcessJob。 auto validateBonderPairProcessJob = [&](CEquipment* pBonder, const char* bonderName) -> bool { if (m_pActiveRobotTask == nullptr) return false; CGlass* pIncomingG1 = (CGlass*)m_pActiveRobotTask->getContext(); CGlass* pExistingG2 = pBonder->getGlassFromSlot(0); if (pIncomingG1 == nullptr || pExistingG2 == nullptr) return true; CProcessJob* pjG1 = pIncomingG1->getProcessJob(); CProcessJob* pjG2 = pExistingG2->getProcessJob(); if (m_bJobMode && (pjG1 == nullptr || pjG2 == nullptr || pjG1 != pjG2)) { std::string pj1 = (pjG1 == nullptr) ? "NULL" : pjG1->id(); std::string pj2 = (pjG2 == nullptr) ? "NULL" : pjG2->id(); LOGW("<Master>%s配对拦截:G1/G2来自不同ProcessJob(G1=%s,G2=%s)。", bonderName, pj1.c_str(), pj2.c_str()); delete m_pActiveRobotTask; m_pActiveRobotTask = nullptr; return false; } // 生产模式固定端口对:Port1<->Port2,Port3<->Port4。 if (m_schedulingMode == SchedulingMode::Production) { int g1Port = 0, g1Slot = 0, g2Port = 0, g2Slot = 0; pIncomingG1->getOrginPort(g1Port, g1Slot); pExistingG2->getOrginPort(g2Port, g2Slot); const bool pairOk = (g1Port == 0 && g2Port == 1) || (g1Port == 2 && g2Port == 3); if (!pairOk) { LOGW("<Master>%s配对拦截:端口对不匹配(要求1<->2或3<->4, 实际G1Port=%d,G2Port=%d)。", bonderName, g1Port + 1, g2Port + 1); delete m_pActiveRobotTask; m_pActiveRobotTask = nullptr; return false; } } return true; }; // Bonder1、Bonder2、Fliper、VacuumBake、Aligner,统计G2和G1的数量, 配对组数, 多出的类型 int nG2Count = 0, nG1Count = 0, nGlassGroup, nExtraType; @@ -837,11 +922,17 @@ // VacuumBake(G1) -> Bonder if (!rmd.armState[0] && pBonder1->slotHasGlass(0) && !pBonder1->slotHasGlass(1)) { m_pActiveRobotTask = createTransferTask(pVacuumBake, pBonder1, MaterialsType::G1, MaterialsType::G0); if (m_pActiveRobotTask != nullptr && !validateBonderPairProcessJob(pBonder1, "Bonder1")) { // 同 PJ 校验失败,本轮不下发搬送 } CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask); } if (!rmd.armState[0] && pBonder2->slotHasGlass(0) && !pBonder2->slotHasGlass(1)) { m_pActiveRobotTask = createTransferTask(pVacuumBake, pBonder2, MaterialsType::G1, MaterialsType::G0); if (m_pActiveRobotTask != nullptr && !validateBonderPairProcessJob(pBonder2, "Bonder2")) { // 同 PJ 校验失败,本轮不下发搬送 } CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask); } @@ -880,14 +971,34 @@ else { primaryType = MaterialsType::G1; } const int preferredPortForPrimary = getPreferredPortForType(primaryType); { static int s_prevPrimaryType = -1; static int s_prevPreferredPort = -2; const int curPrimaryType = (int)primaryType; if (s_prevPrimaryType != curPrimaryType || s_prevPreferredPort != preferredPortForPrimary) { LOGI("<Master>LoadPort->Aligner规则(RUNNING): primaryType=%d, preferredPort=%d", curPrimaryType, preferredPortForPrimary >= 0 ? (preferredPortForPrimary + 1) : 0); s_prevPrimaryType = curPrimaryType; s_prevPreferredPort = preferredPortForPrimary; } } for (int s = 0; s < 4; s++) { PortType pt = pLoadPorts[s]->getPortType(); if (!rmd.armState[0] && pLoadPorts[s]->isEnable() && (pt == PortType::Loading || pt == PortType::Both) && pLoadPorts[s]->getPortStatus() == PORT_INUSE) { if (!isProductionPortTypeMatch(s, primaryType)) { continue; } if (preferredPortForPrimary >= 0 && s != preferredPortForPrimary) { continue; } m_pActiveRobotTask = createTransferTask(pLoadPorts[s], pAligner, primaryType, secondaryType, 1, m_bJobMode); if (m_pActiveRobotTask != nullptr) { LOGI("<Master>LoadPort->Aligner命中(RUNNING): port=%d, primaryType=%d, preferredPort=%d", s + 1, (int)primaryType, preferredPortForPrimary >= 0 ? (preferredPortForPrimary + 1) : 0); CGlass* pGlass = (CGlass*)m_pActiveRobotTask->getContext(); if (pGlass->getBuddy() != nullptr) { delete m_pActiveRobotTask; @@ -969,6 +1080,91 @@ unlock(); // 等当前任务完成或中止后继续 continue; } // 生产模式固定映射:Port1/3 -> G1,Port2/4 -> G2。 auto isProductionPortTypeMatch = [&](int portIndex, MaterialsType type) -> bool { if (m_schedulingMode != SchedulingMode::Production) return true; const bool isG1Port = (portIndex == 0 || portIndex == 2); if (type == MaterialsType::G1) return isG1Port; if (type == MaterialsType::G2) return !isG1Port; return true; }; // 生产模式:根据线上未配对玻璃反推下一片应来自哪个端口(Port1<->2, Port3<->4)。 auto getPreferredPortForType = [&](MaterialsType targetType) -> int { if (m_schedulingMode != SchedulingMode::Production) return -1; auto preferG1ByG2 = [&](CGlass* pG2) -> int { if (pG2 == nullptr || pG2->getType() != MaterialsType::G2 || pG2->getBuddy() != nullptr) return -1; int originPort = -1, originSlot = -1; pG2->getOrginPort(originPort, originSlot); if (originPort == 1) return 0; // Port2 -> Port1 if (originPort == 3) return 2; // Port4 -> Port3 return -1; }; auto preferG2ByG1 = [&](CGlass* pG1) -> int { if (pG1 == nullptr || pG1->getType() != MaterialsType::G1 || pG1->getBuddy() != nullptr) return -1; int originPort = -1, originSlot = -1; pG1->getOrginPort(originPort, originSlot); if (originPort == 0) return 1; // Port1 -> Port2 if (originPort == 2) return 3; // Port3 -> Port4 return -1; }; if (targetType == MaterialsType::G1) { int p = preferG1ByG2(pBonder1->getGlassFromSlot(1)); if (p >= 0) return p; p = preferG1ByG2(pBonder2->getGlassFromSlot(1)); if (p >= 0) return p; p = preferG1ByG2(pFliper->getGlassFromSlot(1)); if (p >= 0) return p; p = preferG1ByG2(pAligner->getGlassFromSlot(1)); if (p >= 0) return p; } else if (targetType == MaterialsType::G2) { int p = preferG2ByG1(pBonder1->getGlassFromSlot(2)); if (p >= 0) return p; p = preferG2ByG1(pBonder2->getGlassFromSlot(2)); if (p >= 0) return p; p = preferG2ByG1(pVacuumBake->getGlassFromSlot(1)); if (p >= 0) return p; p = preferG2ByG1(pVacuumBake->getGlassFromSlot(2)); if (p >= 0) return p; p = preferG2ByG1(pAligner->getGlassFromSlot(1)); if (p >= 0) return p; } return -1; }; // Job 模式下要求 Bonder 内的 G1/G2 来自同一个 ProcessJob。 auto validateBonderPairProcessJob = [&](CEquipment* pBonder, const char* bonderName) -> bool { if (m_pActiveRobotTask == nullptr) return false; CGlass* pIncomingG1 = (CGlass*)m_pActiveRobotTask->getContext(); CGlass* pExistingG2 = pBonder->getGlassFromSlot(0); if (pIncomingG1 == nullptr || pExistingG2 == nullptr) return true; CProcessJob* pjG1 = pIncomingG1->getProcessJob(); CProcessJob* pjG2 = pExistingG2->getProcessJob(); if (m_bJobMode && (pjG1 == nullptr || pjG2 == nullptr || pjG1 != pjG2)) { std::string pj1 = (pjG1 == nullptr) ? "NULL" : pjG1->id(); std::string pj2 = (pjG2 == nullptr) ? "NULL" : pjG2->id(); LOGW("<Master>%s配对拦截:G1/G2来自不同ProcessJob(G1=%s,G2=%s)。", bonderName, pj1.c_str(), pj2.c_str()); delete m_pActiveRobotTask; m_pActiveRobotTask = nullptr; return false; } // 生产模式固定端口对:Port1<->Port2,Port3<->Port4。 if (m_schedulingMode == SchedulingMode::Production) { int g1Port = 0, g1Slot = 0, g2Port = 0, g2Slot = 0; pIncomingG1->getOrginPort(g1Port, g1Slot); pExistingG2->getOrginPort(g2Port, g2Slot); const bool pairOk = (g1Port == 0 && g2Port == 1) || (g1Port == 2 && g2Port == 3); if (!pairOk) { LOGW("<Master>%s配对拦截:端口对不匹配(要求1<->2或3<->4, 实际G1Port=%d,G2Port=%d)。", bonderName, g1Port + 1, g2Port + 1); delete m_pActiveRobotTask; m_pActiveRobotTask = nullptr; return false; } } return true; }; // 5.5) 暂停状态检查:若 CJ 或在制 PJ 处于 Paused,暂缓调度新的搬送 bool pausedByEvent = false; @@ -1098,10 +1294,16 @@ // 13) VacuumBake(G1) -> Bonder(槽级判定:slot0(G2) 已有且 slot1(G1) 为空) if (!rmd.armState[0] && pBonder1->slotHasGlass(0) && !pBonder1->slotHasGlass(1)) { m_pActiveRobotTask = createTransferTask(pVacuumBake, pBonder1, MaterialsType::G1, MaterialsType::G0); if (m_pActiveRobotTask != nullptr && !validateBonderPairProcessJob(pBonder1, "Bonder1")) { // 同 PJ 校验失败,本轮不下发搬送 } CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask); } if (!rmd.armState[0] && pBonder2->slotHasGlass(0) && !pBonder2->slotHasGlass(1)) { m_pActiveRobotTask = createTransferTask(pVacuumBake, pBonder2, MaterialsType::G1, MaterialsType::G0); if (m_pActiveRobotTask != nullptr && !validateBonderPairProcessJob(pBonder2, "Bonder2")) { // 同 PJ 校验失败,本轮不下发搬送 } CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask); } @@ -1123,15 +1325,35 @@ // 16) LoadPort -> Aligner(受组数门限控制;统一 buddy/状态时序) if (blockLoadFromLP) { unlock(); continue; } const int preferredPortForPrimary = getPreferredPortForType(primaryType); { static int s_prevPrimaryType = -1; static int s_prevPreferredPort = -2; const int curPrimaryType = (int)primaryType; if (s_prevPrimaryType != curPrimaryType || s_prevPreferredPort != preferredPortForPrimary) { LOGI("<Master>LoadPort->Aligner规则(RUNNING_BATCH): primaryType=%d, preferredPort=%d", curPrimaryType, preferredPortForPrimary >= 0 ? (preferredPortForPrimary + 1) : 0); s_prevPrimaryType = curPrimaryType; s_prevPreferredPort = preferredPortForPrimary; } } for (int s = 0; s < 4; s++) { PortType pt = pLoadPorts[s]->getPortType(); if (!rmd.armState[0] && pLoadPorts[s]->isEnable() && (pt == PortType::Loading || pt == PortType::Both) && pLoadPorts[s]->getPortStatus() == PORT_INUSE) { if (!isProductionPortTypeMatch(s, primaryType)) { continue; } if (preferredPortForPrimary >= 0 && s != preferredPortForPrimary) { continue; } m_pActiveRobotTask = createTransferTask(pLoadPorts[s], pAligner, primaryType, secondaryType, 1, m_bJobMode); if (m_pActiveRobotTask != nullptr) { LOGI("<Master>LoadPort->Aligner命中(RUNNING_BATCH): port=%d, primaryType=%d, preferredPort=%d", s + 1, (int)primaryType, preferredPortForPrimary >= 0 ? (preferredPortForPrimary + 1) : 0); auto* pGlass = static_cast<CGlass*>(m_pActiveRobotTask->getContext()); if (pGlass->getBuddy() != nullptr) { delete m_pActiveRobotTask; m_pActiveRobotTask = nullptr;