using Aurora; using Aurora.Api; using Aurora.Api.Manager; using DB; using DB.Recipe; using System; using System.Collections.Generic; using System.Configuration; using System.IO; using System.Linq; using System.Windows; using VisionCommon; using WeldingCommon.Sequences; namespace WeldingCommon.Utils { public static class RecipeHelper { public static (int id, string name) SelectedRecipe; static Logger Log = new Logger("RecipeHelper"); // [추가 2026-08-04] static IRecipeManager mRecipeManager = Application.Current.AuroraEnv().RecipeManager; static string GetTeachingEditorFullPath(string recipeName) { var recipePath = ConfigurationManager.AppSettings.Get("DATA_PATH"); var fullPath = Path.Combine( recipePath, RecipeExIds.RECIPE_DIR, recipeName, RecipeExIds.TEACHING_EDITOR_RECIPE); var dir = Path.GetDirectoryName(fullPath); if (!Directory.Exists(dir)) { Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); } return fullPath; } public static void SaveRecipe(string recipeName, bool includeModel = true) { var targetPath = GetTeachingEditorFullPath(recipeName); // [추가 2026-08-04] 관측 전용. 저장을 막지 않는다. // 기준은 DB.Recipe.Recipe.SelectedRecipeFile - Recipe.Load(string) 성공 시에만 // Recipe.cs:69 에서 세팅되며 디스크를 거치지 않는다. // (Dao.SystemRecipe.WorkRecipeFile 은 SystemParameterILSOS.json 에 영속되고 // Aurora SystemParameter 와 파일을 공유하므로 신원 기준으로 쓸 수 없다) // 두 조건은 독립 신호이므로 else if 로 묶지 않는다. var loaded = DB.Recipe.Recipe.SelectedRecipeFile; if (string.IsNullOrWhiteSpace(loaded)) { Log.W($"[관측] 메모리 신원 없음(로드 이력 없음). target=[{targetPath}]"); } if (!string.IsNullOrWhiteSpace(loaded) && !string.Equals(loaded, targetPath, StringComparison.OrdinalIgnoreCase)) { Log.W($"[관측] 메모리/대상 불일치. loaded=[{loaded}] target=[{targetPath}]"); } CommonHelper.GetMainPage().SaveRecipe(targetPath, includeModel); // 아래는 이번 저장과 무관한 다른 경로(ReloadSetting/UpdateScoutField/콤보박스)에서 // 누적된 것일 수 있다. 경로는 각 항목의 파일명으로 판독한다. if (DB.RecipeFieldScan.SkippedEmptySaveCount > 0 || DB.RecipeFieldScan.LastSaveError != null) { Log.W($"[관측] FieldHelper 저장 생략 {DB.RecipeFieldScan.SkippedEmptySaveCount}회 " + $"(대상 {DB.RecipeFieldScan.SkippedFiles.Count}종: {string.Join(" | ", DB.RecipeFieldScan.SkippedFiles)}), " + $"최근오류=[{DB.RecipeFieldScan.LastSaveError}] ※직전 저장과 무관할 수 있음"); DB.RecipeFieldScan.SkippedEmptySaveCount = 0; DB.RecipeFieldScan.SkippedFiles.Clear(); DB.RecipeFieldScan.LastSaveError = null; } } public static void OnSaveRecipeForEditing() { if (string.IsNullOrWhiteSpace(SelectedRecipe.name)) { return; } SaveRecipe(SelectedRecipe.name); if (SelectedRecipe.name == mRecipeManager.ActivatedRecipe().Name) { CommonHelper.UpdateAutoRunPage(); } } public static void UpdateAutorunRecipe() { if (string.IsNullOrWhiteSpace(SelectedRecipe.name)) { return; } if (SelectedRecipe.name == mRecipeManager.ActivatedRecipe().Name) { CommonHelper.UpdateAutoRunPage(); } } 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] 끝 ═════════════════════════════════════ } public static RecipeWeldingLines getRecipeInfo(string recipeName) => CommonHelper.GetMainPage().getRecipeInfo(GetTeachingEditorFullPath(recipeName)); public static void OnActiveReloaded() { var activeRecipe = mRecipeManager.ActivatedRecipe(); SelectedRecipe = (activeRecipe.Id, activeRecipe.Name); ReloadRecipe(activeRecipe.Name); } public static void OnActiveRecipe(int recipeId, string recipeName) { mRecipeManager.ActivateRecipe(recipeId, recipeName); } public static string SearchRecipeItem(int recipeID) { string recipeName = string.Empty; var listRecipe = mRecipeManager.Recipes(); // Find the recipe with the matching ID var recipe = listRecipe.FirstOrDefault(r => r.Id == recipeID); // If a recipe is found, assign its name to recipeName if (recipe != null) { recipeName = recipe.Name; } return recipeName; } //LASA는 Recipe를 1개만 사용 할 수 있음 //TechingRecipe -> ActiavatedReicpe(AutoRun) -> TeachingRecipe //AutoRun 완료 후 다시 Teching 중인 Recipe로 Rollback 해줘야 함 public static void RollbackRecipe() { Application.Current.Dispatcher.InvokeAsync(() => { ReloadRecipe(SelectedRecipe.name); }); } public static bool OnChangeRecipe(int RecipeID) { IList recipes = mRecipeManager.Recipes(); //list 에서 정보 얻을 수 있는것을 찾아보자 return true; } } }