diff --git a/Doc/인수인계/260903_Door인터락_현장검증_가이드.html b/Doc/인수인계/260903_Door인터락_현장검증_가이드.html new file mode 100644 index 0000000..4fd4c35 --- /dev/null +++ b/Doc/인수인계/260903_Door인터락_현장검증_가이드.html @@ -0,0 +1,293 @@ + + + + +Door 인터락 현장 검증 가이드 (2026-09-03) + + + +
+ + +
+ 대상 화면 Manual → Scanner → Manual Laser Power Control +  ·  소스 UCScannerLaserControl.xaml +
+ +
+ 시작 전 필수 + +
+ +

1. 화면 배치와 조작 위치

+

+ 아래는 XAML 기준 실제 배치입니다. Watt 패널은 Visibility="Collapsed" 라 화면에 안 나옵니다 — % 패널로만 진행합니다. +

+ +
+
+
Manual Laser Power Control
+
+ +
+
+
Control Setting
+
2Power #110%
+
Power #20%
+
Frequency1kHz
+
Pulse Width1000000us
+
+
+ +
+
+
Laser Shot Control
+
+
+
+
Timer Set
+
3연속출사로 전환
+
+
No Use Spot Time
Use Spot Time
+
+
+ ← 이쪽이 자동 오프 없는 연속출사 (이번 사고 경로) +
+ +
4실제 발진
+
+
Laser Fire
No Laser (전압만)
+
+
+ ※ DA1/DA2 전압 표시는 No Laser 쪽일 때만 보임 +
+
+
+ +
+
+
Laser Shot Result
+
ListN/A
+
Repeat Count0
+
+
+
5Laser On
+
Laser Off
+
+
+
+
+
+ +
+
+
+ +
+ 1 이 화면을 반드시 먼저 띄워야 합니다. + 출사 중 Door 감시 타이머는 VisibleChanged 로 시작됩니다. 화면을 한 번도 안 열면 + 감시가 돌지 않습니다. (단, 이 작업 자체가 이 화면에서만 하는 것이라 실사용상 문제는 없습니다) +
+ +

2. 검증 순서

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#조작기대 결과판정
T1문을 연 채 [Laser On]
(가장 안전한 첫 테스트)
출사 안 됨 + 노란 경고 토스트
+ Door Open 상태입니다. 문을 닫고 다시 시도하세요.
차단 정상
출사 → §3
T2문 닫고 [Laser On]정상 출사 시작. 계속 출사 상태 유지
(자동 오프 없음)
출사 정상
차단 → §3
T3출사 중에 문을 연다
★ 이번 사고 시나리오
약 0.2초 내 강제 OFF + 빨간 토스트
+ Door Open 감지 → 레이저를 강제 OFF 했습니다.
OFF 정상
유지 → §3
T4문을 다시 닫는다
(아무것도 누르지 않음)
자동 재출사 안 됨 — 의도된 동작정지유지
T5[Laser On] 다시 누름정상 출사 재개출사
T6[Laser Off] 로 종료출사 종료, 감시 래치 해제—
+ +

3. 로그 확인 — 극성 판별

+

로그에서 [DoorInterlock] 으로 검색하면 전부 나옵니다.

+ +
+ 정상 (문 열림에서 True 가 하나라도 뜸) +
[DoorInterlock][ScannerLaserControl] Door OPEN 감지 → 이후 레이저 출사 차단 (PLC=True, IO_DI=True)
+[DoorInterlock] 출사 중 Door OPEN 감지 → 레이저 강제 OFF 실행 (직접 출사 중). PLC비트=True, IO접점=True, PLC연결=True, IO연결=True
+[DoorInterlock] 레이저 강제 OFF 완료. 재출사하려면 문을 닫고 Shot On 을 다시 누르십시오.
+
PLC / IO 둘 중 하나만 True 여도 정상 동작합니다(OR 판정). 어느 쪽이 False 인지는 알려주세요 — 배선/맵 확인 대상입니다.
+
+ +
+ 이상 ①: 문 열었는데 둘 다 False → T1/T3 에서 차단이 안 됨 +
[DoorInterlock] ... (PLC=False, IO_DI=False)
+ +
+ +
+ 이상 ②: 문 닫았는데 True → T2 에서 출사가 막힘 (반대 증상) +
문 닫힘 상태인데 → (PLC=True, IO_DI=True)
+
극성 반대 확정입니다. 판정부 한 줄만 뒤집으면 되니 로그값 그대로 보내주세요.
+
+ +
+ 롱텀 추적용 하트비트 — 출사 중에는 10초마다 아래가 남습니다. 1시간 출사 구간 로그 공백이 없어집니다. +
[DoorInterlock] 직접 출사 중... Door=CLOSE, DA1=1.000V, DA2=0.000V, PLC비트=False, IO접점=False, ...
+
+ +

4. 이 인터락이 못 막는 구간

