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] 기준)
This commit is contained in:
노도연 2026-04-10 17:31:31 +09:00
parent 5b686c503a
commit f5d68248c9
14 changed files with 1495 additions and 41 deletions

View File

@ -1 +0,0 @@
,DESKTOP-CN4O63O/User,DESKTOP-CN4O63O,09.04.2026 15:52,file:///C:/Users/User/AppData/Roaming/LibreOffice/4;

View File

@ -40,6 +40,9 @@ namespace ILSOS_PrismaticScanner
public const string SeqAlarmMonitor = "SeqAlarmMonitor"; public const string SeqAlarmMonitor = "SeqAlarmMonitor";
public const string SeqStatusMonitor = "SeqStatusMonitor"; public const string SeqStatusMonitor = "SeqStatusMonitor";
// 26.04.09 NDY - ACS MOTF AutoRun - > SeqAutoRun은 CommonSequenceIds에 존재함.
public const string SeqAutoRunACS = "SeqAutoRunACS";
} }
public class StreamMessageNames public class StreamMessageNames

View File

@ -64,6 +64,9 @@ namespace ILSOS_PrismaticScanner
private SequenceBuilder _SeqAutoRunPrismaticScanner_Builder; private SequenceBuilder _SeqAutoRunPrismaticScanner_Builder;
private SequenceBuilder _SeqAutoRunComponentWeld_Builder; private SequenceBuilder _SeqAutoRunComponentWeld_Builder;
private SequenceBuilder _SeqAlarmMonitor_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) public override void BuildMainStatusView(MainStatusBuilder builder)
@ -265,28 +268,30 @@ namespace ILSOS_PrismaticScanner
// Common Sequences register // Common Sequences register
SequenceManager.RegisterSequence(AuroraEnv, register); SequenceManager.RegisterSequence(AuroraEnv, register);
base.RegisterSequences(register); base.RegisterSequences(register);
var equipSetting = AuroraEnv.SettingEntry[AuroraSettings.Equipment] as EquipmentInfo; var equipSetting = AuroraEnv.SettingEntry[AuroraSettings.Equipment] as EquipmentInfo;
register(SequenceIds.SeqStreamTest, SeqStreamTest.Builder()); register(SequenceIds.SeqStreamTest, SeqStreamTest.Builder());
// Event Q Test 추가 // Event Q Test 추가
var seqEventQTest = new SeqEventQTest(); var seqEventQTest = new SeqEventQTest();
register(SequenceIds.SeqEventQTest, seqEventQTest.Builder()); register(SequenceIds.SeqEventQTest, seqEventQTest.Builder());
var seqAutoRunPrismaticScanner = new SeqAutoRunPrismaticScanner(); var seqAutoRunPrismaticScanner = new SeqAutoRunPrismaticScanner();
var seqAutoRunComponentWeld = new SeqAutoRunComponentWeld(); var seqAutoRunComponentWeld = new SeqAutoRunComponentWeld();
var seqAlarmMonitor = new SeqAlarmMonitor(); var seqAlarmMonitor = new SeqAlarmMonitor();
_SeqAutoRunPrismaticScanner_Builder = seqAutoRunPrismaticScanner.Builder(); _SeqAutoRunPrismaticScanner_Builder = seqAutoRunPrismaticScanner.Builder();
_SeqAutoRunComponentWeld_Builder = seqAutoRunComponentWeld.Builder(); _SeqAutoRunComponentWeld_Builder = seqAutoRunComponentWeld.Builder();
_SeqAlarmMonitor_Builder = seqAlarmMonitor.Builder(); _SeqAlarmMonitor_Builder = seqAlarmMonitor.Builder();
register(SequenceIds.SeqAutoRun, _SeqAutoRunPrismaticScanner_Builder); register(SequenceIds.SeqAutoRun, _SeqAutoRunPrismaticScanner_Builder);
register(SequenceIds.SeqAutoRunComponentWeld, _SeqAutoRunComponentWeld_Builder); register(SequenceIds.SeqAutoRunComponentWeld, _SeqAutoRunComponentWeld_Builder);
register(SequenceIds.SeqAlarmMonitor, _SeqAlarmMonitor_Builder); register(SequenceIds.SeqAlarmMonitor, _SeqAlarmMonitor_Builder);
register(SequenceIds.SeqStatusMonitor, SeqStatusMonitor.Builder()); register(SequenceIds.SeqStatusMonitor, SeqStatusMonitor.Builder());
// SequenceManager.RegisterSequence(AuroraEnv, register); // 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) { public override void RegisterSettings(SettingRegister register) {
@ -384,13 +389,13 @@ namespace ILSOS_PrismaticScanner
manager.RegisterMainMenu(calibration); manager.RegisterMainMenu(calibration);
// Monitoring // Monitoring
#region 26 04 06 주석처리 PLCBitMonitoring PLCWordMonitoring IOInterfaceView (o) PLCInterfaceView (원래부터 주석처리) #region 26 04 06 주석처리 PLCBitMonitoring PLCWordMonitoring IOInterfaceView (o) PLCInterfaceView (원래부터 주석처리) - 04.10 잠시 추가
//var monitoring = new MainMenu(CommonViewIds.Monitoring, MenuIcon.GetMenuIcon(eMenuIcon.MONITORING), PermissionLevel.NONE, true); var monitoring = new MainMenu(CommonViewIds.Monitoring, MenuIcon.GetMenuIcon(eMenuIcon.MONITORING), PermissionLevel.NONE, true);
////manual.AddSubMenu(CommonViewIds.PLCInterfaceView, new UCPLCInterfaceView(AuroraEnv, CommonDeviceIds.PLCDevice)); //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.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.PLCWordMonitoring, new UCPLCWord() { DataContext = PLCStatic.GetInstanceWordVM(AuroraEnv, CommonDeviceIds.PLCDevice, ePLCNetType.MXComponent, PLCBase.ePLCDevice.D) });
//monitoring.AddSubMenu(CommonViewIds.IOInterfaceView, new UCIOInterfaceView(AuroraEnv, CommonDeviceIds.IODevice)); monitoring.AddSubMenu(CommonViewIds.IOInterfaceView, new UCIOInterfaceView(AuroraEnv, CommonDeviceIds.IODevice));
//manager.RegisterMainMenu(monitoring); manager.RegisterMainMenu(monitoring);
#endregion #endregion
// Device // Device

