diff --git a/Doc/FromLGPRI/각형 고속등속용접 RND설비_MOF테스트관련프로그램개발컨셉및acs변수공유건/.~lock.각형고속용접단동기_ACS_Buffer&Variable_Info_20260407.xlsx# b/Doc/FromLGPRI/각형 고속등속용접 RND설비_MOF테스트관련프로그램개발컨셉및acs변수공유건/.~lock.각형고속용접단동기_ACS_Buffer&Variable_Info_20260407.xlsx#
deleted file mode 100644
index 3d933f0..0000000
--- a/Doc/FromLGPRI/각형 고속등속용접 RND설비_MOF테스트관련프로그램개발컨셉및acs변수공유건/.~lock.각형고속용접단동기_ACS_Buffer&Variable_Info_20260407.xlsx#
+++ /dev/null
@@ -1 +0,0 @@
-,DESKTOP-CN4O63O/User,DESKTOP-CN4O63O,09.04.2026 15:52,file:///C:/Users/User/AppData/Roaming/LibreOffice/4;
\ No newline at end of file
diff --git a/Doc/FromLGPRI/각형 고속등속용접 RND설비_MOF테스트관련프로그램개발컨셉및acs변수공유건/각형배터리 캔캡 MOF용접 제어컨셉_260408.pptx b/Doc/FromLGPRI/각형 고속등속용접 RND설비_MOF테스트관련프로그램개발컨셉및acs변수공유건/각형배터리 캔캡 MOF용접 제어컨셉_260408.pptx
index afa6a70..c86620b 100644
Binary files a/Doc/FromLGPRI/각형 고속등속용접 RND설비_MOF테스트관련프로그램개발컨셉및acs변수공유건/각형배터리 캔캡 MOF용접 제어컨셉_260408.pptx and b/Doc/FromLGPRI/각형 고속등속용접 RND설비_MOF테스트관련프로그램개발컨셉및acs변수공유건/각형배터리 캔캡 MOF용접 제어컨셉_260408.pptx differ
diff --git a/Machines/ILSOS_PrismaticScanner/EquipDef.cs b/Machines/ILSOS_PrismaticScanner/EquipDef.cs
index 1d16b4e..e5c1a6c 100644
--- a/Machines/ILSOS_PrismaticScanner/EquipDef.cs
+++ b/Machines/ILSOS_PrismaticScanner/EquipDef.cs
@@ -40,6 +40,9 @@ namespace ILSOS_PrismaticScanner
public const string SeqAlarmMonitor = "SeqAlarmMonitor";
public const string SeqStatusMonitor = "SeqStatusMonitor";
+ // 26.04.09 NDY - ACS MOTF AutoRun - > SeqAutoRun은 CommonSequenceIds에 존재함.
+ public const string SeqAutoRunACS = "SeqAutoRunACS";
+
}
public class StreamMessageNames
diff --git a/Machines/ILSOS_PrismaticScanner/EquipmentSetup.cs b/Machines/ILSOS_PrismaticScanner/EquipmentSetup.cs
index 9c9ce87..4005908 100644
--- a/Machines/ILSOS_PrismaticScanner/EquipmentSetup.cs
+++ b/Machines/ILSOS_PrismaticScanner/EquipmentSetup.cs
@@ -64,6 +64,9 @@ namespace ILSOS_PrismaticScanner
private SequenceBuilder _SeqAutoRunPrismaticScanner_Builder;
private SequenceBuilder _SeqAutoRunComponentWeld_Builder;
private SequenceBuilder _SeqAlarmMonitor_Builder;
+ #region 26.04.09 NDY - ACS MOTF AutoRun Sequence Builder
+ private SequenceBuilder _SeqAutoRunPrismaticScannerACS_Builder;
+ #endregion
public override void BuildMainStatusView(MainStatusBuilder builder)
@@ -265,28 +268,30 @@ namespace ILSOS_PrismaticScanner
// Common Sequences register
SequenceManager.RegisterSequence(AuroraEnv, register);
base.RegisterSequences(register);
-
var equipSetting = AuroraEnv.SettingEntry[AuroraSettings.Equipment] as EquipmentInfo;
register(SequenceIds.SeqStreamTest, SeqStreamTest.Builder());
-
// Event Q Test 추가
var seqEventQTest = new SeqEventQTest();
register(SequenceIds.SeqEventQTest, seqEventQTest.Builder());
-
var seqAutoRunPrismaticScanner = new SeqAutoRunPrismaticScanner();
var seqAutoRunComponentWeld = new SeqAutoRunComponentWeld();
var seqAlarmMonitor = new SeqAlarmMonitor();
-
_SeqAutoRunPrismaticScanner_Builder = seqAutoRunPrismaticScanner.Builder();
_SeqAutoRunComponentWeld_Builder = seqAutoRunComponentWeld.Builder();
_SeqAlarmMonitor_Builder = seqAlarmMonitor.Builder();
-
register(SequenceIds.SeqAutoRun, _SeqAutoRunPrismaticScanner_Builder);
register(SequenceIds.SeqAutoRunComponentWeld, _SeqAutoRunComponentWeld_Builder);
register(SequenceIds.SeqAlarmMonitor, _SeqAlarmMonitor_Builder);
register(SequenceIds.SeqStatusMonitor, SeqStatusMonitor.Builder());
-
// SequenceManager.RegisterSequence(AuroraEnv, register);
+
+ #region 26.04.09 NDY - ACS MOTF AutoRun Sequence 등록
+ // 기존 SeqAutoRun(SeqAutoRunPrismaticScanner)은 건드리지 않고
+ // ACS MOTF 전용 시퀀스를 별도 Builder로 등록
+ var seqAutoRunPrismaticScannerACS = new SeqAutoRunPrismaticScannerACS();
+ _SeqAutoRunPrismaticScannerACS_Builder = seqAutoRunPrismaticScannerACS.Builder();
+ register(SequenceIds.SeqAutoRunACS, _SeqAutoRunPrismaticScannerACS_Builder);
+ #endregion
}
public override void RegisterSettings(SettingRegister register) {
@@ -384,13 +389,13 @@ namespace ILSOS_PrismaticScanner
manager.RegisterMainMenu(calibration);
// Monitoring
- #region 26 04 06 주석처리 PLCBitMonitoring PLCWordMonitoring IOInterfaceView (o) PLCInterfaceView (원래부터 주석처리)
- //var monitoring = new MainMenu(CommonViewIds.Monitoring, MenuIcon.GetMenuIcon(eMenuIcon.MONITORING), PermissionLevel.NONE, true);
- ////manual.AddSubMenu(CommonViewIds.PLCInterfaceView, new UCPLCInterfaceView(AuroraEnv, CommonDeviceIds.PLCDevice));
- //monitoring.AddSubMenu(CommonViewIds.PLCBitMonitoring, new UCPLCBit() { DataContext = PLCStatic.GetInstanceBitVM(AuroraEnv, CommonDeviceIds.PLCDevice, ePLCNetType.MXComponent, PLCBase.ePLCDevice.M) });
- //monitoring.AddSubMenu(CommonViewIds.PLCWordMonitoring, new UCPLCWord() { DataContext = PLCStatic.GetInstanceWordVM(AuroraEnv, CommonDeviceIds.PLCDevice, ePLCNetType.MXComponent, PLCBase.ePLCDevice.D) });
- //monitoring.AddSubMenu(CommonViewIds.IOInterfaceView, new UCIOInterfaceView(AuroraEnv, CommonDeviceIds.IODevice));
- //manager.RegisterMainMenu(monitoring);
+ #region 26 04 06 주석처리 PLCBitMonitoring PLCWordMonitoring IOInterfaceView (o) PLCInterfaceView (원래부터 주석처리) - 04.10 잠시 추가
+ var monitoring = new MainMenu(CommonViewIds.Monitoring, MenuIcon.GetMenuIcon(eMenuIcon.MONITORING), PermissionLevel.NONE, true);
+ //manual.AddSubMenu(CommonViewIds.PLCInterfaceView, new UCPLCInterfaceView(AuroraEnv, CommonDeviceIds.PLCDevice));
+ monitoring.AddSubMenu(CommonViewIds.PLCBitMonitoring, new UCPLCBit() { DataContext = PLCStatic.GetInstanceBitVM(AuroraEnv, CommonDeviceIds.PLCDevice, ePLCNetType.MXComponent, PLCBase.ePLCDevice.M) });
+ monitoring.AddSubMenu(CommonViewIds.PLCWordMonitoring, new UCPLCWord() { DataContext = PLCStatic.GetInstanceWordVM(AuroraEnv, CommonDeviceIds.PLCDevice, ePLCNetType.MXComponent, PLCBase.ePLCDevice.D) });
+ monitoring.AddSubMenu(CommonViewIds.IOInterfaceView, new UCIOInterfaceView(AuroraEnv, CommonDeviceIds.IODevice));
+ manager.RegisterMainMenu(monitoring);
#endregion
// Device
diff --git a/Machines/ILSOS_PrismaticScanner/ILSOS_PrismaticScanner.csproj b/Machines/ILSOS_PrismaticScanner/ILSOS_PrismaticScanner.csproj
index d332e46..42c2f67 100644
--- a/Machines/ILSOS_PrismaticScanner/ILSOS_PrismaticScanner.csproj
+++ b/Machines/ILSOS_PrismaticScanner/ILSOS_PrismaticScanner.csproj
@@ -110,6 +110,7 @@
+
diff --git a/Machines/ILSOS_PrismaticScanner/Sequences/SeqAutoRunPrismaticScannerACS.cs b/Machines/ILSOS_PrismaticScanner/Sequences/SeqAutoRunPrismaticScannerACS.cs
new file mode 100644
index 0000000..9854880
--- /dev/null
+++ b/Machines/ILSOS_PrismaticScanner/Sequences/SeqAutoRunPrismaticScannerACS.cs
@@ -0,0 +1,1076 @@
+using Aurora;
+using Aurora.Api;
+using Aurora.Api.Devices;
+using Aurora.Api.Manager;
+using Aurora.View;
+using AuroraCellTracker;
+using AuroraIO;
+using AuroraLaser;
+using AuroraLaser.Trumpf;
+using AuroraLaserScanner;
+using AuroraLBAD;
+using AuroraLight;
+using AuroraOCT;
+using AuroraPLC;
+using CoreUnit.Control.UserControl.Canvas;
+using CoreUnit.Scanner.UserControl.ScannerCanvas;
+using DB;
+using DB.Recipe;
+using Hermes;
+using Hermes.Functional.Option;
+using ILSOS_PrismaticScanner.Device;
+using ILSOS_PrismaticScanner.Models;
+using Matrox.MatroxImagingLibrary;
+using MilCommon;
+using MilUI;
+using OpenCvSharp;
+using OpenCvSharp.Dnn;
+using OpenCvSharp.WpfExtensions;
+using System;
+using System.CodeDom.Compiler;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Configuration;
+using System.Data;
+using System.Diagnostics;
+using System.Drawing;
+using System.Globalization;
+using System.IO;
+using System.IO.Pipes;
+using System.Linq;
+using System.Runtime.InteropServices.ComTypes;
+using System.Runtime.Remoting.Contexts;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Forms.VisualStyles;
+using System.Windows.Media.Imaging;
+using VisionCommon;
+using WeldingCommon;
+using WeldingCommon.Alarm;
+using WeldingCommon.Data;
+using WeldingCommon.Devices;
+using WeldingCommon.Devices.ACS;
+using WeldingCommon.Models;
+using WeldingCommon.Sequences;
+using WeldingCommon.Sequences.Main;
+using WeldingCommon.Settings;
+using WeldingCommon.Utils;
+using WeldingCommon.Views;
+using WeldingCommon.Vision.WeldingObjectGenerator;
+using static Aurora.Api.SequenceBuilder;
+using static AuroraIO.AJINEXTEK.AjinIO.IOModuleInfo;
+using static AuroraIO.Vendor.PAIX.PAIXIO;
+using static ILSOS_PrismaticScanner.Device.PLCControllerPrismaticScanner;
+using static WeldingCommon.Devices.PLCController;
+using static WeldingCommon.Sequences.Main.SeqExecutePlcBitInterface_InitParam;
+
+namespace ILSOS_PrismaticScanner.Sequences
+{
+ ///
+ /// ACS 모션 + 갈바노 스캐너 MOTF 자동운전 시퀀스
+ /// 기존 SeqAutoRunPrismaticScanner 기반으로 작성
+ /// - 비전 보상 / 이미지 저장 제거 (MOTF 구조상 불필요)
+ /// - InitFly2D(엔코더 리셋) 노드 추가
+ /// - Object 리스트를 List1 / List2 로 분리하여 각각 MOTF 용접 수행
+ /// 작성일: 26.04.09 NDY
+ ///
+ class SeqAutoRunPrismaticScannerACS
+ {
+ static readonly Logger Log = new Logger("SeqAutoRunPrismaticScannerACS");
+
+ #region 시퀀스 노드 이름
+ // 기존 SeqAutoRunPrismaticScanner 노드명과 동일하게 유지
+ const string SEQ_ENTER = "SEQ_ENTER";
+ const string SEQ_CHECK_INIT_CONDITION = "Check Init Condition";
+
+ const string SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ = "Set Sequence Param (ExecutePlcPageChangeOrWeldRequestInterface Seq)";
+ const string SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST = "Process (Wait Page Change Request Or Weld Request)";
+ const string SEQ_GET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ = "Get Sequence Result (ExecutePlcPageChangeOrWeldRequestInterface Seq)";
+
+ const string SEQ_CHECK_WELDDING_RETRY = "Check Welding Retry";
+
+ const string SEQ_GENERATE_WELDING_OBJECT = "Generate Welding Object";
+
+ const string SEQ_SET_PARAMS_COMPONENT_TEACHINGRUN_SEQ = "Set Params Component Teachingrun Seq";
+ const string SEQ_DO_ID_COMPONENT_TEACHINGRUN_SEQ = "Process (Component Teachingrun Seq)";
+ const string SEQ_GET_RESULT_COMPONENT_TEACHINGRUN_SEQ = "Get Result Component Teachingrun Seq";
+
+ const string SEQ_GET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ = "Get Sequence Result (PLC Weld Complete Interface Seq)";
+ const string SEQ_DO_PLC_WELD_COMPLETE_INTERFACE_SEQ = "Process (PLC Weld Complete Interface Seq)";
+ const string SEQ_SET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ = "Set Sequence Param (PLC Weld Complete Interface Seq)";
+
+ const string SEQ_SAVE_RESULT_DATA = "Save Result Data";
+ const string SEQ_COMPLETE = "SEQ_COMPLETE";
+
+ #region 26.04.09 NDY - MOTF 추가 노드
+ // 기존 노드에 없던 ACS MOTF 전용 노드
+ // 노드명 뒤에 "ACS" 또는 "1"/"2" 붙여서 기존과 구분
+
+ // [MOTF Step 1] Linear 원점에서 엔코더 리셋
+ const string SEQ_RESET_ENCODER_ACS = "Reset Encoder ACS (InitFly2D)";
+
+ // [MOTF Step 3] 1번 Object 리스트 오프셋 이동
+ const string SEQ_MOVE_OBJECT_LIST1_ACS = "Move Object List1 ACS (Scanner Center Offset)";
+
+ // [MOTF Step 5] 2번 Object 리스트 세팅 및 용접
+ const string SEQ_SET_PARAMS_COMPONENT_TEACHINGRUN_SEQ2 = "Set Params Component Teachingrun Seq2";
+ const string SEQ_DO_ID_COMPONENT_TEACHINGRUN_SEQ2 = "Process (Component Teachingrun Seq2)";
+ const string SEQ_GET_RESULT_COMPONENT_TEACHINGRUN_SEQ2 = "Get Result Component Teachingrun Seq2";
+
+ // [MOTF Step 6] 2번 Object 리스트 오프셋 이동
+ const string SEQ_MOVE_OBJECT_LIST2_ACS = "Move Object List2 ACS (Scanner Center Offset)";
+ #endregion
+
+ #region 26.04.10 NDY - 시뮬레이션 우회 전용 노드
+ // PLC 서브시퀀스를 개발 환경에서 스킵하기 위한 노드
+ // 실제 장비에서는 이 노드를 타지 않음
+ const string SEQ_SKIP_PLC_WELD_COMPLETE_ACS = "Skip PLC WeldComplete ACS (Simulation)";
+ #endregion
+
+ #endregion
+
+ static class ContextKey
+ {
+ // 기존과 동일한 패턴, 클래스명만 ACS로 변경
+ public const string KEY_SEQUENCE_RESULT = "KEY_SEQUENCE_RESULT_SeqAutoRunPrismaticScannerACS";
+ public const string KEY_SEQUENCE_START_TIME = "KEY_SEQUENCE_START_TIME_SeqAutoRunPrismaticScannerACS";
+ }
+
+ #region 변수 정의
+
+ #region 26.04.10 NDY - 시뮬레이션 1사이클 테스트용
+ // true: 1사이클 후 루프 중단, false: 기존처럼 계속 루프
+ private bool _IsOneCycleTest = true;
+ #endregion
+
+ // 기존 SeqAutoRunPrismaticScanner 변수 그대로 유지
+
+ // 디바이스 정의
+ private Scanner _Scanner;
+ private Laser _Laser;
+ private PLCControllerPrismaticScanner _PLC;
+ private IODevice _IO;
+ private LightDevice _Light;
+
+ // 비전 정의
+ // MOTF에서는 비전 보상을 사용하지 않으나, 향후 확장 가능성을 위해 선언 유지
+ private CameraAgentBase _CameraAgent;
+ private ICameraDisplay _CameraDisplay;
+ private PatternAgentBase _PatternAgent;
+ private List _Patterns;
+ private PatternInfo _PatternInfo;
+ private PatternResult _PatternResult;
+ private FieldScanParams _FieldScanParams;
+ private object _GrabImage;
+ private MIL_ID _GrabImageMilId;
+ private int _RetryCount = 0;
+ private double _OffsetXfromModelFinder = 0;
+ private double _OffsetYfromModelFinder = 0;
+
+ // 시스템 및 용접 파라미터 정의
+ private SystemParameter _SystemParameter;
+ private static SequenceSetting _SequenceSetting;
+ private ScannerCanvasUserCtrl _ScannerCanvasUserCtrl;
+ private List _SelectedDrawObjectList;
+ private static List _UnpackDrawObject;
+
+ // InitParam 타입만 ACS용으로 변경, 나머지 구조 동일
+ private SeqAutoRunPrismaticScannerACS_InitParam _InitParam;
+
+ // 생산 데이터 정의
+ private uint _TodayProduct;
+ private uint _TotalProduct;
+
+ private DateTime _AutoRunStartTime;
+ private DateTime _WeldStartTime;
+ private DateTime _WeldEndTime;
+ private TimeSpan _WeldSpanTime;
+ private TimeSpan _InterfaceSpanTime;
+ private DateTime _InterfaceStartTime;
+
+ private string _LotId = "N/A";
+ private string _CellId = "N/A";
+ const int timeoutSeconds = 10;
+
+ private bool _FirstUpdateStep = true;
+ private bool _IsWeldingRetry = false;
+ private int _CurrentWeldingStep = 0;
+ private int _TotalWeldingStep = 0;
+ private int _PreviousTotalWeldingStep = 0;
+ private int _PageNo = 1;
+ private MIL_ID _ResultImageMilId;
+ private bool _SuccessVisionResult = false;
+ const int PLC_TIMEOUT = 60;
+
+ public string EVENT_TRIGGER_SAVE_FILE = "EventTriggerSaveFile";
+
+ // 공정 시간 측정
+ private static DateTime _WeldReqTime;
+ private static DateTime _WeldComTime;
+
+ private List lightValues = new List { 255, 255, 255, 255 };
+ private (double X, double Y) TargetObjectCenterPosition = (X: 0.0, Y: 0.0);
+
+ #region 26.04.09 NDY - ACS MOTF 추가 변수
+ // ACS 모션 컨트롤러 참조
+ // WeldingCommon/Devices/ACS/ScannerACS.cs 에 등록된 장치
+ // Stage X 축(AxisX2), Y1/Y2/Z 축 제어 및 엔코더 값 읽기에 사용
+ private ScannerACS _ScannerACS;
+
+ // MOTF용 Object 리스트 분리
+ // 이미지 기준:
+ // List1 - Page1 끝점 ~ Page2 시작점 (RM 용접, Virtual Line 포함)
+ // List2 - Page3 끝점 ~ Page4 시작점 (LM 용접, Virtual Line 포함)
+ // 기존 _SelectedDrawObjectList 명명 패턴 유지, 숫자로 구분
+ private List _SelectedDrawObjectList1;
+ private List _SelectedDrawObjectList2;
+
+ // 각 Object 리스트별 스캐너 오프셋
+ // 스캐너 중심(50, 0) ~ Linear 원점(0, 0) 간 거리만큼 Object 좌표를 이동
+ // → WaitForEncoderX가 RM(25,0) / LM(75,0) 시작점에서 정확히 트리거되도록 보장
+ // TODO: 이정인 선임 ACS 매핑 파일 수령 후 실제 수치로 확정 필요
+ private double _ObjectList1OffsetX = 0;
+ private double _ObjectList1OffsetY = 0;
+ private double _ObjectList2OffsetX = 0;
+ private double _ObjectList2OffsetY = 0;
+ #endregion
+
+ #endregion
+
+ public SequenceBuilder Builder()
+ {
+ var sb = new SequenceBuilder(1000, true);
+
+ // 기존 노드 등록 (SeqAutoRunPrismaticScanner와 동일 구조)
+ sb.AddAction(SEQ_ENTER, Sequence_Enter);
+ sb.AddDecision(SEQ_CHECK_INIT_CONDITION, CheckInitCondition);
+
+ #region 26.04.10 NDY - PLC WeldRequest : Action → Decision 변경
+ // 기존: sb.AddAction(SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ, ...)
+ // 변경 이유: 시뮬레이션 환경에서 서브시퀀스(SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST)를
+ // 타지 않도록 Decision 분기 처리가 필요
+ // 실제 장비: SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST → SEQ_GET_PARAMS → SEQ_CHECK_WELDDING_RETRY
+ // 시뮬레이션: 서브시퀀스 스킵 → 바로 SEQ_CHECK_WELDDING_RETRY
+ sb.AddDecision(SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ, SetPlcPageChangeOrWeldRequest_SeqParams);
+ #endregion
+
+ sb.AddSubsequence(SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST, SequenceManager.GetSeqBuilder_SeqWaitWeldOrChangePage());
+ sb.AddAction(SEQ_GET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ, GetPlcPageChangeOrWeldRequest_SeqParams);
+
+ sb.AddDecision(SEQ_CHECK_WELDDING_RETRY, CheckWeldingRetry);
+ sb.AddAction(SEQ_GENERATE_WELDING_OBJECT, GenerateWeldingObject);
+
+ // List1 용접 (RM)
+ sb.AddAction(SEQ_SET_PARAMS_COMPONENT_TEACHINGRUN_SEQ, SetComponentTeachingrun_SeqParams);
+ sb.AddSubsequence(SEQ_DO_ID_COMPONENT_TEACHINGRUN_SEQ, SequenceManager.GetSeqBuilder_SeqComponentTeachingRun());
+ sb.AddAction(SEQ_GET_RESULT_COMPONENT_TEACHINGRUN_SEQ, GetComponentTeachingrun_SeqResult);
+
+ // List2 용접 (LM)
+ sb.AddAction(SEQ_SET_PARAMS_COMPONENT_TEACHINGRUN_SEQ2, SetComponentTeachingrun_SeqParams2);
+ sb.AddSubsequence(SEQ_DO_ID_COMPONENT_TEACHINGRUN_SEQ2, SequenceManager.GetSeqBuilder_SeqComponentTeachingRun());
+ sb.AddAction(SEQ_GET_RESULT_COMPONENT_TEACHINGRUN_SEQ2, GetComponentTeachingrun_SeqResult2);
+
+ #region 26.04.10 NDY - PLC WeldComplete : Action → Decision 변경
+ // 기존: sb.AddAction(SEQ_SET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ, ...)
+ // 변경 이유: 시뮬레이션 환경에서 서브시퀀스(SEQ_DO_PLC_WELD_COMPLETE_INTERFACE_SEQ)를
+ // 타지 않도록 Decision 분기 처리가 필요
+ // 실제 장비: SEQ_DO_PLC_WELD_COMPLETE → SEQ_GET_PARAMS → SEQ_SAVE_RESULT_DATA
+ // 시뮬레이션: SEQ_SKIP_PLC_WELD_COMPLETE_ACS → SEQ_SAVE_RESULT_DATA
+ sb.AddDecision(SEQ_SET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ, SetPlcWeldCompleteInterface_SeqParams);
+ sb.AddAction(SEQ_SKIP_PLC_WELD_COMPLETE_ACS, SkipPlcWeldComplete);
+ #endregion
+
+ sb.AddSubsequence(SEQ_DO_PLC_WELD_COMPLETE_INTERFACE_SEQ, SequenceManager.GetSeqBuilder_SeqExecutePlcBitInterface());
+ sb.AddAction(SEQ_GET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ, GetPlcWeldCompleteInterface_SeqParams);
+
+ sb.AddAction(SEQ_SAVE_RESULT_DATA, SaveResultData);
+ sb.AddAction(SEQ_COMPLETE, Complete);
+
+ #region 26.04.09 NDY - MOTF 추가 노드 등록
+ sb.AddAction(SEQ_RESET_ENCODER_ACS, ResetEncoder);
+ sb.AddAction(SEQ_MOVE_OBJECT_LIST1_ACS, MoveObjectList1);
+ sb.AddAction(SEQ_MOVE_OBJECT_LIST2_ACS, MoveObjectList2);
+ #endregion
+
+ // ── 메인 흐름 ──────────────────────────────────────────────
+ sb.Flows(sb.Init > sb[SEQ_ENTER] > sb[SEQ_CHECK_INIT_CONDITION]
+ > sb[SEQ_RESET_ENCODER_ACS]
+ > sb[SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ]);
+
+ #region 26.04.10 NDY - PLC WeldRequest 분기 흐름
+ // 실제 장비: 서브시퀀스 진입
+ sb.Flows(sb[SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ]
+ > sb[SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST]
+ > sb[SEQ_GET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ]
+ > sb[SEQ_CHECK_WELDDING_RETRY]);
+ // 시뮬레이션: 서브시퀀스 스킵 → 바로 CheckWeldingRetry
+ sb.Flows(sb[SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ]
+ > sb[SEQ_CHECK_WELDDING_RETRY]);
+ #endregion
+
+ // CheckWeldingRetry 이후 공통 흐름
+ sb.Flows(sb[SEQ_CHECK_WELDDING_RETRY]
+ > sb[SEQ_GENERATE_WELDING_OBJECT]
+ > sb[SEQ_MOVE_OBJECT_LIST1_ACS]
+ > sb[SEQ_SET_PARAMS_COMPONENT_TEACHINGRUN_SEQ]
+ > sb[SEQ_DO_ID_COMPONENT_TEACHINGRUN_SEQ]
+ > sb[SEQ_GET_RESULT_COMPONENT_TEACHINGRUN_SEQ]
+ > sb[SEQ_MOVE_OBJECT_LIST2_ACS]
+ > sb[SEQ_SET_PARAMS_COMPONENT_TEACHINGRUN_SEQ2]
+ > sb[SEQ_DO_ID_COMPONENT_TEACHINGRUN_SEQ2]
+ > sb[SEQ_GET_RESULT_COMPONENT_TEACHINGRUN_SEQ2]
+ > sb[SEQ_SET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ]);
+
+ // 재시도: Object 생성 스킵하고 List1 이동으로
+ sb.Flows(sb[SEQ_CHECK_WELDDING_RETRY] > sb[SEQ_MOVE_OBJECT_LIST1_ACS]);
+
+ #region 26.04.10 NDY - PLC WeldComplete 분기 흐름
+ // 실제 장비: 서브시퀀스 진입 → 결과 수신 → SaveResultData
+ sb.Flows(sb[SEQ_SET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ]
+ > sb[SEQ_DO_PLC_WELD_COMPLETE_INTERFACE_SEQ]
+ > sb[SEQ_GET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ]
+ > sb[SEQ_SAVE_RESULT_DATA]);
+ // 시뮬레이션: 서브시퀀스 스킵 → SkipNode → SaveResultData
+ sb.Flows(sb[SEQ_SET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ]
+ > sb[SEQ_SKIP_PLC_WELD_COMPLETE_ACS]
+ > sb[SEQ_SAVE_RESULT_DATA]);
+ #endregion
+
+ // 결과 저장 후 PLC 요청 대기로 루프
+ sb.Flows(sb[SEQ_SAVE_RESULT_DATA]
+ > sb[SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ]);
+
+ // CheckInitCondition 실패 시 종료
+ sb.Flows(sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_COMPLETE] > sb.Final);
+
+ // 26.04.10 1사이클만 체크하고 빠져나오는 시퀸스로 테스트용 추가 Builder()에 추가 --
+ sb.Flows(sb[SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ]
+ > sb[SEQ_COMPLETE]);
+
+ sb.OnForceError(OnForceError);
+ sb.OnStopped(OnStopped);
+
+ //sb.Draw("SeqAutoRunPrismaticScannerACS.pdf");
+
+ return sb;
+ }
+
+ private string CheckWeldingRetry(SequenceContext context, string id, ICollection branches, SequenceActionResult param)
+ {
+ // 기존 SeqAutoRunPrismaticScanner와 동일 로직
+ if (_IsWeldingRetry) return SEQ_MOVE_OBJECT_LIST1_ACS;
+ else return SEQ_GENERATE_WELDING_OBJECT;
+ }
+
+ #region 시퀀스 함수
+
+ #region 1. 디바이스 및 변수 상태 확인
+ private SequenceActionResult Sequence_Enter(SequenceContext context, string id, SequenceActionResult param)
+ {
+ Log.I("SeqAutoRunPrismaticScannerACS Start");
+
+ _InitParam = param.Param as SeqAutoRunPrismaticScannerACS_InitParam;
+ _SystemParameter = context.GetSetting(CommonSettingIds.SystemParameter);
+ _SequenceSetting = context.GetSetting(CommonSettingIds.Sequence);
+
+ InitDev(context);
+ if (!CheckDeviceReady(context))
+ {
+ AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_DeviceNotReady);
+ Log.E("The devices are not active.");
+ return null;
+ }
+
+ if (InitSequenceVariable(context) == false)
+ {
+ AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_InitVariableError);
+ return null;
+ }
+
+ _PageNo = _SequenceSetting.LastPageNo;
+ Log.I($"Previous Page No is {_PageNo}");
+
+ return SequenceAction.Ok();
+ }
+
+ private string CheckInitCondition(SequenceContext context, string id, ICollection branches, SequenceActionResult param)
+ {
+ Status.SetWorkingPosition(eWorkingPosition.CHECKING_CONDITION);
+
+ if (!CheckCondition(context))
+ {
+ AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_CheckInitCondition);
+ Log.E("Condition is not in state to start sequence.");
+ context.PutValue(ContextKey.KEY_SEQUENCE_RESULT, false);
+ return SEQ_COMPLETE;
+ }
+
+ var startTime = DateTime.Now;
+ context.PutValue(ContextKey.KEY_SEQUENCE_START_TIME, startTime);
+
+ #region 26.04.10 NDY - 시뮬레이션 환경 우회
+ if (ApplicationExt.IsSimulation)
+ {
+ Log.I("Simulation Mode - Skip Laser/PLC CheckInitCondition");
+ return SEQ_RESET_ENCODER_ACS;
+ }
+ #endregion
+
+ // 레이저 레디 확인 (기존과 동일)
+ if (!_Laser.GetLaserReady())
+ {
+ _Laser.LaserOn();
+ Log.I("Start Laser ON in CheckInitCondition");
+ }
+ var res = context.RetryUntilSuccess(_Laser.GetLaserReady, timeoutSeconds * 1000, 10);
+ if (res == E2.TIMEOUT)
+ {
+ Log.E("Laser Ready Fail When Autorun");
+ return null;
+ }
+
+ InitializePlcBits();
+ _PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, true);
+ _PLC.WriteWord(eWordOutput.PC_PAGE_NO, (short)_PageNo);
+
+ Log.I("Complete CheckInitCondition.");
+ return SEQ_RESET_ENCODER_ACS;
+ }
+ #endregion
+
+ #region 26.04.09 NDY - MOTF Step 1 : 엔코더 리셋
+ ///
+ /// [MOTF Step 1]
+ /// 자동운전 시작 시 Linear 원점 위치에서 InitFly2D() 호출
+ /// → 스캐너 내부 X 엔코더 값을 0으로 초기화
+ /// → 이후 SeqUploadWeldingList 내 WaitForEncoderX가
+ /// RM(25,0) / LM(75,0) 시작점에서 정확히 트리거되도록 보장
+ ///
+ private SequenceActionResult ResetEncoder(SequenceContext context, string id, SequenceActionResult param)
+ {
+ Log.I("MOTF Step1: InitFly2D - Encoder Reset at Linear Origin");
+ _Scanner.InitFly2D();
+ return SequenceAction.Ok();
+ }
+ #endregion
+
+ #region 2. PLC 페이지 변경 / 용접 요청 대기
+
+ #region 26.04.10 NDY - Action → Decision 변경 (PLC WeldRequest)
+ ///
+ /// 기존 Action에서 Decision으로 변경
+ /// - 시뮬레이션: SEQ_CHECK_WELDDING_RETRY 로 직접 분기 (서브시퀀스 완전 스킵)
+ /// - 실제 장비: SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST 서브시퀀스로 진입
+ ///
+ private string SetPlcPageChangeOrWeldRequest_SeqParams(SequenceContext context, string id, ICollection branches, SequenceActionResult param)
+ {
+ if (ApplicationExt.IsSimulation)
+ {
+ #region 26.04.10 NDY - 1사이클 테스트
+ if (_IsOneCycleTest && !_FirstUpdateStep)
+ {
+ // 첫 사이클 이후 루프 진입 시 종료
+ Log.I("Simulation Mode - 1 Cycle Test Complete, Stop Loop");
+ return SEQ_COMPLETE;
+ }
+ #endregion
+
+ Log.I("Simulation Mode - Skip PLC WeldRequest, proceed to CheckWeldingRetry");
+ _PageNo = 1;
+ _FirstUpdateStep = false;
+ _TotalWeldingStep = 1;
+ return SEQ_CHECK_WELDDING_RETRY;
+ }
+
+ // 기존 SeqAutoRunPrismaticScanner와 동일
+ Log.I(string.Format("PLC Interface(PageChange, WeldReq) Start; (REQ,ACK); ({0},{1});({2},{3})",
+ (int)eBitInput.PLC_PAGE_CHANGE_REQ, (int)eBitOutput.PC_PAGE_CHANGE_COMPLETE,
+ (int)eBitInput.PLC_WELD_READY_REQ, (int)eBitOutput.PC_WELD_READY_ACK));
+
+ Status.SetWorkingPosition(eWorkingPosition.LOADING_CELL);
+ return SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST;
+ }
+ #endregion
+
+ private SequenceActionResult GetPlcPageChangeOrWeldRequest_SeqParams(SequenceContext context, string id, SequenceActionResult param)
+ {
+ #region 26.04.10 NDY - 시뮬레이션 환경 우회
+ if (ApplicationExt.IsSimulation)
+ {
+ Log.I("Simulation Mode - Skip PLC WeldRequest Result");
+ return SequenceAction.Ok();
+ }
+ #endregion
+
+ // 기존 SeqAutoRunPrismaticScanner와 동일
+ var resultOutParam = param.Param as SeqWaitWeldOrChangePage_OutParam;
+ _WeldReqTime = resultOutParam._WeldReqTime;
+ if (resultOutParam.Success == false)
+ {
+ Log.E("Failed Page Change & Weld Request Interface");
+ return null;
+ }
+
+ _PreviousTotalWeldingStep = _TotalWeldingStep;
+ _PageNo = resultOutParam.PLCPageNo;
+ _TotalWeldingStep = Convert.ToInt16(_PLC.ReadWord(eWordInput.PLC_STEP_NO).data);
+
+ _SequenceSetting.LastPageNo = _PageNo;
+ context.Settings.Save(CommonSettingIds.Sequence, _SequenceSetting, "SeqAutoRunPrismaticScannerACS");
+
+ Status.GetProcessingWeldingData().IsVisionNG = false;
+ _PLC.WriteBit(eBitOutput.PC_WELD_NG_JUDGE, false);
+ var UploadDatas = Status.GetProcessingWeldingData().UploadProcessDatas;
+ foreach (var data in UploadDatas)
+ data.Clear();
+ _PLC.UploadReportDatas(UploadDatas);
+
+ Log.I(string.Format("Read Words; Page No: {0}; Step No: {1}", _PageNo, _TotalWeldingStep));
+
+ if (_PageNo > _ScannerCanvasUserCtrl.Model.Layers.Count() && _PageNo > 0)
+ {
+ Log.E(string.Format("Page No isn't proper. ;Page No : {0}; Layers Count: {1}", _PageNo, _ScannerCanvasUserCtrl.Model.Layers.Count()));
+ return null;
+ }
+
+ Status.GetProcessingWeldingData().CellId = _PLC.CellId();
+ Status.GetProcessingWeldingData().LotId = _PLC.LotId();
+
+ if (_FirstUpdateStep)
+ {
+ _FirstUpdateStep = false;
+ return SequenceAction.Ok();
+ }
+ if (_PreviousTotalWeldingStep != _TotalWeldingStep)
+ {
+ Log.E(string.Format("SeqCheckWeldingComplete Error;Changed Step No; Prev: {0}; Current: {1}", _PreviousTotalWeldingStep, _TotalWeldingStep));
+ return null;
+ }
+
+ return SequenceAction.Ok();
+ }
+ #endregion
+
+ #region 3. 용접 Object 생성 및 List 분리
+ ///
+ /// [MOTF Step 2]
+ /// 레시피 레이어에서 Object를 꺼내 List1 / List2 로 분리
+ /// - _SelectedDrawObjectList : 기존과 동일하게 전체 Object 보관
+ /// - _SelectedDrawObjectList1 : RM 용접용 (Page1 끝~Page2 시작, Virtual Line 포함)
+ /// - _SelectedDrawObjectList2 : LM 용접용 (Page3 끝~Page4 시작, Virtual Line 포함)
+ /// TODO: 실제 분리 기준(ScannerParamID 또는 레이어 구성)은 이정인 선임 확인 후 확정
+ ///
+ private SequenceActionResult GenerateWeldingObject(SequenceContext context, string id, SequenceActionResult param)
+ {
+ // 기존 _SelectedDrawObjectList 유지 (전체 Object)
+ _SelectedDrawObjectList = _ScannerCanvasUserCtrl.Model.Layers[_PageNo - 1].Objects;
+
+ // TODO: 실제 분리 기준 확인 후 구현
+ // 현재는 절반으로 단순 분리 (임시)
+ int half = _SelectedDrawObjectList.Count / 2;
+ _SelectedDrawObjectList1 = _SelectedDrawObjectList.Take(half).ToList();
+ _SelectedDrawObjectList2 = _SelectedDrawObjectList.Skip(half).ToList();
+
+ Log.I($"GenerateWeldingObject: Total({_SelectedDrawObjectList.Count}), List1({_SelectedDrawObjectList1.Count}), List2({_SelectedDrawObjectList2.Count})");
+ return SequenceAction.Ok();
+ }
+ #endregion
+
+ #region 26.04.09 NDY - MOTF Step 3 : List1 오프셋 이동
+ ///
+ /// [MOTF Step 3]
+ /// 1번 Object 리스트를 스캐너 중심으로부터 원점위치까지 떨어진 양만큼 오프셋 이동
+ /// - 스캐너 중심 = (50, 0), Linear 원점 = (0, 0) → 오프셋 X = -50mm
+ /// - 이 오프셋은 SeqUploadWeldingList 내 CellMeasureCenterOffset으로 전달됨
+ /// - WaitForEncoderX가 RM 용접 시작점(25, 0)에서 트리거되도록 준비
+ /// TODO: ScannerConfig 또는 SystemParameter에서 스캐너 중심 좌표 가져오도록 수정
+ ///
+ private SequenceActionResult MoveObjectList1(SequenceContext context, string id, SequenceActionResult param)
+ {
+ _ObjectList1OffsetX = -_Scanner.Config.MarkingOntheFlyOffsetX;
+ _ObjectList1OffsetY = -_Scanner.Config.MarkingOntheFlyOffsetY;
+
+ Log.I($"MOTF Step3: MoveObjectList1 Offset({_ObjectList1OffsetX}, {_ObjectList1OffsetY})");
+ return SequenceAction.Ok();
+ }
+ #endregion
+
+ #region 4. List1 용접 (RM)
+ private SequenceActionResult SetComponentTeachingrun_SeqParams(SequenceContext context, string id, SequenceActionResult param)
+ {
+ // 기존 SeqAutoRunPrismaticScanner와 동일 구조
+ _PLC.WriteBit(eBitOutput.PC_WELD_BUSY, true);
+ Log.I("Weld Busy On ! Set ComponentTeachingrun (List1) Start.");
+
+ _WeldStartTime = DateTime.Now;
+ Status.GetProcessingWeldingData().WeldingStartTime = _WeldStartTime;
+
+ var currentModelInfo = EquipmentSpecificModelUtil.GetCurrentRecipeEquipmentSpecificModel();
+ if (currentModelInfo.equipmentSpecificModel == null)
+ {
+ MessageUtil.ShowWarningToast(string.Format("선택된 model 이 없습니다.", "Laser On"));
+ return null;
+ }
+
+ var intervalTimeBlowOn = 0;
+ var intervalTimeBlowOff = 0;
+ if (currentModelInfo.equipmentSpecificModel.UseBlow)
+ {
+ intervalTimeBlowOn = currentModelInfo.equipmentSpecificModel.IntervalTimeBlowOn;
+ intervalTimeBlowOff = currentModelInfo.equipmentSpecificModel.IntervalTimeBlowOff;
+ }
+
+ SeqTeachingRun_InitParam seqParam = new SeqTeachingRun_InitParam()
+ {
+ IsUseExternalObjects = true,
+ ExternalObjects = _SelectedDrawObjectList1, // List1 사용
+ UseStream = currentModelInfo.equipmentSpecificModel.UseStream,
+ UseBlow = currentModelInfo.equipmentSpecificModel.UseBlow,
+ IntervalTimeBlowOn = intervalTimeBlowOn,
+ IntervalTimeBlowOff = intervalTimeBlowOff,
+ NeedSetLaserTiming = true,
+ // MOTF 오프셋 전달 (기존 CellCenterOffset 파라미터 재활용)
+ CellCenterOffsetX = _ObjectList1OffsetX,
+ CellCenterOffsetY = _ObjectList1OffsetY,
+ };
+
+ Log.I("Set ComponentTeachingrun (List1) Complete!");
+ return SequenceAction.Ok(seqParam);
+ }
+
+ private SequenceActionResult GetComponentTeachingrun_SeqResult(SequenceContext context, string id, SequenceActionResult param)
+ {
+ var resultOutParam = param.Param as SeqTeachingRun_OutParam;
+ if (resultOutParam.Success == false)
+ {
+ Log.E("ComponentTeachingrun (List1) Failed.");
+ return null;
+ }
+ Log.I("ComponentTeachingrun (List1) Complete!");
+ return SequenceAction.Ok();
+ }
+ #endregion
+
+ #region 26.04.09 NDY - MOTF Step 6 : List2 오프셋 이동
+ ///
+ /// [MOTF Step 6]
+ /// 2번 Object 리스트를 스캐너 중심으로부터 원점위치까지 떨어진 양만큼 오프셋 이동
+ /// - LM 용접 시작점(75, 0)에서 WaitForEncoderX가 트리거되도록 준비
+ /// TODO: List2 기준 오프셋 수치 확인 후 수정
+ ///
+ private SequenceActionResult MoveObjectList2(SequenceContext context, string id, SequenceActionResult param)
+ {
+ _ObjectList2OffsetX = -_Scanner.Config.MarkingOntheFlyOffsetX;
+ _ObjectList2OffsetY = -_Scanner.Config.MarkingOntheFlyOffsetY;
+
+ Log.I($"MOTF Step6: MoveObjectList2 Offset({_ObjectList2OffsetX}, {_ObjectList2OffsetY})");
+ return SequenceAction.Ok();
+ }
+ #endregion
+
+ #region 26.04.09 NDY - List2 용접 (LM)
+ private SequenceActionResult SetComponentTeachingrun_SeqParams2(SequenceContext context, string id, SequenceActionResult param)
+ {
+ // List1과 동일 구조, ExternalObjects와 Offset만 List2 기준으로 변경
+ var currentModelInfo = EquipmentSpecificModelUtil.GetCurrentRecipeEquipmentSpecificModel();
+ if (currentModelInfo.equipmentSpecificModel == null)
+ {
+ MessageUtil.ShowWarningToast(string.Format("선택된 model 이 없습니다.", "Laser On"));
+ return null;
+ }
+
+ var intervalTimeBlowOn = 0;
+ var intervalTimeBlowOff = 0;
+ if (currentModelInfo.equipmentSpecificModel.UseBlow)
+ {
+ intervalTimeBlowOn = currentModelInfo.equipmentSpecificModel.IntervalTimeBlowOn;
+ intervalTimeBlowOff = currentModelInfo.equipmentSpecificModel.IntervalTimeBlowOff;
+ }
+
+ SeqTeachingRun_InitParam seqParam = new SeqTeachingRun_InitParam()
+ {
+ IsUseExternalObjects = true,
+ ExternalObjects = _SelectedDrawObjectList2, // List2 사용
+ UseStream = currentModelInfo.equipmentSpecificModel.UseStream,
+ UseBlow = currentModelInfo.equipmentSpecificModel.UseBlow,
+ IntervalTimeBlowOn = intervalTimeBlowOn,
+ IntervalTimeBlowOff = intervalTimeBlowOff,
+ NeedSetLaserTiming = true,
+ CellCenterOffsetX = _ObjectList2OffsetX,
+ CellCenterOffsetY = _ObjectList2OffsetY,
+ };
+
+ Log.I("Set ComponentTeachingrun (List2) Complete!");
+ return SequenceAction.Ok(seqParam);
+ }
+
+ private SequenceActionResult GetComponentTeachingrun_SeqResult2(SequenceContext context, string id, SequenceActionResult param)
+ {
+ var resultOutParam = param.Param as SeqTeachingRun_OutParam;
+ if (resultOutParam.Success == false)
+ {
+ Log.E("ComponentTeachingrun (List2) Failed.");
+ return null;
+ }
+
+ // List2 용접까지 완료되면 WELD_BUSY OFF
+ _PLC.WriteBit(eBitOutput.PC_WELD_BUSY, false);
+ Log.I("Weld Busy OFF!");
+
+ _WeldEndTime = DateTime.Now;
+ _WeldSpanTime = _WeldEndTime.Subtract(_WeldStartTime);
+ Log.I($"[TactTime] Page({_PageNo}) Total Welding Time : {_WeldSpanTime.TotalMilliseconds}msec");
+
+ return SequenceAction.Ok();
+ }
+ #endregion
+
+ #region 5. 용접 완료 PLC 인터페이스
+
+ #region 26.04.10 NDY - Action → Decision 변경 (PLC WeldComplete)
+ ///
+ /// 기존 Action에서 Decision으로 변경
+ /// - 시뮬레이션: SEQ_SKIP_PLC_WELD_COMPLETE_ACS 로 분기 (서브시퀀스 완전 스킵)
+ /// - 실제 장비: SEQ_DO_PLC_WELD_COMPLETE_INTERFACE_SEQ 서브시퀀스로 진입
+ ///
+ private string SetPlcWeldCompleteInterface_SeqParams(SequenceContext context, string id, ICollection branches, SequenceActionResult param)
+ {
+ if (ApplicationExt.IsSimulation)
+ {
+ Log.I("Simulation Mode - Skip PLC WeldComplete Interface");
+ return SEQ_SKIP_PLC_WELD_COMPLETE_ACS;
+ }
+
+ // 기존 SeqAutoRunPrismaticScanner와 동일
+ Log.I("SeqCheckWeldingComplete Set Parameters");
+ return SEQ_DO_PLC_WELD_COMPLETE_INTERFACE_SEQ;
+ }
+
+ ///
+ /// 시뮬레이션 환경에서 PLC WeldComplete 서브시퀀스 스킵 시 호출
+ /// 실제 장비에서는 이 노드를 타지 않음
+ ///
+ private SequenceActionResult SkipPlcWeldComplete(SequenceContext context, string id, SequenceActionResult param)
+ {
+ Log.I("Simulation Mode - PLC WeldComplete Skipped, Update ProductCount");
+ _CurrentWeldingStep = 0;
+ _IsWeldingRetry = false;
+ Status.SetWorkingPosition(eWorkingPosition.UNLOADING_CELL);
+ UpdateProductCount(context);
+ UpdateProductInfoForView();
+ return SequenceAction.Ok();
+ }
+ #endregion
+
+ private SequenceActionResult GetPlcWeldCompleteInterface_SeqParams(SequenceContext context, string id, SequenceActionResult param)
+ {
+ #region 26.04.10 NDY - 시뮬레이션 환경 우회
+ if (ApplicationExt.IsSimulation)
+ {
+ Log.I("Simulation Mode - Skip PLC WeldComplete Result");
+ return SequenceAction.Ok();
+ }
+ #endregion
+
+ // 기존 SeqAutoRunPrismaticScanner와 동일
+ var resultOutParam = param.Param as SeqExecutePlcBitInterface_OutParam;
+ if (resultOutParam.Success == false)
+ {
+ Log.E("SeqCheckWeldingComplete Error;Success Failed.");
+ return null;
+ }
+ Log.I("SeqCheckWeldingComplete Success;Welding Complete!");
+
+ _WeldComTime = DateTime.Now;
+ Log.I($"***** 공정 시간 WeldPage No : {_PageNo} WeldRequest ~ WeldComplete : {_WeldComTime.Subtract(_WeldReqTime).TotalMilliseconds}ms");
+ _CurrentWeldingStep++;
+
+ if (_CurrentWeldingStep >= _TotalWeldingStep)
+ {
+ Log.I(string.Format("Need to Update the Cell Info;{0}/{1}", _CurrentWeldingStep, _TotalWeldingStep));
+ _IsWeldingRetry = false;
+ _CurrentWeldingStep = 0;
+ Status.SetWorkingPosition(eWorkingPosition.UNLOADING_CELL);
+ UpdateProductCount(context);
+ UpdateProductInfoForView();
+ }
+ else
+ {
+ Log.I(string.Format("Update Welding Step;{0}/{1}", _CurrentWeldingStep, _TotalWeldingStep));
+ _IsWeldingRetry = true;
+ }
+
+ return SequenceAction.Ok();
+ }
+ #endregion
+
+ #region 6. 결과 저장 및 종료
+ private SequenceActionResult SaveResultData(SequenceContext context, string id, SequenceActionResult param)
+ {
+ Status.SetWorkingPosition(eWorkingPosition.REPORTING_RESULT);
+ context.PutValue(ContextKey.KEY_SEQUENCE_RESULT, true);
+ return SequenceAction.Ok();
+ }
+
+ private SequenceActionResult Complete(SequenceContext context, string id, SequenceActionResult param)
+ {
+ RestoreDeviceStatus();
+
+ context.TryGetValue(ContextKey.KEY_SEQUENCE_RESULT, out bool seqResult);
+ SeqAutoRunPrismaticScannerACS_OutParam result = new SeqAutoRunPrismaticScannerACS_OutParam() { Success = seqResult };
+
+ if (seqResult == false)
+ AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_ResultFailed);
+
+ Status.SetWorkingPosition(eWorkingPosition.DONE);
+ Log.I($"SeqAutoRunPrismaticScannerACS Complete. Result: {seqResult}"); // ← 추가 확인용
+ return SequenceAction.Ok(result);
+ }
+ #endregion
+
+ #endregion
+
+ #region 구동 함수
+
+ private bool InitSequenceVariable(SequenceContext context)
+ {
+ var mainPage = CommonHelper.GetMainPage();
+ _ScannerCanvasUserCtrl = mainPage.ScannerCanvasCtrlTeaching;
+
+ // 기존과 동일
+ _PageNo = 1;
+ _CurrentWeldingStep = 0;
+ _PreviousTotalWeldingStep = 0;
+ _TotalWeldingStep = 0;
+ _OffsetXfromModelFinder = 0;
+ _OffsetYfromModelFinder = 0;
+ _IsWeldingRetry = false;
+ _FirstUpdateStep = true;
+ _RetryCount = 0;
+
+ #region 26.04.09 NDY - ACS 추가 변수 초기화
+ _SelectedDrawObjectList1 = new List();
+ _SelectedDrawObjectList2 = new List();
+ _ObjectList1OffsetX = 0;
+ _ObjectList1OffsetY = 0;
+ _ObjectList2OffsetX = 0;
+ _ObjectList2OffsetY = 0;
+ #endregion
+
+ return true;
+ }
+
+ private void InitDev(SequenceContext context)
+ {
+ // 기존과 동일
+ _Scanner = context.GetLaserScanner();
+ _Laser = context.GetLaser();
+ _PLC = context.GetPLC() as PLCControllerPrismaticScanner;
+ _Light = context.GetLight();
+
+ #region 26.04.09 NDY - ACS 장치 초기화
+ // ScannerACS: WeldingCommon/Devices/ACS/ScannerACS.cs 에 등록된 장치
+ // Stage X(AxisX2), Y1/Y2/Z 축 제어 및 엔코더 읽기 담당
+ try
+ {
+ _ScannerACS = context.Devices[CommonDeviceIds.ACS] as ScannerACS;
+ }
+ catch (Exception ex)
+ {
+ Log.E($"ScannerACS Device Init Failed: {ex.Message}");
+ }
+ #endregion
+ }
+
+ private bool CheckDeviceReady(SequenceContext context)
+ {
+ #region 26.04.10 NDY - 시뮬레이션 환경 우회
+ // 시뮬레이션 환경에서는 장치 활성화 체크 스킵
+ if (ApplicationExt.IsSimulation)
+ {
+ Log.I("Simulation Mode - Skip Device Ready Check");
+ return true;
+ }
+ #endregion
+
+ Log.I("CheckDeviceReady Start");
+
+ // 기존과 동일
+ List deviceListToCheck = new List() {
+ CommonDeviceIds.Scanner,
+ CommonDeviceIds.Laser,
+ CommonDeviceIds.PLCDevice
+ };
+
+ bool isAllDeviceActivated = true;
+ foreach (string item in deviceListToCheck)
+ {
+ bool isActivated = context.Devices[item].IsActivated;
+ Log.I($"Device: {item}, IsActivated: {isActivated}");
+ if (!isActivated)
+ isAllDeviceActivated = false;
+ }
+
+ #region 26.04.09 NDY - ScannerACS 장치 활성화 확인
+ if (_ScannerACS == null || !_ScannerACS.IsActivated)
+ {
+ Log.E("ScannerACS is not activated.");
+ isAllDeviceActivated = false;
+ }
+ #endregion
+
+ Log.I($"CheckDeviceReady Result: {isAllDeviceActivated}");
+ return isAllDeviceActivated;
+ }
+
+ private bool CheckCondition(SequenceContext context)
+ {
+ if (CheckCondition_Interlock())
+ {
+ if (InitializeDeviceStatus(context))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private bool CheckCondition_Interlock()
+ {
+ // TODO: 필요한 인터락 조건 추가
+ return true;
+ }
+
+ private bool InitializeDeviceStatus(SequenceContext context)
+ {
+ return true;
+ }
+
+ private void InitializePlcBits()
+ {
+ // 기존 SeqAutoRunPrismaticScanner와 동일
+ _PLC.WriteBit(eBitOutput.PC_WELD_READY_ACK, false);
+ _PLC.WriteBit(eBitOutput.PC_WELD_BUSY, false);
+ _PLC.WriteBit(eBitOutput.PC_WELD_COMPLETE, false);
+ _PLC.WriteBit(eBitOutput.PC_PAGE_CHANGE_COMPLETE, false);
+ _PLC.WriteBit(eBitOutput.PC_WELD_NG_JUDGE, false);
+
+ var UploadDatas = Status.GetProcessingWeldingData().UploadProcessDatas;
+ foreach (var data in UploadDatas)
+ data.Clear();
+ _PLC.UploadReportDatas(UploadDatas);
+ }
+
+ private bool RestoreDeviceStatus()
+ {
+ #region 26.04.10 NDY - 시뮬레이션 환경 우회
+ if (ApplicationExt.IsSimulation)
+ {
+ Log.I("Simulation Mode - Skip RestoreDeviceStatus");
+ Status.SetStatus(eEqpStatus.Manual);
+ return true;
+ }
+ #endregion
+
+ if (_Laser.GetLaserReady()) _Laser.LaserOff();
+ Status.SetStatus(eEqpStatus.Manual);
+ _PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, false);
+ InitializePlcBits();
+ return true;
+ }
+
+ private void UpdateProductCount(SequenceContext context)
+ {
+ // 기존 SeqAutoRunPrismaticScanner와 동일
+ _SystemParameter = context.GetSetting(CommonSettingIds.SystemParameter);
+
+ _TodayProduct = _SystemParameter.TodayProductCount;
+ _TotalProduct = _SystemParameter.TotalProductCount;
+ if (_TodayProduct < uint.MaxValue) _TodayProduct++;
+ if (_TotalProduct < uint.MaxValue) _TotalProduct++;
+ _SystemParameter.TodayProductCount = _TodayProduct;
+ _SystemParameter.TotalProductCount = _TotalProduct;
+ _WeldEndTime = DateTime.Now;
+ _SystemParameter.LastProductTime = _WeldEndTime;
+
+ context.Settings.Save(CommonSettingIds.SystemParameter, _SystemParameter, "SeqAutoRunPrismaticScannerACS");
+ }
+
+ private void UpdateProductInfoForView()
+ {
+ // 기존 SeqAutoRunPrismaticScanner와 동일
+ Status.GetProcessingWeldingData().TotalProduct = _TotalProduct;
+ Status.GetProcessingWeldingData().TodayProduct = _TodayProduct;
+ Status.GetProcessingWeldingData().WeldingStartTime = _WeldStartTime;
+ Status.GetProcessingWeldingData().WeldingEndTime = _WeldEndTime;
+ Status.GetProcessingWeldingData().WeldingSpanTimeMilliseconds = _WeldSpanTime.TotalMilliseconds.ToString();
+ Status.GetProcessingWeldingData().LotId = _LotId;
+ Status.GetProcessingWeldingData().CellId = _CellId;
+ }
+
+ private static void UnpackRecipes(List recipes)
+ {
+ // 기존 SeqAutoRunPrismaticScanner와 동일
+ if (recipes == null) return;
+
+ for (int i = 0; i < recipes.Count; i++)
+ {
+ if (recipes[i] is CoreUnit.Control.UserControl.Canvas.Group)
+ {
+ CoreUnit.Control.UserControl.Canvas.Group subGroup = recipes[i] as CoreUnit.Control.UserControl.Canvas.Group;
+ List subrecipes = subGroup.SubObjects as List;
+ UnpackRecipes(subrecipes);
+ }
+ else
+ {
+ _UnpackDrawObject.Add(recipes[i]);
+ }
+ }
+ }
+
+ private void OnForceError(SequenceContext arg1, string arg2)
+ {
+ OnSeqActionStop();
+ Log.E("SeqAutoRunPrismaticScannerACS OnForceError");
+ }
+
+ private void OnStopped(SequenceContext obj)
+ {
+ OnSeqActionStop();
+ Log.E("SeqAutoRunPrismaticScannerACS OnStopped");
+ }
+
+ private void OnSeqActionStop()
+ {
+ #region 26.04.10 NDY - 시뮬레이션 환경 우회
+ if (ApplicationExt.IsSimulation)
+ {
+ Log.I("Simulation Mode - Skip OnSeqActionStop");
+ Status.SetStatus(eEqpStatus.Manual);
+ return;
+ }
+ #endregion
+
+ // 기존 SeqAutoRunPrismaticScanner와 동일
+ if (_Laser.GetLaserReady()) _Laser.LaserOff();
+ Status.SetStatus(eEqpStatus.Manual);
+ _PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, false);
+ InitializePlcBits();
+ }
+
+ #endregion
+ }
+
+ public class SeqAutoRunPrismaticScannerACS_InitParam : InitParam
+ {
+ // 기존 SeqAutoRunPrismaticScanner_InitParam 패턴 유지
+ // 추후 MOTF 관련 파라미터 추가 가능
+ }
+
+ public class SeqAutoRunPrismaticScannerACS_OutParam : OutParam
+ {
+ }
+}
\ No newline at end of file
diff --git a/Machines/ILSOS_PrismaticScanner/Sequences/SequenceManager.cs b/Machines/ILSOS_PrismaticScanner/Sequences/SequenceManager.cs
index c80f470..704db3b 100644
--- a/Machines/ILSOS_PrismaticScanner/Sequences/SequenceManager.cs
+++ b/Machines/ILSOS_PrismaticScanner/Sequences/SequenceManager.cs
@@ -38,6 +38,12 @@ namespace ILSOS_PrismaticScanner.Sequences
SeqTeachingRun _SeqTeachingRun;
SeqComponentTeachingRun _SeqComponentTeachingRun;
+ #region 26.04.09 NDY - ACS MOTF AutoRun Sequence
+ // 기존 SeqAutoRunPrismaticScanner와 별개로 추가
+ // ACS 모션(Stage X 등) + 갈바노 스캐너 MOTF 자동운전 전용
+ SeqAutoRunPrismaticScannerACS _SeqAutoRunPrismaticScannerACS;
+ #endregion
+
private static Dictionary mapSequenceBuilders = new Dictionary();
static SequenceManager()
@@ -56,12 +62,17 @@ namespace ILSOS_PrismaticScanner.Sequences
private void ChangeRunState(ISequenceEntry sequenceEntry, string sequenceId, bool isRunning, object param = null)
{
- if (isRunning) {
- if (sequenceEntry[sequenceId].RunState != SequenceRunState.Running) {
+ if (isRunning)
+ {
+ if (sequenceEntry[sequenceId].RunState != SequenceRunState.Running)
+ {
sequenceEntry[sequenceId].Start(param);
}
- } else {
- if (sequenceEntry[sequenceId].RunState == SequenceRunState.Running) {
+ }
+ else
+ {
+ if (sequenceEntry[sequenceId].RunState == SequenceRunState.Running)
+ {
sequenceEntry[sequenceId].Stop();
}
}
@@ -84,66 +95,87 @@ namespace ILSOS_PrismaticScanner.Sequences
//register(CommonSequenceIds.DeviceMonitor, DeviceMonitoring.Builder());
base.Register(aurora, register);
- if (_SeqMarkGrid == null) {
+ if (_SeqMarkGrid == null)
+ {
_SeqMarkGrid = new SeqMarkGrid();
}
register(CommonSequenceIds.SeqMarkGrid, _SeqMarkGrid.Builder());
- if (_SeqScannerCalibration == null) {
+ if (_SeqScannerCalibration == null)
+ {
_SeqScannerCalibration = new SeqScannerCalibration();
}
register(CommonSequenceIds.SeqScannerCalibration, _SeqScannerCalibration.Builder());
- if (_SeqReceiveCellInfo == null) {
+ if (_SeqReceiveCellInfo == null)
+ {
_SeqReceiveCellInfo = new SeqReceiveCellInfo();
}
register(CommonSequenceIds.SeqReceiveCellInfo, _SeqReceiveCellInfo.Builder());
- if (_SeqReportCellResult == null) {
+ if (_SeqReportCellResult == null)
+ {
_SeqReportCellResult = new SeqReportCellResult();
}
register(CommonSequenceIds.SeqReportCellResult, _SeqReportCellResult.Builder());
- if (_SeqScoutFlyingVision == null) {
+ if (_SeqScoutFlyingVision == null)
+ {
_SeqScoutFlyingVision = new SeqScoutFlyingVision();
}
register(CommonSequenceIds.SeqScoutFlyingVision, _SeqScoutFlyingVision.Builder());
- if (_SeqScoutNormal == null) {
+ if (_SeqScoutNormal == null)
+ {
_SeqScoutNormal = new SeqScoutNormal();
}
register(CommonSequenceIds.SeqScoutNormal, _SeqScoutNormal.Builder());
-
- if (_SeqExecutePlcBitInterface == null) {
+ if (_SeqExecutePlcBitInterface == null)
+ {
_SeqExecutePlcBitInterface = new SeqExecutePlcBitInterface();
}
register(CommonSequenceIds.SeqExecutePlcBitInterface, _SeqExecutePlcBitInterface.Builder());
- if (_SeqExecutePlcBitInterfaceForStatusChange == null) {
+ if (_SeqExecutePlcBitInterfaceForStatusChange == null)
+ {
_SeqExecutePlcBitInterfaceForStatusChange = new SeqExecutePlcBitInterface();
}
register(CommonSequenceIds.SeqExecutePlcBitInterfaceForStatusChange, _SeqExecutePlcBitInterfaceForStatusChange.Builder());
- if (_SeqCheckWeldingComplete == null) {
+ if (_SeqCheckWeldingComplete == null)
+ {
_SeqCheckWeldingComplete = new SeqCheckWeldingComplete();
}
register(CommonSequenceIds.SeqCheckWeldingComplete, _SeqCheckWeldingComplete.Builder());
- if (_SeqTeachingRun == null) {
+ if (_SeqTeachingRun == null)
+ {
_SeqTeachingRun = new SeqTeachingRun();
}
register(CommonSequenceIds.SeqTeachingRun, _SeqTeachingRun.Builder());
- if (_SeqComponentTeachingRun == null) {
+ if (_SeqComponentTeachingRun == null)
+ {
_SeqComponentTeachingRun = new SeqComponentTeachingRun();
}
register(CommonSequenceIds.SeqComponentTeachingRun, _SeqComponentTeachingRun.Builder());
- if (_SeqWaitWeldOrChangePage == null) {
+ if (_SeqWaitWeldOrChangePage == null)
+ {
_SeqWaitWeldOrChangePage = new SeqWaitWeldOrChangePage();
}
register(CommonSequenceIds.SeqWaitWeldOrChangePage, _SeqWaitWeldOrChangePage.Builder());
+
+ #region 26.04.09 NDY - ACS MOTF AutoRun Sequence 등록
+ // 기존 SeqAutoRun(SeqAutoRunPrismaticScanner)은 건드리지 않고
+ // ACS MOTF 전용 시퀀스를 별도 ID(SeqAutoRunACS)로 등록
+ if (_SeqAutoRunPrismaticScannerACS == null)
+ {
+ _SeqAutoRunPrismaticScannerACS = new SeqAutoRunPrismaticScannerACS();
+ }
+ register(SequenceIds.SeqAutoRunACS, _SeqAutoRunPrismaticScannerACS.Builder());
+ #endregion
}
// MainProcess Sequence
@@ -161,9 +193,13 @@ namespace ILSOS_PrismaticScanner.Sequences
public static SequenceBuilder GetSeqBuilder_SeqTeachingRun() { return Instance._SeqTeachingRun.Builder(); }
public static SequenceBuilder GetSeqBuilder_SeqComponentTeachingRun() { return Instance._SeqComponentTeachingRun.Builder(); }
+ #region 26.04.09 NDY - ACS MOTF AutoRun Sequence GetSeqBuilder
+ public static SequenceBuilder GetSeqBuilder_SeqAutoRunPrismaticScannerACS() { return Instance._SeqAutoRunPrismaticScannerACS.Builder(); }
+ #endregion
+
public virtual void Terminate()
{
}
}
-}
+}
\ No newline at end of file
diff --git a/Machines/ILSOS_PrismaticScanner/Views/Main/UCMainAutoRun.xaml b/Machines/ILSOS_PrismaticScanner/Views/Main/UCMainAutoRun.xaml
index 7ccb906..28746e8 100644
--- a/Machines/ILSOS_PrismaticScanner/Views/Main/UCMainAutoRun.xaml
+++ b/Machines/ILSOS_PrismaticScanner/Views/Main/UCMainAutoRun.xaml
@@ -53,13 +53,35 @@
-
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
diff --git a/Machines/ILSOS_PrismaticScanner/Views/Main/UCMainAutoRunViewModel.cs b/Machines/ILSOS_PrismaticScanner/Views/Main/UCMainAutoRunViewModel.cs
index 73e19a3..4e50f8a 100644
--- a/Machines/ILSOS_PrismaticScanner/Views/Main/UCMainAutoRunViewModel.cs
+++ b/Machines/ILSOS_PrismaticScanner/Views/Main/UCMainAutoRunViewModel.cs
@@ -43,6 +43,11 @@ namespace ILSOS_PrismaticScanner.Views
public ICommand CMD_StopSequenceAutoRun { get; }
public ICommand CMD_Reset { get; } // Totoal Product, Today Product
+ #region 26.04.10 NDY - MOTF AutoRun Command
+ public ICommand CMD_StartMOTF { get; }
+ public ICommand CMD_StopMOTF { get; }
+ #endregion
+
private readonly IRecipeManager _RecipeManager;
private readonly ISettingEntry _SettingEntry;
private readonly IAlarmService _AlarmService;
@@ -56,6 +61,12 @@ namespace ILSOS_PrismaticScanner.Views
private ISequence _SeqPlcInterface;
private ISequence _SeqQTest;
+
+ #region 26.04.10 NDY - MOTF AutoRun Sequence
+ // 기존 _SeqAutoRun과 별개로 MOTF 전용 시퀀스 참조
+ private ISequence _SeqMOTF;
+ #endregion
+
public bool IsSequenceRunning_AutoRun { get => _IsSequenceRunning_AutoRun; set => SetProperty(ref _IsSequenceRunning_AutoRun, value); }
private bool _IsSequenceRunning_AutoRun;
@@ -87,6 +98,12 @@ namespace ILSOS_PrismaticScanner.Views
// Q Test
_SeqQTest = _Aurora.SequenceEntry[SequenceIds.SeqEventQTest];
+
+ #region 26.04.10 NDY - MOTF AutoRun Sequence 참조
+ // 기존 SeqAutoRun과 별개로 MOTF 전용 시퀀스 ID로 참조
+ _SeqMOTF = _Aurora.SequenceEntry[SequenceIds.SeqAutoRunACS];
+ #endregion
+
InitVariable();
CMD_VisibleChanged = new DelegateCommand(VisibleChanged);
@@ -95,6 +112,12 @@ namespace ILSOS_PrismaticScanner.Views
CMD_StopSequenceAutoRun = new DelegateCommand(OnStopSequenceAutoRun);
CMD_Reset = new DelegateCommand(PerformCmdReset);
+ #region 26.04.10 NDY - MOTF AutoRun Command 등록
+ // 기존 CMD_RunSequenceAutoRun / CMD_StopSequenceAutoRun 패턴과 동일
+ CMD_StartMOTF = new DelegateCommand(OnMOTFStartRequest);
+ CMD_StopMOTF = new DelegateCommand(OnStopMOTF);
+ #endregion
+
_MonitorTimer = new DispatcherTimer();
_MonitorTimer.Tick += OnMonitoring;
_MonitorTimer.Interval = TimeSpan.FromMilliseconds(100);
@@ -150,6 +173,7 @@ namespace ILSOS_PrismaticScanner.Views
// Page UI 업데이트
RecipeHelper.UpdateAutorunRecipe();
}
+
private void VisibleChanged(object obj)
{
bool visible = (bool)obj;
@@ -271,6 +295,54 @@ namespace ILSOS_PrismaticScanner.Views
_Scanner.SelectCorrectionTable(1);
}
+ #region 26.04.10 NDY - MOTF AutoRun 실행 / 정지
+ // 기존 OnAutoRunStartRequest / OnStopSequenceAutoRun 패턴과 동일
+ // 테스트 버튼용이므로 TimerMessageBox 확인 다이얼로그는 생략
+
+ private void OnMOTFStartRequest(object obj = null)
+ {
+ // 기존 AutoRun 인터락 조건 동일하게 적용
+ if (_SeqMOTF.RunState == SequenceRunState.Running) {
+ MessageUtil.ShowWarningToast("이미 MOTF 시퀀스가 Start 상태입니다.", Log);
+ return;
+ }
+
+ // 자동으로 Activated Recipe로 변경
+ OnUpdateCurrentActivatedRecipe(null);
+
+ Status.GetProcessingWeldingData().AutoRunStartTime = DateTime.Now;
+ Status.SetStatus(eEqpStatus.Auto);
+
+ var param = new SeqAutoRunPrismaticScannerACS_InitParam();
+ _SeqMOTF.Start(param, OnCompleteMOTF);
+
+ Log.I("MOTF Sequence Started");
+ }
+
+ private void OnCompleteMOTF(SequenceActionResult res)
+ {
+ // 기존 OnCompleteAutoRun 패턴과 동일
+ if (res != null) {
+ if (res.Param is SeqAutoRunPrismaticScannerACS_OutParam result && result.Success == true) {
+ MessageUtil.ShowSuccessToast("Success MOTF Sequence");
+ return;
+ }
+ }
+ MessageUtil.ShowWarningToast("Fail MOTF Sequence");
+ }
+
+ private void OnStopMOTF(object obj = null)
+ {
+ // 기존 OnStopSequenceAutoRun 패턴과 동일
+ if (_SeqMOTF.RunState == SequenceRunState.Idle) {
+ return;
+ }
+
+ _SeqMOTF.Stop();
+ Log.I("MOTF Sequence Stopped");
+ }
+ #endregion
+
private void PerformCmdReset(object commandParameter)
{
// SystemParameter를 저장하기 전에는 반드시 업데이트를 해주어야 한다.
@@ -317,4 +389,4 @@ namespace ILSOS_PrismaticScanner.Views
keyboardInput,
ClickInput
}
-}
+}
\ No newline at end of file
diff --git a/WeldingComponents/LASA/GUI/Canvas/Layers/DrawingLayer.cs b/WeldingComponents/LASA/GUI/Canvas/Layers/DrawingLayer.cs
index 00976aa..04d2bec 100644
--- a/WeldingComponents/LASA/GUI/Canvas/Layers/DrawingLayer.cs
+++ b/WeldingComponents/LASA/GUI/Canvas/Layers/DrawingLayer.cs
@@ -1,3 +1,4 @@
+using CoreUnit.Common;
using System;
using System.Collections.Generic;
using System.Drawing;
@@ -31,9 +32,12 @@ namespace CoreUnit.Control.UserControl.Canvas
}
#region 26.04.09
- // 26.04.09 NDY [´ë¸éÀû MOTF] ÆäÀÌÁö ·¹º§ Offset ÆĶó¹ÌÅÍ Ã߰¡
+ // 26.04.09 NDY [´ë¸éÀû MOTF] ÆäÀÌÁö ·¹º§ Offset ÆĶó¹ÌÅÍ Ã߰¡ - 26.04.10 XmlSerializable ¾îƮ¸®ºäƮ Ã߰¡
+ [XmlSerializable]
public double LargeMOTFOffsetX { get; set; } = 0.0;
+ [XmlSerializable]
public double LargeMOTFOffsetY { get; set; } = 0.0;
+
#endregion
public DrawingLayer(int no, string name, Color color, float width)
diff --git a/WeldingComponents/LASA/GUI/DB/RecipeWeldingLines.cs b/WeldingComponents/LASA/GUI/DB/RecipeWeldingLines.cs
index efb9631..5b770f2 100644
--- a/WeldingComponents/LASA/GUI/DB/RecipeWeldingLines.cs
+++ b/WeldingComponents/LASA/GUI/DB/RecipeWeldingLines.cs
@@ -23,8 +23,10 @@ namespace DB.Recipe
{
#region 26.04.09 NDY
- // 26.04.09 NDY [대면적 MOTF] 레시피 레벨 파라미터 추가
+ // 26.04.09 NDY [대면적 MOTF] 레시피 레벨 파라미터 추가 - 04.10 [XmlSerializable] 추가함.
+ [XmlSerializable]
public bool UseLargeMOTF { get; set; } = false;
+ [XmlSerializable]
public int LargeMOTFPageCount { get; set; } = 1;
#endregion
@@ -90,8 +92,11 @@ namespace DB.Recipe
{
try
{
- LoadWeldingParam(xmlElement);
- Model.Load(xmlElement);
+ #region 26.04.10 NDY [대면적 MOTF] 레시피 레벨 파라미터 로드
+ CoreUnit.Common.XmlUtil.ParseProperties(xmlElement, this);
+ #endregion
+ LoadWeldingParam(xmlElement); // WeldingParameters만 로드
+ Model.Load(xmlElement); // DrawingObjects만 로드
}
catch (Exception ex)
{
diff --git a/WeldingComponents/LASA/GUI/Page/MainPage.cs b/WeldingComponents/LASA/GUI/Page/MainPage.cs
index aaa47b5..a365160 100644
--- a/WeldingComponents/LASA/GUI/Page/MainPage.cs
+++ b/WeldingComponents/LASA/GUI/Page/MainPage.cs
@@ -13,6 +13,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
+using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
@@ -34,6 +35,135 @@ namespace GUI
InitializeComponent();
}
+ #region 26.04.10 NDY에 추가함.
+ // MainPage.cs에 추가
+ public void UpdateLargeMOTFPosition()
+ {
+ foreach (ICanvasLayer layer in ScannerCanvasCtrlTeaching.Model.Layers)
+ {
+ var lines = layer.Objects
+ .OfType()
+ .OrderBy(l => l.No)
+ .ToList();
+ if (lines.Count == 0) continue;
+
+ float motfX = lines.Last().PF2.X;
+ float motfY = lines.Last().PF2.Y;
+ foreach (var line in lines)
+ {
+ line.LargeMOTFPositionX = motfX;
+ line.LargeMOTFPositionY = motfY;
+ }
+ }
+ }
+
+ public void GenerateFlippedPages(double largeMOTFOffsetX)
+ {
+ try
+ {
+ var model = ScannerCanvasCtrlTeaching.Model;
+ var layers = model.Layers;
+
+ Debug.WriteLine($"[GenerateFlippedPages] 시작 - Layer 수: {layers.Count()}");
+
+ if (layers.Count() < 2)
+ {
+ Debug.WriteLine("[GenerateFlippedPages] Layer 2개 미만 - 종료");
+ return;
+ }
+
+ // 기존 Layer[2], Layer[3] 삭제
+ while (layers.Count() > 2)
+ {
+ Debug.WriteLine($"[GenerateFlippedPages] Layer 삭제 - 현재 수: {layers.Count()}");
+ model.DeleteLayer(layers.Count() - 1);
+ }
+ Debug.WriteLine($"[GenerateFlippedPages] 삭제 후 Layer 수: {layers.Count()}");
+
+ var p1Lines = layers[0].Objects.OfType().OrderBy(l => l.No).ToList();
+ var p2Lines = layers[1].Objects.OfType().OrderBy(l => l.No).ToList();
+ Debug.WriteLine($"[GenerateFlippedPages] P1 Line 수: {p1Lines.Count}, P2 Line 수: {p2Lines.Count}");
+
+ if (p1Lines.Count == 0 || p2Lines.Count == 0)
+ {
+ Debug.WriteLine("[GenerateFlippedPages] Line 없음 - 종료");
+ return;
+ }
+
+ float flipYforP3 = p2Lines.Last().PF2.Y;
+ float flipYforP4 = p1Lines.First().PF1.Y;
+ Debug.WriteLine($"[GenerateFlippedPages] flipYforP3={flipYforP3:F3}, flipYforP4={flipYforP4:F3}");
+
+ // Page3 생성 = Page2 Y플립
+ model.CreateLayer();
+ Debug.WriteLine($"[GenerateFlippedPages] CreateLayer(P3) 후 Layer 수: {layers.Count()}");
+
+ if (layers.Count() < 3)
+ {
+ Debug.WriteLine("[GenerateFlippedPages] P3 Layer 생성 실패 - 종료");
+ return;
+ }
+
+ var layer2 = layers[2] as DrawingLayer;
+ if (layer2 == null)
+ {
+ Debug.WriteLine("[GenerateFlippedPages] layer2 캐스팅 실패 - 종료");
+ return;
+ }
+
+ foreach (var srcLine in p2Lines)
+ {
+ var newLine = new Line();
+ newLine.Copy(srcLine);
+ newLine.PF1 = new System.Drawing.PointF(
+ srcLine.PF1.X, 2 * flipYforP3 - srcLine.PF1.Y);
+ newLine.PF2 = new System.Drawing.PointF(
+ srcLine.PF2.X, 2 * flipYforP3 - srcLine.PF2.Y);
+ layer2.AddObject(newLine);
+ }
+ Debug.WriteLine($"[GenerateFlippedPages] P3 Line {p2Lines.Count}개 추가 완료");
+
+ // Page4 생성 = Page1 Y플립
+ model.CreateLayer();
+ Debug.WriteLine($"[GenerateFlippedPages] CreateLayer(P4) 후 Layer 수: {layers.Count()}");
+
+ if (layers.Count() < 4)
+ {
+ Debug.WriteLine("[GenerateFlippedPages] P4 Layer 생성 실패 - 종료");
+ return;
+ }
+
+ var layer3 = layers[3] as DrawingLayer;
+ if (layer3 == null)
+ {
+ Debug.WriteLine("[GenerateFlippedPages] layer3 캐스팅 실패 - 종료");
+ return;
+ }
+
+ foreach (var srcLine in p1Lines)
+ {
+ var newLine = new Line();
+ newLine.Copy(srcLine);
+ newLine.PF1 = new System.Drawing.PointF(
+ srcLine.PF1.X, 2 * flipYforP4 - srcLine.PF1.Y);
+ newLine.PF2 = new System.Drawing.PointF(
+ srcLine.PF2.X, 2 * flipYforP4 - srcLine.PF2.Y);
+ layer3.AddObject(newLine);
+ }
+ Debug.WriteLine($"[GenerateFlippedPages] P4 Line {p1Lines.Count}개 추가 완료");
+
+ UpdateWeldingLinesRecipe();
+ Debug.WriteLine($"[GenerateFlippedPages] 완료 - OffsetX={largeMOTFOffsetX:F3}");
+
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[GenerateFlippedPages] 예외 발생: {ex.Message}");
+ Debug.WriteLine($"[GenerateFlippedPages] StackTrace: {ex.StackTrace}");
+ }
+ }
+ #endregion
+
#region 2.메인화면: 다이얼로그 및 페이지 초기화 함수
private void ClearDisplayedError()
@@ -457,6 +587,7 @@ namespace GUI
UtilUnit.logUnit.SetLog(LogUnit.eLogType.ALARM, MethodBase.GetCurrentMethod().Name, ex.Message);
}
}
+ public event Action OnRecipeReloaded; // 26.04.10 추가 MainPage에 콜백추가를 위한 액션
public void ReloadRecipe(string fullpath)
{
ScannerCanvasCtrlTeaching.Canvas.CommandEscape();
@@ -467,12 +598,17 @@ namespace GUI
UpdateWeldingLinesRecipe();
+ // 26.04.10 NDY [대면적 MOTF]
+ OnRecipeReloaded?.Invoke();
+
+
//// Page 불러온 후, 실행되지 않아도 됨. 왜냐하면, 어차피 SelectedIndex 쪽으로 빠짐.
//SaveAndLoadFieldScanFromLasaDb();
//ChangeScoutImage();
}
#endregion
+ // 26.04.10 확인용 --- MainPage의 레시피 정보를 Get 하는 메소드
public RecipeWeldingLines getRecipeInfo(string fullpath)
{
var recipe = new RecipeWeldingLines();
diff --git a/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs b/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs
index 045c9c2..b59a01d 100644
--- a/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs
+++ b/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingControlBoxViewModel.cs
@@ -294,6 +294,18 @@ namespace WeldingCommon.Views
}
// 26.04.09 NDY [대면적 MOTF] End Set 커맨드 구현
+ //최종 구현하고자 하는 방향성
+ //{
+ // 1. Layer[0] (P1), Layer[1] (P2) 원본 확인
+ // 2. Layer[2] 생성 = P3(P2 Y플립)
+ // - 플립 기준Y = Layer[1] 마지막 Line PF2.Y
+ // - P2의 Line들을 역순으로 복사 + Y좌표 플립
+ // 3. Layer[3] 생성 = P4(P1 Y플립)
+ // - 플립 기준Y = Layer[0] 첫 Line PF1.Y
+ // - P1의 Line들을 역순으로 복사 + Y좌표 플립
+ // 4. 기존 Layer[2],[3] 있으면 삭제 후 재생성
+ // 5. MessageUtil로 결과 안내
+ //}
private void PerformCmdLargeMOTFEndSet(object obj)
{
var result = MessageUtil.ShowYesNoMessageDialog("Large MOTF End Set",
@@ -326,6 +338,9 @@ namespace WeldingCommon.Views
Log.I($" - 계산된 Offset Y (Scanner): {LargeMOTFOffsetY:F3} mm");
Log.I($" - 저장된 Layer: Page {mainPage.ScannerCanvasCtrlTeaching.Model.ActiveLayer.No + 1}");
Log.I($" - TODO: 모션→스캐너 좌표 변환 방향/스케일 이정인 선임 확인 필요");
+
+ // 26.04.10 NDY [대면적 MOTF] 플립 페이지 자동 생성
+ CommonHelper.GetMainPage().GenerateFlippedPages(LargeMOTFOffsetX);
}
private void GetPerformCmdStageXJogSpeed(object obj)
{
diff --git a/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingViewModel.cs b/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingViewModel.cs
index a93e2b2..905230e 100644
--- a/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingViewModel.cs
+++ b/WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingViewModel.cs
@@ -67,9 +67,22 @@ namespace WeldingCommon.Views
_Aurora.EventObservable.AsObservable(CommonEventIds.EVENT_EQUIPMENT_STATUS_CHANGE)
.Subscribe(x => OnChangedEquipmentStatus());
+ #region 26.04.10 NDY [대면적 MOTF] 프로그램 시작 시 UI 반영
+ CommonHelper.GetMainPage().OnRecipeReloaded += () =>
+ {
+ ScannerObjectTeachingControlBoxViewModel.IsUseLargeMOTF
+ = DB.Dao.WeldingLinesRecipe.UseLargeMOTF;
+ ScannerObjectTeachingControlBoxViewModel.LargeMOTFPageCount
+ = DB.Dao.WeldingLinesRecipe.LargeMOTFPageCount;
+ };
+ #endregion
RecipeHelper.OnActiveReloaded();
+
+
+
CommonHelper.UpdateAutoRunPage();
+
}
private object OnChangedEquipmentStatus()
@@ -165,12 +178,26 @@ namespace WeldingCommon.Views
RecipeHelper.SelectedRecipe = recipe;
RecipeHelper.ReloadRecipe(recipe.name);
_RecipeBeforeChanged = RecipeHelper.getRecipeInfo(recipe.name);
+
+ #region 26.04.10 NDY [대면적 MOTF] 레시피 로드 시 UI 반영
+ ScannerObjectTeachingControlBoxViewModel.IsUseLargeMOTF
+ = DB.Dao.WeldingLinesRecipe.UseLargeMOTF;
+ ScannerObjectTeachingControlBoxViewModel.LargeMOTFPageCount
+ = DB.Dao.WeldingLinesRecipe.LargeMOTFPageCount;
+
+ #endregion
}
public void Reloaded((int id, string name) recipe)
{
RecipeHelper.SelectedRecipe = recipe;
RecipeHelper.ReloadRecipe(recipe.name);
+
+ // 26.04.10 NDY [대면적 MOTF] 레시피 로드 시 UI 반영
+ ScannerObjectTeachingControlBoxViewModel.IsUseLargeMOTF
+ = DB.Dao.WeldingLinesRecipe.UseLargeMOTF;
+ ScannerObjectTeachingControlBoxViewModel.LargeMOTFPageCount
+ = DB.Dao.WeldingLinesRecipe.LargeMOTFPageCount;
}
public void OnModelSelectChanged(string modelName)
@@ -198,13 +225,35 @@ namespace WeldingCommon.Views
return false;
}
-
+
+ #region 26.04.10 찾음. ScannerCanvasCtrlTeaching.Model.Layers로 Layer 접근하고 각 Layer에서 DrawObject를 Get 할 수 있음. // 까먹을까봐 적어둠
+ //MainPage.SaveRecipe()
+ // → DB.Dao.WeldingLinesRecipe.Save() 전에
+ // → ScannerCanvasCtrlTeaching.Model.Layers
+ // → layer.Objects(List)
+ // → Line.LargeMOTFPositionX/Y SET
+ #endregion
public void OnSaved(int id, string recipeName)
{
if (string.IsNullOrWhiteSpace(RecipeHelper.SelectedRecipe.name)) {
return;
}
+ #region 26.04.10 NDY [대면적 MOTF] 저장 직전 레시피에 반영
+ DB.Dao.WeldingLinesRecipe.UseLargeMOTF
+ = ScannerObjectTeachingControlBoxViewModel.IsUseLargeMOTF;
+ DB.Dao.WeldingLinesRecipe.LargeMOTFPageCount
+ = ScannerObjectTeachingControlBoxViewModel.LargeMOTFPageCount;
+ #endregion
+ #region 26.04.10 NDY [대면적 MOTF] 저장 직전 LargeMOTFPosition 자동 계산
+ // 방법1 현재 viewmodel에 구현함.
+ //UpdateLargeMOTFPosition();
+ // 방법2 현재 MainPage.cs에 메소드 추가함. 그것을 호출하는 식
+
+ CommonHelper.GetMainPage().UpdateLargeMOTFPosition();
+
+ #endregion
+
RecipeHelper.SaveRecipe(RecipeHelper.SelectedRecipe.name);
var ChangedRecipe = RecipeHelper.getRecipeInfo(RecipeHelper.SelectedRecipe.name);
var diffs = new ObservableCollection();
@@ -236,5 +285,36 @@ namespace WeldingCommon.Views
}
Log.I($"Saved Recipe : [{RecipeHelper.SelectedRecipe.name}]");
}
+ #region 26.04.10 NDY [대면적 MOTF] Layer별 마지막 Line의 PF2를 LargeMOTFPosition에 SET
+ //주의사항:
+ //ScannerCanvasCtrlTeaching이 MainPage 내부 필드라 internal 또는 private일 수 있습니다.접근이 안 되면 MainPage에 별도 메서드를 추가하는 방향으로 가야 합니다:
+
+ private void UpdateLargeMOTFPosition()
+ {
+ var model = CommonHelper.GetMainPage()
+ .ScannerCanvasCtrlTeaching
+ .Model;
+
+ foreach (ICanvasLayer layer in model.Layers)
+ {
+ var lines = layer.Objects
+ .OfType()
+ .OrderBy(l => l.No)
+ .ToList();
+
+ if (lines.Count == 0) continue;
+
+ var lastLine = lines.Last();
+ float motfX = lastLine.PF2.X;
+ float motfY = lastLine.PF2.Y;
+
+ foreach (var line in lines)
+ {
+ line.LargeMOTFPositionX = motfX;
+ line.LargeMOTFPositionY = motfY;
+ }
+ }
+ }
+ #endregion
}
}