+ + + + + + + + + + + + + + + + + +
경로차단 시점완전성
파워측정 직접 출사
(No Use Spot Time)
출사 시작 전 + 출사 중 100ms 주기즉시
Teaching Run 시퀀스시퀀스 진입 시 + 객체 사이사이
+ 리스트 1개 실행 중에는 개입 불가
부분
Auto 운전이번 범위 아님 — SeqAutoRunPTOP 은 자체 ExecuteWelding 사용미적용
+ +
+ 본질적 한계 — 반드시 같이 보고할 것
+ 이건 SW 방어이지 안전 인터락이 아닙니다. Door 감시가 UI 스레드 DispatcherTimer 기반이라 + UI가 멈추면 감시도 같이 멈춥니다. Class 4 레이저 기준 최종 차단은 + PLC / 하드와이어 안전회로로 별도 협의가 필요합니다. +
+ +
+ 변경 파일 3개 — AlarmManager.cs (PLC비트 OR IO접점 판정) · + UCScannerLaserControlViewModel.cs (시작 게이트 + 출사 중 감시) · + SeqComponentTeachingRun.cs (주석 처리됐던 차단 복구 + 출사 직전 게이트)
+ 미결: EMO 차단 복구 · Auto 시작 게이트(UCMainAutoRunViewModel 399~431행 블록 주석) · 하드와이어 안전회로 +
+ +
+ + diff --git a/Doc/인수인계/260903_파워캘_3000W오적용_레시피환산.md b/Doc/인수인계/260903_파워캘_3000W오적용_레시피환산.md new file mode 100644 index 0000000..1c7da75 --- /dev/null +++ b/Doc/인수인계/260903_파워캘_3000W오적용_레시피환산.md @@ -0,0 +1,281 @@ +# 파워캘 CSV 3000W 오적용 및 레시피 Power 환산 (2026-09-03) + +> **목적** : 2000W 발진기에 3000W 기준 파워캘 CSV가 적용되어 있어 레시피 지령값의 2/3만 +> 실제 출사되던 건의 원인·근거·환산값을 남긴다. +> +> - 대상 설비 : PtoP 각형화 단동 용접기 (유니셀) +> - 관련 문서 : `Doc/인수인계/2026_08_28_레이저발진기교체_및_진행현황.md` §2.4 +> - 파라미터 원본 : `docs/파라미터PTOP_260903.xlsx` 시트 `260903` + +**표기 규칙** (기존 인수인계 문서와 동일) + +| 표기 | 의미 | +|---|---| +| 🟩 **소스확인** | PC 소스/설정 파일에서 직접 대조해 확인한 사실 | +| 🟦 **현장기록** | 현장 로그·시험 기록에 남아 있는 값 | +| 🟨 **미확보** | 아직 확인되지 않음. 이어받는 사람이 채워야 할 항목 | + +--- + +## 1. 한 줄 요약 + +발진기는 **2000W/2000W (Core/Ring, 싱글모드)** 인데 파워캘 CSV가 **만점 3000W** 짜리로 물려 있어, +레시피에 적힌 W의 **2/3만 실제로 출사**되고 있었다. CSV를 2000W 기준으로 재작성했으므로 +**레시피의 Power #1 / #2 를 × 2/3 으로 환산**해야 종전과 같은 실출력이 유지된다. + +``` +레시피 1500W → 지령 50% → DA 5.0V → 2000W × 50% = 1000W 출사 +``` + +--- + +## 2. 원인 🟩 + +### 2.1 레시피 W 는 발진기로 그대로 가지 않는다 + +`SeqUploadWeldingList.cs:639-671` — 자동 용접 시 파워를 싣는 경로. + +``` +레시피 LaserPower1/2 (W) + → CalibrateWattToPercent(W) ← 파워캘 CSV 조회 + → percentLaser1/2 (0~100) + → WriteDA(DA1/DA2, percent) ← ×40.95 → 12bit DAC → 0~10V + → 발진기 아날로그 입력 = 자기 정격의 그 % +``` + +**CSV의 만점이 곧 "설비가 믿고 있는 발진기 정격"** 이다. +`MaxCenterPower` / `MaxRingPower` 도 레이저 Config 가 아니라 **CSV 마지막 행**에서 읽는다 +(`LaserPowerCalParam.cs:70-71`). 발진기를 바꿔도 CSV 를 안 바꾸면 소프트웨어는 옛 정격으로 계산한다. + +### 2.2 실제로 참조되는 열 + +현장은 로그상 `Normal Models::` 분기(`TruFiberType` 미지정)를 탄다. 이 분기의 `CalibrateWattToPercent` 가 +보는 열은 두 개뿐이다. + +| 열 | 이름 | 용도 | +|---|---|---| +| 0 | Index (%) | **반환값 — 그대로 `WriteDA` 지령** | +| 1 | DesiredPower | 이 분기에선 미사용 | +| 2 | CalibratedPower | **조회 키 — 레시피 W 를 여기서 찾음** | +| 3 | volt | 이 분기에선 미사용 | + +> `TruFiberType` 을 `TruFiberP` / `CalPercentToMeasuredVoltage` 로 바꾸면 3열(전압)을 쓰는 +> 다른 분기로 넘어간다. CSV 를 새로 만들 때는 **어느 분기를 타는지부터 확인**할 것. + +--- + +## 3. 근거 + +### 3.1 로그 실측 🟦 + +`Original Power → Calibrated Percent` 가 전부 정확히 `W ÷ 3000 × 100` 이다. + +| 로그 | 지령 W | Calibrated % | 검산 | +|---|---|---|---| +| Aurora_20260825.log | 150 | 5.000 | 150/3000 | +| Aurora_20260826.log | 500 | 16.667 | 500/3000 | +| Aurora_20260826.log | 800 | 26.667 | 800/3000 | +| Aurora_20260826.log | 600 | 20.000 | 600/3000 | +| Aurora_20260826.log | 1100 | 36.667 | 1100/3000 | + +Center · Ring 양쪽 모두 3000 으로 나뉘었다. + +### 3.2 CSV 대조 🟩 + +| 파일 | 관계 | 성격 | +|---|---|---| +| `docs/3000W_CenterPower.csv` · `3000W_RingPower.csv` (구) | 파워 = **30 × Index** | 선형, 보정 없음 (CalibratedPower = DesiredPower) | +| `docs/2000W_CenterPower.csv` · `2000W_RingPower.csv` (신) | 파워 = **20 × Index** | 선형, 보정 없음 | + +**양쪽 다 선형이므로 환산계수 `2000/3000 = 2/3` 는 근사가 아니라 정확값이다.** + +`volt` 열이 Center/Ring 미세하게 다르지만(50% 에서 5.0 vs 4.8) `Normal Models` 분기는 이 열을 쓰지 않는다. + +### 3.3 ⭐ 교차검증 — 「출력을 올려야 했다」의 정체 🟦 + +기존 인수인계 문서 §2.4 에 **발진기 교체 후 기존 출력으로는 접합이 되지 않아 출력을 올렸다**는 기록이 있다. +이번 건으로 계산해 보면 **실제 출사 파워는 거의 그대로였다.** + +| 12_1page (CC Anode) | 레시피 값 | 당시 CSV | 실제 출사 | 결과 | +|---|---|---|---|---| +| 교체 전 (3000W 발진기) | 450 | 3000 (정상) | **450W** | 접합 O | +| 교체 직후 | 450 | 3000 (오적용) | **300W** | 접합 X | +| 탐색 | 500 / 550 | 3000 (오적용) | 333 / 367W | 접합 X | +| 최종 적용 | **650** | 3000 (오적용) | **433W** | 접합 O | + +즉 **접합에 필요한 실출력은 계속 430~450W 였고**, 레시피 숫자를 450 → 650 으로 올린 것은 +공정상 필요가 아니라 **CSV 오적용을 사람이 손으로 보정한 것**이었다. + +× 2/3 환산으로 650 → **433** 이 되는데, 이는 교체 전 정상값 450 과 사실상 같은 실출력이다. +(싱글모드 전환으로 파워밀도가 높아져 약간 낮은 값에서 접합되는 것도 방향이 맞다.) + +> 12_2page · 12_3page 는 Ramping 변경(50%→70%)과 싱글모드 효과가 섞여 있어 이만큼 깔끔하게 떨어지지 않는다. + +--- + +## 4. 환산 + +### 4.1 규칙 + +``` +새 레시피 W = 기존 레시피 W × 2/3 +``` + +### 4.2 환산 대상 / 제외 🟩 + +| 항목 | 환산 | 이유 | +|---|---|---| +| **Power #1 (Core, W)** | **× 2/3** | 절대 W → 지령 % 로 변환되는 값 | +| **Power #2 (Ring, W)** | **× 2/3** | Ring 표도 만점 3000 선형이었음 | +| Ramping Start/End Power | 그대로 | **%** 단위. `percentLaser1 × RampingStartPower / 100` 이라 자동 추종 (`SeqUploadWeldingList.cs:1319-1323`) | +| Laser Duty | 그대로 | % | +| Draw/Jump speed, 각종 Delay | 그대로 | 파워 무관 | +| Frequency / Pulse Duration | 그대로 | 파워 무관 | +| Wobbel · Zigzag | 그대로 | 파워 무관 | +| Center/Ring 비율 | 자동 | 코드가 `round(100×P1/(P1+P2))` 로 매번 계산 | +| 레이저 프로그램 워드 IW03/IW04 | 자동 | Power #1/#2 에서 파생 (`SeqWaitWeldOrChangePage.cs:205-223`) | + +### 4.3 환산표 — `파라미터PTOP_260903.xlsx` 시트 `260903` 기준 🟦 + +| 레시피 | No | Page | 항목 | 기존 (W) | 지령 % | **변경 (W)** | +|---|---|---|---|---|---|---| +| cc welding | 12 | 1 (직렬 음) | Power #1 | 650 | 21.67 | **433** | +| | | | Power #2 | 1050 | 35.00 | **700** | +| | | 2 (sensing) | Power #1 | 550 | 18.33 | **367** | +| | | | Power #2 | 1050 | 35.00 | **700** | +| | | 3 (직렬 양) | Power #1 | 600 | 20.00 | **400** | +| | | | Power #2 | 1200 | 40.00 | **800** | +| U-Frame_pre | 21 | 1~6 | Power #1 | 700 | 23.33 | **467** | +| | | | Power #2 | 900 | 30.00 | **600** | +| U-Frame_Back_pre | 22 | 1~6 | Power #1 | 700 | 23.33 | **467** | +| | | | Power #2 | 900 | 30.00 | **600** | +| U-Frame_Main | 23 | 1~6 | Power #1 | 700 | 23.33 | **467** | +| | | | Power #2 | 900 | 30.00 | **600** | +| U-Frame_Back_Main | 24 | 1~6 | Power #1 | 700 | 23.33 | **467** | +| | | | Power #2 | 900 | 30.00 | **600** | +| Top_Frame | 31 | 1~2 | Power #1 | 700 | 23.33 | **467** | +| | | | Power #2 | 900 | 30.00 | **600** | +| Top_Frame | 32 | 1~2 | Power #1 | 700 | 23.33 | **467** | +| | | | Power #2 | 900 | 30.00 | **600** | + +고유값 7개 — `550→367, 600→400, 650→433, 700→467, 900→600, 1050→700, 1200→800` + +### 4.4 검산 + +**지령 % 불변** — 환산 목적이 "기존에 실제로 나가던 파워 재현"이므로 지령 % 와 DA 전압은 변하지 않아야 한다. + +``` +기존 : 700W ÷ 30 = 23.33% → DA 2.33V → 2000W × 23.33% = 467W +신규 : 467W ÷ 20 = 23.35% → DA 2.33V → 2000W × 23.35% = 467W ✅ +``` + +**Center/Ring 비율 유지** — 반올림 후에도 전부 동일하다. + +| | 기존 | 신규 | +|---|---|---| +| cc 1page (650/1050) | 38 | 38 (433/700) | +| cc 2page (550/1050) | 34 | 34 (367/700) | +| cc 3page (600/1200) | 33 | 33 (400/800) | +| U-Frame · Top (700/900) | 44 | 44 (467/600) | + +정수 반올림 오차는 최대 0.08 % (433 vs 433.33) 로 무시 가능하다. + +--- + +## 5. ⚠ 주의 + +### 5.1 환산 전에 신 CSV 만 적용된 상태는 위험하다 🟩 + +| 기존 레시피 값 | 신 CSV 에서의 결과 | +|---|---| +| 2000 이하 | 의도의 **1.5배 과출력** | +| 2000 초과 | `CalibrateWattToPercent` 가 구간을 못 찾아 **0 반환 → 0V, 무출력** | + +같은 레시피 안에서 과출력 라인과 무출력 라인이 섞인다. 환산은 한 번에 끝낼 것. +(환산 후에는 모든 값이 2000 이하가 되어 이 함정은 사라진다.) + +### 5.2 지령 상한 클램프가 없다 🟩 + +`percentLaser1/2` 는 `WriteDA` 로 가기 전 **상한 검사를 받지 않는다** (`SeqUploadWeldingList.cs:665-671`). +램핑 구간만 `<= 100 ? … : 100` 으로 막혀 있다. + +따라서 **열 구성이 어긋난 CSV**(예: 0열에 Index % 대신 W 가 들어간 파일)를 물리면 +`WriteDA(600)` → `600 × 40.95 = 24570` → DAC 풀스케일 부근 → **정격 전출력**이 나간다. +CSV 를 교체할 때는 0열이 0~100 인지 반드시 확인할 것. + +### 5.3 환산은 W 를 복원할 뿐, 용접 결과를 복원하지 않는다 🟦 + +듀얼모드(3000W/3000W) → **싱글모드(2000W/2000W)** 전환이라 빔 품질(BPP/M²)이 달라졌다. +같은 W 라도 집광 스팟이 작아 **파워밀도(W/mm²)가 훨씬 높다.** + +| | 환산으로 해결 | 남는 것 | +|---|---|---| +| 실제 출사 W | ✅ 종전과 동일 복원 | — | +| 용입 깊이 | ❌ | 싱글모드라 더 깊음 | +| 비드 폭 | ❌ | 싱글모드라 더 좁음 | + +현장 기록에도 싱글모드는 **"깊게 쏨 / 얇게 나옴"** 으로 적혀 있다. +환산값은 **재튜닝의 출발점**이지 최종값이 아니다. 실물 비드 기준 재검증이 필요하다. + +--- + +## 6. 추적성 — 어떤 CSV 가 물렸는지 확인하는 방법 🟩 + +**소프트웨어는 적용된 CSV 의 경로도 값도 로그에 남기지 않는다.** 남는 것은 아래뿐이다. + +| 방법 | 얻는 것 | +|---|---| +| `_config_setting/LaserPowerCalParam.json` | **현재** 물린 Center/Ring CSV 경로 (이력 없음, 덮어쓰기) | +| 로그 `AURORA_SYS_SETTING_CHANGED :: Param[LaserPowerCalParam]` | 설정을 **바꾼 시점** (경로는 안 나옴) | +| 로그 `Original Power1 : X … Calibrated Percent1 : Y` | **X ÷ Y × 100 = 그 시점 CSV 만점** ← 가장 확실한 역산 수단 | +| CSV 파일 수정일시 | 경로 그대로 두고 내용만 고친 경우의 유일한 단서 | + +> 출사 시마다 `Initialize()` 가 CSV 를 다시 읽으므로(`UCScannerLaserControlViewModel.cs:332`, `:422`, +> `SeqUploadWeldingList.cs:154`), 앱 재기동 없이 파일 내용만 바꿔도 즉시 반영된다. + +### 이력 확인 결과 🟦 + +| 로그 | `Param[LaserPowerCalParam]` 변경 | +|---|---| +| Aurora_20260825.log | 0 건 | +| Aurora_20260826.log | 0 건 | +| Aurora_20260902.log | **1 건 — 11:44:32 `From[USER_via_UI]`** | + +2026-09-02 11:44:32 이 CSV 경로 교체 시점이고, 이어진 13:45~15:03 의 `Laser On` 58 회가 +2000W 재캘리브레이션 작업이다. + +--- + +## 7. 이어받는 사람에게 + +- 🟨 **발진기 교체 시에는 CSV 재작성과 레시피 W 환산을 반드시 한 세트로 처리할 것.** + 둘 중 하나만 하면 이번과 같이 조용히 1.5배 또는 2/3 로 어긋난다. +- 🟨 파워 이상 신고가 오면 §6 의 `Original Power1 / Calibrated Percent1` 비로 CSV 만점부터 역산할 것. +- 🟨 §5.3 의 싱글모드 재검증은 **미완**이다. 환산 적용 후 비드 단면 확인 필요. +- 🟨 CSV 경로·값을 로그에 남기는 계측을 추가하면 이런 추적이 훨씬 쉬워진다. + 출사 시작 로그(`UCScannerLaserControlViewModel.cs:361`, `:452`)에 CSV 경로 + Max 파워 + + 산출된 `powerVolt1/2` 를 붙이는 것만으로 충분하다. **미적용.** + +--- + +## 8. 참고 파일 위치 + +| 구분 | 경로 | +|---|---| +| 파라미터 원본 | `docs/파라미터PTOP_260903.xlsx` 시트 `260903`, 환산 이력 시트 `260903_2000W환산` | +| 구 파워캘 CSV | `docs/3000W_CenterPower.csv` · `docs/3000W_RingPower.csv` | +| 신 파워캘 CSV | `docs/2000W_CenterPower.csv` · `docs/2000W_RingPower.csv` | +| 현장 로그 | `docs/Aurora_20260825.log` · `20260826` · `20260902` | +| 파워캘 설정 클래스 | `WeldingComponents/WeldingCommon/Settings/LaserPowerCalParam.cs` | +| 자동 용접 파워 적용부 | `WeldingComponents/WeldingCommon/Sequences/Main/SeqUploadWeldingList.cs:639-671` | +| 수동 출사 파워 적용부 | `WeldingComponents/WeldingCommon/Views/Manual/Scanner/UCScannerLaserControlViewModel.cs:329-345`, `:419-436` | +| DA 전압 변환 | `AuroraDevices/AuroraLaserScanner/ScannerRTC5.cs:342-351` | +| 발진기 교체 경위 | `Doc/인수인계/2026_08_28_레이저발진기교체_및_진행현황.md` §2.4 | + +--- + +## 변경 이력 + +| 일자 | 작성 | 내용 | +|---|---|---| +| 2026-09-03 | 노도연 | 최초 작성. 3000W CSV 오적용 원인·근거·환산표·주의사항 정리. 기존 인수인계 §2.4 「출력을 올려야 했다」가 CSV 오적용 보정이었음을 교차검증 | diff --git a/Doc/인수인계/260907_Arc용접_설정가이드_현장용.pptx b/Doc/인수인계/260907_Arc용접_설정가이드_현장용.pptx new file mode 100644 index 0000000..b4c5c59 Binary files /dev/null and b/Doc/인수인계/260907_Arc용접_설정가이드_현장용.pptx differ diff --git a/Doc/인수인계/260907_Arc출사_파라미터_시각화.html b/Doc/인수인계/260907_Arc출사_파라미터_시각화.html new file mode 100644 index 0000000..1665463 --- /dev/null +++ b/Doc/인수인계/260907_Arc출사_파라미터_시각화.html @@ -0,0 +1,661 @@ + + + + + +Arc 출사 파라미터 시각화 — PTOP (260907) + + + + + + + +
+ + +

