# 작업지시서 — 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` 백업은 계측 제거 후에도 당분간 보관하십시오.