using Aurora; using Aurora.Api; using Aurora.View; using AuroraIO; using AuroraLaser; using AuroraLaserScanner; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reactive.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Windows.Threading; using VisionCommon; using WeldingCommon; using WeldingCommon.Devices.Laser; using WeldingCommon.Models; using WeldingCommon.Sequences; using WeldingCommon.Settings; using WeldingCommon.Utils; using static AuroraLaser.Ekspla.NL202; using static WeldingCommon.Devices.Laser.PAIX_PrismaticScanner; namespace WeldingCommon.Views { public class UCScannerObjectTeachingControlBoxViewModel : NotifyPropertyChangedBase { private readonly Logger Log = new Logger("UCScannerObjectTeachingControlBoxViewModel"); public ICommand CMD_VisibleChanged { get; } public ICommand CMD_RunScoutNormal { get; } public ICommand CMD_StopScoutNormal { get; } public ICommand CMD_RunScoutFlyingVision { get; } public ICommand CMD_StopScoutFlyingVision { get; } public ICommand CMD_RunTeachingRun { get; } public ICommand CMD_StopTeachingRun { get; } public ICommand CMD_LaserGuideBeam { get; } public ICommand CMD_LaserReadyCheck { get; } public ICommand CMD_ImportObjectFromDXF { get; } public ICommand CMD_ToggleScannerCamView { get; } public ICommand CMD_ShowSelectFieldWindow { get; } private readonly IAuroraEnvironment _Aurora; private SystemParameter _SystemParameter; //private readonly EquipmentInfo _EquipmentInfo; private readonly Scanner _Scanner; private readonly Laser _Laser; private readonly LaserConfig _LaserConfig; private IODevice _IODevice; private ISequence _SeqScoutNormal; private bool _IsSequenceRunning_ScoutNormal; public bool IsSequenceRunning_ScoutNormal { get => _IsSequenceRunning_ScoutNormal; set => SetProperty(ref _IsSequenceRunning_ScoutNormal, value); } private ISequence _SeqScoutFlyingVision; private bool _IsSequenceRunning_ScoutFlyingVision; public bool IsSequenceRunning_ScoutFlyingVision { get => _IsSequenceRunning_ScoutFlyingVision; set => SetProperty(ref _IsSequenceRunning_ScoutFlyingVision, value); } private ISequence _SeqTeachingRun; private bool _IsSequenceRunning_TeachingRun; public bool IsSequenceRunning_TeachingRun { get => _IsSequenceRunning_TeachingRun; set => SetProperty(ref _IsSequenceRunning_TeachingRun, value); } private bool _IsSelectedScoutNormal; public bool IsSelectedScoutNormal { get => _IsSelectedScoutNormal; set => SetProperty(ref _IsSelectedScoutNormal, value); } private bool _IsGuidBeam; public bool IsGuidBeam { get => _IsGuidBeam; set => SetProperty(ref _IsGuidBeam, value); } private bool _IsLaserReady; public bool IsLaserReady { get => _IsLaserReady; set => SetProperty(ref _IsLaserReady, value); } // Object Select Type private bool _IsObjectSelectAll; public bool IsObjectSelectAll { get => _IsObjectSelectAll; set => SetProperty(ref _IsObjectSelectAll, value); } #region 260820 PLC 지시 Recipe/Page 표시 (원복: 이 region + OnMonitoring 의 SyncPlcGuide() 호출 삭제) // 값은 설비별로 Status 에 들어온다(PTOP: bit15 구간에만). 공용은 Status 만 읽으므로 설비 의존이 없다. // 바인딩은 UCScannerObjectTeaching.xaml 상단 띠 — 그 화면의 DataContext(바깥 VM)가 // ScannerObjectTeachingControlBoxViewModel 을 공개하고 있어 여기 프로퍼티를 그대로 쓴다. // (바깥 VM 에는 폴링 타이머가 없고 이 VM 에는 100ms 타이머가 이미 돌고 있어 재활용한다) private bool _IsPlcGuideVisible; public bool IsPlcGuideVisible { get => _IsPlcGuideVisible; set => SetProperty(ref _IsPlcGuideVisible, value); } private string _PlcGuideText = string.Empty; public string PlcGuideText { get => _PlcGuideText; set => SetProperty(ref _PlcGuideText, value); } private void SyncPlcGuide() { var recipeNo = Status.PlcGuideRecipeNo; var pageNo = Status.PlcGuidePageNo; if (recipeNo == null || pageNo == null) { IsPlcGuideVisible = false; return; } PlcGuideText = $"RECIPE {recipeNo} PAGE {pageNo}"; IsPlcGuideVisible = true; } #endregion // 레시피별 Laser 방향 발진 차단 체크 (Recipe_Laser_Interlock.json) — 변경 시 즉시 활성 레시피에 저장 private bool _IsFrontInterlock; public bool IsFrontInterlock { get => _IsFrontInterlock; set { SetProperty(ref _IsFrontInterlock, value); SaveLaserInterlock(); } } private bool _IsDownInterlock; public bool IsDownInterlock { get => _IsDownInterlock; set { SetProperty(ref _IsDownInterlock, value); SaveLaserInterlock(); } } // 로드로 인한 set 시 저장 억제(레시피 전환 시 stale 덮어쓰기 방지) private bool _suppressInterlockSave; private void SaveLaserInterlock() { if (_suppressInterlockSave) { return; } RecipeLaserInterlock.Save(new RecipeLaserInterlock { Front = _IsFrontInterlock, Down = _IsDownInterlock }); } private void LoadLaserInterlock() { var rl = RecipeLaserInterlock.Load(); _suppressInterlockSave = true; try { IsFrontInterlock = rl.Front; IsDownInterlock = rl.Down; } finally { _suppressInterlockSave = false; } } // ScoutNormal 행 전환 sleep 분리 (같은 행 / 행 전환) private int _ScoutSameRowSleepMs; public int ScoutSameRowSleepMs { get => _ScoutSameRowSleepMs; set { SetProperty(ref _ScoutSameRowSleepMs, value); _SystemParameter.ScoutSameRowSleepMs = value; SaveScoutSleep(); } } private int _ScoutRowChangeSleepMs; public int ScoutRowChangeSleepMs { get => _ScoutRowChangeSleepMs; set { SetProperty(ref _ScoutRowChangeSleepMs, value); _SystemParameter.ScoutRowChangeSleepMs = value; SaveScoutSleep(); } } private void SaveScoutSleep() { _Aurora.SettingEntry.Save(CommonSettingIds.SystemParameter, _SystemParameter, "ScoutSleep"); } private readonly DispatcherTimer _MonitorTimer; private SequenceDialog _SequenceDialog; // 레시피 화면에서 토글로 띄우는 스캐너 카메라 + 모션 창 (재사용을 위해 Hide/Show) private WDScannerCamMotion _ScannerCamWindow; // Scout Field 선택 팝업 (매뉴얼 화면과 동일한 창을 레시피 화면에서도 사용) private WDSelectScoutField _SelectFieldWindow; private WinSequenceDiagram _WinSequenceDiagramScoutNormal; private WinSequenceDiagram _WinSequenceDiagramScoutFlyingVision; private WinSequenceDiagram _WinSequenceDiagramTeachingRun; private WinSequenceDiagram _WinSequenceDiagramComponentTeachingRun; public bool toggleIO = true; public UCScannerObjectTeachingControlBoxViewModel(IAuroraEnvironment aurora) { _Aurora = aurora; _SystemParameter = aurora.SettingEntry[CommonSettingIds.SystemParameter] as SystemParameter; _ScoutSameRowSleepMs = _SystemParameter.ScoutSameRowSleepMs; _ScoutRowChangeSleepMs = _SystemParameter.ScoutRowChangeSleepMs; _SeqScoutNormal = _Aurora.SequenceEntry[CommonSequenceIds.SeqScoutNormal]; _SeqScoutFlyingVision = _Aurora.SequenceEntry[CommonSequenceIds.SeqScoutFlyingVision]; _Scanner = _Aurora.DeviceManager[CommonDeviceIds.Scanner] as Scanner; _Laser = _Aurora.DeviceManager[CommonDeviceIds.Laser] as Laser; _IODevice = _Aurora.DeviceManager[CommonDeviceIds.IODevice] as IODevice; // Trumpf 레이저인 경우에는 ComponentTeachingRun 실행 _LaserConfig = _Aurora.DeviceManager.GetDeviceConfig(CommonDeviceIds.Laser) as LaserConfig; _SeqTeachingRun = _Aurora.SequenceEntry[CommonSequenceIds.SeqComponentTeachingRun]; InitVariable(); LoadLaserInterlock(); CMD_VisibleChanged = new DelegateCommand(VisibleChanged); CMD_RunScoutNormal = new DelegateCommand(PerformCmdRunScoutNormal); CMD_StopScoutNormal = new DelegateCommand(PerformCmdStopScoutNormal); CMD_RunScoutFlyingVision = new DelegateCommand(PerformCmdRunScoutFlyingVision); CMD_StopScoutFlyingVision = new DelegateCommand(PerformCmdStopScoutFlyingVision); CMD_RunTeachingRun = new DelegateCommand(PerformCmdRunTeachingRun); CMD_StopTeachingRun = new DelegateCommand(PerformCmdStopTeachingRun); CMD_LaserGuideBeam = new DelegateCommand(PerformCmdLaserGuideBeam); CMD_LaserReadyCheck = new DelegateCommand(PerformCmdLaserReadyCheck); CMD_ImportObjectFromDXF = new DelegateCommand(PerformCmdImportObjectFromDXF); CMD_ToggleScannerCamView = new DelegateCommand(PerformCmdToggleScannerCamView); CMD_ShowSelectFieldWindow = new DelegateCommand(PerformCmdShowSelectFieldWindow, CanShowSelectFieldWindow); _Aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_SETTING_CHANGED) .ObserveOnDispatcher() .Subscribe(OnSettingReloaded); // 레시피 전환 시 체크 상태를 새 레시피의 json 값으로 갱신. // SYS_RECIPE_ACTIVATED(1-홉)에서 WorkRecipeFile은 아직 미갱신(ReloadRecipe는 2-홉 뒤) → // Background 우선순위로 지연 실행해 ReloadRecipe(Normal) 완료 후 새 경로로 로드. _Aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_RECIPE_ACTIVATED) .ObserveOnDispatcher() .Subscribe(_ => Application.Current.Dispatcher.BeginInvoke( new Action(LoadLaserInterlock), DispatcherPriority.Background)); _MonitorTimer = new DispatcherTimer(); _MonitorTimer.Tick += OnMonitoring; _MonitorTimer.Interval = TimeSpan.FromMilliseconds(100); } private void InitVariable() { if (_SystemParameter.ScoutType == eScoutType.Normal) { IsSelectedScoutNormal = true; } else { IsSelectedScoutNormal = false; } IsSequenceRunning_ScoutNormal = false; IsSequenceRunning_ScoutFlyingVision = false; IsSequenceRunning_TeachingRun = false; IsGuidBeam = false; IsLaserReady = false; IsObjectSelectAll = true; } private void VisibleChanged(object obj) { bool visible = (bool)obj; if (visible) { LoadLaserInterlock(); // 패널 표시 시 현재 활성 레시피의 체크값 반영 _MonitorTimer.Start(); } else { _MonitorTimer.Stop(); } } private void OnSettingReloaded(object obj) { _SystemParameter = _Aurora.SettingEntry[CommonSettingIds.SystemParameter] as SystemParameter; if (_SystemParameter.ScoutType == eScoutType.Normal) { IsSelectedScoutNormal = true; } else { IsSelectedScoutNormal = false; } } private void OnMonitoring(object sender, EventArgs e) { // 260820: 아래 IO 블록은 미연결 시 return 하므로, PLC 지시 표시는 반드시 그 앞에서 갱신한다. SyncPlcGuide(); // [기존 소스 보존] Laser 디바이스 기반 Pilot 상태 (DO 3=GuideLaserEnable) — 일부 Laser 미구현 시 항상 false //if (_Laser.GetPilotLaserStatus()) { // IsGuidBeam = true; //} else { // IsGuidBeam = false; //} try { // Guide Beam: DI 결선 없음 → 가이드빔 제어 DO 17 명령 비트를 그대로 표시 // (현장 가이드빔 제어선은 DO 17). IO 미연결 시 ReadDO 예외 → 아래 catch 에서 처리(앱 종료 방지) // 시뮬 아님 + IO보드 없음 → _iOUnit == null → ReadDI에서 NRE로 꺼짐. - 현장PC 아닌 경우 개발PC에서 주석 해제 (시뮬안하고 레시피 들어갔을 때 프로그램 종료 방지) // IsConnected()가 _iOUnit null/미연결을 체크하므로, 미연결이면 폴링 스킵(꺼짐 방지). if (_IODevice == null || !_IODevice.IsConnected()) { IsGuidBeam = false; IsLaserReady = false; return; } #region 260824 이전 원본 (임시레이저 채널 하드코딩) //IsGuidBeam = _IODevice.ReadDO(17); //IsLaserReady = _IODevice.ReadDI(11) && _IODevice.ReadDI(15); // 변경 후 (260618: IO 결선 변경 12&24 → 11&15) #endregion // 260824 본 레이저 단일 발진기 + enum 참조로 전환 (하드코딩 제거) // 구조: 구 DI 11/15 = Core/Ring_Control_Status(명령 DO 9/13 의 에코). // 본 레이저의 대응 짝은 DO Laser_REMOTE(External Start) → DI Laser_EMISSION_ON 이다. // ★ Laser_READY(Pin21)를 쓰면 안 된다 — "Power Supply ON & No error" 라 // 전원만 들어오면 상시 High 이므로 버튼(CMD_LaserReadyCheck)에 반응하지 않고, // :468 의 발진 차단 인터락도 무력해진다. (현장 확인 260824) // // 260826 협의 확정 — DI 에코(Laser_EMISSION_ON) 가 아니라 DO 명령 비트를 직접 표시한다. // 버튼(CMD_LaserReadyCheck)이 토글하는 대상이 DO Laser_REMOTE 이므로, // 같은 비트를 되읽어야 조작과 표시가 1:1 로 맞는다. IsGuidBeam 과 동일한 방식. IsGuidBeam = _IODevice.ReadDO((int)ePrismaticDOAddress.Laser_GUIDE_BEAM_CONTROL); IsLaserReady = _IODevice.ReadDO((int)ePrismaticDOAddress.Laser_REMOTE); } catch (Exception ex) { Log.E($"[OnMonitoring] DI 읽기 실패: {ex.Message}"); IsLaserReady = false; IsGuidBeam = false; } // TODO: PRIBAT_L1, PRIBAT_L2 는 Blow 의 상태 check 가 들어 있음. 장비 한정이므로 이를 check 하는 것을 modelItem 이나 setting 으로 빼서 진행되도록 함. } private void PerformCmdRunScoutNormal(object obj) { var result = MessageUtil.ShowYesNoMessageDialog("Scanner Scout", "Start Scanner Scout Normal Sequence?"); if (result == false) { return; } if (VisionRecipeManager.GetFovInfos().Count == 0) { MessageUtil.ShowWarningToast(string.Format("Please check the Vision Recipe of the currently active Recipe.")); return; } if (_SeqScoutNormal.RunState == SequenceRunState.Running) { MessageUtil.ShowWarningToast(string.Format("이미 시퀀스가 Start 상태입니다.", "SeqScoutNormal"), Log); return; } var scoutSeqParams = new ScoutSeqParams(VisionRecipeManager.GetFovInfos()[0], eDisplayType.Machine_B); #region 260807 이전 원본 (주석 처리 — 원복 시 이 블록을 살리고 아래 260807 블록을 제거한다) //var param = new SeqScoutNormal_InitParam() { // IsRealTimeScout = false, // ScoutSeqParams = scoutSeqParams, // // [추가 2026-06-19] 현재 표시 중인 페이지의 스카우트 필드를 사용하도록 명시 주입 // PageIndex = CommonHelper.GetMainPage().GetCurrentPageIndex(), //}; #endregion #region 260807: 레시피/페이지 번호 반영 SaveTag 변경 — 파일명 {기존}_normal_{recipeId}_{pageNo}.PNG int pageIndex = CommonHelper.GetMainPage().GetCurrentPageIndex(); // 0-base (콤보박스 SelectedIndex) int recipeId = _Aurora.RecipeManager.ActivatedRecipe()?.Id ?? 0; // 레시피 미활성 시 0 var param = new SeqScoutNormal_InitParam() { IsRealTimeScout = false, ScoutSeqParams = scoutSeqParams, // [추가 2026-06-19] 현재 표시 중인 페이지의 스카우트 필드를 사용하도록 명시 주입 PageIndex = pageIndex, // 파일명 접미어: normal_{레시피ID}_{화면 페이지 번호(1-base)} → AutoRun 의 auto_{recipe}_{page}_pre/post 와 숫자 정합 SaveTag = $"normal_{recipeId}_{pageIndex + 1}", }; #endregion PopupSequenceDiagram(ref _WinSequenceDiagramScoutNormal, CommonSequenceIds.SeqScoutNormal, param); IsSequenceRunning_ScoutNormal = true; _SeqScoutNormal.Start(param, OnCompleteScoutNormal); } private void OnCompleteScoutNormal(SequenceActionResult res) { if (res != null) { if (res.Param is SeqScoutNormal_OutParam result && result.Success == true) { IsSequenceRunning_ScoutNormal = false; MessageUtil.ShowSuccessToast(string.Format("Success SeqScoutNormal")); CloseSequenceDiagram(_WinSequenceDiagramScoutNormal); return; } } if (_Laser.GetLaserReady()) { _Laser.LaserOff(); } IsSequenceRunning_ScoutNormal = false; MessageUtil.ShowWarningToast(string.Format("Fail SeqScoutNormal")); } private void PerformCmdStopScoutNormal(object obj) { if (_SeqScoutNormal.RunState == SequenceRunState.Idle) { return; } IsSequenceRunning_ScoutNormal = false; _SeqScoutNormal.Stop(); } private void PerformCmdRunScoutFlyingVision(object obj) { var result = MessageUtil.ShowYesNoMessageDialog("Flying Vision", "Start Scanner Scout Flying Vision Sequence?"); if (result == false) { return; } if (VisionRecipeManager.GetFovInfos().Count == 0) { MessageUtil.ShowWarningToast(string.Format("Please check the Vision Recipe of the currently active Recipe.")); return; } if (_SeqScoutFlyingVision.RunState == SequenceRunState.Running) { MessageUtil.ShowWarningToast(string.Format("이미 시퀀스가 Start 상태입니다.", "SeqScoutFlyingVision"), Log); return; } var scoutSeqParams = new ScoutSeqParams(VisionRecipeManager.GetFovInfos()[0], eDisplayType.Machine_B); var param = new SeqScoutFlyingVision_InitParam() { IsRealTimeScout = false, ScoutSeqParams = scoutSeqParams, SequenceAutoRunCount = 0, WeldingScannerListNo = eListNo.LIST2, }; PopupSequenceDiagram(ref _WinSequenceDiagramScoutFlyingVision, CommonSequenceIds.SeqScoutFlyingVision, param); IsSequenceRunning_ScoutFlyingVision = true; _SeqScoutFlyingVision.Start(param, OnCompleteScoutFlyingVision); } private void OnCompleteScoutFlyingVision(SequenceActionResult res) { if (res != null) { if (res.Param is SeqScoutFlyingVision_OutParam result && result.Success == true) { IsSequenceRunning_ScoutFlyingVision = false; MessageUtil.ShowSuccessToast(string.Format("Success SeqScoutFlyingVision")); CloseSequenceDiagram(_WinSequenceDiagramScoutFlyingVision); return; } } if (_Laser.GetLaserReady()) { _Laser.LaserOff(); } IsSequenceRunning_ScoutFlyingVision = false; MessageUtil.ShowWarningToast(string.Format("Fail SeqScoutFlyingVision")); } private void PerformCmdStopScoutFlyingVision(object obj) { if (_SeqScoutFlyingVision.RunState == SequenceRunState.Idle) { return; } IsSequenceRunning_ScoutFlyingVision = false; _SeqScoutFlyingVision.Stop(); } private void PerformCmdRunTeachingRun(object obj) { var result = MessageUtil.ShowYesNoMessageDialog("Teaching Run", "Start TeachingRun Sequence?"); if (result == false) { return; } if (IsLaserReady == false) { MessageUtil.ShowWarningToast(string.Format("Please check Laser Ready.")); return; } // TODO: PRIBAT_L1, PRIBAT_L2 에서 Suction 과 CLAMP 상태 등에 따라 Laser 가 나가지 않도록 해야 함. // TODO: 이 부분은, Delegate 를 사용해서, Interlock 을 각 장비별로 설정할 수 있도록 해주는게 좋겠음. if (_SeqTeachingRun.RunState == SequenceRunState.Running) { MessageUtil.ShowWarningToast(string.Format("이미 시퀀스가 Start 상태입니다.", "SeqTeachingRun"), Log); return; } // TODO: NeedSetLaserTiming 는 트럼프 레이저 를 사용할 때, UploadWeldingList Sequence 에서 LaserTiming Set 을 해야 할때 사용함. // TODO: SMW 와 LHS 는 트럼프 레이저를 사용하지 않으므로 false, PRIBAT_L1 과 PRIBAT_L2 할때 LaserType 을 읽어서 설정하는 것으로 추가하자 var currentModelInfo = EquipmentSpecificModelUtil.GetCurrentRecipeEquipmentSpecificModel(); if (currentModelInfo.equipmentSpecificModel == null) { MessageUtil.ShowWarningToast(string.Format("선택된 model 이 없습니다.", "SeqTeachingRun")); return; } var intervalTimeBlowOn = 0; var intervalTimeBlowOff = 0; if (currentModelInfo.equipmentSpecificModel.UseBlow) { intervalTimeBlowOn = currentModelInfo.equipmentSpecificModel.IntervalTimeBlowOn; intervalTimeBlowOff = currentModelInfo.equipmentSpecificModel.IntervalTimeBlowOff; } var param = new SeqTeachingRun_InitParam() { IsObjectSelectAll = IsObjectSelectAll, //UseStream = _EquipmentInfo.UseStream, UseStream = currentModelInfo.equipmentSpecificModel.UseStream, UseBlow = currentModelInfo.equipmentSpecificModel.UseBlow, IntervalTimeBlowOn = intervalTimeBlowOn, IntervalTimeBlowOff = intervalTimeBlowOff, NeedSetLaserTiming = false, }; //PopupSequenceDiagram(ref _WinSequenceDiagramTeachingRun, CommonSequenceIds.SeqComponentTeachingRun, param); IsSequenceRunning_TeachingRun = true; _SeqTeachingRun.Start(param, OnCompleteTeachingRun); } private void OnCompleteTeachingRun(SequenceActionResult res) { //if (res != null) { // if (res.Param is SeqTeachingRun_OutParam result && result.Success == true) { // IsSequenceRunning_TeachingRun = false; // MessageUtil.ShowSuccessToast(string.Format("Success SeqTeachingRun")); // CloseSequenceDiagram(_WinSequenceDiagramTeachingRun); // return; // } //} if (_Laser.GetLaserReady()) { _Laser.LaserOff(); } IsSequenceRunning_TeachingRun = false; //MessageUtil.ShowWarningToast(string.Format("Fail SeqTeachingRun")); } private void PerformCmdStopTeachingRun(object obj) { IsSequenceRunning_TeachingRun = false; if (_SeqTeachingRun.RunState == SequenceRunState.Idle) { return; } _SeqTeachingRun.Stop(); } private void PopupSequenceDiagram(ref WinSequenceDiagram winSequenceDiagram, string seqId, object param) { if (winSequenceDiagram == null) { winSequenceDiagram = new WinSequenceDiagram(seqId); winSequenceDiagram.Closing += ClosingEvent; winSequenceDiagram.SetParam(new SeqDrawerParam() { SequenceId = seqId, DiagramType = eDiagramType.Tree, LineType = eLineType.Straight, IsShowSubSequence = false, VisibleKeys = new List() }); winSequenceDiagram.Owner = Application.Current.MainWindow; winSequenceDiagram.Width = 600; winSequenceDiagram.SequenceParam = param; winSequenceDiagram.Show(); } else { winSequenceDiagram.SequenceParam = param; winSequenceDiagram.WindowState = WindowState.Normal; winSequenceDiagram.Show(); } } private void CloseSequenceDiagram(WinSequenceDiagram winSequenceDiagram) { Application.Current.Dispatcher.Invoke(() => { if (winSequenceDiagram != null) { winSequenceDiagram.Close(); } }); } private void ClosingEvent(object sender, CancelEventArgs e) { e.Cancel = true; (sender as Window).Hide(); } private void PerformCmdLaserGuideBeam(object obj) { // Guide Beam DO 토글 — 모니터링(OnMonitoring)도 같은 DO 를 읽어 표시 // 260824: DO 17(EX_Red_Control) → ePrismaticDOAddress.Laser_GUIDE_BEAM_CONTROL 로 변경됨 // IO 미연결 가드는 현장PC에서 동작 안 해 주석 (개발PC에서 주석 해제) //if (_IODevice == null || !_IODevice.IsConnected()) { // return; //} #region 260824 이전 원본 (임시레이저 DO 17 하드코딩) //bool cur = _IODevice.ReadDO(17); //_IODevice._iOUnit.WriteDoBit(17, !cur); #endregion // 260824 본 레이저 가이드빔 DO 로 전환 (EX_Red_Control 17 → Laser_GUIDE_BEAM_CONTROL) bool cur = _IODevice.ReadDO((int)ePrismaticDOAddress.Laser_GUIDE_BEAM_CONTROL); _IODevice._iOUnit.WriteDoBit((int)ePrismaticDOAddress.Laser_GUIDE_BEAM_CONTROL, !cur); // [기존 소스 보존] Laser 디바이스 기반 Pilot On/Off 토글 (DO 3=GuideLaserEnable) //if (!_Laser.GetPilotLaserSetBitStatus()) { // _Laser.PilotLaserOn(); //} else { // _Laser.PilotLaserOff(); //} } private void PerformCmdLaserReadyCheck(object obj) { // 추후 Write bit 파라미터화 해야함 #region 260824 이전 원본 (임시레이저 Core/Ring_Control 2발 하드코딩) //if(toggleIO) //{ // _IODevice._iOUnit.WriteDoBit(9, true); // _IODevice._iOUnit.WriteDoBit(13, true); // toggleIO = false; //} //else //{ // _IODevice._iOUnit.WriteDoBit(9, false); // _IODevice._iOUnit.WriteDoBit(13, false); // toggleIO = true; //} #endregion // 260824 본 레이저 단일 발진기 — 제어권(REMOTE) 1발 토글 // ※ AC_KEY(DO 8)는 전원 투입 계층이라 여기서 건드리지 않는다(반 하드와이어 여부 미확정, Q3). if (toggleIO) { _IODevice._iOUnit.WriteDoBit((int)ePrismaticDOAddress.Laser_REMOTE, true); toggleIO = false; } else { _IODevice._iOUnit.WriteDoBit((int)ePrismaticDOAddress.Laser_REMOTE, false); toggleIO = true; } } private void PerformCmdToggleScannerCamView(object obj) { if (_ScannerCamWindow == null) { _ScannerCamWindow = new WDScannerCamMotion(_Aurora, CommonDeviceIds.Scanner) { Owner = Application.Current.MainWindow, }; } if (_ScannerCamWindow.IsVisible) { _ScannerCamWindow.Hide(); } else { _ScannerCamWindow.Show(); _ScannerCamWindow.Activate(); } } private bool CanShowSelectFieldWindow(object o) => _SelectFieldWindow == null; private void PerformCmdShowSelectFieldWindow(object o) { // 매뉴얼 화면과 동일한 Scout Field 선택 팝업을 띄움. 저장은 팝업 VM(PerformCmdSave)에서 자체 처리. try { Application.Current.Dispatcher.Invoke( new Action(() => { if (_SelectFieldWindow != null) { _SelectFieldWindow.Close(); _SelectFieldWindow = null; } _SelectFieldWindow = new WDSelectScoutField(_Aurora) { Owner = Application.Current.MainWindow }; _SelectFieldWindow.Closed += (sender, _) => { _SelectFieldWindow = null; }; _SelectFieldWindow.Show(); })); } catch (Exception ex) { Log.E($"[PerformCmdShowSelectFieldWindow] 실패: {ex.Message}"); } } private void PerformCmdImportObjectFromDXF(object obj) { System.Windows.Forms.FileDialog dlg = new System.Windows.Forms.OpenFileDialog(); dlg.InitialDirectory = @"D:\share\jungwoo.jin\모바일글라스도면 외 (3) 파일_20241028"; dlg.DefaultExt = "dxf"; dlg.Filter = "DXF 도면 파일 (*.dxf)|*.dxf"; dlg.Title = "DXF 도면 열기"; if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) { CommonHelper.GetMainPage().DxfParser(dlg.FileName); } } } }