Merge branch 'field/ptop-1st' of https://gitea.ubisampaju.synology.me/RobotAutomationDevelopment-Dept/LGES-OC5-PtoP-Welder into field/ptop-1st
This commit is contained in:
commit
bef3107f38
@ -202,3 +202,24 @@
|
||||
- 서브에이전트 검증 통과(로직·XAML 그리드·VM·인코딩 PASS) → 작업지시서 삭제
|
||||
- 공유(WeldingCommon)라 같은행은 150(기존 동일), **행 전환 기본 300ms**(기존 150) — 타 머신도 영향, Recipe View에서 조정 가능
|
||||
- → 커밋·push (e263ab8)
|
||||
|
||||
## 16:30 — ScoutField 페이지별 로드 버그 분석 + 작업지시서 작성 (소스 미수정)
|
||||
|
||||
### 분석 (레시피 데이터 + 코드)
|
||||
- `_config_recipe/<레시피>/` 분석: "Selected Scout Field" 팝업 값의 실제 저장처는 **`FieldHelper.json`** (페이지 배열, 각 페이지 `FieldScans` 80셀=10×8, `Enabled`=선택 셀)
|
||||
- `ScannerObjectTeaching.json`/`LaserScannerTeaching.json`/`PLCRecipe.json`/`VisionInspectionRecipe.json` 은 **전부 빈 `{}`(미사용)**
|
||||
- **버그**: 페이지 이동해도 그 페이지 필드를 다시 안 불러옴 → 항상 마지막 머문 페이지(보통 Page1) 필드로 ScoutNormal 동작
|
||||
- 원인: `LoadScoutField()`(MainPage.cs:499/502)가 GUI 전역 `selectedIndexNo`만 키로 사용. 이 값은 WinForms 티칭 콤보박스 `toolStripComboBoxPage_SelectedIndexChanged`(1393)에서만 갱신 → 운전(WPF)/자동운전/시퀀스 경로에선 stale
|
||||
- 비대칭: 저장 `UpdateScoutField`는 라이브 `SelectedIndex`(490) 사용해 페이지별 정상 저장되나, **로드만 stale**
|
||||
- `SeqScoutNormal_InitParam`(SeqScoutNormal.cs:373)엔 페이지 인덱스 필드 없음 → 시퀀스가 처리 페이지를 모름
|
||||
|
||||
### 작업지시서 작성 (방식 A 채택, 소스는 미수정)
|
||||
- 문서: `docs/작업지시서/2026-06-19_ScoutField_페이지인덱스_시퀀스주입.md`
|
||||
- **A안**: InitParam에 `PageIndex` 추가 → `MainPage.LoadScoutField(int)` 오버로드 + `GetCurrentPageIndex()`(콤보박스 라이브값, fallback ActiveLayer.No) → `SeqScoutNormal.cs:151`에서 `_InitParam.PageIndex`로 로드 → 호출부 2곳(`UCScannerObjectTeachingControlBoxViewModel.cs:251`, `UCTestSequenceViewModel.cs:361`)에서 주입
|
||||
- **null 가드 확정**: `LoadScoutField(PageIndex) ?? LoadScoutField() ?? new List<>()` (인덱스 초과 시 NRE 방지, 빈 리스트면 스캔 대상 없이 정상 종료)
|
||||
- B안(GUI 콤보박스 동기화)은 위험성으로 제외
|
||||
- 기존 소스는 주석 처리 / 신규·변경 라인은 의도 주석 표기 형식으로 작성 (Before/After 포함)
|
||||
- **상태: 도연님이 더블체크 후 git 반영 예정 → 추후 pull 받아 적용** (이번엔 소스 변경/커밋 없음)
|
||||
|
||||
## 주말 출근 예정
|
||||
- **06.20(토)~06.21(일) 주말 출근 예정**
|
||||
|
||||
198
docs/작업지시서/2026-06-19_ScoutField_페이지인덱스_시퀀스주입.md
Normal file
198
docs/작업지시서/2026-06-19_ScoutField_페이지인덱스_시퀀스주입.md
Normal file
@ -0,0 +1,198 @@
|
||||
# 작업지시서 — ScoutField 페이지별 로드 버그 수정 (시퀀스 PageIndex 주입, 방식 A)
|
||||
|
||||
- 작성일: 2026-06-19
|
||||
- 대상 브랜치: `field/ptop-1st`
|
||||
- 작업 분류: 버그 수정 (스카우트 필드 페이지 정합성)
|
||||
- 방식: **A안 — `SeqScoutNormal`에 처리 중인 페이지 번호를 명시적으로 주입** (B안=GUI 콤보박스 동기화는 위험성으로 제외)
|
||||
- ⚠️ 본 문서는 **수정안(검토용)** 입니다. 아직 소스는 변경하지 않았습니다. 다른 PC에서 본 지시서대로 적용하세요.
|
||||
|
||||
---
|
||||
|
||||
## 1. 증상 (현장 보고)
|
||||
|
||||
레시피별·페이지별로 "Selected Scout Field" 팝업에서 선택한 필드는 정상 저장되어 있음에도,
|
||||
- 레시피를 활성화하거나 페이지1 ↔ 페이지2를 이동한 뒤
|
||||
- 페이지2에서 저장한 필드가 아니라 **페이지1(또는 마지막에 머문 페이지)에서 저장한 필드 형태로** `SeqScoutNormal`(ScoutImageNormal)이 동작/표시됨.
|
||||
- 즉, 페이지를 이동해도 그 페이지의 스카우트 필드를 다시 불러오지 않음.
|
||||
|
||||
## 2. 데이터 저장 구조 (분석 결과)
|
||||
|
||||
- "Selected Scout Field" 팝업 값의 실제 저장처는 `_config_recipe/<레시피>/FieldHelper.json`.
|
||||
- `ScannerObjectTeaching.json` / `LaserScannerTeaching.json` / `PLCRecipe.json` / `VisionInspectionRecipe.json`은 **전부 빈 `{}`(미사용)**.
|
||||
- `FieldHelper.json` = 페이지 배열. 각 원소 `{ PageName:"selectedIndex{N}", FileName:<배경 이미지>, FieldScans:[ ChildBufRowCol × 80 (10행×8열) ] }`.
|
||||
- `ChildBufRowCol` 키: `Enabled`(선택 여부=실제 스카우트 대상), `RowIdx`/`ColIdx`/`PosIdx`, `MotorPos{X,Y}`, `MotorPosOffset{X,Y}`, `Rect`.
|
||||
- 실측: 레시피별 2~5페이지가 distinct 하게 저장됨(저장 자체는 정상).
|
||||
|
||||
## 3. 근본 원인 (코드 레벨)
|
||||
|
||||
### 3-1. 시퀀스가 페이지 번호를 모름
|
||||
- `WeldingComponents/WeldingCommon/Sequences/Main/SeqScoutNormal.cs:151`
|
||||
```csharp
|
||||
_ChildBufRowCols = mainPage.LoadScoutField(); // 인자 없음
|
||||
```
|
||||
- `SeqScoutNormal_InitParam`(SeqScoutNormal.cs:373)에는 **페이지 인덱스 필드가 없음.**
|
||||
- `LoadScoutField()`(MainPage.cs:499)는 전적으로 GUI 상태 변수 `selectedIndexNo`만 키로 사용:
|
||||
```csharp
|
||||
public List<ChildBufRowCol> LoadScoutField()
|
||||
=> RecipeFieldScan.GetPageFieldScan(GetAutoPageName(selectedIndexNo));
|
||||
```
|
||||
|
||||
### 3-2. `selectedIndexNo`는 WinForms 티칭 콤보박스에서만 갱신됨
|
||||
- `MainPage.cs:1379~1393` `toolStripComboBoxPage_SelectedIndexChanged`에서만 `selectedIndexNo = box.SelectedIndex` 로 대입.
|
||||
- 운전 화면(Recipe/Manual, WPF) 페이지 이동·자동운전·시퀀스 실행 경로에서는 갱신되지 않아 **stale** 상태가 됨 → 항상 "티칭 콤보박스가 마지막으로 머문 페이지"(보통 0=Page1) 로드.
|
||||
|
||||
### 3-3. 저장/로드 비대칭 (참고)
|
||||
| 동작 | 사용 인덱스 |
|
||||
|---|---|
|
||||
| 저장 `UpdateScoutField` (MainPage.cs:490) | `toolStripComboBoxPage.SelectedIndex` (라이브) |
|
||||
| 로드 `LoadScoutField` (MainPage.cs:502) | `selectedIndexNo` (캐시) |
|
||||
|
||||
→ 저장은 라이브 값이라 페이지별로 맞게 저장되지만, 로드는 캐시 값이라 stale → "저장은 맞고 로드만 틀림".
|
||||
|
||||
## 4. 수정 방침 (A안)
|
||||
|
||||
GUI 전역 상태(`selectedIndexNo`) 의존을 제거하고, **처리 대상 페이지 번호를 시퀀스 InitParam으로 명시 주입**한다.
|
||||
|
||||
1. `SeqScoutNormal_InitParam`에 `PageIndex` 추가.
|
||||
2. `MainPage`에 페이지 인덱스를 받는 `LoadScoutField(int pageIndex)` 오버로드 추가 + 현재 표시 페이지를 돌려주는 공개 접근자 `GetCurrentPageIndex()` 추가. 기존 `LoadScoutField()`는 호환 위해 보존(주석 설명).
|
||||
3. `SeqScoutNormal`이 `_InitParam.PageIndex`로 로드하도록 변경.
|
||||
4. 호출부 2곳에서 InitParam 생성 시 `PageIndex = CommonHelper.GetMainPage().GetCurrentPageIndex()` 주입.
|
||||
|
||||
> 규칙: 기존 라인은 삭제하지 말고 **주석 처리**, 신규/변경 라인은 주석으로 의도 표기.
|
||||
|
||||
---
|
||||
|
||||
## 5. 파일별 변경안 (Before/After)
|
||||
|
||||
### [변경 1] `WeldingComponents/WeldingCommon/Sequences/Main/SeqScoutNormal.cs` — InitParam에 PageIndex 추가
|
||||
위치: 373~378행
|
||||
|
||||
```csharp
|
||||
public class SeqScoutNormal_InitParam : InitParam
|
||||
{
|
||||
public bool IsRealTimeScout { get; set; }
|
||||
public ScoutSeqParams ScoutSeqParams { get; set; }
|
||||
|
||||
// [추가 2026-06-19] 스카우트 필드를 불러올 페이지 인덱스(0-base).
|
||||
// 기존: SeqScoutNormal 이 MainPage.selectedIndexNo(GUI 전역 상태)에만 의존 → 페이지 이동/자동운전 시 stale.
|
||||
// 변경: 호출부가 "현재 처리/표시 중인 페이지 번호"를 명시 주입하도록 함.
|
||||
public int PageIndex { get; set; } = 0;
|
||||
}
|
||||
```
|
||||
|
||||
### [변경 2] `WeldingComponents/WeldingCommon/Sequences/Main/SeqScoutNormal.cs` — PageIndex로 로드
|
||||
위치: 150~152행 (`InitSequenceVariable` 내부)
|
||||
|
||||
```csharp
|
||||
var mainPage = CommonHelper.GetMainPage();
|
||||
// [기존 2026-06-19 주석처리] GUI 전역 상태(selectedIndexNo)에 의존 → 페이지 정합성 버그
|
||||
//_ChildBufRowCols = mainPage.LoadScoutField();
|
||||
// [변경 2026-06-19] InitParam으로 전달된 페이지 인덱스로 명시 로드
|
||||
// null 가드: 해당 페이지가 FieldHelper.json에 없으면(인덱스 초과 등) GetPageFieldScan이 null →
|
||||
// 기존 LoadScoutField()(selectedIndexNo 기반)로 폴백, 그래도 null이면 빈 리스트로 NRE 방지.
|
||||
_ChildBufRowCols = mainPage.LoadScoutField(_InitParam.PageIndex)
|
||||
?? mainPage.LoadScoutField()
|
||||
?? new List<ChildBufRowCol>();
|
||||
_ScoutNoList = GenerateSortedScoutNoList(_ChildBufRowCols);
|
||||
_ScoutNo = 0;
|
||||
```
|
||||
※ `_InitParam`은 `Sequence_Enter`(107행)에서 이미 세팅되며, `InitSequenceVariable`은 그 이후 호출되므로 null 아님. (호출 순서: Sequence_Enter → InitSequenceVariable)
|
||||
※ `List<ChildBufRowCol>` 사용을 위해 `using System.Collections.Generic;` 존재 확인(SeqScoutNormal.cs는 이미 포함). `ChildBufRowCol`은 `VisionCommon`(이미 using).
|
||||
※ 빈 리스트로 폴백되면 `_ScoutNoList`도 비어 `CaptureNextPosition`의 foreach가 즉시 종료 → 스캔 대상 없이 정상 진행(크래시 없음). 필요 시 빈 경우 Alarm/Log 추가 검토.
|
||||
|
||||
### [변경 3] `WeldingComponents/LASA/GUI/Page/MainPage.cs` — 오버로드 + 현재 페이지 접근자 추가
|
||||
위치: 499~503행 (`LoadScoutField()`)
|
||||
|
||||
```csharp
|
||||
public List<ChildBufRowCol> LoadScoutField()
|
||||
{
|
||||
// [기존 보존 2026-06-19] selectedIndexNo(GUI 캐시) 기반. 하위 호환용으로 남겨둠.
|
||||
return DB.RecipeFieldScan.Instance.GetPageFieldScan(
|
||||
DB.RecipeFieldScan.Instance.GetAutoPageName(selectedIndexNo));
|
||||
}
|
||||
|
||||
// [추가 2026-06-19] 페이지 인덱스를 명시 지정해 스카우트 필드 로드 (시퀀스 PageIndex 주입용)
|
||||
public List<ChildBufRowCol> LoadScoutField(int pageIndex)
|
||||
{
|
||||
return DB.RecipeFieldScan.Instance.GetPageFieldScan(
|
||||
DB.RecipeFieldScan.Instance.GetAutoPageName(pageIndex));
|
||||
}
|
||||
|
||||
// [추가 2026-06-19] 현재 화면에 표시 중인 페이지 인덱스(라이브). 콤보박스 우선, 없으면 ActiveLayer.No.
|
||||
// 로드 키를 stale 캐시(selectedIndexNo) 대신 라이브 값으로 얻기 위함.
|
||||
public int GetCurrentPageIndex()
|
||||
{
|
||||
int idx = toolStripComboBoxPage.SelectedIndex;
|
||||
if (idx < 0) {
|
||||
var active = ScannerCanvasCtrlTeaching?.Model?.ActiveLayer;
|
||||
idx = active != null ? active.No : 0;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
```
|
||||
※ `selectedIndexNo`(1379행), `toolStripComboBoxPage`(디자이너 필드), `ScannerCanvasCtrlTeaching`(202행 public)는 모두 동일 클래스/접근 가능. `GetCurrentPageIndex`는 WinForms UI 스레드에서 호출되어야 함(아래 6절 주의 참고).
|
||||
|
||||
### [변경 4] `WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs` — 호출부 주입
|
||||
위치: 251~254행 (`PerformCmdRunScoutNormal`)
|
||||
|
||||
```csharp
|
||||
var scoutSeqParams = new ScoutSeqParams(VisionRecipeManager.GetFovInfos()[0], eDisplayType.Machine_B);
|
||||
var param = new SeqScoutNormal_InitParam() {
|
||||
IsRealTimeScout = false,
|
||||
ScoutSeqParams = scoutSeqParams,
|
||||
// [추가 2026-06-19] 현재 표시 중인 페이지의 스카우트 필드를 사용하도록 명시 주입
|
||||
PageIndex = CommonHelper.GetMainPage().GetCurrentPageIndex(),
|
||||
};
|
||||
```
|
||||
※ 파일 상단 `using WeldingCommon.Utils;` 존재 여부 확인(미존재 시 추가). `CommonHelper`는 `WeldingCommon.Utils` 네임스페이스.
|
||||
|
||||
### [변경 5] `WeldingComponents/WeldingCommon/Views/Test/UCTestSequenceViewModel.cs` — 테스트 호출부 주입
|
||||
위치: 361~364행 (`PerformCmdRunSeqScoutNormal`, case "Run")
|
||||
|
||||
```csharp
|
||||
var param = new SeqScoutNormal_InitParam() {
|
||||
IsRealTimeScout = false,
|
||||
ScoutSeqParams = new ScoutSeqParams(VisionRecipeManager.GetFovInfos()[0], eDisplayType.Machine_B),
|
||||
// [추가 2026-06-19] 현재 표시 중인 페이지 인덱스 주입
|
||||
PageIndex = CommonHelper.GetMainPage().GetCurrentPageIndex(),
|
||||
};
|
||||
```
|
||||
※ 동일하게 `CommonHelper`(WeldingCommon.Utils) using 확인.
|
||||
|
||||
---
|
||||
|
||||
## 6. 검토/주의 사항 (적용 전 확인)
|
||||
|
||||
1. **스레드**: `GetCurrentPageIndex()`는 WinForms 컨트롤(`toolStripComboBoxPage`)을 읽음. 두 호출부 모두 WPF UI(Dispatcher) 스레드에서 실행되며 MainPage는 같은 UI 스레드에 호스팅되므로 정상. 만약 백그라운드 스레드에서 호출될 가능성이 생기면 `Invoke` 래핑 필요.
|
||||
2. **페이지 인덱스 유효성 — null 가드 [확정 적용]**: `GetCurrentPageIndex()`가 반환한 인덱스가 `FieldHelper.json` 페이지 수를 초과하면 `GetPageFieldScan`이 `null` 반환 → `GenerateSortedScoutNoList(null)` NRE 위험. **변경 2에 null 가드를 반드시 포함**한다.
|
||||
```csharp
|
||||
_ChildBufRowCols = mainPage.LoadScoutField(_InitParam.PageIndex)
|
||||
?? mainPage.LoadScoutField() // selectedIndexNo 기반 폴백
|
||||
?? new List<ChildBufRowCol>(); // 그래도 null이면 빈 리스트(NRE 방지)
|
||||
```
|
||||
빈 리스트로 폴백되면 스캔 대상 없이 정상 종료(크래시 없음). 운영 판단에 따라 빈 경우 Alarm/Log 추가 가능(이번 범위 외).
|
||||
3. **자동운전 경로**: 현재 PTOP에서 `SeqScoutNormal`은 위 두 VM에서만 Start됨(SequenceManager는 등록만, AlarmManager는 Stop만). 자동운전 시퀀스가 추후 ScoutNormal을 페이지 루프로 호출하게 되면, 그 호출부에서도 처리 중 페이지를 `PageIndex`로 넣어야 함(동일 패턴).
|
||||
4. **저장 경로**: 본 수정은 로드만 교정. 저장(`UpdateScoutField`)은 이미 라이브 `SelectedIndex` 사용이라 변경 불필요. 단 일관성을 위해 추후 저장도 `GetCurrentPageIndex()`로 통일 검토 가능(이번 범위 외).
|
||||
|
||||
## 7. 영향 범위 / 빌드
|
||||
|
||||
- 수정 파일 4개:
|
||||
- `WeldingComponents/WeldingCommon/Sequences/Main/SeqScoutNormal.cs`
|
||||
- `WeldingComponents/LASA/GUI/Page/MainPage.cs`
|
||||
- `WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs`
|
||||
- `WeldingComponents/WeldingCommon/Views/Test/UCTestSequenceViewModel.cs`
|
||||
- 기존 `LoadScoutField()` 시그니처 보존 → 다른 호출부 영향 없음(호출부: SeqScoutNormal만, 본 작업에서 오버로드로 전환).
|
||||
- 빌드 대상: `WeldingCommon`, `LASA(GUI)`, 시작 프로젝트(PTOP).
|
||||
|
||||
## 8. 검증 시나리오 (현장)
|
||||
|
||||
1. 레시피 활성화 → 페이지1에서 필드 A1 저장, 페이지2에서 필드 A2 저장.
|
||||
2. 페이지2 표시 상태에서 ScoutNormal 실행 → **A2**로 스캔/표시되는지 확인.
|
||||
3. 페이지1로 이동 후 실행 → **A1** 확인.
|
||||
4. 페이지2 재이동 후 실행 → **A2** 확인 (이전엔 A1으로 동작하던 버그).
|
||||
5. 페이지 수보다 큰 인덱스/예외 상황에서 NRE 없이 동작(6-2 null 가드 — 폴백 후 빈 리스트로 정상 종료).
|
||||
|
||||
## 9. 롤백
|
||||
|
||||
- 변경 1~5의 신규 라인 제거 및 주석 처리한 기존 라인 복원.
|
||||
- 데이터(`FieldHelper.json`) 변경 없음 → 데이터 롤백 불필요.
|
||||
Loading…
x
Reference in New Issue
Block a user