0결론 — Arc가 원점 방향으로 발광한 원인 후보 3건

+

+모두 레시피/스캐너 파라미터로 바로 회피 가능합니다. 아래 카드의 조치값을 먼저 적용한 뒤 재현 여부를 보면 +어느 후보인지 확정됩니다. 후보 1과 2는 Arc 전용 경로이고, 후보 3은 전 타입 공통입니다. +

+ +
+
+
✕CRITICAL · 최유력
+

후보 1 — Arc Ramping Interval = 0 (0으로 나눗셈)

+

ArcRampingInterval은 초기값이 없어 기본 0.0입니다(ScannerParam.cs:331). + 여기에 Ramping Start/End Distance를 하나라도 넣으면 theta = (0/R)×(180/π) = 0이 되고, + (int)(Dist/0) → (int)+∞ = int.MinValue로 카운터가 붕괴합니다. + Constant 루프가 약 21.5억 회 ArcAbs(center, 0°)를 리스트에 밀어넣어 + RTC 리스트가 넘치고, 넘친 자리의 미초기화 좌표(0,0 = 원점)가 마크(레이저 ON)로 실행됩니다.

+
조치 · Arc를 쓰는 모든 레시피에서 Arc Ramping Interval = 0.1 mm + (툴팁 권장값). 램핑이 필요 없으면 Start Distance = End Distance = 0으로 두어 + 램핑 분기 자체를 타지 않게 하는 편이 더 안전합니다.
+
+ +
+
!SERIOUS
+