View File

@ -110,6 +110,7 @@
<Compile Include="Sequences\Monitoring\SeqStatusMonitor.cs" /> <Compile Include="Sequences\Monitoring\SeqStatusMonitor.cs" />
<Compile Include="Sequences\SeqAutoRunComponentWeld.cs" /> <Compile Include="Sequences\SeqAutoRunComponentWeld.cs" />
<Compile Include="Sequences\SeqAutoRunPrismaticScanner.cs" /> <Compile Include="Sequences\SeqAutoRunPrismaticScanner.cs" />
<Compile Include="Sequences\SeqAutoRunPrismaticScannerACS.cs" />
<Compile Include="Sequences\SeqEventQTest.cs" /> <Compile Include="Sequences\SeqEventQTest.cs" />
<Compile Include="Sequences\SeqStreamTest.cs" /> <Compile Include="Sequences\SeqStreamTest.cs" />
<Compile Include="Sequences\SequenceContextEx.cs" /> <Compile Include="Sequences\SequenceContextEx.cs" />

File diff suppressed because it is too large Load Diff

View File

@ -38,6 +38,12 @@ namespace ILSOS_PrismaticScanner.Sequences
SeqTeachingRun _SeqTeachingRun; SeqTeachingRun _SeqTeachingRun;
SeqComponentTeachingRun _SeqComponentTeachingRun; SeqComponentTeachingRun _SeqComponentTeachingRun;
#region 26.04.09 NDY - ACS MOTF AutoRun Sequence
// 기존 SeqAutoRunPrismaticScanner와 별개로 추가
// ACS 모션(Stage X 등) + 갈바노 스캐너 MOTF 자동운전 전용
SeqAutoRunPrismaticScannerACS _SeqAutoRunPrismaticScannerACS;
#endregion
private static Dictionary<string, SequenceBuilder> mapSequenceBuilders = new Dictionary<string, SequenceBuilder>(); private static Dictionary<string, SequenceBuilder> mapSequenceBuilders = new Dictionary<string, SequenceBuilder>();
static SequenceManager() static SequenceManager()
@ -56,12 +62,17 @@ namespace ILSOS_PrismaticScanner.Sequences
private void ChangeRunState(ISequenceEntry sequenceEntry, string sequenceId, bool isRunning, object param = null) private void ChangeRunState(ISequenceEntry sequenceEntry, string sequenceId, bool isRunning, object param = null)
{ {
if (isRunning) { if (isRunning)
if (sequenceEntry[sequenceId].RunState != SequenceRunState.Running) { {
if (sequenceEntry[sequenceId].RunState != SequenceRunState.Running)
{
sequenceEntry[sequenceId].Start(param); sequenceEntry[sequenceId].Start(param);
} }
} else { }
if (sequenceEntry[sequenceId].RunState == SequenceRunState.Running) { else
{
if (sequenceEntry[sequenceId].RunState == SequenceRunState.Running)
{
sequenceEntry[sequenceId].Stop(); sequenceEntry[sequenceId].Stop();
} }
} }
@ -84,66 +95,87 @@ namespace ILSOS_PrismaticScanner.Sequences
//register(CommonSequenceIds.DeviceMonitor, DeviceMonitoring.Builder()); //register(CommonSequenceIds.DeviceMonitor, DeviceMonitoring.Builder());
base.Register(aurora, register); base.Register(aurora, register);
if (_SeqMarkGrid == null) { if (_SeqMarkGrid == null)
{
_SeqMarkGrid = new SeqMarkGrid(); _SeqMarkGrid = new SeqMarkGrid();
} }
register(CommonSequenceIds.SeqMarkGrid, _SeqMarkGrid.Builder()); register(CommonSequenceIds.SeqMarkGrid, _SeqMarkGrid.Builder());
if (_SeqScannerCalibration == null) { if (_SeqScannerCalibration == null)
{
_SeqScannerCalibration = new SeqScannerCalibration(); _SeqScannerCalibration = new SeqScannerCalibration();
} }
register(CommonSequenceIds.SeqScannerCalibration, _SeqScannerCalibration.Builder()); register(CommonSequenceIds.SeqScannerCalibration, _SeqScannerCalibration.Builder());
if (_SeqReceiveCellInfo == null) { if (_SeqReceiveCellInfo == null)
{
_SeqReceiveCellInfo = new SeqReceiveCellInfo(); _SeqReceiveCellInfo = new SeqReceiveCellInfo();
} }
register(CommonSequenceIds.SeqReceiveCellInfo, _SeqReceiveCellInfo.Builder()); register(CommonSequenceIds.SeqReceiveCellInfo, _SeqReceiveCellInfo.Builder());
if (_SeqReportCellResult == null) { if (_SeqReportCellResult == null)
{
_SeqReportCellResult = new SeqReportCellResult(); _SeqReportCellResult = new SeqReportCellResult();
} }
register(CommonSequenceIds.SeqReportCellResult, _SeqReportCellResult.Builder()); register(CommonSequenceIds.SeqReportCellResult, _SeqReportCellResult.Builder());
if (_SeqScoutFlyingVision == null) { if (_SeqScoutFlyingVision == null)
{
_SeqScoutFlyingVision = new SeqScoutFlyingVision(); _SeqScoutFlyingVision = new SeqScoutFlyingVision();
} }
register(CommonSequenceIds.SeqScoutFlyingVision, _SeqScoutFlyingVision.Builder()); register(CommonSequenceIds.SeqScoutFlyingVision, _SeqScoutFlyingVision.Builder());
if (_SeqScoutNormal == null) { if (_SeqScoutNormal == null)
{
_SeqScoutNormal = new SeqScoutNormal(); _SeqScoutNormal = new SeqScoutNormal();
} }
register(CommonSequenceIds.SeqScoutNormal, _SeqScoutNormal.Builder()); register(CommonSequenceIds.SeqScoutNormal, _SeqScoutNormal.Builder());
if (_SeqExecutePlcBitInterface == null)
if (_SeqExecutePlcBitInterface == null) { {
_SeqExecutePlcBitInterface = new SeqExecutePlcBitInterface(); _SeqExecutePlcBitInterface = new SeqExecutePlcBitInterface();
} }
register(CommonSequenceIds.SeqExecutePlcBitInterface, _SeqExecutePlcBitInterface.Builder()); register(CommonSequenceIds.SeqExecutePlcBitInterface, _SeqExecutePlcBitInterface.Builder());
if (_SeqExecutePlcBitInterfaceForStatusChange == null) { if (_SeqExecutePlcBitInterfaceForStatusChange == null)
{
_SeqExecutePlcBitInterfaceForStatusChange = new SeqExecutePlcBitInterface(); _SeqExecutePlcBitInterfaceForStatusChange = new SeqExecutePlcBitInterface();
} }
register(CommonSequenceIds.SeqExecutePlcBitInterfaceForStatusChange, _SeqExecutePlcBitInterfaceForStatusChange.Builder()); register(CommonSequenceIds.SeqExecutePlcBitInterfaceForStatusChange, _SeqExecutePlcBitInterfaceForStatusChange.Builder());
if (_SeqCheckWeldingComplete == null) { if (_SeqCheckWeldingComplete == null)
{
_SeqCheckWeldingComplete = new SeqCheckWeldingComplete(); _SeqCheckWeldingComplete = new SeqCheckWeldingComplete();
} }
register(CommonSequenceIds.SeqCheckWeldingComplete, _SeqCheckWeldingComplete.Builder()); register(CommonSequenceIds.SeqCheckWeldingComplete, _SeqCheckWeldingComplete.Builder());
if (_SeqTeachingRun == null) { if (_SeqTeachingRun == null)
{
_SeqTeachingRun = new SeqTeachingRun(); _SeqTeachingRun = new SeqTeachingRun();
} }
register(CommonSequenceIds.SeqTeachingRun, _SeqTeachingRun.Builder()); register(CommonSequenceIds.SeqTeachingRun, _SeqTeachingRun.Builder());
if (_SeqComponentTeachingRun == null) { if (_SeqComponentTeachingRun == null)
{
_SeqComponentTeachingRun = new SeqComponentTeachingRun(); _SeqComponentTeachingRun = new SeqComponentTeachingRun();
} }
register(CommonSequenceIds.SeqComponentTeachingRun, _SeqComponentTeachingRun.Builder()); register(CommonSequenceIds.SeqComponentTeachingRun, _SeqComponentTeachingRun.Builder());
if (_SeqWaitWeldOrChangePage == null) { if (_SeqWaitWeldOrChangePage == null)
{
_SeqWaitWeldOrChangePage = new SeqWaitWeldOrChangePage(); _SeqWaitWeldOrChangePage = new SeqWaitWeldOrChangePage();
} }
register(CommonSequenceIds.SeqWaitWeldOrChangePage, _SeqWaitWeldOrChangePage.Builder()); 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 // MainProcess Sequence
@ -161,9 +193,13 @@ namespace ILSOS_PrismaticScanner.Sequences
public static SequenceBuilder GetSeqBuilder_SeqTeachingRun() { return Instance._SeqTeachingRun.Builder(); } public static SequenceBuilder GetSeqBuilder_SeqTeachingRun() { return Instance._SeqTeachingRun.Builder(); }
public static SequenceBuilder GetSeqBuilder_SeqComponentTeachingRun() { return Instance._SeqComponentTeachingRun.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() public virtual void Terminate()
{ {
} }
} }
} }

