NohDoyeon241104 f5d68248c9 feat: [대면적 MOTF] 레시피 저장/로드 및 페이지 자동 생성 기반 구현 (26.04.10)
- [Line.cs] LargeMOTFPositionX/Y 프로퍼티 추가 ([XmlSerializable])
- [DrawingLayer.cs] LargeMOTFOffsetX/Y 프로퍼티 추가 ([XmlSerializable])
- [RecipeWeldingLines.cs] UseLargeMOTF, LargeMOTFPageCount 프로퍼티 추가 ([XmlSerializable])
  - Load() 에 XmlUtil.ParseProperties() 추가하여 로드 시 반영
- [MainPage.cs]
  - UpdateLargeMOTFPosition() 추가: 저장 직전 Layer별 마지막 Line PF2를 LargeMOTFPositionX/Y에 자동 SET
  - OnRecipeReloaded 이벤트 추가: 레시피 로드 완료 시 콜백
  - GenerateFlippedPages() 추가: EndSet 시 Page3(P2 Y플립), Page4(P1 Y플립) 자동 생성
- [UCScannerObjectTeachingViewModel.cs]
  - OnSaved(): UpdateLargeMOTFPosition, UseLargeMOTF/LargeMOTFPageCount 저장 직전 반영
  - OnChanged(), Reloaded(): 레시피 로드 시 IsUseLargeMOTF, LargeMOTFPageCount UI 반영
  - 생성자: OnRecipeReloaded 이벤트 등록하여 프로그램 시작 시 UI 반영
- [UCScannerObjectTeachingControlBoxViewModel.cs]
  - PerformCmdLargeMOTFEndSet(): LargeMOTFOffsetX 계산 후 GenerateFlippedPages() 호출
  - OnRecipeReloaded 시 LargeMOTFOffsetX UI 반영 (Layer[1] 기준)
2026-04-10 17:31:31 +09:00

1076 lines
46 KiB
C#

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
{
/// <summary>
/// ACS 모션 + 갈바노 스캐너 MOTF 자동운전 시퀀스
/// 기존 SeqAutoRunPrismaticScanner 기반으로 작성
/// - 비전 보상 / 이미지 저장 제거 (MOTF 구조상 불필요)
/// - InitFly2D(엔코더 리셋) 노드 추가
/// - Object 리스트를 List1 / List2 로 분리하여 각각 MOTF 용접 수행
/// 작성일: 26.04.09 NDY
/// </summary>
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<PatternInfo> _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<IDrawObject> _SelectedDrawObjectList;
private static List<IDrawObject> _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<int> lightValues = new List<int> { 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<IDrawObject> _SelectedDrawObjectList1;
private List<IDrawObject> _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<string> 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<SystemParameter>(CommonSettingIds.SystemParameter);
_SequenceSetting = context.GetSetting<SequenceSetting>(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<string> 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 : 엔코더 리셋
/// <summary>
/// [MOTF Step 1]
/// 자동운전 시작 시 Linear 원점 위치에서 InitFly2D() 호출
/// → 스캐너 내부 X 엔코더 값을 0으로 초기화
/// → 이후 SeqUploadWeldingList 내 WaitForEncoderX가
/// RM(25,0) / LM(75,0) 시작점에서 정확히 트리거되도록 보장
/// </summary>
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)
/// <summary>
/// 기존 Action에서 Decision으로 변경
/// - 시뮬레이션: SEQ_CHECK_WELDDING_RETRY 로 직접 분기 (서브시퀀스 완전 스킵)
/// - 실제 장비: SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST 서브시퀀스로 진입
/// </summary>
private string SetPlcPageChangeOrWeldRequest_SeqParams(SequenceContext context, string id, ICollection<string> 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 분리
/// <summary>
/// [MOTF Step 2]
/// 레시피 레이어에서 Object를 꺼내 List1 / List2 로 분리
/// - _SelectedDrawObjectList : 기존과 동일하게 전체 Object 보관
/// - _SelectedDrawObjectList1 : RM 용접용 (Page1 끝~Page2 시작, Virtual Line 포함)
/// - _SelectedDrawObjectList2 : LM 용접용 (Page3 끝~Page4 시작, Virtual Line 포함)
/// TODO: 실제 분리 기준(ScannerParamID 또는 레이어 구성)은 이정인 선임 확인 후 확정
/// </summary>
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 오프셋 이동
/// <summary>
/// [MOTF Step 3]
/// 1번 Object 리스트를 스캐너 중심으로부터 원점위치까지 떨어진 양만큼 오프셋 이동
/// - 스캐너 중심 = (50, 0), Linear 원점 = (0, 0) → 오프셋 X = -50mm
/// - 이 오프셋은 SeqUploadWeldingList 내 CellMeasureCenterOffset으로 전달됨
/// - WaitForEncoderX가 RM 용접 시작점(25, 0)에서 트리거되도록 준비
/// TODO: ScannerConfig 또는 SystemParameter에서 스캐너 중심 좌표 가져오도록 수정
/// </summary>
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 오프셋 이동
/// <summary>
/// [MOTF Step 6]
/// 2번 Object 리스트를 스캐너 중심으로부터 원점위치까지 떨어진 양만큼 오프셋 이동
/// - LM 용접 시작점(75, 0)에서 WaitForEncoderX가 트리거되도록 준비
/// TODO: List2 기준 오프셋 수치 확인 후 수정
/// </summary>
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)
/// <summary>
/// 기존 Action에서 Decision으로 변경
/// - 시뮬레이션: SEQ_SKIP_PLC_WELD_COMPLETE_ACS 로 분기 (서브시퀀스 완전 스킵)
/// - 실제 장비: SEQ_DO_PLC_WELD_COMPLETE_INTERFACE_SEQ 서브시퀀스로 진입
/// </summary>
private string SetPlcWeldCompleteInterface_SeqParams(SequenceContext context, string id, ICollection<string> 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;
}
/// <summary>
/// 시뮬레이션 환경에서 PLC WeldComplete 서브시퀀스 스킵 시 호출
/// 실제 장비에서는 이 노드를 타지 않음
/// </summary>
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<IDrawObject>();
_SelectedDrawObjectList2 = new List<IDrawObject>();
_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<string> deviceListToCheck = new List<string>() {
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<SystemParameter>(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<IDrawObject> 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<IDrawObject> subrecipes = subGroup.SubObjects as List<IDrawObject>;
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
{
}
}