후보 2 — 램핑 길이 가드의 단위 버그 → 과회전

+

분기 조건(SeqUploadWeldingList.cs:1303)이 호 길이를 R × |Sweep°|로 계산합니다. + 실제 호 길이는 R × |Sweep°| × π/180이라 우변이 약 57.3배 과대합니다. + (정확한 값은 Arc.Length에 이미 있는데 쓰지 않습니다.) + 그래서 램핑 거리가 실제 호보다 길어도 분기에 진입하고, + rising+falling 반복이 totalCount를 넘겨 의도한 각도를 초과해 레이저 ON 상태로 회전합니다. + 끝나면 _LastX/_LastY에 실제와 다른 좌표(PF2)를 기록해, + 다음 객체가 점프를 생략하고 엉뚱한 지점에서 마크를 시작합니다.

+
조치 · Start Dist + End Dist ≤ 호길이 × 0.5. + 호길이 = R × |Sweep°| × 0.01745. §4에 계산 예시가 있습니다.
+
+ +
+
!WARNING · 병행 확인
+

후보 3 — LaserOnDelay −1800 µs + JumpDelay 0

+

LaserOnDelay는 부호 있는 int(ScannerParam.cs:58)이고 단위는 µs입니다 + (ScannerRTC6.cs:691, 1bit=0.5µs). 음수는 마크 시작 이전에 레이저를 켜므로 + 직전 점프 구간 끝에서 발광합니다. 현장 로그 실측값이 −1800 µs이고 JumpDelay가 0이라 + 미러 정정 대기 없이 발광합니다. JumpSpeed 1000 mm/s 기준 최대 1.8 mm 선행 발광.

+
조치 · LaserOnDelay −1800 → −200~0 검토, + JumpDelay 0 → 100~300 µs. + 단 1.8 mm 규모라 "원점까지"는 이것만으로 설명되지 않습니다. 후보 1·2와 함께 보십시오.
+
+
+ + +

1기준 파라미터 세트 (현장 로그 실측값)

+

+아래 A는 Aurora_20260906.log에서 그대로 읽은 값이고, B·C는 이 문서의 시뮬레이션 예시입니다. +이후 모든 격자는 이 세 세트를 기준으로 그렸습니다. +

+ + + + + + + + + + + + + + + + + + + + +
파라미터A · 현장 실측B · 권장(램핑 사용)C · 재현 조건(NG)
LaserOnDelay−1800 µs−200 µs−1800 µs
LaserOffDelay (uint·음수 불가)0 µs0 µs0 µs
JumpDelay0 µs200 µs0 µs
MarkDelay200 µs200 µs200 µs
PolygonDelay (Arc 분절 간)0 µs50~100 µs0 µs
JumpSpeed1000 mm/s1000 mm/s1000 mm/s
MarkSpeed100 mm/s100 mm/s100 mm/s
LaserPower1 (Center)467 W467 W467 W
ArcRampingInterval— (미설정 = 0)0.1 mm0 mm
RampingStartDist / EndDist0 / 02 / 2 mm10 / 10 mm
RampingStartPower / EndPower—30 / 30 %30 / 30 %
Arc 형상 (예시)R 10 mm, Sweep 90°R 10 mm, Sweep 90°R 10 mm, Sweep 90°
→ 실제 호 길이R × Sweep° × π/180 = 10 × 90 × 0.01745 = 15.71 mm
+ + +

2격자 읽는 법

+
+

+색만으로 구분하지 않습니다 — 점프는 파선, 마크는 실선, 이상 발광은 굵은 실선 + ✕ 표식입니다. +파워 크기는 같은 파랑 계열의 명도(연함 = 저출력, 진함 = 고출력)로만 표현합니다. +각 셀에 마우스를 올리면 해당 조합의 파라미터가 표시됩니다. 전체 수치는 §6 표로도 제공합니다. +

