LGES-OC5-PtoP-Welder/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs
NohDoyeon241104 2ef276e093 fix: Laser Ready 버튼을 DO 되읽기 토글로 전환 (ISSUE-260826-03)
증상: 버튼을 한 번 눌러도 반응이 없고 두 번 눌러야 Laser_REMOTE 가 켜진다(현장 확인).

원인: toggleIO 가 "다음에 쓸 값"을 VM 이 기억하는 방식이라 실제 DO 와 어긋난다.
SeqAutoRunPTOP 가 같은 DO 를 쓰면서 toggleIO 를 갱신하지 않기 때문 —
  :1220 용접시작 true / :1243 용접종료 false / :1701 정지 false
어긋나면 버튼이 같은 값을 다시 써서 아무 변화가 없다.

조치: 현재 DO 를 읽어 반전한다(바로 위 PerformCmdLaserGuideBeam :607-608 과 동일 패턴).
- 버튼 1회 입력에 TRUE/FALSE 가 정확히 기록된다
- 누가 중간에 DO 를 건드려도 항상 현재 상태 기준으로 동작한다
- 표시(OnMonitoring 의 IsLaserReady = ReadDO(Laser_REMOTE))와 1:1 로 맞는다
- AC_KEY(DO 8)는 전원 투입 계층이라 건드리지 않는다

toggleIO 필드는 원복 대비로 남겨둔다(주석 블록 2개가 참조). 외부 참조·XAML 바인딩 없음.
원복: "260826 이전 원본" region 을 살리고 아래 2줄을 제거.

부수 확인: "뷰 전환 시 REMOTE 꺼짐" 은 VisibleChanged(:251-260)에 DO 쓰기가 없고
시퀀스가 끄는 정상 동작이라 별도 조치하지 않는다.
2026-08-27 09:27:25 +09:00

720 lines
32 KiB
C#

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;
// 260826 현재 미사용 — PerformCmdLaserReadyCheck 가 DO 되읽기 방식으로 바뀌었다.
// 해당 메서드의 "260824/260826 이전 원본" 주석 블록이 이 필드를 참조하므로 원복 대비로 남겨둔다.
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<string>()
});
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
#region 260826 이전 원본 (toggleIO 필드 토글 — 원복 시 이 블록을 살리고 아래 2줄을 제거)
//// 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;
//}
#endregion
// 260826 현재 DO 를 읽어 반전하는 방식으로 전환 (바로 위 PerformCmdLaserGuideBeam :607-608 과 동일 패턴).
// 기존 toggleIO 는 "다음에 쓸 값"을 VM 이 기억하는 방식이라 실제 DO 와 어긋났다.
// SeqAutoRunPTOP 가 같은 DO 를 쓰면서 toggleIO 를 갱신하지 않기 때문 —
// :1220 용접시작 true / :1243 용접종료 false / :1701 정지 false
// 어긋나면 버튼을 눌러도 같은 값을 다시 써서 아무 변화가 없다(= 두 번 눌러야 반응. 현장 확인 260826).
// 현재 DO 를 읽어 반전하면 누가 중간에 건드려도 1회 입력에 TRUE/FALSE 가 정확히 기록되고,
// 표시(OnMonitoring 의 IsLaserReady = ReadDO(Laser_REMOTE))와도 1:1 로 맞는다.
// ※ AC_KEY(DO 8)는 전원 투입 계층이라 여기서 건드리지 않는다(반 하드와이어 여부 미확정, Q3).
bool curRemote = _IODevice.ReadDO((int)ePrismaticDOAddress.Laser_REMOTE);
_IODevice._iOUnit.WriteDoBit((int)ePrismaticDOAddress.Laser_REMOTE, !curRemote);
}
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);
}
}
}
}