기동 중 RecipeFieldScan 싱글톤이 비어 있는 상태에서 SaveAndLoadFieldScanFromLasaDb 가 Save 를 Load 보다 먼저 수행해(MainPage.cs:539 -> :540) 대상 FieldHelper.json 이 "[]" 2바이트로 파괴됐다. 현장 3건 확인 — RMS(07-29), U_Frame _pre welding(08-03), Bottom_Frame_Welding(08-03). 3건 모두 Aurora 초기화 직후 4~19초, 사용자 입력이 불가능한 구간이며 형제 TeachingEditor.json 은 옛 mtime 을 유지했다. RecipeFieldScan - SaveFieldHelper 진입부: 완주한 로드의 결과가 아닌 빈 상태면 저장을 건너뛴다. Load(:540)는 그대로 돌아 파일의 실제 데이터로 싱글톤을 채운다. 사용자가 페이지를 지워 정당하게 비운 경우는 loadedOnce 로 구분해 통과시킨다. - LoadFieldHelper: loadedOnce 를 Clear 와 짝지어 리셋하고, 완주해도 결과가 0건이면 로드로 보지 않는다. Clear 이후 예외가 나는 경로에서 플래그만 남아 가드가 무력화되는 것을 막는다. - 맨 catch 를 LastSaveError 기록으로 교체. throw 하지 않는 이유는 ReloadSetting 경로가 Rx OnNext 안이라(UCScannerObjectTeachingViewModel.cs:55-57) 전파 시 구독이 끊겨 티칭 화면 갱신이 영구 정지하기 때문이다. RecipeHelper - SaveRecipe 에 저장 대상과 메모리 신원(Recipe.SelectedRecipeFile) 불일치를 Aurora Logger 로 남기는 관측 코드 추가. 저장은 막지 않는다. 기준을 Dao.SystemRecipe.WorkRecipeFile 로 잡지 않은 것은 그 값이 SystemParameterILSOS.json 에 영속되고 Aurora SystemParameter 와 파일을 공유해 신원으로 쓸 수 없기 때문이다. 교차 오염(내용이 채워진 채 엉뚱한 레시피에 기록되는 현상)은 이 변경으로 막히지 않는다. 관측 로그로 발생 경로를 확정한 뒤 별건으로 조치한다. 검증: LASAGUI/WeldingCommon/ILSOS_PrismaticRD_PTOP 빌드 오류 0, 빌드 산출물 직접 로드한 시뮬레이션 31건 전수 통과.
150 lines
6.0 KiB
C#
150 lines
6.0 KiB
C#
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)
|
|
{
|
|
var camConfig = VisionEnv.AuroraEnv.DeviceManager.GetDeviceConfig(eCamId.CAM1.ToString()) as CamConfig;
|
|
CommonHelper.GetMainPage().UpdateDefaultField(camConfig.ChildImage.ChildBufRowCols);
|
|
CommonHelper.GetMainPage().ReloadRecipe(GetTeachingEditorFullPath(recipeName));
|
|
}
|
|
|
|
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<RecipeInfo> recipes = mRecipeManager.Recipes();
|
|
//list 에서 정보 얻을 수 있는것을 찾아보자
|
|
return true;
|
|
}
|
|
}
|
|
}
|