+ + +

3격자 A — Draw 타입별 출사 형상 (출사 분기 8종)

+

+FuncLoadDrawObjectScannerParamSelected()가 실제로 처리하는 8개 분기입니다. +Ellipse · Rectangle · Roi는 분기가 없어 여기에 없습니다(별건 — 예외처리 대기 중). +

+
+ + +

4격자 B — Arc × 램핑/와블/스노우맨 조합 (핵심)

+

+행 = 램핑 상태, 열 = 와블/스노우맨 상태. 모두 R 10 mm · Sweep 90° · CCW 동일 형상입니다. +빨간 셀이 이번 현상의 재현 조건입니다. +

+
+
Wobble OFF
Wobble ON · 진폭 小
Wobble ON · 진폭 大
SnowMan ON
+
+
+ + +

5격자 C — 값의 크고 작음이 만드는 차이

+

각 행은 파라미터 하나를 작게 / 권장 / 크게 3단으로 비교합니다.

+
+
작게
권장
크게
+
+
+ + +

6표 뷰 — 격자 B 전체 수치

+

그림 없이도 같은 내용을 읽을 수 있도록 격자 B를 표로 옮겼습니다.

+ + + +
램핑 상태와블 상태IntervalStart+EndthetatotalCount실제 회전각판정근거
+ + +

7현장 조치 체크리스트