View File

@ -53,13 +53,35 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="Manual Control" Margin="0 0 0 0"> <GroupBox Grid.Row="0" Header="Manual Control" Margin="0 0 0 0">
<Grid Height="50" Margin="0 0 0 0"> <Grid Height="100" Margin="0 0 0 0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"></ColumnDefinition> <ColumnDefinition Width="2*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition> <ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Button Grid.Column="0" Margin="3 0 3 2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="auto" FontWeight="Bold" Style="{StaticResource StButtonAccentPurple}" Command="{Binding CMD_RunSequenceAutoRun}" Content="Run AutoRun" IsEnabled="{Binding IsSequenceRunning_AutoRun, Converter={StaticResource convReverse}}"/> <Button Grid.Row="0" Grid.Column="0" Margin="3 0 3 2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="auto" FontWeight="Bold" Style="{StaticResource StButtonAccentPurple}" Command="{Binding CMD_RunSequenceAutoRun}" Content="Run AutoRun" IsEnabled="{Binding IsSequenceRunning_AutoRun, Converter={StaticResource convReverse}}"/>
<Button Grid.Column="1" Margin="3 0 3 2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="auto" FontWeight="Bold" Style="{StaticResource StButtonAccentPurple}" Command="{Binding CMD_StopSequenceAutoRun}" Content="Stop" IsEnabled="{Binding IsSequenceRunning_AutoRun}"/> <Button Grid.Row="0" Grid.Column="1" Margin="3 0 3 2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="auto" FontWeight="Bold" Style="{StaticResource StButtonAccentPurple}" Command="{Binding CMD_StopSequenceAutoRun}" Content="Stop" IsEnabled="{Binding IsSequenceRunning_AutoRun}"/>
<GridSplitter HorizontalAlignment="Stretch" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Height="2" ToolTip="구분선 -> 상단 기존 동작 / 하단 추가된 시퀸스 테스트 버튼"></GridSplitter>
<!-- 26.04.10 NDY AUTORUN ACS 시퀸스 테스트 버튼 -->
<Button Grid.Row="2" Grid.Column="0" Margin="3 0 3 2"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Height="auto" FontWeight="Bold"
Style="{StaticResource StButtonAccentPurple}"
Command="{Binding CMD_StartMOTF}"
Content="Run MOTF"
IsEnabled="{Binding IsSequenceRunning_AutoRun, Converter={StaticResource convReverse}}"/>
<Button Grid.Row="2" Grid.Column="1" Margin="3 0 3 2"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Height="auto" FontWeight="Bold"
Style="{StaticResource StButtonAccentPurple}"
Command="{Binding CMD_StopMOTF}"
Content="Stop"
IsEnabled="{Binding IsSequenceRunning_AutoRun}"/>
</Grid> </Grid>
</GroupBox> </GroupBox>
<GroupBox Grid.Row="1" Header="Equipment Status" Margin="0 5 0 0"> <GroupBox Grid.Row="1" Header="Equipment Status" Margin="0 5 0 0">

