/* * Copyright (c) 2025 LG Electronics Inc. * SPDX-License-Identifier: LicenseRef-LGE-Proprietary */ using System; using System.Collections.Generic; using System.Linq; using Aurora; using System.Reactive.Linq; using System.Windows; using Aurora.Api; using System.Collections.ObjectModel; using System.Windows.Input; using Aurora.View; using VisionCommon; using VisionSequence.Setup; using VisionCommonUI; using VisionCommonUI.Resources; using VisionSequence.Aligner; using System.Reactive.Disposables; namespace VisionUI.Setup { /// /// 비전 반복 View Model /// public class VisionRepeatViewModel : Aurora.View.NotifyPropertyChangedBase, IUpdateRecipe { private Logger _Logger; private ISequence _Seq; private FovInfo _FovInfo; private FovRecipe _FovRecipe; private CsvManager _CsvManager = new CsvManager(); private VisionRepeatRecipe _VisionRepeatRecipe; /// /// 비전 반복도 측정 레시피 /// public VisionRepeatRecipe VisionRepeatRecipe { get => _VisionRepeatRecipe; set => SetProperty(ref _VisionRepeatRecipe, value); } private int _count; /// 반복 횟수 public int RepeatCount { get => _count; set => SetProperty(ref _count, value); } private bool _enalbed; /// 활성화 public bool Enabled { get => _enalbed; set => SetProperty(ref _enalbed, value); } /// 비전 반복도 로그 아이템 리스트 public ObservableCollection VisionRepeatLogItems { get; set; } = new ObservableCollection(); private bool _isShapeModelFinder; /// 패턴 종류가 Shape인 여부 public bool IsShapeModelFinder { get => _isShapeModelFinder; set { SetProperty(ref _isShapeModelFinder, value); NotifyPropertyChanged(nameof(IsShapeModelFinder)); } } private DateTime selectedDate; /// 선택한 날짜 public DateTime SelectedDate { get => selectedDate; set { SetProperty(ref selectedDate, value); CalendarChanged(value); } } private PatternInfo _selectedPatternInfo; public PatternInfo SelectedPatternInfo { get => _selectedPatternInfo; set => SetProperty(ref _selectedPatternInfo, value); } #region Image Pos Binding Properties private double _meanX_I; /// 평균 X (이미지 좌표) public double MeanX_I { get => _meanX_I; set => SetProperty(ref _meanX_I, value); } private double _meanY_I; /// 평균 Y (이미지 좌표) public double MeanY_I { get => _meanY_I; set => SetProperty(ref _meanY_I, value); } private double _sdX_I; /// SdX (이미지 좌표) public double SdX_I { get => _sdX_I; set => SetProperty(ref _sdX_I, value); } private double _sdY_I; /// SdY (이미지 좌표) public double SdY_I { get => _sdY_I; set => SetProperty(ref _sdY_I, value); } private double _minX_I; /// 최소값 X (이미지 좌표) public double MinX_I { get => _minX_I; set => SetProperty(ref _minX_I, value); } private double _minY_I; /// 최소값 Y (이미지 좌표) public double MinY_I { get => _minY_I; set => SetProperty(ref _minY_I, value); } private double _maxX_I; /// 최댓값 X (이미지 좌표) public double MaxX_I { get => _maxX_I; set => SetProperty(ref _maxX_I, value); } private double _maxY_I; /// 최댓값 Y (이미지 좌표) public double MaxY_I { get => _maxY_I; set => SetProperty(ref _maxY_I, value); } private double _rangeX_I; /// 범위 X (이미지 좌표) public double RangeX_I { get => _rangeX_I; set => SetProperty(ref _rangeX_I, value); } private double _rangeY_I; /// 범위 Y (이미지 좌표) public double RangeY_I { get => _rangeY_I; set => SetProperty(ref _rangeY_I, value); } #endregion #region Motion Pos Binding Properties /// 반복도 로그 아이템리스트 (모터 좌표) private double _meanX_S; /// 평균 X (모터 좌표) public double MeanX_S { get => _meanX_S; set => SetProperty(ref _meanX_S, value); } private double _meanY_S; /// 평균 Y (모터 좌표) public double MeanY_S { get => _meanY_S; set => SetProperty(ref _meanY_S, value); } private double _sdX_S; /// SdX (모터 좌표) public double SdX_S { get => _sdX_S; set => SetProperty(ref _sdX_S, value); } private double _sdY_S; /// SdY (모터 좌표) public double SdY_S { get => _sdY_S; set => SetProperty(ref _sdY_S, value); } private double _minX_S; /// 최소값 X (모터 좌표) public double MinX_S { get => _minX_S; set => SetProperty(ref _minX_S, value); } private double _minY_S; /// 최소값 Y (모터 좌표) public double MinY_S { get => _minY_S; set => SetProperty(ref _minY_S, value); } private double _maxX_S; /// 최댓값 X (모터 좌표) public double MaxX_S { get => _maxX_S; set => SetProperty(ref _maxX_S, value); } private double _maxY_S; /// 최댓값 Y (모터 좌표) public double MaxY_S { get => _maxY_S; set => SetProperty(ref _maxY_S, value); } private double _rangeX_S; /// 범위 X (모터 좌표) public double RangeX_S { get => _rangeX_S; set => SetProperty(ref _rangeX_S, value); } private double _rangeY_S; /// 범위 Y (모터 좌표) public double RangeY_S { get => _rangeY_S; set => SetProperty(ref _rangeY_S, value); } #endregion #region Button Commands public ICommand ButtonCommandStart { get; set; } public ICommand ButtonCommandStartDynamic { get; set; } public ICommand ButtonCommandCancel { get; set; } public ICommand ButtonCommandSave { get; set; } public ICommand ButtonCommandLog { get; set; } #endregion /// /// 생성자 /// /// Fov 정보 public VisionRepeatViewModel(FovInfo fovInfo) { _FovInfo = fovInfo; _FovRecipe = VisionRecipeManager.GetFovRecipe(fovInfo); _Logger = new Logger(string.Format("VisionRepeatViewModel[{0}]", fovInfo.SubSysName)); ButtonCommandStart = new DelegateCommand(new Action(DoStart)); ButtonCommandStartDynamic = new DelegateCommand(new Action(DoStartDynamic)); ButtonCommandCancel = new DelegateCommand(new Action(DoCancel)); ButtonCommandSave = new DelegateCommand(new Action(DoSave)); SelectedDate = DateTime.Now.Date; UpdateRecipeFromFile(); SetObservable(); } private void SetObservable() { var disposable = VisionEnv.AuroraEnv.EventObservable.AsObservable(VisionEventId.UPDATE_VISION_REPEAT_LOG). Where(x => x.From == _FovInfo.GetKey()).Subscribe(param => UpdateVisionRepeatLog(param)); DisposableManager.AddDisposable(eDpsGroupKey.SubMenu_VisionRepeat, disposable); } private void UpdateVisionRepeatLog(EventMessage msg) { var visionRepeatLog = msg.Param as VisionRepeatLog; Application.Current.Dispatcher.BeginInvoke( new Action(() => { VisionRepeatLogItems.Add(visionRepeatLog); })); } /// /// 비전 반복도 시퀀스 시작 /// /// public void DoStart(object display = null) { if (RepeatCount > 0 && RepeatCount <= 100) { if (MessageBoxUtil.ShowMessageBoxQuestion( string.Format(StrTokens.MSG_Q_ConfirmRunVisionRepeat)) == MessageBoxResult.Yes) { VisionRepeatLogItems.Clear(); _FovRecipe = VisionRecipeManager.GetFovRecipe(_FovInfo); _Seq = Application.Current.AuroraEnv().SequenceEntry[VisionKeys.VisionRepeatSeqKey]; _Seq?.Start(new SetupSeqParams_VisionRepeat(_FovInfo, SelectedPatternInfo, RepeatCount), OnCompleted); } } else { MessageBoxUtil.ShowMessageBoxError( string.Format(StrTokens.MSG_E_NumberInvalid, "Step Count", 0, 100)); } } /// /// 다이나믹 반복 측정 /// /// public void DoStartDynamic(object display = null) { if (RepeatCount > 0 && RepeatCount <= 100) { if (MessageBoxUtil.ShowMessageBoxQuestion( string.Format(StrTokens.MSG_Q_ConfirmRunVisionRepeat)) == MessageBoxResult.Yes) { VisionRepeatLogItems.Clear(); _FovRecipe = VisionRecipeManager.GetFovRecipe(_FovInfo); _Seq = Application.Current.AuroraEnv().SequenceEntry[VisionKeys.VisionRepeatDynamicSeqKey]; _Seq?.Start(new SetupSeqParams_DynamicRepeat(_FovInfo, SelectedPatternInfo, motionRepeatCount: RepeatCount), OnCompletedDynamic); } } else { MessageBoxUtil.ShowMessageBoxError( string.Format(StrTokens.MSG_E_NumberInvalid, "Step Count", 0, 100)); } } /// /// 시퀀스 정지 /// /// null public void DoCancel(object display = null) { _Seq?.Stop(); } /// /// UI로부터 레시피 저장 /// /// null public void DoSave(object o = null) { UpdateRecipeFromUI(); RecipeFileManager.SaveVisionRecipe(); VisionEnv.AuroraEnv.EventPublisher.Publish(new EventMessage( _FovRecipe.FovName, VisionEventId.NOTIFY_VISION_REPEAT_SAVE_PARAMS, VisionRepeatRecipe)); } private void OnCompleted(SequenceActionResult SeqResult) { if (SeqResult == null) { _Logger.FuncE("SeqResult is null."); return; } Application.Current.Dispatcher.Invoke(() => { PrintVisionRepeatResult_I(SeqResult); PrintVisionRepeatResult_S(SeqResult); }); } private void OnCompletedDynamic(SequenceActionResult SeqResult) { if (SeqResult == null) { _Logger.FuncE("SeqResult is null."); return; } Application.Current.Dispatcher.Invoke(() => { PrintVisionRepeatResult_I(SeqResult); PrintVisionRepeatResult_S(SeqResult); }); } private void PrintVisionRepeatResult_I(SequenceActionResult SeqResult) { var visionRepeatSeqResult = SeqResult.Param as SetupSeqResultParams_VisionRepeat; if (visionRepeatSeqResult.ImagePoints.Count == 0) { return; } var imagePoints = visionRepeatSeqResult.ImagePoints.Select(item => item.Clone()); int i = 1; MeanX_I = MeanY_I = SdX_I = SdY_I = MinX_I = MinY_I = MaxX_I = MaxY_I = RangeX_I = RangeY_I = 0.0; foreach (var imagePoint in imagePoints) { MeanX_I += imagePoint.X; MeanY_I += imagePoint.Y; if (i == 1) { MinX_I = imagePoint.X; MinY_I = imagePoint.Y; MaxX_I = imagePoint.X; MaxY_I = imagePoint.Y; } else { if (MinX_I > imagePoint.X) MinX_I = imagePoint.X; if (MinY_I > imagePoint.Y) MinY_I = imagePoint.Y; if (MaxX_I < imagePoint.X) MaxX_I = imagePoint.X; if (MaxY_I < imagePoint.Y) MaxY_I = imagePoint.Y; } i++; } MeanX_I /= imagePoints.Count(); MeanY_I /= imagePoints.Count(); foreach (var item in imagePoints) { #warning SdX_S 계산식 확인 할 것 SdX_I = (item.X - MeanX_I) * (item.X - MeanX_I); SdY_I = (item.Y - MeanY_I) * (item.Y - MeanY_I); } if (imagePoints.Count() > 1) { SdX_I = Math.Sqrt(SdX_I) / (imagePoints.Count() - 1); SdY_I = Math.Sqrt(SdY_I) / (imagePoints.Count() - 1); RangeX_I = MaxX_I - MinX_I; RangeY_I = MaxY_I - MinY_I; } } private void PrintVisionRepeatResult_S(SequenceActionResult SeqResult) { var visionRepeatSeqResult = SeqResult.Param as SetupSeqResultParams_VisionRepeat; if (visionRepeatSeqResult.StaticPoints.Count == 0) { return; } var staticPoints = visionRepeatSeqResult.StaticPoints.Select(item => item.Clone()); int i = 1; MeanX_S = MeanY_S = SdX_S = SdY_S = MinX_S = MinY_S = MaxX_S = MaxY_S = RangeX_S = RangeY_S = 0.0; foreach (var staticPoint in staticPoints) { MeanX_S += staticPoint.X; MeanY_S += staticPoint.Y; if (i == 1) { MinX_S = staticPoint.X; MinY_S = staticPoint.Y; MaxX_S = staticPoint.X; MaxY_S = staticPoint.Y; } else { if (MinX_S > staticPoint.X) MinX_S = staticPoint.X; if (MinY_S > staticPoint.Y) MinY_S = staticPoint.Y; if (MaxX_S < staticPoint.X) MaxX_S = staticPoint.X; if (MaxY_S < staticPoint.Y) MaxY_S = staticPoint.Y; } i++; } MeanX_S /= staticPoints.Count(); MeanY_S /= staticPoints.Count(); #warning SdX_S 계산식 확인 할 것 foreach (var item in staticPoints) { SdX_S = (item.X - MeanX_S) * (item.X - MeanX_S); SdY_S = (item.Y - MeanY_S) * (item.Y - MeanY_S); } if (staticPoints.Count() > 1) { SdX_S = Math.Sqrt(SdX_S) / (staticPoints.Count() - 1); SdY_S = Math.Sqrt(SdY_S) / (staticPoints.Count() - 1); RangeX_S = MaxX_S - MinX_S; RangeY_S = MaxY_S - MinY_S; } } public void PatternChanged() { } /// /// 비전 레시피 Refresh /// public void RefreshVisionRecipes() { _FovRecipe = VisionRecipeManager.GetFovRecipe(_FovInfo); } /// /// 파일로부터 레시피 불러오기 /// public void UpdateRecipeFromFile() { var visionRepeatRecipe = VisionRecipeManager.GetVisionRepeatRecipe(_FovInfo); VisionRepeatRecipe = visionRepeatRecipe.Clone(); RepeatCount = VisionRepeatRecipe.RepeatCount; } /// /// UI로부터 레시피 불러오기 /// public void UpdateRecipeFromUI() { var visionRepeatRecipe = VisionRecipeManager.GetVisionRepeatRecipe(_FovInfo); VisionRepeatRecipe.RepeatCount = RepeatCount; VisionRepeatRecipe.CopyTo(visionRepeatRecipe); } /// /// 현재 시간 기준으로 CSV 불러오기 /// public void LoadCsvNow() { DateTime dateTime = DateTime.Now; LoadCsv(dateTime); } private void CalendarChanged(DateTime dateTime) { LoadCsv(dateTime); } private void LoadCsv(DateTime dateTime) { try { VisionRepeatLogItems?.Clear(); List items = _CsvManager.LoadCsv(_FovInfo.ToSubSysInfo(), dateTime, eSaveDataType.VisionRepeat); if (items != null) { // 로드할 파일이 없으면 items가 null임. foreach (VisionRepeatLog item in items) { VisionRepeatLogItems.Add(item); } } } catch (Exception e) { _Logger.FuncE(e.Message); } } } }