+ + + + + + + + + + + + + + + + +
#항목내용
1Arc Ramping Interval 확인Arc를 쓰는 모든 레시피 · 모든 Scanner Param No에 대해 값이 0이 아닌지 확인. + 0이면 0.1로 설정. (램핑 미사용이면 2번으로)
2램핑 미사용 시Start Distance = 0, End Distance = 0 → 단일 ArcAbs 경로(:1369)로 빠져 + 카운터 붕괴·과회전 모두 회피됩니다. 가장 안전한 설정입니다.
3램핑 사용 시 길이 검산호길이 = R × Sweep° × 0.01745. Start + End ≤ 호길이 × 0.5를 만족하는지 + Arc마다 확인. 코드의 자동 가드는 57.3배 느슨해서 걸러주지 않습니다.
4선행 발광 확인LaserOnDelay −1800 µs 유지 여부 판단. 점프 종단 1.8 mm 발광이 허용되는 공정인지 확인. + 줄인다면 −200 µs부터. 동시에 JumpDelay를 100~300 µs로.
5Arc 분절 정정PolygonDelay = 0은 Arc 분절 사이 정정 대기가 없다는 뜻입니다. + 원호 오버슈트가 보이면 50~100 µs를 넣고 비교.
6재현 시험1·2번만 적용하고 재현되는지 먼저 확인 → 재현되면 후보 3(딜레이) 쪽으로 이동. + 한 번에 다 바꾸면 어느 것이 원인인지 확정되지 않습니다.
+ +
+참조한 소스 위치 + + + + + + + + + + + + + + + +
위치내용
SeqUploadWeldingList.cs:1269length = param.ArcRampingInterval
SeqUploadWeldingList.cs:1273-1278theta = (length/radius)×(180/π), CW는 부호 반전
SeqUploadWeldingList.cs:1302-1303램핑 분기 가드 — R×|Sweep°| (π/180 누락)
SeqUploadWeldingList.cs:1313-1316risingEdgeCount·fallingEdgeCount·totalCount
SeqUploadWeldingList.cs:1342-1344Constant 구간 루프 (카운터 붕괴 시 폭주 지점)
SeqUploadWeldingList.cs:1366-1367lastSweep 보정 ArcAbs
SeqUploadWeldingList.cs:1374-1375_LastX/_LastY = obj.PF2 (물리 위치와 어긋날 수 있음)
SeqUploadWeldingList.cs:1220, 800"이미 시작점인가" 판정 → 점프 생략 분기
ScannerParam.cs:331-341ArcRampingInterval — 초기값 없음(=0), 권장 0.1 mm
ScannerParam.cs:56-70LaserOnDelay=int(음수 가능), LaserOffDelay=uint
ScannerRTC6.cs:691-699딜레이 단위 — 레이저 0.5 µs/bit, 스캐너 10 µs/bit
ScannerRTC6.cs:262-271ArcAbs(x,y,angle) — 각도 degree, CW가 +
ArcCircleTool.cs:905-924SweepAngle degree, Length = R×Sweep×π/180 (정확값 존재)
+
+ +
+ +
+ + + + diff --git a/Doc/인수인계/260907_Arc출사_파라미터_시각화.pptx b/Doc/인수인계/260907_Arc출사_파라미터_시각화.pptx new file mode 100644 index 0000000..6973b33 Binary files /dev/null and b/Doc/인수인계/260907_Arc출사_파라미터_시각화.pptx differ diff --git a/Doc/인수인계/260907_Draw타입별_설정가이드_현장용.pptx b/Doc/인수인계/260907_Draw타입별_설정가이드_현장용.pptx new file mode 100644 index 0000000..4ccbbe8 Binary files /dev/null and b/Doc/인수인계/260907_Draw타입별_설정가이드_현장용.pptx differ diff --git a/Doc/인수인계/260907_LaserFrequency_단위계통_분석.md b/Doc/인수인계/260907_LaserFrequency_단위계통_분석.md new file mode 100644 index 0000000..7e4cf59 --- /dev/null +++ b/Doc/인수인계/260907_LaserFrequency_단위계통_분석.md @@ -0,0 +1,191 @@ +# Laser Frequency / Pulse Duration 단위 계통 분석 + +- 작성 : 2026-09-07 +- 계기 : 현장 Arc 출사 이상 → 조치 후 정상. 실제 원인은 **Frequency 값 오입력**으로 확인됨. + 그 값이 어떤 경로로 스캐너에 들어가는지 역추적한 결과. +- 범위 : **정적 분석 전용. 소스 미수정. 현장 검증 미실시.** +- 결론 요약 : 화면값 → RTC 전달 경로에 **단위/캐스팅 문제 3건 + 적용 범위 문제 1건 + 크래시 경로 1건**이 있습니다. + +--- + +## 1. 값이 흘러가는 경로 + +``` +Scanner Parameter 화면 + Laser Frequency(kHz) double, 범위 0.2 ~ 5 ScannerParam.cs:142 + Laser Pulse Duration(ms) double ScannerParam.cs:164 + │ + ▼ SeqUploadWeldingList.InitializeDeviceStatus() :206-212 + IDrawObject firstObject = _InitParam.SelectedDrawObjectList[0] as IDrawObject; + int firstObjectScannerNo = firstObject.ScannerParamID - 1; + _ScannerParam = ScannerCanvasUserCtrl.ScannerParams[firstObjectScannerNo]; + + _Scanner.SetLaserTiming( (uint)_ScannerParam.LaserFrequency, + _ScannerParam.LaserPulseDuration, + _ScannerParam.LaserPulseDuration ); + │ + ▼ Scanner.cs:163 + SetLaserTiming(uint periodHz, double laser1PulseWidthUs, double laser2PulseWidthUs) + │ + ▼ ScannerRTC6.cs:772-784 (ScannerRTC5.cs:770-782 동일) + //uint halfPeriodBit = (32 / periodkHz) * 1000; ← 구버전(주석 처리됨) + uint halfPeriodBit = (uint)((32.0 / (double)periodHz) * 1000000.0); + uint laser1PulseWidthBit = (uint)(laser1PulseWidthUs * 64); + uint laser2PulseWidthBit = (uint)(laser2PulseWidthUs * 64); + RTC6Wrap.n_set_laser_timing(dev, halfPeriodBit, laser1PulseWidthBit, laser2PulseWidthBit, 1); +``` + +기준 단위 : RTC의 half period·pulse width는 **1/64 µs (15.625 ns)** 단위. +펄스폭 변환이 `× 64` 인 것으로 이 기준이 확인됩니다. + +--- + +## 2. 발견 사항 + +### ① `(uint)` 캐스팅이 소수점을 버립니다 — `SeqUploadWeldingList.cs:211` + +`LaserFrequency` 는 **double** 이고 화면 설명상 범위가 **0.2 ~ 5 kHz** 입니다. +그런데 전달 시 `(uint)` 로 캐스팅합니다. + +| 화면 입력 | `(uint)` 후 | 비고 | +|---|---|---| +| 0.2 | **0** | 0 이 되어 아래 ②에서 0 나눗셈 | +| 0.5 | **0** | 〃 | +| 2.5 | 2 | 0.5 손실 | +| 4.9 | 4 | 0.9 손실 | +| 5 | 5 | 정상 | + +**화면에서는 소수 입력이 가능한데 전달 과정에서 정수로 잘립니다.** +설명상 범위(0.2~5)의 상당 부분이 0 또는 절삭값이 됩니다. + +### ② 0 이 들어가면 0 으로 나눕니다 — `ScannerRTC6.cs:775` + +```csharp +uint halfPeriodBit = (uint)((32.0 / (double)periodHz) * 1000000.0); +``` + +`periodHz == 0` 이면 `32.0 / 0` = `+Infinity` → `(uint)+Infinity` 는 **C# 명세상 정의되지 않은 동작**입니다. +(x64 실측 동작은 0) 예외는 발생하지 않고 **조용히 엉뚱한 값**이 RTC로 넘어갑니다. + +### ③ 환산식이 1000배 큽니다 — `ScannerRTC6.cs:775` / `ScannerRTC5.cs:773` + +정확한 환산 (f 단위 kHz, 결과 단위 1/64 µs) : + +``` +반주기[µs] = 1 / (2 · f[kHz] · 1000) · 1e6 = 500 / f +halfPeriodBit = 반주기[µs] × 64 = 32000 / f +``` + +| 구분 | 식 | f=5 일 때 | +|---|---|---| +| 정확값 | `32000 / f` | 6,400 | +| **현재 코드** | `(32.0 / f) × 1,000,000` | **6,400,000** | +| 바로 위 주석(구버전) | `(32 / f) × 1000` | 6,000 ※정수나눗셈 절삭은 있으나 자릿수는 맞음 | + +**현재 코드는 정확값의 1000배**입니다. 주석 처리된 구버전이 자릿수상 맞는 식이었습니다. + +결과적으로 **화면에 kHz 로 넣은 값이 실제로는 Hz 수준으로 동작**합니다. + +| 화면 입력 | 의도 | 실제 (현재 코드) | +|---|---|---| +| 1 kHz | 1,000 Hz | 1 Hz | +| 2.5 kHz | 2,500 Hz | 2 Hz (①의 절삭까지 겹침) | +| 5 kHz | 5,000 Hz | 5 Hz | + +### ④ Pulse Duration 이 ms → µs 변환 없이 전달됩니다 — `SeqUploadWeldingList.cs:211` + +- 화면 항목 : `Laser Pulse Duration(ms)` — **ms** +- 받는 파라미터 : `laser1PulseWidthUs` — **µs** +- 활성 코드 : `_ScannerParam.LaserPulseDuration` **그대로 전달** (변환 없음) +- 같은 파일에 주석 처리된 코드(`:628`) : `(uint)(param.LaserPulseDuration * 1000)` ← **ms→µs 변환이 있었음** + +| 화면 입력 | 의도 (ms→µs) | 실제 | +|---|---|---| +| 0.1 ms | 100 µs → 6,400 bit | 0.1 → **6 bit ≈ 0.094 µs** | +| 1.0 ms | 1,000 µs → 64,000 bit | 1.0 → **64 bit = 1 µs** | + +**1000배 작게** 전달됩니다. + +### ⑤ 첫 번째 객체의 값만 적용됩니다 — `SeqUploadWeldingList.cs:206-212` + +`SetLaserTiming` 은 **시퀀스당 1회**, `SelectedDrawObjectList[0]` 의 Parameter ID 값으로만 호출됩니다. +리스트가 아니라 COMMAND 방식이라 리스트 실행 전에 한 번 세팅되고 끝입니다. + +→ **Parameter ID 2, 3 … 의 `Laser Frequency` · `Laser Pulse Duration` 은 무시됩니다.** +  (`Laser Power`, `Mark Speed`, `Delay` 등은 객체마다 리스트에 들어가므로 정상 적용됩니다) + +현장에서 "ID 마다 Frequency 를 다르게 넣었는데 차이가 없다" 면 이것이 원인입니다. + +### ⑥ 크래시 경로 — `SeqUploadWeldingList.cs:207-209` + +```csharp +int firstObjectScannerNo = firstObject.ScannerParamID - 1; +_ScannerParam = ScannerCanvasUserCtrl.ScannerParams[firstObjectScannerNo]; +``` + +`ScannerParamID` 가 0 이면 인덱스 **-1** → `IndexOutOfRangeException`. +`SelectedDrawObjectList` 가 비어 있으면 `[0]` 에서 예외. +둘 다 방어 코드가 없습니다. (2026-09-07 추가한 예외처리는 `FuncLoadDrawObjectScannerParamSelected` 쪽이라 이 경로는 여전히 무방비) + +--- + +## 3. 정황 근거 + +`SeqUploadWeldingList.cs:624-626` 주석 : + +> TrumpLaser의 경우 SetLaserTiming 함수로 Gate 신호를 살려줘야 함 +> 우선 무조건 셋팅하는 것으로 쓰고 … +> **LaserFrequency = 10, LaserPulseDuration = 0.1로 셋팅하면 출사 되는 것 확인_241025** + +물리적으로 계산해서 정한 값이 아니라 **"이 값을 넣으니 나오더라"로 찾은 경험값**이라는 기록입니다. +위 ①~④의 단위 오류가 있는 상태에서 경험적으로 맞춘 것이므로 서로 정합합니다. + +이 값으로 현재 코드가 만드는 실제 신호 : +- halfPeriodBit = 32,000,000 / 10 = 3,200,000 bit = 50,000 µs → **10 Hz** +- pulseWidthBit = 0.1 × 64 = 6 bit → **약 0.094 µs** + +즉 **10 Hz 주기에 0.1 µs 폭**의 신호입니다. 듀티로 보면 거의 0 에 가깝습니다. + +--- + +## 4. 그래서 실제로 문제인가 — 판단 보류 + +**단위 오류 자체는 코드상 명확합니다.** 다만 실제 용접 품질에 영향을 주는지는 별개입니다. + +현재 본 레이저는 **아날로그 파워(DA) + 게이트(DO Laser_REMOTE)** 로 제어됩니다 +(`SeqAutoRunPTOP.cs:1305`, `SetScannerProcessCondition` 의 `WriteDA`). +이 구조에서 RTC 의 LASER1/LASER2 펄스 신호는 **"살아만 있으면 되는" 역할**일 수 있습니다. +위 주석도 "Gate 신호를 살려줘야 함" 이라고 적고 있습니다. + +→ **발진기가 이 펄스 신호를 실제로 어떻게 쓰는지 확인해야 결론이 납니다.** +  발진기 매뉴얼의 변조/게이트 입력 사양 확인 필요. + +--- + +## 5. 확인·조치 후보 (미실행) + +| # | 항목 | 내용 | +|---|---|---| +| 1 | 발진기 사양 확인 | RTC LASER1/LASER2 펄스를 실제 변조에 쓰는지, 게이트로만 쓰는지 | +| 2 | 현재 현장값 확인 | 각 레시피 Parameter ID 의 `Laser Frequency` · `Laser Pulse Duration` 실측 | +| 3 | ①⑤ 우선 | 소수점 절삭과 "첫 객체만 적용"은 사양 확인 없이도 **현장이 오해하기 쉬운 지점** | +| 4 | ③④ 수정 시 | 환산식을 고치면 **현재 경험값이 전부 어긋납니다.** 반드시 재티칭 전제로 진행 | +| 5 | ⑥ 방어 | `ScannerParamID <= 0` / 빈 리스트 방어 추가 | + +**주의 — ③④ 를 고치면 현재 잘 나오는 조건이 깨집니다.** 경험값이 오류를 상쇄하고 있을 수 있으므로, +발진기 사양 확인 → 시험 조건 확보 → 재티칭 계획이 선 뒤에 손대야 합니다. + +--- + +## 참조 위치 + +| 위치 | 내용 | +|---|---| +| `ScannerParam.cs:142` | `LaserFrequency` double, kHz, 0.2~5 | +| `ScannerParam.cs:164` | `LaserPulseDuration` double, ms | +| `SeqUploadWeldingList.cs:206-212` | 첫 객체 param 으로 `SetLaserTiming` 1회 호출 + `(uint)` 캐스팅 | +| `SeqUploadWeldingList.cs:624-629` | 경험값 주석 + ms→µs 변환이 있던 주석 코드 | +| `Scanner.cs:163-169` | 래퍼 (`periodHz`, `laser1PulseWidthUs`) | +| `ScannerRTC6.cs:772-784` | 환산식 — 1000배 문제 지점 | +| `ScannerRTC5.cs:770-782` | RTC5 동일 | +| `ScannerObj.cs:264` | 추상 선언 (`halfPeriod`, `laser1PulseWidth`) — 이름이 계층마다 다름 | diff --git a/Doc/인수인계/260907_NRE_크래시_위험구간_점검.md b/Doc/인수인계/260907_NRE_크래시_위험구간_점검.md new file mode 100644 index 0000000..b1a1738 --- /dev/null +++ b/Doc/인수인계/260907_NRE_크래시_위험구간_점검.md @@ -0,0 +1,158 @@ +# NRE / 크래시 위험 구간 점검 + +- 작성 : 2026-09-07 +- 계기 : 출사 미지원 객체(Ellipse/Rectangle/Roi) NRE 방어(`ISSUE-260907-01`) 적용 후, + 같은 유형이 더 있는지 전수 확인 요청. +- 성격 : **점검 목록 전용. 소스 미수정. 현장 검증 미실시.** +- 표기 : 🟩 소스 확인 / 🟦 현장 기록 / 🟨 미확보·판정 불가 + +## 점검 범위와 한계 + +| 구분 | 내용 | +|---|---| +| 포함 | `Machines/ILSOS_PrismaticRD_PTOP/**`, `WeldingComponents/WeldingCommon/Sequences/**` | +| 제외 | AuroraVision, LASA GUI(Canvas·DrawTools), AuroraDevices 하위 대부분, Aurora 프레임워크 | +| 방법 | 패턴 검색(`FirstOrDefault`/`First()`/`[0]`/`as`/인덱서) 후 해당 라인 전후 확인 | +| 한계 | 정적 분석. 런타임 재현·디버깅 없음. 제외 범위에 같은 유형이 더 있을 수 있음 | + +--- + +## 0. 선행 확인 — 예외가 실제로 프로그램을 종료시키는가 + +🟩 우리 소스에 **전역 예외 핸들러가 없습니다.** +`DispatcherUnhandledException` / `AppDomain.CurrentDomain.UnhandledException` / +`UnobservedTaskException` / `SetUnhandledExceptionMode` 검색 결과 **0건**. + +🟩 `ILSOS_PrismaticRD_PTOP.csproj` → `OutputType=library`. +즉 실행 주체는 **Aurora.exe** 이고 저장소에 소스가 없습니다. + +🟨 따라서 아래 항목이 **실제로 프로세스를 종료시키는지는 판정할 수 없습니다.** +- Aurora.exe 가 전역 핸들러를 걸어 두었는지 미확인 +- Aurora `SequenceBuilder` 가 노드 실행을 try/catch 로 감싸는지 미확인 + (`sb.OnForceError(...)` 훅이 존재하므로 **잡을 가능성은 있음**) + +> **이 문서는 "NRE 가 발생할 수 있는 지점" 목록입니다.** +> "여기서 프로그램이 죽는다" 로 읽지 마십시오. 종료 여부는 별도 확인 필요. + +--- + +## A. 널 체크 없이 즉시 역참조 — 위험도 최상 + +### A-1. `SeqComponentTeachingRun.cs:456-459` 🟩 + +```csharp +var power = Status.GetProcessingWeldingData().UploadProcessDatas + .Where(data => data.DataName == $"Power_{SlotNo}_{_SequenceSetting.LastPageNo}") + .FirstOrDefault(); +power.Value = ScannerCanvasUserCtrl.ScannerParams[paramIndex].LaserPower1 + + ScannerCanvasUserCtrl.ScannerParams[paramIndex].LaserPower2; // ← power == null 이면 NRE + +var velocity = ...Where(data => data.DataName == $"Velocity_{SlotNo}_{...}").FirstOrDefault(); +velocity.Value = ScannerCanvasUserCtrl.ScannerParams[paramIndex].MarkSpeed; // ← 동일 +``` + +- `DataName` 을 `SlotNo` + `LastPageNo` 로 **문자열 조합**해 찾습니다. +- 그 조합이 `UploadProcessDatas` 에 없으면 `FirstOrDefault()` 가 `null` 을 반환합니다. +- **페이지 수가 늘거나 슬롯 번호 체계가 바뀌면 즉시 발생**합니다. +- 실제 운전 경로(Manual 티칭런)라 재현 가능성이 목록 중 가장 높습니다. + +### A-2. `SeqEventQTest.cs:142-145` 🟩 — 3중 위험 + +```csharp +FovInfo _fovInfo = VisionRecipeManager.GetFovInfos().FirstOrDefault(); +_PatternAgent = FovPatternAgentManager.GetAgent(_fovInfo.ToSubSysInfo()); // ① _fovInfo null → NRE +List patterns = _PatternAgent.GetMainPatterns(_fovInfo); // ② agent null → NRE +_PatternInfo = patterns[0]; // ③ 빈 리스트 → IndexOutOfRange +``` + +### A-3. `SeqAutoRunPrismatic.cs:1101-1104` 🟩 + +A-2 와 동일 패턴 + `_Patterns[_InitParam.VisionRecipeNo]` 인덱스 무검증. + +> 🟩 **참고 — 올바른 패턴이 같은 파일에 이미 있습니다.** +> `SeqAutoRunPrismatic.cs:743-745` 는 `_PatternResult?.Results?.FirstOrDefault()` 로 +> 널 조건 연산자를 쓰고 이후 `if (baseResult != null)` 까지 확인합니다. +> A-2 · A-3 만 이 방어가 빠져 있습니다. + +--- + +## B. `as` 캐스팅 결과를 가드 없이 보관 — 지연 발화형 + +캐스팅 실패 또는 키 부재 시 조용히 `null` 이 되고, **한참 뒤 그 필드를 사용하는 시점**에 터집니다. +발생 위치와 원인 위치가 달라 추적이 어렵습니다. + +| 위치 | 대상 | 🟩 | +|---|---|---| +| `SeqAutoRunPTOP.cs:291` | `_SystemParameter` | 🟩 | +| `Views/Main/UCMainAutoRunViewModel.cs:95-97` | `_SystemParameter`, `_Scanner`, `_Plc` | 🟩 | +| `Views/Main/UCMainAutoRunViewModel.cs:472, 505` | `_SystemParameter` 재대입 | 🟩 | +| `SeqTestRun.cs:472` | `_IOLaser` | 🟩 | +| `SeqComponentTeachingRun.cs:179` | `_SequenceSetting` | 🟩 | +| `Monitoring/SeqStatusMonitor.cs:60` | `_SequenceSetting` | 🟩 | +| `SeqAutoRunPrismatic.cs:1050` | `SystemSetting` | 🟩 | +| `EquipmentSetup.cs:90, 199, 240, 342` | `EquipmentInfo` | 🟩 | +| `EquipmentSetup.cs:129, 133` | `IODevice`, `PLCControllerPTOP` | 🟩 | +| `Recipes/RMSHandlerForPTOP.cs:52-53` | `_plc`, `_RmsSetting` | 🟩 | + +대부분 **초기화 구간**이라 정상 구성에서는 성공합니다. +위험은 "설정 키 이름 변경 / 디바이스 등록 누락 / 타입 교체" 시 **즉시 실패하지 않는다**는 점입니다. + +🟩 **예외 — 이미 방어된 곳** : `SeqAutoRunPTOP.cs:1140, 1252` 의 `LaserInterlockSetting` 은 +`LaserAngleOffsetSelector.Select()` 안에서 `setting != null` 삼항 처리로 방어됩니다. + +--- + +## C. 인덱스 무검증 — NRE 는 아니나 동일하게 종료 요인 + +| 위치 | 위험 | 🟩 | +|---|---|---| +| `SeqUploadWeldingList.cs:206` | `SelectedDrawObjectList[0]` — 빈 리스트 | 🟩 | +| `SeqUploadWeldingList.cs:207-209` | `ScannerParams[ScannerParamID - 1]` — **ID 가 0 이면 인덱스 -1** | 🟩 | +| `SeqUploadWeldingList.cs:474, 617-631, 719, 1006, 1052, 1152, 1204, 1404, 1605` | `ScannerParams[obj.ScannerParamNo]` 범위 무검증 (배열은 128개 고정 생성) | 🟩 | +| `SeqWaitWeldOrChangePage.cs:194-195` | `ScannerParams[paramIndex]` | 🟩 | +| `SeqTestRun.cs:237-238` | `ScannerParams[paramIndex]` | 🟩 | +| `SeqComponentTeachingRun.cs:457-465` | `ScannerParams[paramIndex]` | 🟩 | +| `SeqAutoRunPrismatic.cs:1104` | `_Patterns[VisionRecipeNo]` | 🟩 | + +**`SeqUploadWeldingList.cs:206-209` 주의** — 2026-09-07 적용한 예외처리는 +`FuncLoadDrawObjectScannerParamSelected()` 쪽이라 **이 경로는 여전히 무방비**입니다. + +--- + +## D. 예외는 안 나지만 값이 오염되는 것 (참고) + +double 나눗셈이라 예외 대신 `Infinity`/`NaN` 이 조용히 흘러갑니다. + +| 위치 | 내용 | +|---|---| +| `ScannerRTC6.cs:775` · `ScannerRTC5.cs:773` | `32.0 / periodHz` — `periodHz == 0` 이면 `Infinity` → `(uint)` 캐스팅은 정의되지 않은 동작 | +| `SeqUploadWeldingList.cs:1313-1316` | `ArcRampingInterval == 0` 시 동일 | + +상세 : [`260907_LaserFrequency_단위계통_분석.md`](260907_LaserFrequency_단위계통_분석.md) + +--- + +## 우선순위 제안 + +| 순위 | 항목 | 근거 | +|---|---|---| +| 1 | **A-1** | 실제 운전 경로(Manual 티칭런) + 조합 문자열 탐색이라 재현 가능성 최상 | +| 2 | **C 의 `SeqUploadWeldingList.cs:206-209`** | 이번 예외처리가 커버하지 못한 인접 경로 | +| 3 | **A-2 / A-3** | 비전 경로. Vision 미사용이면 후순위 | +| 4 | **B** | 개별 수정보다 초기화 구간 일괄 널 가드 + 알람이 적절 | +| — | **선행** | Aurora.exe / SequenceBuilder 의 예외 처리 정책 확인. 이게 정해져야 "가드 후 어떻게 할지"(스킵 / 알람 / 시퀀스 중단)가 결정됨 | + +## 조치 시 주의 + +- **조용히 스킵하면 안 되는 경우가 있습니다.** A-1 은 공정 데이터 상신 실패를 뜻하므로, + 널이면 로그만 남기고 넘어가면 **데이터 누락이 은폐**됩니다. 알람 여부를 함께 정해야 합니다. +- 반대로 이번 `ISSUE-260907-01` 처럼 **원래도 동작하지 않던 경로**는 스킵이 맞습니다. +- 즉 "널이면 무조건 return" 을 일괄 적용하지 마십시오. 항목마다 판단이 다릅니다. + +--- + +## 변경 이력 + +| 일자 | 내용 | 비고 | +|---|---|---| +| 2026-09-07 | 최초 작성. PTOP + WeldingCommon Sequences 범위 점검 | 소스 미수정 | diff --git a/Doc/인수인계/260907_레시피화면_사용설명서_현장용.pptx b/Doc/인수인계/260907_레시피화면_사용설명서_현장용.pptx new file mode 100644 index 0000000..4d5b093 Binary files /dev/null and b/Doc/인수인계/260907_레시피화면_사용설명서_현장용.pptx differ diff --git a/Doc/인수인계/260915_레시피화면_작업매뉴얼_현장용.pptx b/Doc/인수인계/260915_레시피화면_작업매뉴얼_현장용.pptx new file mode 100644 index 0000000..69b32e3 Binary files /dev/null and b/Doc/인수인계/260915_레시피화면_작업매뉴얼_현장용.pptx differ diff --git a/Doc/인수인계/260916_PTOP_프로그램_사용설명서.pptx b/Doc/인수인계/260916_PTOP_프로그램_사용설명서.pptx new file mode 100644 index 0000000..e9230bd Binary files /dev/null and b/Doc/인수인계/260916_PTOP_프로그램_사용설명서.pptx differ diff --git a/Doc/인수인계/260916_자동운전_전환_사전점검_현장용.pptx b/Doc/인수인계/260916_자동운전_전환_사전점검_현장용.pptx new file mode 100644 index 0000000..75d9ec2 Binary files /dev/null and b/Doc/인수인계/260916_자동운전_전환_사전점검_현장용.pptx differ