# 작업지시서 — Laser Recipe Save 진단계측 **원복** (2026-08-04)
| 항목 | 내용 |
|---|---|
| 대상 | LGES-OC5-PtoP-Welder / ILSOS_PrismaticRD_PTOP |
| 분류 | **원복** (진단계측 제거, 원본 복귀) |
| 적용 문서 | `docs/작업지시서/2026-08-04_Laser_Recipe_Save_진단계측.md` |
| 파일 목록 | `docs/작업지시서/2026-08-04_Laser_Recipe_Save_진단계측_변경파일목록.md` |
| 적용 브랜치 | `feature/ubisam-pptx-integration` |
| 적용 일자 | 2026-08-04 |
## 이 문서의 목적
2026-08-04 진단계측으로 변경된 소스를 **계측 이전 원본 상태로 완전히 되돌린다.**
계측은 진단 전용이며, 원인 경로가 확정되면 반드시 제거한다.
> 원인 확정 후 실제 **수정**(2~4단계)은 선행 문서를 따른다 —
> `docs/작업지시서/작업지시서_2026-08-03_Laser_Recipe_Save_교차오염.txt`
## 원복 대상 (총 6곳)
| # | 파일 | 작업 |
|---|---|---|
| 1 | `WeldingComponents\WeldingCommon\Utils\RecipeTrace.cs` | **파일 삭제** |
| 2 | `WeldingComponents\WeldingCommon\WeldingCommon.csproj` | `Compile Include` 1줄 + 주석 1줄 삭제 |
| 3 | `WeldingComponents\WeldingCommon\Utils\RecipeHelper.cs` | 계측 블록 3곳 → 원본 복귀 |
| 4 | `WeldingComponents\LASA\GUI\Page\MainPage.cs` | 계측 블록 2곳 삭제 |
| 5 | `WeldingComponents\WeldingCommon\Utils\CommonHelper.cs` | 계측 블록 1곳 삭제 |
| 6 | `D:\ILSOSConfig\ILSOS_PrismaticRD_PTOP\RecipeTrace.txt` | 로그 파일 삭제 (설비 PC) |
**변경된 소스는 위 5개 파일뿐입니다.** 그 외 파일은 손대지 않았습니다.
> 4·5번은 Page(`FieldHelper.json`) 계측입니다. 4번의 `Tracer` 필드와 5번의 `Attach()` 는 **쌍**이므로
> 한쪽만 지우면 안 됩니다. 5번만 지우면 `Tracer` 가 `null` 이라 동작은 정상이지만 죽은 코드가 남고,
> 4번만 지우면 5번이 컴파일 에러가 납니다. **반드시 4 → 5 순서로 함께** 지우십시오.
## 계측 위치 찾는 법
삽입한 모든 코드에 마커를 달아 두었습니다. 저장소 전체에서 아래 문자열로 검색하면 **빠짐없이** 찾을 수 있습니다.
```
[진단계측 2026-08-04]
```
`RecipeHelper.cs` 안에서는 아래 형태로 **시작/끝이 쌍**을 이룹니다. 3쌍이 있어야 정상입니다.
```csharp
// ═══ [진단계측 2026-08-04] 시작 ═══
… 이 사이가 전부 제거/복원 대상 …
// ═══ [진단계측 2026-08-04] 끝 ═══
```
각 블록의 머리 주석에 **원본 코드가 그대로 적혀 있습니다.** 그것으로 되돌리면 됩니다.
---
# 원복 1. `RecipeTrace.cs` 삭제
**파일**: `WeldingComponents\WeldingCommon\Utils\RecipeTrace.cs`
파일 전체가 진단 전용입니다. Visual Studio 솔루션 탐색기에서 **제외가 아니라 삭제**하십시오 (제외만 하면 csproj 항목은 남습니다).
VS에서 삭제하면 원복 2가 자동 처리됩니다.
# 원복 2. csproj 항목 삭제
**파일**: `WeldingComponents\WeldingCommon\WeldingCommon.csproj`
**[현재]**
```xml
```
**[원복 후]**
```xml
```
주석 줄까지 **2줄 삭제**입니다.
# 원복 3. `RecipeHelper.cs` — 계측 블록 3곳
**파일**: `WeldingComponents\WeldingCommon\Utils\RecipeHelper.cs`
**인코딩**: UTF-8 유지 (한글 주석 포함)
> 3곳 모두 같은 파일입니다. 위에서부터 지우면 줄 번호가 밀리므로 **줄 번호보다 마커 문자열로 찾으십시오.**
## 3-1. `SelectedRecipe` — 프로퍼티 → 필드
**위치**: `mRecipeManager` 선언 바로 위 (계측 전 기준 18행)
**[현재]**
```csharp
// ═══ [진단계측 2026-08-04] 시작 ═══════════════════════════════════════
// 원본 1줄 : public static (int id, string name) SelectedRecipe;
// 대상(SelectedRecipe)이 바뀌는 순간을 잡기 위해 필드 → 프로퍼티로 전환.
// 외부에서의 읽기/쓰기 방식은 동일하다(대입 4곳, 나머지는 .name/.id 읽기뿐).
// 원복 : 아래 블록 전체를 위 원본 1줄로 되돌린다.
static (int id, string name) _SelectedRecipe;
public static (int id, string name) SelectedRecipe {
get { return _SelectedRecipe; }
set {
var old = _SelectedRecipe;
_SelectedRecipe = value;
RecipeTrace.Mark("대상변경 SELECTED",
$"[{old.id}][{old.name}] → [{value.id}][{value.name}]");
}
}
// ═══ [진단계측 2026-08-04] 끝 ═════════════════════════════════════════
static IRecipeManager mRecipeManager = Application.Current.AuroraEnv().RecipeManager;
```
**[원복 후]**
```csharp
public static (int id, string name) SelectedRecipe;
static IRecipeManager mRecipeManager = Application.Current.AuroraEnv().RecipeManager;
```
## 3-2. `SaveRecipe` — 전/후 계측 제거
**위치**: `GetTeachingEditorFullPath` 바로 아래 (계측 전 기준 36~39행)
**[현재]**
```csharp
public static void SaveRecipe(string recipeName, bool includeModel = true)
{
// ═══ [진단계측 2026-08-04] 시작 ═══════════════════════════════════
// 원본 1줄 : CommonHelper.GetMainPage().SaveRecipe(GetTeachingEditorFullPath(recipeName), includeModel);
// 저장 전/후를 모두 찍어 무엇이 무엇으로 바뀌었는지 확인한다. 저장 자체는 막지 않는다.
// 원복 : 이 블록 전체를 위 원본 1줄로 되돌린다.
var targetPath = GetTeachingEditorFullPath(recipeName);
var snap = RecipeTrace.BeforeSave(recipeName, targetPath);
CommonHelper.GetMainPage().SaveRecipe(targetPath, includeModel);
RecipeTrace.AfterSave(snap);
// ═══ [진단계측 2026-08-04] 끝 ═════════════════════════════════════
}
```
**[원복 후]**
```csharp
public static void SaveRecipe(string recipeName, bool includeModel = true)
{
CommonHelper.GetMainPage().SaveRecipe(GetTeachingEditorFullPath(recipeName), includeModel);
}
```
## 3-3. `ReloadRecipe` — try/catch 및 계측 제거
**위치**: `UpdateAutorunRecipe` 바로 아래 (계측 전 기준 64~69행)
**[현재]**
```csharp
public static void ReloadRecipe(string recipeName)
{
// ═══ [진단계측 2026-08-04] 시작 ═══════════════════════════════════
// 원본 3줄 (try 블록 안의 3줄이 원본 그대로다) :
// var camConfig = VisionEnv.AuroraEnv.DeviceManager.GetDeviceConfig(eCamId.CAM1.ToString()) as CamConfig;
// CommonHelper.GetMainPage().UpdateDefaultField(camConfig.ChildImage.ChildBufRowCols);
// CommonHelper.GetMainPage().ReloadRecipe(GetTeachingEditorFullPath(recipeName));
// 내용 전환의 성공/실패/무동작을 구분해 기록한다. 동작은 그대로다.
// 원복 : try/catch 와 Mark 호출을 걷어내고 위 원본 3줄만 남긴다.
var before = RecipeTrace.MemFingerprint();
RecipeTrace.Mark("내용변경 RELOAD 진입", $"요청=[{recipeName}] 지문(전)={before}");
try {
var camConfig = VisionEnv.AuroraEnv.DeviceManager.GetDeviceConfig(eCamId.CAM1.ToString()) as CamConfig;
CommonHelper.GetMainPage().UpdateDefaultField(camConfig.ChildImage.ChildBufRowCols);
CommonHelper.GetMainPage().ReloadRecipe(GetTeachingEditorFullPath(recipeName));
var after = RecipeTrace.MemFingerprint();
RecipeTrace.Mark(before == after ? "RELOAD 완료(내용무변화!)" : "RELOAD 완료",
$"요청=[{recipeName}] 지문(후)={after}");
} catch (System.Exception ex) {
RecipeTrace.Mark("*** RELOAD 실패 ***",
$"요청=[{recipeName}] {ex.GetType().Name}: {ex.Message}");
throw; // 기존 동작 보존 — 예외 전파는 그대로
}
// ═══ [진단계측 2026-08-04] 끝 ═════════════════════════════════════
}
```
**[원복 후]**
```csharp
public static void ReloadRecipe(string recipeName)
{
var camConfig = VisionEnv.AuroraEnv.DeviceManager.GetDeviceConfig(eCamId.CAM1.ToString()) as CamConfig;
CommonHelper.GetMainPage().UpdateDefaultField(camConfig.ChildImage.ChildBufRowCols);
CommonHelper.GetMainPage().ReloadRecipe(GetTeachingEditorFullPath(recipeName));
}
```
# 원복 4. `MainPage.cs` — Page 계측 블록 2곳
**파일**: `WeldingComponents\LASA\GUI\Page\MainPage.cs`
**인코딩**: UTF-8 유지 (한글 주석 포함)
> **주의** — 이 파일은 `LASAGUI` 프로젝트입니다. 원복 5와 **반드시 함께** 진행하십시오.
## 4-1. `Tracer` 필드 삭제
**위치**: `SaveAndLoadFieldScanFromLasaDb` 정의 바로 위 (≈535~540행)
**[현재]**
```csharp
// ═══ [진단계측 2026-08-04] 시작 ═══════════════════════════════════════
// WeldingCommon(RecipeTrace)이 LASAGUI 를 참조하므로 역방향 호출이 불가능하다.
// 델리게이트로 우회한다. CommonHelper.SetMainPage 에서 RecipeTrace.Mark 가 꽂힌다.
// 원복 : 이 블록과 SaveAndLoadFieldScanFromLasaDb 안의 Tracer?.Invoke 블록을 삭제.
public static Action Tracer;
// ═══ [진단계측 2026-08-04] 끝 ═════════════════════════════════════════
void SaveAndLoadFieldScanFromLasaDb(string inputStr = null)
```
**[원복 후]**
```csharp
void SaveAndLoadFieldScanFromLasaDb(string inputStr = null)
```
블록 **6줄 전체 삭제**입니다.
## 4-2. `Tracer?.Invoke` 삭제
**위치**: `SaveAndLoadFieldScanFromLasaDb` 안, `newpath` 계산 직후 (≈545~548행)
**[현재]**
```csharp
string fullpath = inputStr == null ? DB.Dao.SystemRecipe.WorkRecipeFile : inputStr;
string newpath = fullpath.Replace(Path.GetFileName(fullpath), "FieldHelper.json");
// ═══ [진단계측 2026-08-04] 시작 — 원복 시 이 블록 삭제 ═══
Tracer?.Invoke("PAGE 저장 FieldHelper",
$"경로={newpath} 기준={(inputStr == null ? "WorkRecipeFile" : "인자지정")}");
// ═══ [진단계측 2026-08-04] 끝 ═══
DB.RecipeFieldScan.Instance.SaveFieldHelper(newpath);
```
**[원복 후]**
```csharp
string fullpath = inputStr == null ? DB.Dao.SystemRecipe.WorkRecipeFile : inputStr;
string newpath = fullpath.Replace(Path.GetFileName(fullpath), "FieldHelper.json");
DB.RecipeFieldScan.Instance.SaveFieldHelper(newpath);
```
블록 **4줄 삭제**입니다. 위아래 원본 줄은 그대로 둡니다.
# 원복 5. `CommonHelper.cs` — 훅 연결 삭제
**파일**: `WeldingComponents\WeldingCommon\Utils\CommonHelper.cs`
**위치**: `SetMainPage()` 안 (≈13~21행)
**[현재]**
```csharp
public static void SetMainPage(MainPage mainPage)
{
MainPage = mainPage;
//If Using WeldingManager, This Method Not Necessary.
// ═══ [진단계측 2026-08-04] 시작 — 원복 시 이 블록 삭제 ═══
RecipeTrace.Attach(); // MainPage.Tracer 에 RecipeTrace.Mark 를 연결(참조 방향 우회)
// ═══ [진단계측 2026-08-04] 끝 ═══
}
```
**[원복 후]**
```csharp
public static void SetMainPage(MainPage mainPage)
{
MainPage = mainPage;
//If Using WeldingManager, This Method Not Necessary.
}
```
빈 줄 포함 **4줄 삭제**입니다.
# 원복 6. 설비 PC 로그 파일 삭제
```
D:\ILSOSConfig\ILSOS_PrismaticRD_PTOP\RecipeTrace.txt
```
회수·분석이 끝난 뒤 삭제하십시오. **삭제 전에 반드시 사본을 확보**하십시오 — 원인 확정의 유일한 근거입니다.
---
# 원복 확인
1. 저장소 전체에서 `[진단계측 2026-08-04]` 검색 → **소스에서 0건**이어야 합니다.
(본 문서와 적용 문서 등 `docs/` 하위에는 남아 있는 것이 정상입니다.)
2. `RecipeTrace` 검색 → **소스에서 0건**.
3. `Tracer` 검색 → `MainPage.cs` 에서 **0건**.
4. `ILSOS_All.sln` **Release | x64** 빌드 성공.
5. 기능 확인 — Manual 상태에서 레시피 선택 → 파라미터 수정 → Save → **팝업이 뜨지 않고** 정상 저장.
6. 페이지 콤보박스 전환 → 정상 동작, `RecipeTrace.txt` 가 다시 생기지 않음.
# git 으로 원복하는 경우
본 계측은 `feature/ubisam-pptx-integration` 브랜치에서 적용되었습니다. 커밋 전이라면 아래로 한 번에 되돌릴 수 있습니다.
```powershell
git checkout -- WeldingComponents/WeldingCommon/Utils/RecipeHelper.cs
git checkout -- WeldingComponents/WeldingCommon/Utils/CommonHelper.cs
git checkout -- WeldingComponents/WeldingCommon/WeldingCommon.csproj
git checkout -- WeldingComponents/LASA/GUI/Page/MainPage.cs
del WeldingComponents\WeldingCommon\Utils\RecipeTrace.cs
```
`RecipeTrace.cs` 는 신규 파일이라 `git checkout` 으로는 지워지지 않습니다. **직접 삭제**해야 합니다.
이미 커밋했다면 해당 커밋을 `git revert` 하고, `RecipeTrace.cs` 가 남아 있는지 확인하십시오.
# 주의
- **원복 전에 데이터를 먼저 회수하십시오.** `RecipeTrace.txt` 와 팝업 촬영본이 없으면 계측을 넣은 의미가 사라집니다.
- 원복은 **계측 제거일 뿐 버그 수정이 아닙니다.** 원복하면 교차 오염은 그대로 재발합니다.
실제 수정은 선행 문서(`작업지시서_2026-08-03_Laser_Recipe_Save_교차오염.txt`)의 1~4단계를 따르십시오.
- `_config_recipe` 백업은 계측 제거 후에도 당분간 보관하십시오.