Merge branch 'field/ptop-1st' into field/ptop-recipe-scanner-move
This commit is contained in:
commit
9f72e6caaf
BIN
Doc/메뉴얼/REF_OLD/Scanner Operating Manual.pptx
Normal file
BIN
Doc/메뉴얼/REF_OLD/Scanner Operating Manual.pptx
Normal file
Binary file not shown.
@ -106,6 +106,7 @@
|
||||
<Compile Include="EquipmentSetup.cs" />
|
||||
<Compile Include="Models\EquipmentSpecificModel_PTOP.cs" />
|
||||
<Compile Include="Recipes\RMSHandlerForPTOP.cs" />
|
||||
<Compile Include="Sequences\LaserAngleOffsetSelector.cs" />
|
||||
<Compile Include="Sequences\Manual\SeqComponentTeachingRun.cs" />
|
||||
<Compile Include="Sequences\Monitoring\SeqAlarmMonitor.cs" />
|
||||
<Compile Include="Sequences\Monitoring\SeqStatusMonitor.cs" />
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
using Aurora.Api;
|
||||
using ILSOS_PrismaticRD_PTOP.Device;
|
||||
using ILSOS_PrismaticRD_PTOP.Settings;
|
||||
using static ILSOS_PrismaticRD_PTOP.Device.PLCControllerPTOP;
|
||||
using SystemParameter = WeldingCommon.Settings.SystemParameter;
|
||||
|
||||
namespace ILSOS_PrismaticRD_PTOP.Sequences
|
||||
{
|
||||
// 카메라 헤드 각도(D70510)에 따라 Front/Down 발진 offset 을 선택한다.
|
||||
// thetaDeg = D70510 / PlcScale 가 (Front angle + Down angle)/2 보다 크면 Down, 아니면 Front.
|
||||
// PLC 미연결/미활성으로 각도를 못 읽으면 Front offset 적용(fallback).
|
||||
internal static class LaserAngleOffsetSelector
|
||||
{
|
||||
public static (double x, double y) Select(
|
||||
PLCControllerPTOP plc, SystemParameter sp, LaserInterlockSetting setting, Logger log, string tag)
|
||||
{
|
||||
double frontAngle = setting != null ? setting.HeadThetaFront : 0;
|
||||
double downAngle = setting != null ? setting.HeadThetaDown : 90;
|
||||
double avg = (frontAngle + downAngle) / 2.0;
|
||||
double scale = (setting != null && setting.HeadThetaPlcScale != 0) ? setting.HeadThetaPlcScale : 1;
|
||||
|
||||
bool useDown = false;
|
||||
double thetaDeg = double.NaN;
|
||||
if (plc != null && plc.IsActivated)
|
||||
{
|
||||
short raw = plc.ReadWord(eWordInput.HEAD_THETA_POSITION).data; // D70510
|
||||
thetaDeg = raw / scale;
|
||||
useDown = thetaDeg > avg; // 평균보다 높으면 Down
|
||||
}
|
||||
else
|
||||
{
|
||||
log.W($"[{tag}] 각도 offset: PLC 미연결/미활성 → Front offset 적용(fallback)");
|
||||
}
|
||||
|
||||
double selX = useDown ? sp.DownOffsetX : sp.FrontOffsetX;
|
||||
double selY = useDown ? sp.DownOffsetY : sp.FrontOffsetY;
|
||||
|
||||
log.I($"[{tag}] 각도 offset 선택: D70510={thetaDeg:F1}deg, avg={avg:F1} " +
|
||||
$"(FrontAngle={frontAngle}, DownAngle={downAngle}) → {(useDown ? "Down" : "Front")} 적용 | " +
|
||||
$"FrontOffset=({sp.FrontOffsetX},{sp.FrontOffsetY}) DownOffset=({sp.DownOffsetX},{sp.DownOffsetY}) " +
|
||||
$"→ 적용=({selX},{selY})");
|
||||
|
||||
return (selX, selY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,7 @@ using GUI;
|
||||
using Hermes;
|
||||
using ILSOS_PrismaticRD_PTOP.Device;
|
||||
using ILSOS_PrismaticRD_PTOP.Device.IO;
|
||||
using ILSOS_PrismaticRD_PTOP.Settings;
|
||||
using Matrox.MatroxImagingLibrary;
|
||||
using OpenCvSharp.Dnn;
|
||||
using System;
|
||||
@ -502,21 +503,23 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
|
||||
{
|
||||
Status.SetWorkingPosition(eWorkingPosition.UPLOADING_WELD_LIST);
|
||||
|
||||
// 각도(D70510) 기반 Front/Down offset 선택 (기존 단일 CellCenterOffset 대체)
|
||||
var thetaOffsetSetting = context.GetSetting<LaserInterlockSetting>(SettingIds.LaserInterlock);
|
||||
var (angleOffX, angleOffY) = LaserAngleOffsetSelector.Select(_plc, _SystemParameter, thetaOffsetSetting, Log, "ComponentTeachingRun");
|
||||
|
||||
// TeachingRun에는 용접 시퀀스만 정의해서 Auto Run에서도 사용하므로 Offset 넣어줘야 함
|
||||
SeqUploadWeldingList_InitParam seqParams = new SeqUploadWeldingList_InitParam() {
|
||||
NeedSetLaserTiming = _InitParam.NeedSetLaserTiming,
|
||||
IncludeListStartEnd = true,
|
||||
CellMeasureCenterOffsetX = _SystemParameter.CellPosMeasureCenterOffsetX + _InitParam.CellCenterOffsetX,
|
||||
CellMeasureCenterOffsetY = _SystemParameter.CellPosMeasureCenterOffsetY + _InitParam.CellCenterOffsetY,
|
||||
CellMeasureCenterOffsetX = angleOffX + _InitParam.CellCenterOffsetX,
|
||||
CellMeasureCenterOffsetY = angleOffY + _InitParam.CellCenterOffsetY,
|
||||
SelectedDrawObjectList = _DivideObjectsQueue.Peek(),
|
||||
WeldingScannerListNo = _WeldingScannerListNo,
|
||||
};
|
||||
|
||||
var _PageNo = _SequenceSetting.LastPageNo;
|
||||
|
||||
Log.W($"page {_PageNo}({Status.GetProcessingWeldingData().CellId}) : Cell Offset Value Check ; X : {_SystemParameter.CellPosMeasureCenterOffsetX} + {_InitParam.CellCenterOffsetX} ; Y : {_SystemParameter.CellPosMeasureCenterOffsetY} + {_InitParam.CellCenterOffsetY}");
|
||||
|
||||
Log.D(string.Format($"[ComponentTeachingRun] Set UploadWelindList Seq Param"));
|
||||
Log.D(string.Format($"[ComponentTeachingRun] Set UploadWelindList Seq Param (page {_PageNo}, Cell {Status.GetProcessingWeldingData().CellId})"));
|
||||
|
||||
return SequenceAction.Ok(seqParams);
|
||||
}
|
||||
|
||||
@ -636,13 +636,16 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
|
||||
}
|
||||
#endregion
|
||||
|
||||
// TeachingRun 에서는 CellMeasureCenterOffset 사용하지 않음. 0 으로 넘기자
|
||||
// 각도(D70510) 기반 Front/Down offset 선택 (기존 단일 CellCenterOffset 대체)
|
||||
var thetaOffsetSetting = Application.Current.AuroraEnv().SettingEntry[SettingIds.LaserInterlock] as LaserInterlockSetting;
|
||||
var (angleOffX, angleOffY) = LaserAngleOffsetSelector.Select(_PLC, _SystemParameter, thetaOffsetSetting, Log, "ExecuteWelding");
|
||||
|
||||
SeqUploadWeldingList_InitParam seqParams = new SeqUploadWeldingList_InitParam()
|
||||
{
|
||||
NeedSetLaserTiming = _InitParam.NeedSetLaserTiming,
|
||||
IncludeListStartEnd = true,
|
||||
CellMeasureCenterOffsetX = 0.0,
|
||||
CellMeasureCenterOffsetY = 0.0,
|
||||
CellMeasureCenterOffsetX = angleOffX + _InitParam.CellCenterOffsetX,
|
||||
CellMeasureCenterOffsetY = angleOffY + _InitParam.CellCenterOffsetY,
|
||||
SelectedDrawObjectList = _SelectedObjectList, // 이게 중요 포인트
|
||||
WeldingScannerListNo = _WeldingScannerListNo,
|
||||
};
|
||||
@ -761,12 +764,12 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
|
||||
short thetaRaw = _PLC.ReadWord(eWordInput.HEAD_THETA_POSITION).data;
|
||||
double scale = thetaSetting.HeadThetaPlcScale != 0 ? thetaSetting.HeadThetaPlcScale : 1; // 0 가드
|
||||
double thetaDeg = thetaRaw / scale;
|
||||
bool okH = Math.Abs(thetaDeg - thetaSetting.HeadThetaHorizontal) <= thetaSetting.HeadThetaTolerance;
|
||||
bool okV = Math.Abs(thetaDeg - thetaSetting.HeadThetaVertical) <= thetaSetting.HeadThetaTolerance;
|
||||
Log.I($"[ExecuteWelding] HeadTheta raw={thetaRaw}, deg={thetaDeg:F1} (H={thetaSetting.HeadThetaHorizontal}/V={thetaSetting.HeadThetaVertical}/±{thetaSetting.HeadThetaTolerance})");
|
||||
if (!okH && !okV)
|
||||
bool okFront = Math.Abs(thetaDeg - thetaSetting.HeadThetaFront) <= thetaSetting.HeadThetaTolerance;
|
||||
bool okDown = Math.Abs(thetaDeg - thetaSetting.HeadThetaDown) <= thetaSetting.HeadThetaTolerance;
|
||||
Log.I($"[ExecuteWelding] HeadTheta raw={thetaRaw}, deg={thetaDeg:F1} (Front={thetaSetting.HeadThetaFront}/Down={thetaSetting.HeadThetaDown}/±{thetaSetting.HeadThetaTolerance})");
|
||||
if (!okFront && !okDown)
|
||||
{
|
||||
Log.W($"[ExecuteWelding] HeadTheta 인터락 → 수평/수직 ±{thetaSetting.HeadThetaTolerance}° 벗어남(deg={thetaDeg:F1}) → 발진 차단");
|
||||
Log.W($"[ExecuteWelding] HeadTheta 인터락 → Front/Down ±{thetaSetting.HeadThetaTolerance}° 벗어남(deg={thetaDeg:F1}) → 발진 차단");
|
||||
blocked = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,14 +16,14 @@ namespace ILSOS_PrismaticRD_PTOP.Settings
|
||||
[Browsable(true)]
|
||||
[ReadOnly(false)]
|
||||
[Category("Head Theta Interlock")]
|
||||
[DisplayName("Horizontal Angle (deg)")]
|
||||
public double HeadThetaHorizontal { get; set; } = 0;
|
||||
[DisplayName("Front Angle (deg)")]
|
||||
public double HeadThetaFront { get; set; } = 0;
|
||||
|
||||
[Browsable(true)]
|
||||
[ReadOnly(false)]
|
||||
[Category("Head Theta Interlock")]
|
||||
[DisplayName("Vertical Angle (deg)")]
|
||||
public double HeadThetaVertical { get; set; } = 90;
|
||||
[DisplayName("Down Angle (deg)")]
|
||||
public double HeadThetaDown { get; set; } = 90;
|
||||
|
||||
[Browsable(true)]
|
||||
[ReadOnly(false)]
|
||||
|
||||
@ -387,13 +387,23 @@ namespace WeldingCommon.Settings
|
||||
[Browsable(true)]
|
||||
[ReadOnly(false)]
|
||||
[Category("CellPosMeasure")]
|
||||
[DisplayName("CellCenterOffsetX")]
|
||||
public double CellPosMeasureCenterOffsetX { get; set; } = 0;
|
||||
[DisplayName("Front Offset X")]
|
||||
public double FrontOffsetX { get; set; } = 0;
|
||||
[Browsable(true)]
|
||||
[ReadOnly(false)]
|
||||
[Category("CellPosMeasure")]
|
||||
[DisplayName("CellCenterOffsetY")]
|
||||
public double CellPosMeasureCenterOffsetY { get; set; } = 0;
|
||||
[DisplayName("Front Offset Y")]
|
||||
public double FrontOffsetY { get; set; } = 0;
|
||||
[Browsable(true)]
|
||||
[ReadOnly(false)]
|
||||
[Category("CellPosMeasure")]
|
||||
[DisplayName("Down Offset X")]
|
||||
public double DownOffsetX { get; set; } = 0;
|
||||
[Browsable(true)]
|
||||
[ReadOnly(false)]
|
||||
[Category("CellPosMeasure")]
|
||||
[DisplayName("Down Offset Y")]
|
||||
public double DownOffsetY { get; set; } = 0;
|
||||
|
||||
[Category("Transfer")]
|
||||
[ReadOnly(false)]
|
||||
|
||||
@ -0,0 +1,191 @@
|
||||
# Manual "Select Field" 저장 위치 / Recipe "Run Scout Normal" 읽기 경로 분석
|
||||
|
||||
- 작성일: 2026-06-24
|
||||
- 대상: Manual 화면의 **Select Field(Scout Field 선택)** 저장 흐름과, Recipe 화면의 **Run Scout Normal** 실행 시 선택 필드 영역을 어디서 읽어오는지
|
||||
|
||||
---
|
||||
|
||||
## 0. 결론 요약
|
||||
|
||||
| 항목 | 값/위치 |
|
||||
|------|---------|
|
||||
| 저장되는 값 | `List<ChildBufRowCol>` (각 셀의 `RowIdx`, `ColIdx`, `Enabled`, `PosIdx`, `MotorPos`, `MotorPosOffset`, `Rect` 등) |
|
||||
| 저장 키 | 페이지별로 `selectedIndex{N}` (예: `selectedIndex0`) PageName 으로 구분 |
|
||||
| 저장 파일 | **현재 작업 레시피 폴더의 `FieldHelper.json`** (레시피 파일과 같은 디렉터리) |
|
||||
| 부가 저장 | CAM1 `CamConfig.ChildImage.ChildBufRowCols` → `SaveDeviceConfig`(디바이스 config) |
|
||||
| Run Scout Normal 읽기 | 같은 `FieldHelper.json` 의 싱글톤 캐시(`RecipeFieldScan.Instance`)에서 **PageIndex** 로 조회 |
|
||||
|
||||
핵심: 저장도 읽기도 **`FieldHelper.json` 한 파일**이며, `RecipeFieldScan.Instance`(싱글톤 인메모리 캐시)를 매개로 페이지 인덱스(`selectedIndex{N}`)를 키로 주고받는다. `ScannerObjectTeaching.json` 은 사용되지 않는다. ([[scout-field-storage]] 메모리와 정합)
|
||||
|
||||
---
|
||||
|
||||
## 1. Manual "Select Field" — 저장 흐름
|
||||
|
||||
### 1.1 화면 진입
|
||||
- `UCManualScannerOperationViewModel.PerformCmdShowSelectFieldWindow` → `WDSelectScoutField` 창 오픈
|
||||
- 파일: `WeldingComponents/WeldingCommon/Views/Manual/Scanner/UCManualScannerOperationViewModel.cs:52`
|
||||
- 창의 ViewModel = `WDSelectScoutFieldViewModel`
|
||||
|
||||
### 1.2 초기 로드(편집 대상 채우기)
|
||||
- `WDSelectScoutFieldViewModel.LoadFieldScanParams()` (`...WDSelectScoutFieldViewModel.cs:78`)
|
||||
```csharp
|
||||
FieldScanParamList = CommonHelper.GetMainPage().LoadScoutField();
|
||||
```
|
||||
- `MainPage.LoadScoutField()` (`WeldingComponents/LASA/GUI/Page/MainPage.cs:512`)
|
||||
→ `RecipeFieldScan.Instance.GetPageFieldScan(GetAutoPageName(selectedIndexNo))`
|
||||
→ 현재 페이지(`selectedIndex{N}`)의 `FieldScans` 리스트를 싱글톤 캐시에서 가져옴.
|
||||
|
||||
### 1.3 저장(PerformCmdSave)
|
||||
파일: `...WDSelectScoutFieldViewModel.cs:91`
|
||||
```csharp
|
||||
public void PerformCmdSave()
|
||||
{
|
||||
var camConfig = ...GetDeviceConfig(eCamId.CAM1) as CamConfig;
|
||||
camConfig.ChildImage.ChildBufRowCols = FieldScanParamList.ConvertAll(o => o.Clone());
|
||||
|
||||
CommonHelper.GetMainPage().UpdateScoutField(camConfig.ChildImage.ChildBufRowCols); // (A)
|
||||
VisionEnv.AuroraEnv.DeviceManager.SaveDeviceConfig(eCamId.CAM1); // (B)
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**(A) FieldHelper.json 경로 저장** — `MainPage.UpdateScoutField()` (`MainPage.cs:499`)
|
||||
```csharp
|
||||
SaveAndLoadFieldScanFromLasaDb(); // 기존 캐시 → 파일 동기화
|
||||
int selectedNo = toolStripComboBoxPage.SelectedIndex; // 현재 페이지 인덱스
|
||||
DB.RecipeFieldScan.Instance.UpdateScoutField(a, selectedNo); // 캐시의 해당 페이지 교체
|
||||
SaveAndLoadFieldScanFromLasaDb(); // 캐시 → 파일 재저장+재로드
|
||||
```
|
||||
- `SaveAndLoadFieldScanFromLasaDb()` (`MainPage.cs:535`)
|
||||
- `fullpath = DB.Dao.SystemRecipe.WorkRecipeFile` (현재 작업 레시피 경로)
|
||||
- `newpath = fullpath` 의 파일명을 **`FieldHelper.json`** 으로 치환 → 레시피와 같은 폴더
|
||||
- `RecipeFieldScan.Instance.SaveFieldHelper(newpath)` → JSON 직렬화 후 `File.WriteAllText`
|
||||
(`RecipeFieldScan.cs:96`)
|
||||
|
||||
**(B) 디바이스 config 저장** — `SaveDeviceConfig(CAM1)`
|
||||
- CAM1 `CamConfig.ChildImage.ChildBufRowCols` 를 Aurora 디바이스 config 로 별도 저장(부수적, 실행 읽기에는 미사용).
|
||||
|
||||
### 1.4 저장되는 실제 값(데이터 모델)
|
||||
`RecipeFieldScan.FieldScanData` (`RecipeFieldScan.cs:175`)
|
||||
```json
|
||||
[
|
||||
{
|
||||
"PageName": "selectedIndex0",
|
||||
"FileName": "...FieldScanImage 경로...",
|
||||
"FieldScans": [
|
||||
{ "RowIdx":0, "ColIdx":0, "Enabled":true, "PosIdx":0,
|
||||
"MotorPos":{...}, "MotorPosOffset":{...}, "Rect":{...} },
|
||||
...
|
||||
]
|
||||
},
|
||||
{ "PageName": "selectedIndex1", ... }
|
||||
]
|
||||
```
|
||||
- 페이지별로 한 엔트리, 각 엔트리에 그 페이지의 모든 셀(`ChildBufRowCol`) 영역 정보가 담긴다.
|
||||
- 페이지 키는 `GetAutoPageName(N) = "selectedIndex{N}"` (`RecipeFieldScan.cs:130`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Recipe "Run Scout Normal" — 읽기 경로
|
||||
|
||||
### 2.1 실행 트리거
|
||||
- `UCScannerObjectTeachingControlBoxViewModel.PerformCmdRunScoutNormal` (`...Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs:229`)
|
||||
```csharp
|
||||
var param = new SeqScoutNormal_InitParam() {
|
||||
IsRealTimeScout = false,
|
||||
ScoutSeqParams = scoutSeqParams,
|
||||
PageIndex = CommonHelper.GetMainPage().GetCurrentPageIndex(), // 현재 표시 페이지 명시 주입
|
||||
};
|
||||
_SeqScoutNormal.Start(param, OnCompleteScoutNormal);
|
||||
```
|
||||
- `GetCurrentPageIndex()` (`MainPage.cs:526`): 콤보박스 `SelectedIndex` 우선, 없으면 `ActiveLayer.No`.
|
||||
|
||||
### 2.2 시퀀스 내부 로드
|
||||
- `SeqScoutNormal.InitSequenceVariable()` (`...Sequences/Main/SeqScoutNormal.cs:129`)
|
||||
```csharp
|
||||
_ChildBufRowCols = mainPage.LoadScoutField(_InitParam.PageIndex) // ← 명시 페이지 로드
|
||||
?? mainPage.LoadScoutField() // 폴백: selectedIndexNo
|
||||
?? new List<ChildBufRowCol>(); // 최종 폴백: 빈 리스트(NRE 방지)
|
||||
_ScoutNoList = GenerateSortedScoutNoList(_ChildBufRowCols);
|
||||
```
|
||||
- `MainPage.LoadScoutField(int pageIndex)` (`MainPage.cs:519`)
|
||||
→ `RecipeFieldScan.Instance.GetPageFieldScan(GetAutoPageName(pageIndex))`
|
||||
→ 싱글톤 캐시에서 `selectedIndex{pageIndex}` 엔트리의 `FieldScans` 반환 (`RecipeFieldScan.cs:119`)
|
||||
|
||||
### 2.3 캡처에 사용
|
||||
- `CaptureNextPosition()` (`SeqScoutNormal.cs:230`)에서 `Enabled` 셀만 순회하며
|
||||
`MotorPos + MotorPosOffset` 으로 스캐너 Jump → Grab → `Rect.X/Y` 위치에 부모 이미지로 복사.
|
||||
|
||||
---
|
||||
|
||||
## 3. 두 경로의 연결 고리 (Select Field 저장 ↔ Scout Normal 읽기)
|
||||
|
||||
```
|
||||
[Manual] WDSelectScoutFieldViewModel.PerformCmdSave
|
||||
└─ MainPage.UpdateScoutField(page=SelectedIndex)
|
||||
└─ RecipeFieldScan.Instance.UpdateScoutField (인메모리 캐시 갱신)
|
||||
└─ SaveFieldHelper(newpath) ──┐ 같은 파일
|
||||
│
|
||||
┌────────────────────────────────────────────────────── FieldHelper.json
|
||||
│ (레시피 폴더 내, 페이지별 selectedIndex{N})
|
||||
▼
|
||||
[Recipe] RunScoutNormal(PageIndex=GetCurrentPageIndex)
|
||||
└─ SeqScoutNormal.LoadScoutField(PageIndex)
|
||||
└─ RecipeFieldScan.Instance.GetPageFieldScan("selectedIndex{N}") (같은 싱글톤 캐시)
|
||||
```
|
||||
|
||||
- **매개 저장소**: `RecipeFieldScan.Instance`(싱글톤 인메모리) + 디스크 `FieldHelper.json`.
|
||||
- **키**: 페이지 인덱스 → `selectedIndex{N}`.
|
||||
- 레시피 전환 시에는 `MainPage.ReloadRecipe()`(`MainPage.cs:460`)가 `LoadFieldScanFromLasaDb()`(로드 전용)로 새 레시피의 `FieldHelper.json` 만 싱글톤에 반영 → 이전 레시피 값(stale) 덮어쓰기 방지. (2026-06-20 수정, [[recipe-page-handshake-redesign]] 맥락)
|
||||
|
||||
---
|
||||
|
||||
## 3.5 FieldHelper.json 엔트리 해석 — `FileName`(표시용) vs `FieldScans`(스캔 입력용)
|
||||
|
||||
레시피에서 `FieldHelper.json` 을 열면, `PageName(selectedIndex{N})` 별 엔트리에 다음 두 가지가 들어 있고 **역할이 서로 다르다.**
|
||||
|
||||
| 필드 | 내용 | 실제 쓰임 |
|
||||
|------|------|-----------|
|
||||
| `FileName` | 그 페이지의 스카우트 필드 **이미지 경로** | **화면 표시(배경)용** — Teaching/AutoRun 캔버스에 띄우는 용도 |
|
||||
| `FieldScans` | 셀(`ChildBufRowCol`) 배열: `RowIdx`/`ColIdx`, `Enabled`, `PosIdx`, `MotorPos`/`MotorPosOffset`, `Rect` | **Run Scout Normal 의 실제 입력** — "어디로 가서 찍을지" 결정 |
|
||||
|
||||
### `FileName` 은 표시용이지 스캔 입력이 아님
|
||||
- 사용처: `MainPage.ChangeScoutImage`, `ReloadSetting` → `ChangeFieldString(..., ScannerFieldImageFile)` 로 캔버스 배경 이미지에 반영.
|
||||
- **Run Scout Normal 실행 시 `FileName` 이미지를 불러와 쓰지 않는다.**
|
||||
|
||||
### Run Scout Normal 이 실제로 하는 일 (`FieldScans` 기반)
|
||||
1. `FieldScans` 중 **`Enabled == true` 셀만** 순회 (`SeqScoutNormal.cs:234`)
|
||||
2. 각 셀의 `MotorPos + MotorPosOffset` 좌표로 스캐너 Jump → **카메라로 새로 Grab** (`GrabSync`, `SeqScoutNormal.cs:260`)
|
||||
3. 각 셀의 `Rect.X/Y` 위치에 부모 이미지로 복사 (`MbufCopyClip`, `SeqScoutNormal.cs:268`)
|
||||
4. 완성된 필드 이미지를 **새 경로에 저장** (`{DATA_PATH}/_vision/FieldScanImage/{날짜}/{n}_{시각}.PNG`, `SeqScoutNormal.cs:284` `SaveImageFile`)
|
||||
|
||||
### 한 줄 정리
|
||||
- `FieldScans`(Enable + 좌표/Rect) = Scout Normal 이 읽어 "어디로 가서 찍을지" 정하는 입력 데이터.
|
||||
- `FileName` = 이전에 스캔해 둔 결과 이미지를 화면에 보여주는 표시용(스캔 입력 아님). 실행 시에는 매번 새로 촬영해 새 PNG 로 저장한다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 주의/참고
|
||||
|
||||
- `selectedIndexNo`(GUI 전역 캐시) 의존 버그를 피하기 위해, Scout Normal 은 `PageIndex` 를 **명시 주입**한다(2026-06-19 변경). `LoadScoutField()`(무인자)는 하위호환 폴백으로만 남아 있다. ([[scout-field-storage]])
|
||||
- 배경 이미지(편집 화면 배경)는 별개로 `LASARecipeHelper.GetValue("ScannerFieldImageFile", default)` 경로의 Jpeg 를 사용(`WDSelectScoutFieldViewModel.SetBackgroundImage`).
|
||||
- `ScannerObjectTeaching.json` 은 이 흐름에서 사용되지 않음(다른 컨트롤박스 관련).
|
||||
|
||||
---
|
||||
|
||||
## 부록 — 핵심 파일/라인
|
||||
|
||||
| 역할 | 파일:라인 |
|
||||
|------|-----------|
|
||||
| Select Field 창 오픈 | `WeldingCommon/Views/Manual/Scanner/UCManualScannerOperationViewModel.cs:52` |
|
||||
| 초기 로드 | `.../WDSelectScoutFieldViewModel.cs:78` (`LoadScoutField`) |
|
||||
| 저장 진입 | `.../WDSelectScoutFieldViewModel.cs:91` (`PerformCmdSave`) |
|
||||
| 페이지별 캐시 갱신 | `LASA/GUI/Page/MainPage.cs:499` (`UpdateScoutField`) |
|
||||
| 파일 동기화 경로(FieldHelper.json) | `MainPage.cs:535` (`SaveAndLoadFieldScanFromLasaDb`) |
|
||||
| JSON 직렬화 저장 | `LASA/GUI/DB/RecipeFieldScan.cs:96` (`SaveFieldHelper`) |
|
||||
| 페이지 키 규칙 | `RecipeFieldScan.cs:130` (`GetAutoPageName`) |
|
||||
| Run Scout Normal 트리거 | `WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs:229` |
|
||||
| PageIndex 산출 | `MainPage.cs:526` (`GetCurrentPageIndex`) |
|
||||
| 시퀀스 내 로드 | `WeldingCommon/Sequences/Main/SeqScoutNormal.cs:155` |
|
||||
| 페이지 명시 로드 | `MainPage.cs:519` (`LoadScoutField(int)`) → `RecipeFieldScan.cs:119` (`GetPageFieldScan`) |
|
||||
| 캡처 사용 | `SeqScoutNormal.cs:230` (`CaptureNextPosition`) |
|
||||
@ -7,3 +7,10 @@
|
||||
- 매뉴얼 티칭런(`SeqTeachingRun`/`SeqComponentTeachingRun`)은 발진 전 `SelectCorrectionTable(2)` 선택 후 발진.
|
||||
- **조치**: `ExecuteWelding`의 Laser Gate ON 직전에 **`_Scanner.SelectCorrectionTable(1)`** 명시 추가 + **장비측 보정테이블을 테이블 1 기준으로 정합**. → Auto/매뉴얼 발진 위치 일치(현장 확인).
|
||||
- 참고: 매뉴얼은 테이블 2를 사용하나, 이번에는 장비측을 테이블 1에 맞추는 방식으로 통일함.
|
||||
|
||||
## 11:50 — LGES 검수 (26.06.23 ~ 26.06.24)
|
||||
|
||||
- 사용 매뉴얼 요청으로 인한 **작동 순으로 매뉴얼 작성** (260624 완성)
|
||||
- 추가 UI: **Scanner에 있던 기능들을 Recipe에서 사용할 수 있도록 View 재구성** 적용
|
||||
- SW 대응 + 이후 **금요일까지 SW 대응 및 파일 정리**
|
||||
- (260624 수요일 오전 11시 **Laser 메인 용접·가접 용접 실제 출사 AutoRun 시나리오** 예정)
|
||||
|
||||
18
docs/작업일지/2026_06_24.md
Normal file
18
docs/작업일지/2026_06_24.md
Normal file
@ -0,0 +1,18 @@
|
||||
# 작업일지 — 2026-06-24
|
||||
|
||||
## 각도(D70510) 기반 Laser 발진 Offset 분기 구현
|
||||
|
||||
- **배경**: D70510(카메라 헤드 각도, Head_Theta_Position)에 따라 Laser 발진 위치가 달라지는데, 기존엔 단일 Cell center offset 하나만 적용해 각도 변화 시 위치가 어긋남. → 각도에 따라 Front/Down 두 벌의 offset을 자동 선택 적용하도록 변경.
|
||||
- **선택 로직**: `thetaDeg = D70510 / HeadThetaPlcScale` vs `avg = (Front angle + Down angle)/2`.
|
||||
- `thetaDeg > avg` → **Down offset**, 이하 → **Front offset** 적용.
|
||||
- PLC 미연결/미활성으로 각도 못 읽으면 **Front offset** 적용(fallback) + 경고 로그.
|
||||
- **적용 범위**: 자동(`SeqAutoRunPTOP.SetUploadWeldingList_SeqParams`)·수동(`SeqComponentTeachingRun.SetUploadWeldingList_SeqParams`) 발진 모두. 발진 offset = `선택 Front/Down offset + 비전 offset(_InitParam.CellCenterOffset)` — 기존 SystemParameter 단일 offset을 **대체**.
|
||||
- **변경 파일**:
|
||||
- `Settings/LaserInterlockSetting.cs`: `HeadThetaHorizontal/Vertical` → `HeadThetaFront/Down` (DisplayName "Front/Down Angle (deg)")
|
||||
- `WeldingCommon/Settings/SystemParameter.cs`: `CellPosMeasureCenterOffsetX/Y`(2개) → `FrontOffsetX/Y` + `DownOffsetX/Y`(4개)
|
||||
- `Sequences/LaserAngleOffsetSelector.cs`(신규): 각도 비교 → offset 선택 + 로그 헬퍼
|
||||
- `Sequences/SeqAutoRunPTOP.cs` / `Sequences/Manual/SeqComponentTeachingRun.cs`: 헬퍼 호출로 offset 대체. (자동 인터락 참조·로그 라벨도 Horizontal/Vertical → Front/Down)
|
||||
- `ILSOS_PrismaticRD_PTOP.csproj`: 신규 헬퍼 Compile Include 등록
|
||||
- **LOG**: 발진 시 Front/Down angle, Front/Down offset(x,y), 선택 결과를 한 줄로 기록.
|
||||
- **검증**: Debug|x64 컴파일 통과. sub agent 점검 완료(Critical 없음, 옛 이름 잔존 참조 0건, 공유 모듈 부작용 없음).
|
||||
- **⚠ 배포 전 주의**: 설정 클래스 프로퍼티명 rename으로 **기존 저장 설정값이 자동 이관되지 않음**(Front/Down offset·angle 기본값으로 리셋). 현장 적용 전 기존 보정값을 신규 Front/Down 필드에 **수동 재입력** 필요.
|
||||
Loading…
x
Reference in New Issue
Block a user