From f873e066e404e1c5dfcc63664b4931653ccc2f4f Mon Sep 17 00:00:00 2001 From: NohDoyeon241104 Date: Fri, 4 Sep 2026 16:13:37 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=88=98=EB=8F=99=EC=B6=9C=EC=82=AC=20?= =?UTF-8?q?=EB=A0=88=EC=8B=9C=ED=94=BC/=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=A0=95=ED=95=A9=EC=84=B1=20=EC=9D=B8=ED=84=B0=EB=9D=BD=20(IS?= =?UTF-8?q?SUE-260904-03)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AutoRun 중 수동출사(Run Welding) 시 화면 띠의 PLC 지시와 실제 활성·로드 상태가 어긋난 채 출사되는 것을 차단. 검증 4종 - 활성==선택 / 활성==실제 로드파일(SelectedRecipeFile) / PLC지시 RECIPE==활성번호 / PLC지시 PAGE==현재 페이지. PLC 지시 2종은 bit15 구간에만 채워지므로 null 이면 생략. 검증 예외 시 fail-closed. 시퀀스는 무수정. --- ...cannerObjectTeachingControlBoxViewModel.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs b/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs index 51e0cc2..a4ad8fb 100644 --- a/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs +++ b/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs @@ -506,8 +506,97 @@ namespace WeldingCommon.Views _SeqScoutFlyingVision.Stop(); } + #region [레시피/페이지 인터락 2026-09-04] 수동출사 전 정합성 검증 (요청 260904-03) + // 배경: AutoRun 진행 중 수동출사를 할 때, 작업자는 화면 상단 띠(SyncPlcGuide, :96)에 뜬 + // "PLC 지시 RECIPE/PAGE" 를 보고 레시피와 페이지를 맞춘 뒤 Run Welding 을 누른다. + // 그 맞춤이 실제로 되어 있는지 확인하는 코드가 없어, 어긋난 채로 출사될 수 있었다. + // + // 검사 4종: + // ① 활성 레시피 == 선택 레시피 + // ② 활성 레시피 == 실제 캔버스에 로드된 파일 ← 가장 중요 + // LASA 는 레시피를 메모리에 1개만 유지하므로 활성과 화면 내용이 어긋날 수 있다. + // 판정 기준 DB.Recipe.Recipe.SelectedRecipeFile 은 Recipe.Load() 성공 시에만 + // 세팅되는 실제 로드 이력이라 신뢰할 수 있다. + // ③ PLC 지시 RECIPE == 활성 레시피 번호 + // ④ PLC 지시 PAGE == 현재 캔버스 페이지 + // + // ③④ 의 Status.PlcGuide* 는 bit15(수동용접) 구간에만 채워진다(UCMainAutoRunViewModel:223). + // null 이면 해당 검사를 생략한다 — PLC 워드를 직접 읽으면 공용 코드가 설비에 종속되므로 + // 의도적으로 Status 경유만 쓴다(260904 협의). + // + // 예외 시 fail-closed(차단). 검증이 안 되면 출사를 막는 것이 이 인터락의 목적이다. + // ※ HeadTheta 인터락(SeqAutoRunPTOP)과는 별개 기능이며 서로 영향이 없다. + + private bool CheckRecipePageBeforeTeachingRun() + { + try { + var active = _Aurora.RecipeManager.ActivatedRecipe(); + if (active == null) { + Log.E("[레시피/페이지 인터락] 활성 레시피 없음 → 출사 차단"); + MessageUtil.CreateAndShowMessageDialog("출사 차단", "활성 레시피가 없습니다."); + return false; + } + + var mismatches = new List(); + int curPage = CommonHelper.GetMainPage().GetCurrentPageIndex() + 1; // 0-base → 1-base + + // ① 활성 == 선택 + var selected = RecipeHelper.SelectedRecipe.name; + if (!string.IsNullOrWhiteSpace(selected) && + !string.Equals(active.Name, selected, StringComparison.OrdinalIgnoreCase)) { + mismatches.Add($"활성 레시피 [{active.Name}] ≠ 선택 레시피 [{selected}]"); + } + + // ② 활성 == 실제 캔버스에 로드된 파일 + var loadedFile = DB.Recipe.Recipe.SelectedRecipeFile; + if (!string.IsNullOrWhiteSpace(loadedFile)) { + var loadedName = System.IO.Path.GetFileName(System.IO.Path.GetDirectoryName(loadedFile)); + if (!string.Equals(active.Name, loadedName, StringComparison.OrdinalIgnoreCase)) { + mismatches.Add($"활성 레시피 [{active.Name}] ≠ 화면 로드 [{loadedName}]"); + } + } + + // ③④ PLC 지시 (bit15 구간에만 채워짐) + var gRecipe = Status.PlcGuideRecipeNo; + var gPage = Status.PlcGuidePageNo; + if (gRecipe != null && gPage != null) { + if (gRecipe.Value != active.Id) { + mismatches.Add($"PLC 지시 RECIPE [{gRecipe}] ≠ 활성 레시피 번호 [{active.Id}]"); + } + if (gPage.Value != curPage) { + mismatches.Add($"PLC 지시 PAGE [{gPage}] ≠ 현재 페이지 [{curPage}]"); + } + } + + if (mismatches.Count == 0) { + Log.I($"[레시피/페이지 인터락] 통과. 활성=[{active.Id}:{active.Name}] " + + $"페이지=[{curPage}] PLC지시=[{gRecipe}/{gPage}]"); + return true; + } + + Log.E($"[레시피/페이지 인터락] 출사 차단 — {string.Join(" / ", mismatches)}"); + MessageUtil.CreateAndShowMessageDialog("출사 차단 - 레시피/페이지 불일치", + "다음 항목이 일치하지 않아 출사를 차단했습니다.\n\n · " + + string.Join("\n · ", mismatches) + + "\n\n레시피와 페이지를 다시 맞춘 뒤 시도하십시오."); + return false; + + } catch (Exception ex) { + Log.E($"[레시피/페이지 인터락] 검사 실패 → 출사 차단(fail-closed): {ex.Message}"); + MessageUtil.CreateAndShowMessageDialog("출사 차단", + $"레시피/페이지 검증 중 오류가 발생했습니다.\n\n{ex.Message}"); + return false; + } + } + #endregion + private void PerformCmdRunTeachingRun(object obj) { + // [레시피/페이지 인터락 2026-09-04] 출사 전 정합성 검증. 불일치면 시퀀스를 태우지 않는다. + if (!CheckRecipePageBeforeTeachingRun()) { + return; + } + var result = MessageUtil.ShowYesNoMessageDialog("Teaching Run", "Start TeachingRun Sequence?"); if (result == false) { return;