View File

@ -43,6 +43,11 @@ namespace ILSOS_PrismaticScanner.Views
public ICommand CMD_StopSequenceAutoRun { get; } public ICommand CMD_StopSequenceAutoRun { get; }
public ICommand CMD_Reset { get; } // Totoal Product, Today Product 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 IRecipeManager _RecipeManager;
private readonly ISettingEntry _SettingEntry; private readonly ISettingEntry _SettingEntry;
private readonly IAlarmService _AlarmService; private readonly IAlarmService _AlarmService;
@ -56,6 +61,12 @@ namespace ILSOS_PrismaticScanner.Views
private ISequence _SeqPlcInterface; private ISequence _SeqPlcInterface;
private ISequence _SeqQTest; 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); } public bool IsSequenceRunning_AutoRun { get => _IsSequenceRunning_AutoRun; set => SetProperty(ref _IsSequenceRunning_AutoRun, value); }
private bool _IsSequenceRunning_AutoRun; private bool _IsSequenceRunning_AutoRun;
@ -87,6 +98,12 @@ namespace ILSOS_PrismaticScanner.Views
// Q Test // Q Test
_SeqQTest = _Aurora.SequenceEntry[SequenceIds.SeqEventQTest]; _SeqQTest = _Aurora.SequenceEntry[SequenceIds.SeqEventQTest];
#region 26.04.10 NDY - MOTF AutoRun Sequence 참조
// 기존 SeqAutoRun과 별개로 MOTF 전용 시퀀스 ID로 참조
_SeqMOTF = _Aurora.SequenceEntry[SequenceIds.SeqAutoRunACS];
#endregion
InitVariable(); InitVariable();
CMD_VisibleChanged = new DelegateCommand(VisibleChanged); CMD_VisibleChanged = new DelegateCommand(VisibleChanged);
@ -95,6 +112,12 @@ namespace ILSOS_PrismaticScanner.Views
CMD_StopSequenceAutoRun = new DelegateCommand(OnStopSequenceAutoRun); CMD_StopSequenceAutoRun = new DelegateCommand(OnStopSequenceAutoRun);
CMD_Reset = new DelegateCommand(PerformCmdReset); 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 = new DispatcherTimer();
_MonitorTimer.Tick += OnMonitoring; _MonitorTimer.Tick += OnMonitoring;
_MonitorTimer.Interval = TimeSpan.FromMilliseconds(100); _MonitorTimer.Interval = TimeSpan.FromMilliseconds(100);
@ -150,6 +173,7 @@ namespace ILSOS_PrismaticScanner.Views
// Page UI 업데이트 // Page UI 업데이트
RecipeHelper.UpdateAutorunRecipe(); RecipeHelper.UpdateAutorunRecipe();
} }
private void VisibleChanged(object obj) private void VisibleChanged(object obj)
{ {
bool visible = (bool)obj; bool visible = (bool)obj;
@ -271,6 +295,54 @@ namespace ILSOS_PrismaticScanner.Views
_Scanner.SelectCorrectionTable(1); _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) private void PerformCmdReset(object commandParameter)
{ {
// SystemParameter를 저장하기 전에는 반드시 업데이트를 해주어야 한다. // SystemParameter를 저장하기 전에는 반드시 업데이트를 해주어야 한다.
@ -317,4 +389,4 @@ namespace ILSOS_PrismaticScanner.Views
keyboardInput, keyboardInput,
ClickInput ClickInput
} }
} }

View File

@ -1,3 +1,4 @@
using CoreUnit.Common;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
@ -31,9 +32,12 @@ namespace CoreUnit.Control.UserControl.Canvas
} }
#region 26.04.09 #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; public double LargeMOTFOffsetX { get; set; } = 0.0;
[XmlSerializable]
public double LargeMOTFOffsetY { get; set; } = 0.0; public double LargeMOTFOffsetY { get; set; } = 0.0;
#endregion #endregion
public DrawingLayer(int no, string name, Color color, float width) public DrawingLayer(int no, string name, Color color, float width)

View File

@ -23,8 +23,10 @@ namespace DB.Recipe
{ {
#region 26.04.09 NDY #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; public bool UseLargeMOTF { get; set; } = false;
[XmlSerializable]
public int LargeMOTFPageCount { get; set; } = 1; public int LargeMOTFPageCount { get; set; } = 1;
#endregion #endregion
@ -90,8 +92,11 @@ namespace DB.Recipe
{ {
try try
{ {
LoadWeldingParam(xmlElement); #region 26.04.10 NDY [대면적 MOTF] 레시피 레벨 파라미터 로드
Model.Load(xmlElement); CoreUnit.Common.XmlUtil.ParseProperties(xmlElement, this);
#endregion
LoadWeldingParam(xmlElement); // WeldingParameters만 로드
Model.Load(xmlElement); // DrawingObjects만 로드
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -13,6 +13,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -34,6 +35,135 @@ namespace GUI
InitializeComponent(); InitializeComponent();
} }
#region 26.04.10 NDY에 추가함.
// MainPage.cs에 추가
public void UpdateLargeMOTFPosition()
{
foreach (ICanvasLayer layer in ScannerCanvasCtrlTeaching.Model.Layers)
{
var lines = layer.Objects
.OfType<Line>()
.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<Line>().OrderBy(l => l.No).ToList();
var p2Lines = layers[1].Objects.OfType<Line>().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.메인화면: 다이얼로그 및 페이지 초기화 함수 #region 2.메인화면: 다이얼로그 및 페이지 초기화 함수
private void ClearDisplayedError() private void ClearDisplayedError()
@ -457,6 +587,7 @@ namespace GUI
UtilUnit.logUnit.SetLog(LogUnit.eLogType.ALARM, MethodBase.GetCurrentMethod().Name, ex.Message); UtilUnit.logUnit.SetLog(LogUnit.eLogType.ALARM, MethodBase.GetCurrentMethod().Name, ex.Message);
} }
} }
public event Action OnRecipeReloaded; // 26.04.10 추가 MainPage에 콜백추가를 위한 액션
public void ReloadRecipe(string fullpath) public void ReloadRecipe(string fullpath)
{ {
ScannerCanvasCtrlTeaching.Canvas.CommandEscape(); ScannerCanvasCtrlTeaching.Canvas.CommandEscape();
@ -467,12 +598,17 @@ namespace GUI
UpdateWeldingLinesRecipe(); UpdateWeldingLinesRecipe();
// 26.04.10 NDY [대면적 MOTF]
OnRecipeReloaded?.Invoke();
//// Page 불러온 후, 실행되지 않아도 됨. 왜냐하면, 어차피 SelectedIndex 쪽으로 빠짐. //// Page 불러온 후, 실행되지 않아도 됨. 왜냐하면, 어차피 SelectedIndex 쪽으로 빠짐.
//SaveAndLoadFieldScanFromLasaDb(); //SaveAndLoadFieldScanFromLasaDb();
//ChangeScoutImage(); //ChangeScoutImage();
} }
#endregion #endregion
// 26.04.10 확인용 --- MainPage의 레시피 정보를 Get 하는 메소드
public RecipeWeldingLines getRecipeInfo(string fullpath) public RecipeWeldingLines getRecipeInfo(string fullpath)
{ {
var recipe = new RecipeWeldingLines(); var recipe = new RecipeWeldingLines();

View File

@ -294,6 +294,18 @@ namespace WeldingCommon.Views
} }
// 26.04.09 NDY [대면적 MOTF] End Set 커맨드 구현 // 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) private void PerformCmdLargeMOTFEndSet(object obj)
{ {
var result = MessageUtil.ShowYesNoMessageDialog("Large MOTF End Set", 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($" - 계산된 Offset Y (Scanner): {LargeMOTFOffsetY:F3} mm");
Log.I($" - 저장된 Layer: Page {mainPage.ScannerCanvasCtrlTeaching.Model.ActiveLayer.No + 1}"); Log.I($" - 저장된 Layer: Page {mainPage.ScannerCanvasCtrlTeaching.Model.ActiveLayer.No + 1}");
Log.I($" - TODO: 모션→스캐너 좌표 변환 방향/스케일 이정인 선임 확인 필요"); Log.I($" - TODO: 모션→스캐너 좌표 변환 방향/스케일 이정인 선임 확인 필요");
// 26.04.10 NDY [대면적 MOTF] 플립 페이지 자동 생성
CommonHelper.GetMainPage().GenerateFlippedPages(LargeMOTFOffsetX);
} }
private void GetPerformCmdStageXJogSpeed(object obj) private void GetPerformCmdStageXJogSpeed(object obj)
{ {

View File

@ -67,9 +67,22 @@ namespace WeldingCommon.Views
_Aurora.EventObservable.AsObservable(CommonEventIds.EVENT_EQUIPMENT_STATUS_CHANGE) _Aurora.EventObservable.AsObservable(CommonEventIds.EVENT_EQUIPMENT_STATUS_CHANGE)
.Subscribe(x => OnChangedEquipmentStatus()); .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(); RecipeHelper.OnActiveReloaded();
CommonHelper.UpdateAutoRunPage(); CommonHelper.UpdateAutoRunPage();
} }
private object OnChangedEquipmentStatus() private object OnChangedEquipmentStatus()
@ -165,12 +178,26 @@ namespace WeldingCommon.Views
RecipeHelper.SelectedRecipe = recipe; RecipeHelper.SelectedRecipe = recipe;
RecipeHelper.ReloadRecipe(recipe.name); RecipeHelper.ReloadRecipe(recipe.name);
_RecipeBeforeChanged = RecipeHelper.getRecipeInfo(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) public void Reloaded((int id, string name) recipe)
{ {
RecipeHelper.SelectedRecipe = recipe; RecipeHelper.SelectedRecipe = recipe;
RecipeHelper.ReloadRecipe(recipe.name); 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) public void OnModelSelectChanged(string modelName)
@ -198,13 +225,35 @@ namespace WeldingCommon.Views
return false; 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<IDrawObject>)
// → Line.LargeMOTFPositionX/Y SET
#endregion
public void OnSaved(int id, string recipeName) public void OnSaved(int id, string recipeName)
{ {
if (string.IsNullOrWhiteSpace(RecipeHelper.SelectedRecipe.name)) { if (string.IsNullOrWhiteSpace(RecipeHelper.SelectedRecipe.name)) {
return; 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); RecipeHelper.SaveRecipe(RecipeHelper.SelectedRecipe.name);
var ChangedRecipe = RecipeHelper.getRecipeInfo(RecipeHelper.SelectedRecipe.name); var ChangedRecipe = RecipeHelper.getRecipeInfo(RecipeHelper.SelectedRecipe.name);
var diffs = new ObservableCollection<PropertyDiff>(); var diffs = new ObservableCollection<PropertyDiff>();
@ -236,5 +285,36 @@ namespace WeldingCommon.Views
} }
Log.I($"Saved Recipe : [{RecipeHelper.SelectedRecipe.name}]"); 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<Line>()
.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
} }
} }