Compare commits

..

8 Commits

Author SHA1 Message Date
a01f9d4cd7 RCC 패턴 변경 알림(B2506/B2409) 처리 보완
B2506 : PC → PLC  RCC 패턴 변경 알림 (PC_RCC_PATTERN_CHANGED)
B2409 : PLC → PC  알림 수신 ACK     (PLC_RCC_PATTERN_CHANGED_ACK)

- 알림 완료 기록을 B2506 ON 시점이 아닌 ACK(B2409) 수신 시점으로 변경
  (ACK 없이 오토런이 끝나면 기록하지 않고 다음 오토런에서 다시 알림)
- ACK 는 B2409 OFF→ON 순간만 인정
- 알림 호출을 PC_AUTO_STATUS ON 이후로 이동(InitializeDeviceStatus)
- 오토런 정지·종료 시 B2506 직접 OFF
- B2506/B2409 상태를 직접 읽어 로그 기록, LBAD Trigger/Stop 쓰기 반환값 로그 추가
2026-10-28 10:39:46 +09:00
c517993e1a RCC 패턴 변경 알림(B2506/B2409), 서브 패턴 10개 확장 및 현장 소스 동기화
- 오토런 시작 시 RCC 패턴 폴더 변경 감지 → B2506 ON, PLC ACK(B2409) 수신 시 OFF
- 서브 패턴 1~10, 패턴 목록에 서브 썸네일 표시(행 내 좌우 스크롤)
- 서브 패턴 등록 직후 활성화 OFF
- 패턴 파일이 다른 프로그램에서 열려 있으면 삭제하지 않고 안내
- 현장 소스 반영: Train ROI 슬라이더, KS 조명 밝기 저장/적용, 조명 ID 분리,
  스캔 후 카메라 라이브 재시작, 레시피 21 RCC NG 우회, 오토런 상태 판단
2026-10-15 14:36:29 +09:00
d11907dc3c 이전 셀의 LBAD 판정값이 남아있으면 이번 셀에서 판정을 못했을 때
그대로 재전송되므로 초기화하는 코드 추가
LBAD_Judge = string.Empty;
2026-09-09 18:04:14 +09:00
KS_Welder
2dafd9096b 1.0.0.3 프로그램 업데이트
- RCC NG 프로세스 추가
2026-07-07 17:49:48 +09:00
KS_Welder
24e1d09d8e RCC NG 배출 현장 프로그램 수정 2026-07-07 17:45:02 +09:00
KS_Welder
702a80fba2 RCC 옵션 선택 기능 Disable 2026-07-07 16:58:29 +09:00
KS_Welder
44d79db962 현장 코드에 있는 프로젝트 수정 2026-07-07 16:58:29 +09:00
e9dea7d845 RCC NG 기능 추가 2026-07-07 16:57:20 +09:00
31 changed files with 962 additions and 118 deletions

View File

@ -1,9 +1,9 @@
using Aurora.Api;
using Aurora.Api;
using Aurora.Api.DataType;
using Aurora.Api.Devices;
using PropertyTools.DataAnnotations;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO.Ports;
using System.Linq;
using System.Text;
@ -14,6 +14,50 @@ namespace AuroraLight
public class LightConfig : IDeviceConfig
{
public IConnConfig ConnConfig { get; set; }
[Category("Light Level")]
[DisplayName("Channel 1 Level (0~255)")]
[Description("Channel 1 Brightness")]
public int Ch1Level { get; set; } = 200;
[Category("Light Level")]
[DisplayName("Channel 2 Level (0~255)")]
[Description("Channel 2 Brightness")]
public int Ch2Level { get; set; } = 200;
[Category("Light Level")]
[DisplayName("Channel 3 Level (0~255)")]
[Description("Channel 3 Brightness")]
public int Ch3Level { get; set; } = 200;
[Category("Light Level")]
[DisplayName("Channel 4 Level (0~255)")]
[Description("Channel 4 Brightness")]
public int Ch4Level { get; set; } = 200;
public int GetChannelLevel(int channel)
{
switch (channel)
{
case 0: return Ch1Level;
case 1: return Ch2Level;
case 2: return Ch3Level;
case 3: return Ch4Level;
default: return Ch1Level;
}
}
public void SetChannelLevel(int channel, int value)
{
switch (channel)
{
case 0: Ch1Level = value; break;
case 1: Ch2Level = value; break;
case 2: Ch3Level = value; break;
case 3: Ch4Level = value; break;
}
}
public LightConfig()
{
ConnConfig = new SerialConnConfig() {
@ -24,6 +68,10 @@ namespace AuroraLight
DataBits = 8,
ReadTimeout = 500
};
Ch1Level = 200;
Ch2Level = 200;
Ch3Level = 200;
Ch4Level = 200;
}
public static LightConfig GetDefaultConfig()
@ -37,6 +85,10 @@ namespace AuroraLight
DataBits = 8,
ReadTimeout = 500
};
config.Ch1Level = 200;
config.Ch2Level = 200;
config.Ch3Level = 200;
config.Ch4Level = 200;
return config;
}

View File

@ -1,4 +1,4 @@
using Aurora.Api;
using Aurora.Api;
using Aurora.Api.DataType;
using Aurora.Api.Devices;
using AuroraLight.ALT;
@ -25,6 +25,7 @@ namespace AuroraLight
Logger Log;
internal eLight _lightType;
internal ILight _light;
public LightConfig Config { get; private set; }
public LightDevice(string deviceId, eLight light)
{
@ -38,6 +39,7 @@ namespace AuroraLight
protected override int DoActivate(IDeviceConfig config)
{
var l_config = config as LightConfig;
Config = l_config;
if (IsSimul) {
return E1.OK;
@ -57,7 +59,15 @@ namespace AuroraLight
break;
}
return _light.InitInternal(config.ConnConfig as SerialConnConfig) ? E1.OK : E2.NOT_CONNECTED;
bool connected = _light.InitInternal(config.ConnConfig as SerialConnConfig);
if (connected && l_config != null) {
int chCount = _lightType == eLight.ALT_8CH ? 8 : 4;
for (int i = 0; i < chCount; i++) {
_light.SetLightValueInternal(i, l_config.GetChannelLevel(i));
}
}
return connected ? E1.OK : E2.NOT_CONNECTED;
}
}

View File

@ -1,4 +1,4 @@
<UserControl x:Class="AuroraLight.View.KsLight"
<UserControl x:Class="AuroraLight.View.KsLight"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -19,8 +19,9 @@
<StackPanel Orientation="Vertical" Margin="0,20,0,20" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel>
<Button Margin="0,20,0,20" Width="100" Height="40" Command="{Binding CMD_Reload}" Content="ReLoad"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Margin="10,20,10,20" Width="100" Height="40" Command="{Binding CMD_Reload}" Content="ReLoad"/>
<Button Margin="10,20,10,20" Width="100" Height="40" Command="{Binding CMD_Save}" Content="Save"/>
<!-- <Button Width="100" Height="40" Margin="10" Command="{Binding CMD_GetStat}" Content="GetStat"/> -->
</StackPanel>

View File

@ -1,4 +1,4 @@
using Aurora;
using Aurora;
using Aurora.Api;
using Aurora.View;
using Hermes;
@ -140,6 +140,12 @@ namespace AuroraLight.View
{
if (_LightDevice1 != null && _LightDevice1._light != null)
_LightDevice1._light.SetLightValueInternal(cbChannel1, value);
var config = _aurora?.DeviceManager?.GetDeviceConfig(_deviceId1) as LightConfig;
if (config != null)
{
config.SetChannelLevel(cbChannel1, value);
}
}
}
catch (Exception ex)
@ -161,6 +167,12 @@ namespace AuroraLight.View
{
if (_LightDevice2 != null && _LightDevice2._light != null)
_LightDevice2._light.SetLightValueInternal(cbChannel2, value);
var config = _aurora?.DeviceManager?.GetDeviceConfig(_deviceId2) as LightConfig;
if (config != null)
{
config.SetChannelLevel(cbChannel2, value);
}
}
}
catch (Exception ex)
@ -191,10 +203,48 @@ namespace AuroraLight.View
public List<int> lsChannel2 { get => _lsChannel2; set => SetProperty(ref _lsChannel2, value); }
private int _cbChannel1;
public int cbChannel1 { get => _cbChannel1; set => SetProperty(ref _cbChannel1, value); }
public int cbChannel1
{
get => _cbChannel1;
set
{
if (SetProperty(ref _cbChannel1, value))
{
var config = _aurora?.DeviceManager?.GetDeviceConfig(_deviceId1) as LightConfig;
if (config != null)
{
int chLevel = config.GetChannelLevel(value);
if (_nSliderValue1 != chLevel)
{
_nSliderValue1 = chLevel;
NotifyPropertyChanged(nameof(nSliderValue1));
}
}
}
}
}
private int _cbChannel2;
public int cbChannel2 { get => _cbChannel2; set => SetProperty(ref _cbChannel2, value); }
public int cbChannel2
{
get => _cbChannel2;
set
{
if (SetProperty(ref _cbChannel2, value))
{
var config = _aurora?.DeviceManager?.GetDeviceConfig(_deviceId2) as LightConfig;
if (config != null)
{
int chLevel = config.GetChannelLevel(value);
if (_nSliderValue2 != chLevel)
{
_nSliderValue2 = chLevel;
NotifyPropertyChanged(nameof(nSliderValue2));
}
}
}
}
}
@ -221,6 +271,17 @@ namespace AuroraLight.View
_LightDevice2._light.SetLightValueInternal(cbChannelALL, value);
}
var config1 = _aurora?.DeviceManager?.GetDeviceConfig(_deviceId1) as LightConfig;
if (config1 != null)
{
config1.SetChannelLevel(cbChannelALL, value);
}
var config2 = _aurora?.DeviceManager?.GetDeviceConfig(_deviceId2) as LightConfig;
if (config2 != null)
{
config2.SetChannelLevel(cbChannelALL, value);
}
nSliderValue1 = value;
nSliderValue2 = value;
}
@ -262,9 +323,13 @@ namespace AuroraLight.View
public ICommand CMD_LightOnOffALL { get; }
public ICommand CMD_Reload { get; }
public ICommand CMD_Save { get; }
public ICommand CMD_GetStat { get; }
#endregion
private IAuroraEnvironment _aurora;
private string _deviceId1;
private string _deviceId2;
private LightDevice _LightDevice1;
private LightDevice _LightDevice2;
@ -275,35 +340,30 @@ namespace AuroraLight.View
public KsLightModel(IAuroraEnvironment aurora, string deviceId1, string deviceId2)
{
_aurora = aurora;
_deviceId1 = deviceId1;
_deviceId2 = deviceId2;
_LightDevice1 = aurora.DeviceManager[deviceId1] as LightDevice;
_LightDevice2 = aurora.DeviceManager[deviceId2] as LightDevice;
IsActive1 = true;
IsActive2 = true;
IsActiveALL = true;
SliderName1 = _LightDevice1.DeviceId;
SliderName2 = _LightDevice2.DeviceId;
SliderName1 = _LightDevice1 != null ? _LightDevice1.DeviceId : deviceId1;
SliderName2 = _LightDevice2 != null ? _LightDevice2.DeviceId : deviceId2;
#region Initialize Binding Properties
// config 에서 읽어와서 update 해야한다. device 등록할 때 config 만든 config 를 받아올 것. 임시로 고정값 입력
nSliderMax = 100;
nSliderValue1 = 0;
nSliderValue2 = 0;
nSliderValueALL = 0;
_nSliderValue1 = 0;
_nSliderValue2 = 0;
_nSliderValueALL = 0;
if ((_LightDevice1 != null && _LightDevice1._lightType == eLight.ERI) && (_LightDevice2 != null && _LightDevice2._lightType == eLight.ERI))
{
// 안타깝게도 채널수나 config 는 추상화항목에 없다. 추후에 업데이트하겠음. 일단은 type convert 해서 가져옴
// activate 된 후에나 class 생성이 된다. 클래스에서 property 를 가져올수없음
//var lightdevice = _LightDevice as ERILight;
//nChannel = lightdevice.GetChannelCount();
nChannel = 4;
nSliderMax = 255;
nSliderValue1 = 200;
nSliderValue2 = 200;
nSliderValueALL = 200;
}
else nChannel = 8;
@ -319,16 +379,39 @@ namespace AuroraLight.View
int nChannelMax2 = nChannel; // default setting
for (int i = 0; i < nChannelMax2; i++) lsChannel2.Add(i);
buttonOnOffToggle1 = true;
strButtonOnOff1 = "ON";
buttonOnOffToggle2 = true;
strButtonOnOff2 = "ON";
lsChannelALL = new List<int>();
int nChannelMaxALL = nChannel; // default setting
for (int i = 0; i < nChannelMaxALL; i++) lsChannelALL.Add(i);
strButtonOnOffALL = "ON";
// Read saved values from LightConfig
var config1 = _aurora?.DeviceManager?.GetDeviceConfig(_deviceId1) as LightConfig;
var config2 = _aurora?.DeviceManager?.GetDeviceConfig(_deviceId2) as LightConfig;
_nSliderValue1 = config1 != null ? config1.GetChannelLevel(cbChannel1) : 200;
_nSliderValue2 = config2 != null ? config2.GetChannelLevel(cbChannel2) : 200;
_nSliderValueALL = _nSliderValue1;
NotifyPropertyChanged(nameof(nSliderValue1));
NotifyPropertyChanged(nameof(nSliderValue2));
NotifyPropertyChanged(nameof(nSliderValueALL));
// Turn on lights with configured brightness values
if (_LightDevice1 != null && _LightDevice1._light != null)
{
_LightDevice1._light.SetLightValueInternal(cbChannel1, _nSliderValue1);
}
if (_LightDevice2 != null && _LightDevice2._light != null)
{
_LightDevice2._light.SetLightValueInternal(cbChannel2, _nSliderValue2);
}
buttonOnOffToggle1 = false;
strButtonOnOff1 = "OFF";
buttonOnOffToggle2 = false;
strButtonOnOff2 = "OFF";
buttonOnOffToggleALL = false;
strButtonOnOffALL = "OFF";
#endregion
@ -338,6 +421,7 @@ namespace AuroraLight.View
CMD_LightOnOffALL = new DelegateCommand(LightOnOffALL);
CMD_Reload = new DelegateCommand(ReLoad);
CMD_Save = new DelegateCommand(Save);
CMD_GetStat = new DelegateCommand(GetStat);
#endregion
}
@ -351,7 +435,7 @@ namespace AuroraLight.View
private void ReLoad(object obj)
{
if (_LightDevice1.IsActivated)
if (_LightDevice1 != null && _LightDevice1.IsActivated)
{
IsActive1 = true;
}
@ -360,7 +444,7 @@ namespace AuroraLight.View
IsActive1 = false;
}
if (_LightDevice2.IsActivated)
if (_LightDevice2 != null && _LightDevice2.IsActivated)
{
IsActive2 = true;
}
@ -377,6 +461,51 @@ namespace AuroraLight.View
{
IsActiveALL = false;
}
if (_aurora != null && _aurora.DeviceManager != null)
{
var config1 = _aurora.DeviceManager.GetDeviceConfig(_deviceId1) as LightConfig;
if (config1 != null)
{
_nSliderValue1 = config1.GetChannelLevel(cbChannel1);
NotifyPropertyChanged(nameof(nSliderValue1));
}
var config2 = _aurora.DeviceManager.GetDeviceConfig(_deviceId2) as LightConfig;
if (config2 != null)
{
_nSliderValue2 = config2.GetChannelLevel(cbChannel2);
NotifyPropertyChanged(nameof(nSliderValue2));
}
}
}
private void Save(object obj)
{
try
{
if (_aurora != null && _aurora.DeviceManager != null)
{
var config1 = _aurora.DeviceManager.GetDeviceConfig(_deviceId1) as LightConfig;
if (config1 != null)
{
config1.SetChannelLevel(cbChannel1, nSliderValue1);
_aurora.DeviceManager.SaveDeviceConfig(_deviceId1);
}
var config2 = _aurora.DeviceManager.GetDeviceConfig(_deviceId2) as LightConfig;
if (config2 != null)
{
config2.SetChannelLevel(cbChannel2, nSliderValue2);
_aurora.DeviceManager.SaveDeviceConfig(_deviceId2);
}
MessageUtil.ShowSuccessToast("Light configuration saved.");
}
}
catch (Exception ex)
{
MessageUtil.ShowErrorToast($"Failed to save light config: {ex.Message}");
}
}
private void LightOnOff1(object obj)

View File

@ -1,4 +1,4 @@
using Aurora;
using Aurora;
using Aurora.View;
using System;
using System.Collections.Generic;
@ -17,6 +17,12 @@ namespace AuroraLight.View
if (SetProperty(ref _nSliderValue, value)) {
if (_LightDevice != null && _LightDevice._light != null)
_LightDevice._light.SetLightValueInternal(cbChannel, value);
var config = _aurora?.DeviceManager?.GetDeviceConfig(_deviceId) as LightConfig;
if (config != null)
{
config.SetChannelLevel(cbChannel, value);
}
}
} catch (Exception ex) {
}
@ -34,32 +40,45 @@ namespace AuroraLight.View
private List<int> _lsChannel;
public List<int> lsChannel { get => _lsChannel; set => SetProperty(ref _lsChannel, value); }
private int _cbChannel;
public int cbChannel { get => _cbChannel; set => SetProperty(ref _cbChannel, value); }
public int cbChannel {
get => _cbChannel;
set {
if (SetProperty(ref _cbChannel, value)) {
var config = _aurora?.DeviceManager?.GetDeviceConfig(_deviceId) as LightConfig;
if (config != null) {
int chLevel = config.GetChannelLevel(value);
if (_nSliderValue != chLevel) {
_nSliderValue = chLevel;
NotifyPropertyChanged(nameof(nSliderValue));
}
}
}
}
}
#endregion
#region EventCommands
public ICommand CMD_LightOnOff { get; }
public ICommand CMD_Save { get; }
#endregion
private IAuroraEnvironment _aurora;
private string _deviceId;
private LightDevice _LightDevice;
public UCLightViewModel(IAuroraEnvironment aurora, string deviceId)
{
_aurora = aurora;
_deviceId = deviceId;
_LightDevice = aurora.DeviceManager[deviceId] as LightDevice;
#region Initialize Binding Properties
// config 에서 읽어와서 update 해야한다. device 등록할 때 config 만든 config 를 받아올 것. 임시로 고정값 입력
nSliderMax = 100;
nSliderValue = 0;
_nSliderValue = 0;
if (_LightDevice != null && _LightDevice._lightType == eLight.ERI) {
// 안타깝게도 채널수나 config 는 추상화항목에 없다. 추후에 업데이트하겠음. 일단은 type convert 해서 가져옴
// activate 된 후에나 class 생성이 된다. 클래스에서 property 를 가져올수없음
//var lightdevice = _LightDevice as ERILight;
//nChannel = lightdevice.GetChannelCount();
nChannel = 4;
nSliderMax = 255;
nSliderValue = 180;
} else nChannel = 8;
if (lsChannel != null)
@ -69,17 +88,42 @@ namespace AuroraLight.View
for (int i = 0; i < nChannelMax; i++)
lsChannel.Add(i);
buttonOnOffToggle = true;
strButtonOnOff = "ON";
var config = _aurora?.DeviceManager?.GetDeviceConfig(_deviceId) as LightConfig;
_nSliderValue = config != null ? config.GetChannelLevel(cbChannel) : 200;
NotifyPropertyChanged(nameof(nSliderValue));
if (_LightDevice != null && _LightDevice._light != null) {
_LightDevice._light.SetLightValueInternal(cbChannel, _nSliderValue);
}
buttonOnOffToggle = false;
strButtonOnOff = "OFF";
#endregion
#region DelegateCommands
CMD_LightOnOff = new DelegateCommand(LightOnOff);
CMD_Save = new DelegateCommand(Save);
#endregion
}
private void Save(object obj)
{
try {
if (_aurora != null && _aurora.DeviceManager != null) {
var config = _aurora.DeviceManager.GetDeviceConfig(_deviceId) as LightConfig;
if (config != null) {
config.SetChannelLevel(cbChannel, nSliderValue);
_aurora.DeviceManager.SaveDeviceConfig(_deviceId);
}
MessageUtil.ShowSuccessToast("Light configuration saved.");
}
} catch (Exception ex) {
MessageUtil.ShowErrorToast($"Failed to save light config: {ex.Message}");
}
}
private void LightOnOff(object obj)
{
if (_LightDevice != null && _LightDevice._light != null) {

View File

@ -431,6 +431,9 @@ namespace CogUI {
return DisplayDrawer.ExistsGetInteractiveGraphic(cogDisplay, CogConstants.KEY_TRAIN_ORIGIN);
}
public void UpdateRadiusOfTrainOrigin(double radius) {
}
public void AddRoi(ROI roi, eColor color, bool isRotatable = false) {
if (ROI.IsNullOrEmpty(roi)) {
DisplayDrawer.AddRoi(cogDisplay, ColorConverter.ConvertColor(color), roi, CogConstants.KEY_ROI);

View File

@ -219,5 +219,8 @@ namespace CogUI {
NotifyPropertyChanged(nameof(RunParam));
}
}
public void UpdateRadiusOfRoi(eGraRoiType graRoiType, double radius) {
}
}
}

View File

@ -409,5 +409,8 @@ namespace CogUI {
NotifyPropertyChanged(nameof(TrainParam));
}
}
public void UpdateRadiusOfRoi(eGraRoiType graRoiType, double radius) {
}
}
}

View File

@ -290,5 +290,8 @@ namespace CogUI {
NotifyPropertyChanged(nameof(RunParam));
}
}
public void UpdateRadiusOfRoi(eGraRoiType graRoiType, double radius) {
}
}
}

View File

@ -12,6 +12,7 @@ namespace MilPattern {
public class PmParamExt : ICloneable {
private const string FILE_NAME = "PmParamExt.json";
public ROI FindROI { get; set; } = new ROI();
public double TrainRoiSize { get; set; } = 25.0;
public PmParamExt() {
}
@ -22,7 +23,8 @@ namespace MilPattern {
public object Clone() {
return new PmParamExt() {
FindROI = FindROI.Clone() as ROI
FindROI = FindROI.Clone() as ROI,
TrainRoiSize = TrainRoiSize
};
}

View File

@ -1,4 +1,4 @@
using Aurora.Api;
using Aurora.Api;
using Aurora.Api.DataType;
using Hermes;
using Hermes.Functional;
@ -61,8 +61,10 @@ namespace MilUI {
#region TrainRoi & FindRoi Changed Event
private GCHandle _TrainRoiChangedDataHandle;
private GCHandle _TrainOriginChangedDataHandle;
private Dictionary<eRoiIdx, GCHandle> _FindRoiChangedDataHandles = new Dictionary<eRoiIdx, GCHandle>();
private MIL_GRA_HOOK_FUNCTION_PTR _OnTrainRoiChangedFuncPtr;
private MIL_GRA_HOOK_FUNCTION_PTR _OnTrainOriginChangedFuncPtr;
private Dictionary<eRoiIdx, MIL_GRA_HOOK_FUNCTION_PTR> _OnFindRoiChangedFuncPtrs = new Dictionary<eRoiIdx, MIL_GRA_HOOK_FUNCTION_PTR>();
#endregion
@ -644,11 +646,15 @@ namespace MilUI {
return E1.GENERIC;
}
MIL.MgraArc(GraphicContext, StaticGraphicList, imageCenter.Origin.X, imageCenter.Origin.Y, 25, 25, 0, 360);
double radius = imageCenter.Length > 0 ? imageCenter.Length : 25;
MIL.MgraArc(GraphicContext, StaticGraphicList, imageCenter.Origin.X, imageCenter.Origin.Y, radius, radius, 0, 360);
MIL.MgraInquireList(StaticGraphicList, MIL.M_LIST, MIL.M_DEFAULT, MIL.M_LAST_LABEL, ref _trainOriginLabel);
MIL.MgraControlList(StaticGraphicList, MIL.M_GRAPHIC_LABEL(_trainOriginLabel), MIL.M_DEFAULT, MIL.M_RESIZABLE, MIL.M_DISABLE);
MIL.MgraControlList(StaticGraphicList, MIL.M_GRAPHIC_LABEL(_trainOriginLabel), MIL.M_DEFAULT, MIL.M_RESIZABLE, MIL.M_ENABLE);
MIL.MgraControlList(StaticGraphicList, MIL.M_GRAPHIC_LABEL(_trainOriginLabel), MIL.M_DEFAULT, MIL.M_CONSTRAIN_ASPECT_RATIO, 1.0);
MIL.MgraControlList(StaticGraphicList, MIL.M_GRAPHIC_LABEL(_trainOriginLabel), MIL.M_DEFAULT, MIL.M_SPECIFIC_FEATURES_EDITABLE, MIL.M_DISABLE);
SetTrainOriginChangedEvent();
_trainGraphics.Add();
return E1.OK;
@ -661,10 +667,23 @@ namespace MilUI {
double x = 0;
double y = 0;
double radius = 0;
MIL.MgraInquireList(StaticGraphicList, MIL.M_GRAPHIC_LABEL(_trainOriginLabel), MIL.M_DEFAULT, MIL.M_POSITION_X, ref x);
MIL.MgraInquireList(StaticGraphicList, MIL.M_GRAPHIC_LABEL(_trainOriginLabel), MIL.M_DEFAULT, MIL.M_POSITION_Y, ref y);
MIL.MgraInquireList(StaticGraphicList, MIL.M_GRAPHIC_LABEL(_trainOriginLabel), MIL.M_DEFAULT, MIL.M_RADIUS_X, ref radius);
return new Cross(new ImagePoint(x, y));
return new Cross(new ImagePoint(x, y), radius);
}
public void UpdateRadiusOfTrainOrigin(double radius) {
if (HasTrainOrigin()) {
MIL.MgraControlList(StaticGraphicList, MIL.M_GRAPHIC_LABEL(_trainOriginLabel), MIL.M_DEFAULT, MIL.M_RADIUS_X, radius);
MIL.MgraControlList(StaticGraphicList, MIL.M_GRAPHIC_LABEL(_trainOriginLabel), MIL.M_DEFAULT, MIL.M_RADIUS_Y, radius);
}
}
public bool HasTrainOrigin() {
return _trainGraphics != null && _trainGraphics.HasGraphicLabel(_trainOriginLabel);
}
public int AddDefaultFindRoi(ImageSize imageSize, bool isRotatable = false) {
@ -860,6 +879,8 @@ namespace MilUI {
}
public int ClearTrainGraphics(eGraphicToClear graphicToClear = eGraphicToClear.ALL) {
RemoveTrainRoiChangedEvent();
RemoveTrainOriginChangedEvent();
_trainGraphics?.Clear();
_trainRoiLabel = MIL.M_NULL;
_trainOriginLabel = MIL.M_NULL;
@ -1051,6 +1072,68 @@ namespace MilUI {
}
}
private void SetTrainOriginChangedEvent() {
RemoveTrainOriginChangedEvent();
if (!_TrainOriginChangedDataHandle.IsAllocated) {
if (_trainOriginLabel != MIL.M_NULL) {
_TrainOriginChangedDataHandle = GCHandle.Alloc(_trainOriginLabel);
_OnTrainOriginChangedFuncPtr = new MIL_GRA_HOOK_FUNCTION_PTR(OnTrainOriginChanged);
MIL.MgraHookFunction(StaticGraphicList, MIL.M_INTERACTIVE_GRAPHIC_STATE_MODIFIED, _OnTrainOriginChangedFuncPtr, GCHandle.ToIntPtr(_TrainOriginChangedDataHandle));
}
}
}
private void RemoveTrainOriginChangedEvent() {
if (_TrainOriginChangedDataHandle.IsAllocated) {
MIL.MgraHookFunction(StaticGraphicList, MIL.M_INTERACTIVE_GRAPHIC_STATE_MODIFIED + MIL.M_UNHOOK, _OnTrainOriginChangedFuncPtr, GCHandle.ToIntPtr(_TrainOriginChangedDataHandle));
_OnTrainOriginChangedFuncPtr = null;
_TrainOriginChangedDataHandle.Free();
}
}
private MIL_INT OnTrainOriginChanged(MIL_INT HookType, MIL_ID EventId, IntPtr UserDataPtr) {
try {
if (_trainOriginLabel == MIL.M_NULL) {
return MIL.M_NULL;
}
if (!IntPtr.Zero.Equals(UserDataPtr)) {
MIL_INT curState = 0;
MIL.MgraGetHookInfo(EventId, MIL.M_INTERACTIVE_GRAPHIC_STATE, ref curState);
if (curState == MIL.M_STATE_GRAPHIC_HOVERED || curState == MIL.M_STATE_HANDLE_HOVERED) {
return MIL.M_NULL;
}
MIL_INT modifiedGraphicLabel = 0;
MIL.MgraGetHookInfo(EventId, MIL.M_GRAPHIC_LABEL_VALUE, ref modifiedGraphicLabel);
if (modifiedGraphicLabel == _trainOriginLabel) {
double radiusX = 0;
MIL.MgraInquireList(StaticGraphicList, MIL.M_GRAPHIC_LABEL(_trainOriginLabel), MIL.M_DEFAULT, MIL.M_RADIUS_X, ref radiusX);
if (radiusX > 500.0) {
radiusX = 500.0;
MIL.MgraControlList(StaticGraphicList, MIL.M_GRAPHIC_LABEL(_trainOriginLabel), MIL.M_DEFAULT, MIL.M_RADIUS_X, 500.0);
MIL.MgraControlList(StaticGraphicList, MIL.M_GRAPHIC_LABEL(_trainOriginLabel), MIL.M_DEFAULT, MIL.M_RADIUS_Y, 500.0);
}
if (_PatternInfos.ContainsKey(eRoiIdx.Roi0) && _GraEventSubscribers?.Count > 0) {
var patternInfo = _PatternInfos[eRoiIdx.Roi0];
if (_GraEventSubscribers.ContainsKey(patternInfo)) {
_GraEventSubscribers[patternInfo]?.UpdateRadiusOfRoi(eGraRoiType.TrainOrigin, radiusX);
}
}
}
}
}
catch (Exception e) {
_Logger?.FuncE(e.Message);
}
return MIL.M_NULL;
}
private void RemoveFindRoiChangedEvent(eRoiIdx roiIdx) {
if (_FindRoiChangedDataHandles.ContainsKey(roiIdx) && _FindRoiChangedDataHandles[roiIdx].IsAllocated) {
MIL.MgraHookFunction(StaticGraphicList, MIL.M_INTERACTIVE_GRAPHIC_STATE_MODIFIED + MIL.M_UNHOOK, _OnFindRoiChangedFuncPtrs[roiIdx], GCHandle.ToIntPtr(_FindRoiChangedDataHandles[roiIdx]));
@ -1127,6 +1210,7 @@ namespace MilUI {
private void RemoveHookFunc() {
RemoveTrainRoiChangedEvent();
RemoveTrainOriginChangedEvent();
for (int i = 0; i < _FindRoiChangedDataHandles.Count; i++) {
RemoveFindRoiChangedEvent((eRoiIdx)i);
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
@ -220,5 +220,8 @@ namespace MilUI {
NotifyPropertyChanged(nameof(RunParam));
}
}
public void UpdateRadiusOfRoi(eGraRoiType graRoiType, double radius) {
}
}
}

View File

@ -174,6 +174,7 @@ namespace MilUI {
if (result == MessageBoxResult.Yes) {
var mainPatternInfo = _patternAgent.GetMainPatternInfo(_patternInfo.GetPatternIndex());
if (E1.OK == _patternAgent.CopyPattern(mainPatternInfo.GetPatternIndex(), _patternInfo.GetPatternIndex())) {
DeactivateRegisteredSubPattern();
InitRunParam();
InitFixturePatterns();
UpdateControlButtons(true);
@ -186,6 +187,7 @@ namespace MilUI {
if (!_patternAgent.IsPatternExist(_patternInfo.GetPatternIndex())) {
if (E1.OK == _patternAgent.Create(_patternInfo) &&
E1.OK == _patternAgent.Save(_patternInfo)) {
DeactivateRegisteredSubPattern();
InitRunParam();
InitFixturePatterns();
UpdateControlButtons(true);
@ -214,13 +216,35 @@ namespace MilUI {
var result = MessageBoxUtil.ShowMessageBoxQuestion(StrTokens.MSG_Q_Delete);
if (result == MessageBoxResult.Yes) {
if (_patternAgent.IsPatternExist(_patternInfo.GetPatternIndex())) {
if (_patternAgent.IsPatternFileLocked(_patternInfo, out string lockedFilePath)) {
MessageBoxUtil.ShowMessageBoxError(PatternAgentBase.GetPatternFileLockedMessage(lockedFilePath));
return;
}
if (E1.OK == _patternAgent.Remove(_patternInfo)) {
UpdateControlButtons(false);
}
else {
MessageBoxUtil.ShowMessageBoxError(PatternAgentBase.GetPatternFileLockedMessage(string.Empty));
}
}
}
}
// 서브 패턴은 OFF(IsActivated = false) 로 등록한다. 작업자가 확인한 뒤 ON 으로 바꾼다.
private void DeactivateRegisteredSubPattern() {
if (!IsSubPattern) {
return;
}
_patternInfo.IsActivated = false;
var registeredPatternInfo = _patternAgent.GetPatternInfo(_patternInfo.GetPatternIndex());
if (registeredPatternInfo != null) {
registeredPatternInfo.IsActivated = false;
_patternAgent.Save(registeredPatternInfo);
}
}
private void ClosingWindow() {
_cameraDisplay.ClearTrainGraphics();
_cameraDisplay.ClearFindGraphics();
@ -410,5 +434,8 @@ namespace MilUI {
NotifyPropertyChanged(nameof(TrainParam));
}
}
public void UpdateRadiusOfRoi(eGraRoiType graRoiType, double radius) {
}
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
@ -290,5 +290,8 @@ namespace MilUI {
NotifyPropertyChanged(nameof(RunParam));
}
}
public void UpdateRadiusOfRoi(eGraRoiType graRoiType, double radius) {
}
}
}

View File

@ -170,6 +170,9 @@ namespace MilUI {
}
}
public void UpdateRadiusOfRoi(eGraRoiType graRoiType, double radius) {
}
private double ConvertDegreeDecimalPlaces(double degree) {
return Math.Round(degree, 1);
}

View File

@ -1,4 +1,4 @@
<UserControl x:Class="MilUI.PatternMatchingSettingView"
<UserControl x:Class="MilUI.PatternMatchingSettingView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -65,6 +65,7 @@
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
@ -127,6 +128,15 @@
</TextBlock>
</visionCommonUI:UCContentComboBox>
</Grid>
<visionCommonUI:UCContentSlider Grid.Row="3" Grid.ColumnSpan="2" Margin="5"
MinValue="1" MaxValue="500" TickFrequency="10" Step="1"
KeyValue="{Binding ElementName=patternMatchingSettingView, Path=DataContext.TrainRoiSize}">
<TextBlock Style="{StaticResource stylePatternSliderTextBlock}">
<Run Text="{Binding [Train], Source={StaticResource langTokens}, Mode=OneWay}"/>
<Run Text="{Binding [ROI], Source={StaticResource langTokens}, Mode=OneWay}"/>
</TextBlock>
</visionCommonUI:UCContentSlider>
</Grid>
</GroupBox>

View File

@ -19,7 +19,7 @@ using VisionCommonUI;
using VisionCommonUI.Resources;
namespace MilUI {
public class PatternMatchingSettingViewModel : NotifyPropertyChangedBase {
public class PatternMatchingSettingViewModel : NotifyPropertyChangedBase, IGraEventSubscriber {
private Logger _logger = new Logger("PatternMatchingSettingVM");
public ICommand CMD_VisibleChanged { get; }
@ -57,6 +57,25 @@ namespace MilUI {
private PatternMatchingRunParam _runParam;
public PatternMatchingRunParam RunParam { get => _runParam; set => SetProperty(ref _runParam, value); }
private double _trainRoiSize = 25;
public double TrainRoiSize {
get => _trainRoiSize;
set {
if (SetProperty(ref _trainRoiSize, value)) {
if (RunParam?.PmParamExt != null) {
RunParam.PmParamExt.TrainRoiSize = value;
}
if (!_isUpdatingFromDisplay) {
if (_cameraDisplay != null && _cameraDisplay.HasTrainOrigin()) {
_cameraDisplay.UpdateRadiusOfTrainOrigin(value);
}
}
}
}
}
private bool _isUpdatingFromDisplay = false;
private IEnumerable<int> _fixturePatterns;
public IEnumerable<int> FixturePatterns { get => _fixturePatterns; set => SetProperty(ref _fixturePatterns, value); }
@ -96,6 +115,7 @@ namespace MilUI {
InitRunParam();
InitFixturePatterns();
UpdateControlButtons(IsPatternRegistered);
_cameraDisplay.SetGraEventSubscriber(_patternInfo, this);
}
public void InitRegistered() {
@ -109,6 +129,10 @@ namespace MilUI {
else {
RunParam = new PatternMatchingRunParam();
}
if (RunParam?.PmParamExt != null && RunParam.PmParamExt.TrainRoiSize > 0) {
_trainRoiSize = RunParam.PmParamExt.TrainRoiSize;
NotifyPropertyChanged(nameof(TrainRoiSize));
}
}
public void InitFixturePatterns() {
@ -134,6 +158,7 @@ namespace MilUI {
if (result == MessageBoxResult.Yes) {
var mainPatternInfo = _patternAgent.GetMainPatternInfo(_patternInfo.GetPatternIndex());
if (E1.OK == _patternAgent.CopyPattern(mainPatternInfo.GetPatternIndex(), _patternInfo.GetPatternIndex())) {
DeactivateRegisteredSubPattern();
InitRunParam();
InitFixturePatterns();
UpdateControlButtons(true);
@ -146,6 +171,7 @@ namespace MilUI {
if (!_patternAgent.IsPatternExist(_patternInfo.GetPatternIndex())) {
if (E1.OK == _patternAgent.Create(_patternInfo) &&
E1.OK == _patternAgent.Save(_patternInfo)) {
DeactivateRegisteredSubPattern();
InitRunParam();
InitFixturePatterns();
UpdateControlButtons(true);
@ -158,6 +184,9 @@ namespace MilUI {
if (_cameraDisplay.HasFindRoi()) {
RunParam.SetFindROI(_cameraDisplay.GetFindRoi().Clone() as ROI);
}
if (RunParam?.PmParamExt != null) {
RunParam.PmParamExt.TrainRoiSize = TrainRoiSize;
}
_patternAgent.SetRunParam(_patternInfo, RunParam);
_patternAgent.Save(_patternInfo);
MessageBoxUtil.ShowMessageBoxOk(StrTokens.MSG_O_SaveCompleted);
@ -173,13 +202,35 @@ namespace MilUI {
var result = MessageBoxUtil.ShowMessageBoxQuestion(StrTokens.MSG_Q_Delete);
if (result == MessageBoxResult.Yes) {
if (_patternAgent.IsPatternExist(_patternInfo.GetPatternIndex())) {
if (_patternAgent.IsPatternFileLocked(_patternInfo, out string lockedFilePath)) {
MessageBoxUtil.ShowMessageBoxError(PatternAgentBase.GetPatternFileLockedMessage(lockedFilePath));
return;
}
if (E1.OK == _patternAgent.Remove(_patternInfo)) {
UpdateControlButtons(false);
}
else {
MessageBoxUtil.ShowMessageBoxError(PatternAgentBase.GetPatternFileLockedMessage(string.Empty));
}
}
}
}
// Sub pattern is registered as OFF (IsActivated = false). The operator turns it ON after checking.
private void DeactivateRegisteredSubPattern() {
if (!IsSubPattern) {
return;
}
_patternInfo.IsActivated = false;
var registeredPatternInfo = _patternAgent.GetPatternInfo(_patternInfo.GetPatternIndex());
if (registeredPatternInfo != null) {
registeredPatternInfo.IsActivated = false;
_patternAgent.Save(registeredPatternInfo);
}
}
private void ClosingWindow() {
_cameraDisplay.ClearTrainGraphics();
_cameraDisplay.ClearFindGraphics();
@ -216,6 +267,7 @@ namespace MilUI {
}
private void SetTrainROI(object obj) {
_cameraDisplay.SetPatternInfo(_patternInfo);
var milImage = _cameraDisplay.GetImage();
if (milImage != 0) {
var curPattern = _patternAgent.GetPattern(_patternInfo) as IPatternTrain;
@ -225,9 +277,15 @@ namespace MilUI {
var imageCenter = MilEnvironment.GetImageCenter(milImage);
if (ROI.IsNullOrEmpty(TrainParam.TrainRoi) || TrainParam.TrainOrigin is null) {
_cameraDisplay.AddTrainRoi(imageCenter);
_cameraDisplay.AddTrainOrigin(new Cross(imageCenter));
_cameraDisplay.AddTrainOrigin(new Cross(imageCenter, TrainRoiSize));
}
else {
if (TrainParam.TrainOrigin.Length <= 0) {
TrainParam.TrainOrigin.Length = TrainRoiSize;
}
else {
TrainRoiSize = TrainParam.TrainOrigin.Length;
}
_cameraDisplay.AddTrainRoi(TrainParam.TrainRoi);
_cameraDisplay.AddTrainOrigin(TrainParam.TrainOrigin);
}
@ -377,5 +435,20 @@ namespace MilUI {
ResetWindow();
}
}
public void UpdateAngleOfRoi(eGraRoiType graRoiType, object param) {
}
public void UpdateRadiusOfRoi(eGraRoiType graRoiType, double radius) {
if (graRoiType == eGraRoiType.TrainOrigin) {
_isUpdatingFromDisplay = true;
try {
TrainRoiSize = Math.Min(500, Math.Max(1, Math.Round(radius, 1)));
}
finally {
_isUpdatingFromDisplay = false;
}
}
}
}
}

View File

@ -495,9 +495,30 @@ namespace VisionCommon {
public int Remove(PatternInfo patternInfo) {
if (patternInfo != null) {
if (GetPatterns().ContainsKey(patternInfo)) {
GetPatterns()[patternInfo].Remove(); // Remove Sub Pattern Directory
GetPatterns().Remove(patternInfo);
DeletePatternDirectory(patternInfo);
// MIL 프로그램 등에서 패턴 파일을 열어 두면 폴더 삭제 중 예외가 나므로, 먼저 확인하고 아무것도 지우지 않는다.
if (IsPatternFileLocked(patternInfo, out string lockedFilePath)) {
_logger.E($"Pattern file is locked : {lockedFilePath}");
return E1.GENERIC;
}
try {
GetPatterns()[patternInfo].Remove(); // Remove Sub Pattern Directory
GetPatterns().Remove(patternInfo);
DeletePatternDirectory(patternInfo);
}
catch (Exception e) {
_logger.E($"Remove pattern error : {patternInfo}, {e.Message}");
// 삭제 도중 실패하면 남은 파일로 패턴을 다시 불러와 둔다.
try {
if (GetPatterns().ContainsKey(patternInfo)) {
ReloadPattern(patternInfo);
}
}
catch (Exception reloadException) {
_logger.E($"Reload pattern error : {patternInfo}, {reloadException.Message}");
}
return E1.GENERIC;
}
return E1.OK;
}
@ -505,6 +526,48 @@ namespace VisionCommon {
return E2.NOT_FOUND;
}
// 패턴(메인 또는 서브 하나) 폴더의 파일 중 다른 프로그램이 열고 있는 파일이 있는지 확인한다.
public bool IsPatternFileLocked(PatternInfo patternInfo, out string lockedFilePath) {
lockedFilePath = string.Empty;
var patternDirPath = Path.GetDirectoryName(PatternConstants.GetSubPatternInfoFilePath(patternInfo));
if (!Directory.Exists(patternDirPath)) {
return false;
}
foreach (var filePath in Directory.GetFiles(patternDirPath, "*", SearchOption.AllDirectories)) {
try {
using (File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) {
}
}
catch (IOException) {
lockedFilePath = filePath;
return true;
}
catch (UnauthorizedAccessException) {
lockedFilePath = filePath;
return true;
}
}
return false;
}
// 메인과 서브 패턴 전체 중 다른 프로그램이 열고 있는 파일이 있는지 확인한다.
public bool IsMainSubPatternFileLocked(PatternInfo patternInfo, out string lockedFilePath) {
lockedFilePath = string.Empty;
foreach (var curPatternInfo in GetMainSubPatterns(patternInfo)) {
if (IsPatternFileLocked(curPatternInfo, out lockedFilePath)) {
return true;
}
}
return false;
}
public static string GetPatternFileLockedMessage(string lockedFilePath) {
return "패턴 파일이 다른 프로그램에서 열려 있어 삭제할 수 없습니다.\n" +
"MIL 프로그램 등에서 파일을 닫은 뒤 다시 삭제하세요.\n\n" + lockedFilePath;
}
public abstract IPatternFinder CreatePattern(PatternInfo patternInfo);
public void DeletePatternDirectory(PatternInfo patternInfo) {

View File

@ -34,6 +34,8 @@ namespace VisionCommon {
ROI GetTrainRoi();
int AddTrainOrigin(Cross imageCenter);
Cross GetTrainOrigin();
void UpdateRadiusOfTrainOrigin(double radius);
bool HasTrainOrigin();
int AddDefaultFindRoi(ImageSize imageSize, bool isRotatable = false);
int AddFindRoi(ROI findRoi, bool isRotatable = false);

View File

@ -5,10 +5,12 @@
namespace VisionCommon {
public enum eGraRoiType {
TrainRoi,
FindRoi
FindRoi,
TrainOrigin
}
public interface IGraEventSubscriber {
void UpdateAngleOfRoi(eGraRoiType graRoiType, object param);
void UpdateRadiusOfRoi(eGraRoiType graRoiType, double radius);
}
}

View File

@ -48,6 +48,9 @@ namespace VisionCommon {
public int MilSystemId { get; set; }
[JsonIgnore]
public Bitmap Thumbnail { get; set; } = ThumbnailUtil.GetDefaultImage();
// Sub pattern list for display (pattern list thumbnails)
[JsonIgnore]
public System.Collections.Generic.List<PatternInfo> SubPatternInfos { get; set; } = new System.Collections.Generic.List<PatternInfo>();
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)

View File

@ -17,7 +17,7 @@ namespace VisionCommon {
public const int MAIN_PATTERN_INDEX = 0;
public const int FOV_PATTERN_COUNT = 20;
public const int CAM_PATTERN_COUNT = 20;
public const int SUB_PATTERN_COUNT = 5;
public const int SUB_PATTERN_COUNT = 11; // 메인(0) 포함. 서브는 1 ~ 10
public static bool IsMainPattern(PatternIndex patternIndex) {
return patternIndex.PatternSubId == MAIN_PATTERN_INDEX;

View File

@ -4,6 +4,7 @@ using VisionSequence.Pattern;
using VisionCommonUI;
using VisionCommonUI.Resources;
using Aurora.View;
using Hermes;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@ -133,6 +134,7 @@ namespace VisionUI {
var curPatternInfo = Patterns[Patterns.Count - 1];
curPatternInfo.Thumbnail = _PatternAgent.GetThumbnail(curPatternInfo);
curPatternInfo.SubPatternInfos = GetSubPatternsWithThumbnail(curPatternInfo);
}
else {
Patterns.Add(CreateDefaultPatternInfo(i));
@ -147,6 +149,22 @@ namespace VisionUI {
}
}
// 목록의 행마다 서브 패턴 썸네일을 함께 보여주기 위해, 서브 번호순으로 썸네일을 채운 서브 패턴 목록을 만든다.
private List<PatternInfo> GetSubPatternsWithThumbnail(PatternInfo mainPatternInfo) {
var subPatternInfos = new List<PatternInfo>();
try {
foreach (var subPatternInfo in _PatternAgent.GetSubPatterns(mainPatternInfo)
.OrderBy(pi => pi.GetPatternIndex().PatternSubId)) {
subPatternInfo.Thumbnail = _PatternAgent.GetThumbnail(subPatternInfo);
subPatternInfos.Add(subPatternInfo);
}
}
catch (Exception e) {
_logger.FuncE(e.Message);
}
return subPatternInfos;
}
public List<PatternInfo> GetExistingPatterns() {
if (_PatternAgent == null) {
ResetPatternAgent();
@ -256,23 +274,39 @@ namespace VisionUI {
var result = MessageBoxUtil.ShowMessageBoxQuestion(
string.Format(StrTokens.MSG_Q_DeletePatternFormat, SelectedPatternIndex));
if (result == MessageBoxResult.Yes) {
// 메인/서브 중 하나라도 다른 프로그램이 열고 있으면 아무것도 지우지 않는다.
if (_PatternAgent.IsMainSubPatternFileLocked(Patterns[SelectedPatternIndex], out string lockedFilePath)) {
_logger.FuncE($"Pattern file is locked : {lockedFilePath}");
MessageBoxUtil.ShowMessageBoxError(PatternAgentBase.GetPatternFileLockedMessage(lockedFilePath));
return;
}
Application.Current.Dispatcher.Invoke(() => {
DeleteSubPatterns(Patterns[SelectedPatternIndex]);
DeleteMainPattern(Patterns[SelectedPatternIndex]);
Patterns[SelectedPatternIndex] = CreateDefaultPatternInfo(SelectedPatternIndex);
try {
bool isRemoved = DeleteSubPatterns(Patterns[SelectedPatternIndex]);
isRemoved &= DeleteMainPattern(Patterns[SelectedPatternIndex]);
if (!isRemoved) {
MessageBoxUtil.ShowMessageBoxError(PatternAgentBase.GetPatternFileLockedMessage(string.Empty));
}
}
catch (Exception e) {
_logger.FuncE(e.Message);
}
InitPatternList(lastSelectedPatternIndex);
});
}
}
private void DeleteSubPatterns(PatternInfo patternInfo) {
private bool DeleteSubPatterns(PatternInfo patternInfo) {
bool isRemoved = true;
var subPatternList = _PatternAgent.GetSubPatterns(patternInfo);
foreach (var subPatternInfo in subPatternList) {
_PatternAgent.Remove(subPatternInfo);
isRemoved &= (E1.OK == _PatternAgent.Remove(subPatternInfo));
}
return isRemoved;
}
private void DeleteMainPattern(PatternInfo patternInfo) {
_PatternAgent.Remove(patternInfo);
private bool DeleteMainPattern(PatternInfo patternInfo) {
return E1.OK == _PatternAgent.Remove(patternInfo);
}
private void Apply(object obj) {

View File

@ -50,9 +50,9 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="15*"/>
<ColumnDefinition Width="6*"/>
<ColumnDefinition Width="8*"/>
<ColumnDefinition Width="16*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Style="{StaticResource styleGroupBoxHeaderTextBlock}" TextAlignment="Center" FontSize="15" Text="ID"/>
<TextBlock Grid.Column="1" Style="{StaticResource styleGroupBoxHeaderTextBlock}" TextAlignment="Center" FontSize="15"
@ -77,16 +77,58 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="15*"/>
<ColumnDefinition Width="6*"/>
<ColumnDefinition Width="8*"/>
<ColumnDefinition Width="16*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Style="{StaticResource styleTextBlockDataGridItem}" FontSize="15" Text="{Binding PatternIndex.PatternId}"/>
<TextBlock Grid.Column="1" Style="{StaticResource styleTextBlockDataGridItem}" FontSize="15" Text="{Binding IsActivated}"/>
<TextBlock Grid.Column="2" Style="{StaticResource styleTextBlockDataGridItem}" FontSize="15" Text="{Binding PatternType}"/>
<Image Grid.Column="3" Margin="0" Width="38" Height="38"
RenderOptions.BitmapScalingMode="HighQuality"
Source="{Binding Thumbnail, Converter={StaticResource bitmapConverter}}"/>
<!-- 메인(M) 썸네일 뒤에 서브 패턴 썸네일을 서브 번호순으로 한 줄로 보여준다. 서브는 2개 폭만 보이고 나머지는 좌우 스크롤. OFF 인 서브는 흐리게 표시한다. -->
<DockPanel Grid.Column="3" VerticalAlignment="Center" LastChildFill="True">
<StackPanel DockPanel.Dock="Left" Margin="2, 0" ToolTip="Main">
<Image Margin="0" Width="38" Height="38"
RenderOptions.BitmapScalingMode="HighQuality"
Source="{Binding Thumbnail, Converter={StaticResource bitmapConverter}}"/>
<TextBlock Style="{StaticResource styleTextBlockDataGridItem}" FontSize="10" HorizontalAlignment="Center" Text="M"/>
</StackPanel>
<ScrollViewer MaxWidth="88" HorizontalAlignment="Left"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled"
PanningMode="HorizontalOnly"
PreviewMouseWheel="svSubThumbnails_PreviewMouseWheel">
<ItemsControl ItemsSource="{Binding SubPatternInfos}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="spSubThumbnail" Margin="2, 0">
<StackPanel.ToolTip>
<TextBlock>
<Run Text="Sub"/>
<Run Text="{Binding PatternIndex.PatternSubId, Mode=OneWay}"/>
<Run Text=" : "/>
<Run Text="{Binding IsActivated, Mode=OneWay}"/>
</TextBlock>
</StackPanel.ToolTip>
<Image Margin="0" Width="38" Height="38"
RenderOptions.BitmapScalingMode="HighQuality"
Source="{Binding Thumbnail, Converter={StaticResource bitmapConverter}}"/>
<TextBlock Style="{StaticResource styleTextBlockDataGridItem}" FontSize="10" HorizontalAlignment="Center"
Text="{Binding PatternIndex.PatternSubId}"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsActivated}" Value="False">
<Setter TargetName="spSubThumbnail" Property="Opacity" Value="0.35"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
<TextBlock Grid.Column="4" Style="{StaticResource styleTextBlockDataGridItem}" FontSize="15" Text="{Binding Description}"/>
</Grid>
</StackPanel>

View File

@ -161,6 +161,25 @@ namespace VisionUI.Pattern {
}
}
// 썸네일 행 안 : Shift + 휠은 좌우 스크롤, 그냥 휠은 목록 위아래 스크롤로 넘긴다.
private void svSubThumbnails_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e) {
if (!(sender is ScrollViewer scrollViewer)) {
return;
}
e.Handled = true;
if (System.Windows.Input.Keyboard.Modifiers == System.Windows.Input.ModifierKeys.Shift) {
scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - e.Delta);
return;
}
var parent = System.Windows.Media.VisualTreeHelper.GetParent(scrollViewer) as UIElement;
parent?.RaiseEvent(new System.Windows.Input.MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) {
RoutedEvent = UIElement.MouseWheelEvent,
Source = scrollViewer
});
}
private void UpdateVisionRecipes() {
_FovRecipe = VisionRecipeManager.GetFovRecipe(_FovInfo);
}

View File

@ -123,6 +123,7 @@ namespace ILSOS_KS.Device
PLC_DOOR_INTERLOCK,
PLC_EMO_STATUS,
PLC_RECIPE_CHANGE_REQ,
PLC_RCC_PATTERN_CHANGED_ACK = 0X09, // B2409 : B2506(RCC 패턴 변경) 수신 ACK
PLC_JIG_LOAD_COMPLETE = 0X0C,
/// <summary>
@ -156,6 +157,7 @@ namespace ILSOS_KS.Device
PC_MANUAL_COMPLETE,
PC_LIGHT_ALARM_STATUS,
PC_HEAVY_ALARM_STATUS,
PC_RCC_PATTERN_CHANGED = 0X06, // B2506 : RCC 패턴 변경 알림 (B2409 ACK 를 받으면 끈다)
PC_RECIPE_CHANGE_COMPLETE = 0X08,
PC_RECIPE_PARAM_UPLOAD_COMPLETE,
PC_WELD_MANUAL_TEST,

View File

@ -1,4 +1,4 @@
using Aurora;
using Aurora;
using Aurora.Api;
using Aurora.Api.Devices;
using Aurora.Api.Manager;
@ -156,10 +156,10 @@ namespace ILSOS_KS
// () => _plc, PLCMelsecNetConfig.GetDefaultConfig(),
// device => new UCPLCDevice() { DataContext = new UCPLCDeviceViewModel(AuroraEnv, _plc) });
registry.Register(DeviceIds.KSLight1, () => new LightDevice(CommonDeviceIds.Light, eLight.ERI), new LightConfig(),
registry.Register(DeviceIds.KSLight1, () => new LightDevice(DeviceIds.KSLight1, eLight.ERI), new LightConfig(),
device => new UCLight() { DataContext = new UCLightViewModel(AuroraEnv, DeviceIds.KSLight1) });
registry.Register(DeviceIds.KSLight2, () => new LightDevice(CommonDeviceIds.Light, eLight.ERI), new LightConfig(),
registry.Register(DeviceIds.KSLight2, () => new LightDevice(DeviceIds.KSLight2, eLight.ERI), new LightConfig(),
device => new UCLight() { DataContext = new UCLightViewModel(AuroraEnv, DeviceIds.KSLight2) });
RegisterStreamServices(registry);

View File

@ -328,6 +328,10 @@
<Project>{3db398e6-80b7-4bb9-8be7-caf12979c269}</Project>
<Name>VisionCommon</Name>
</ProjectReference>
<ProjectReference Include="..\..\AuroraVision\VisionUI\VisionUI.csproj">
<Project>{DE586F5E-5BC0-4CAC-9CCB-360473A411B4}</Project>
<Name>VisionUI</Name>
</ProjectReference>
<ProjectReference Include="..\..\WeldingComponents\LASA\GUI\LASAGUI.csproj">
<Project>{e4658ed8-bbb0-4aad-899a-0bad56f2732a}</Project>
<Name>LASAGUI</Name>

View File

@ -49,5 +49,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.3")]
[assembly: AssemblyFileVersion("1.0.0.3")]
[assembly: AssemblyVersion("1.0.0.4")]
[assembly: AssemblyFileVersion("1.0.0.4")]

View File

@ -61,6 +61,7 @@ using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Xml.Linq;
using VisionCommon;
using VisionUI.Camera;
using WeldingCommon;
using WeldingCommon.Alarm;
using WeldingCommon.Data;
@ -263,6 +264,9 @@ namespace ILSOS_KS.Sequences
bool ksExit = false;
// RCC 패턴 변경 알림 : 변경이 있으면 B2506 을 켜고, PLC ACK(B2409) 가 오면 끈다.
private CancellationTokenSource _RccChangeNoticeCts;
int[] ResultDataList = new int[4];
public SeqAutoRunKS()
@ -1329,6 +1333,111 @@ namespace ILSOS_KS.Sequences
return SequenceAction.Ok();
}
// RCC 패턴이 바뀌었으면 B2506 을 켜고, PLC ACK(B2409) 를 받았을 때만 알림 완료로 기록한다.
// ACK 없이 오토런이 끝나면 B2506 만 끄고 기록하지 않아서 다음 오토런에서 다시 알린다. 오토런은 막지 않는다.
private void StartRccPatternChangeNotice(SequenceContext context)
{
_RccChangeNoticeCts?.Cancel();
WriteRccPatternChangedBit(false, "AutoRun Start Reset");
if (_SystemParameter.SequenceType != eSequenceType.RCC)
{
return;
}
string recipeKey;
string hash;
try
{
recipeKey = RccPatternChangeRecord.GetCurrentRecipeKey();
hash = RccPatternChangeRecord.ComputePatternHash();
}
catch (Exception e)
{
Log.E($"[RccPatternChange] Pattern hash failed : {e.Message}");
return;
}
if (RccPatternChangeRecord.IsAcknowledged(recipeKey, hash))
{
Log.I($"[RccPatternChange] No change. {GetRccPatternBitState()}");
return;
}
WriteRccPatternChangedBit(true, "RCC pattern changed -> Wait PLC ACK");
var cts = new CancellationTokenSource();
_RccChangeNoticeCts = cts;
Task.Run(() =>
{
bool acked = false;
try
{
// 이전 ACK 가 켜진 채로 남아 있으면 꺼질 때까지 기다린 뒤, 새로 켜지는 순간(OFF -> ON)만 ACK 로 본다.
bool ackArmed = !_PLC.ReadBit((int)eBitInput.PLC_RCC_PATTERN_CHANGED_ACK).data;
if (!ackArmed)
{
Log.I($"[RccPatternChange] PLC ACK is already ON. Wait ACK OFF. {GetRccPatternBitState()}");
}
while (!cts.IsCancellationRequested && !context.Stopped)
{
bool ack = _PLC.ReadBit((int)eBitInput.PLC_RCC_PATTERN_CHANGED_ACK).data;
if (!ackArmed)
{
if (!ack)
{
ackArmed = true;
Log.I($"[RccPatternChange] PLC ACK OFF. Wait new ACK. {GetRccPatternBitState()}");
}
}
else if (ack)
{
acked = true;
break;
}
Thread.Sleep(100);
}
if (acked)
{
RccPatternChangeRecord.Acknowledge(recipeKey, hash);
Log.I($"[RccPatternChange] PLC ACK received -> Recorded. {GetRccPatternBitState()}");
}
}
catch (Exception e)
{
Log.E($"[RccPatternChange] {e.Message}");
}
finally
{
// 오토런을 다시 시작해 새 감시가 돌고 있으면, 새로 켠 비트를 건드리지 않는다.
if (ReferenceEquals(_RccChangeNoticeCts, cts))
{
WriteRccPatternChangedBit(false, acked
? "After PLC ACK"
: "AutoRun ended without ACK -> Not recorded, notify again next AutoRun");
}
}
});
}
// B2506 을 쓰고, 쓴 뒤의 B2506 / B2409 상태를 읽어서 로그로 남긴다.
private void WriteRccPatternChangedBit(bool value, string reason)
{
int ret = _PLC.WriteBit((int)eBitOutput.PC_RCC_PATTERN_CHANGED, value);
Log.I($"[RccPatternChange] {reason} : ret={ret}, {GetRccPatternBitState()}");
}
// PC_RCC_PATTERN_CHANGED(B2506, PC 출력 블록) 와 PLC_RCC_PATTERN_CHANGED_ACK(B2409, PLC 입력) 를 읽어서 상태 문자열로 만든다.
private string GetRccPatternBitState()
{
var pc = _PLC.ReadBit((int)eBitOutput.PC_RCC_PATTERN_CHANGED, isInput: false);
var ack = _PLC.ReadBit((int)eBitInput.PLC_RCC_PATTERN_CHANGED_ACK);
return $"PC_RCC_PATTERN_CHANGED(B2506)={(pc.err == 0 ? pc.data.ToString() : $"ReadErr{pc.err}")}, " +
$"PLC_RCC_PATTERN_CHANGED_ACK(B2409)={(ack.err == 0 ? ack.data.ToString() : $"ReadErr{ack.err}")}";
}
private bool InitSequenceVariable(SequenceContext context)
{
_AutoRunStartTime = DateTime.Now;
@ -1459,6 +1568,9 @@ namespace ILSOS_KS.Sequences
// PLC Interface AutoStatus Start
InitAutoRunBit();
// RCC 패턴 변경 알림 : 해시 계산에 시간이 걸리므로 PC_AUTO_STATUS 를 켠 뒤에 한다.
StartRccPatternChangeNotice(context);
// Laser Initialize
_Laser.PilotLaserOff();
_Laser.LaserOn();
@ -1554,9 +1666,9 @@ namespace ILSOS_KS.Sequences
Log.I(string.Format($"[IOInterface] Start AutoRun IO Bit Initialize"));
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADReadyStatus), false);
Log.I(string.Format($"[IOInterface]SET PCLBADReadyStatus False"));
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADWeldingTrigger), false);
WriteLBADDo(eADOAddress.PCLBADWeldingTrigger, false);
Log.I(string.Format($"[IOInterface]SET PCLBADWeldingTrigger False"));
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADWeldingStop), false);
WriteLBADDo(eADOAddress.PCLBADWeldingStop, false);
Log.I(string.Format($"[IOInterface]SET PCLBADWeldingStop False"));
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADJudgementCompleteAck), false);
Log.I(string.Format($"[IOInterface]SET PCLBADJudgementCompleteAck False"));
@ -1802,6 +1914,10 @@ namespace ILSOS_KS.Sequences
_PLC.WriteBit((int)eBitOutput.PC_LBAD_RESULT_NG, false);
Log.I(string.Format($"[PLCInterface]SET PC_LBAD_RESULT_NG False"));
// 이전 셀의 LBAD 판정값이 남아있으면 이번 셀에서 판정을 못했을 때 그대로 재전송되므로 여기서 초기화한다.
Status.GetProcessingWeldingData().LBAD_Judge = string.Empty;
Log.I(string.Format($"[PLCInterface]CLEAR LBAD_Judge"));
_InterfaceStartTime = DateTime.Now;
while (!context.Stopped)
@ -1986,6 +2102,9 @@ namespace ILSOS_KS.Sequences
{
var resultOutParam = param.Param as SeqScoutFlyingVision_OutParam;
CameraUcManager.Instance.StartLiveAll(eDisplayType.Machine_A);
return SequenceAction.Ok();
//if (pre_welding)
@ -2268,6 +2387,18 @@ namespace ILSOS_KS.Sequences
return SequenceAction.Ok();
}
// LBAD IO 출력 : 쓰기 반환값(0 = 성공)을 로그로 남기고, 실패하면 에러 로그를 남긴다.
private int WriteLBADDo(eADOAddress address, bool value)
{
int ret = _IO._iOUnit.WriteDoBit(Convert.ToInt32(address), value);
Log.I(string.Format($"[IOInterface] WriteDoBit {address}({Convert.ToInt32(address)}) {value} ret={ret}"));
if (ret != 0)
{
Log.E(string.Format($"[IOInterface] WriteDoBit {address}({Convert.ToInt32(address)}) {value} FAIL ret={ret}"));
}
return ret;
}
private SequenceActionResult ExecuteWelding(SequenceContext context, string id, SequenceActionResult param)
{
Status.SetWorkingPosition(eWorkingPosition.EXECUTING_WELD);
@ -2279,7 +2410,7 @@ namespace ILSOS_KS.Sequences
int recipeNo = _RecipeManager.ActivatedRecipe().Id;
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADWeldingTrigger), true);
WriteLBADDo(eADOAddress.PCLBADWeldingTrigger, true);
//int sendresult = _LBAD.SendCellData(_LotId, _CellId, recipeNo, _ModelNo);
if (_BcrID.Contains(""))
@ -2345,11 +2476,11 @@ namespace ILSOS_KS.Sequences
_PLC.WriteBit((int)eBitOutput.PC_WELD_READY, false);
Log.I(string.Format($"[PLCInterface]SET PC_WELD_READY False"));
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADWeldingTrigger), false);
WriteLBADDo(eADOAddress.PCLBADWeldingTrigger, false);
Log.I(string.Format($"[IOInterface]SET PCWeldingTrigger false"));
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADWeldingStop), true);
WriteLBADDo(eADOAddress.PCLBADWeldingStop, true);
Log.I(string.Format($"[IOInterface]{_PageNo} setp --> SET PCWeldingStop true"));
return SequenceAction.Ok();
@ -2394,7 +2525,7 @@ namespace ILSOS_KS.Sequences
//LBAD 결과 받아오는 부분 우선 여기서 추가하여 테스트하고 Sub 시퀀스로 빼자
if (_LBAD.IsSimul)
{
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADWeldingStop), false);
WriteLBADDo(eADOAddress.PCLBADWeldingStop, false);
Log.I(string.Format($"[IOInterface]SET PCLBADWeldingStop false, LBAD is Simul"));
}
else
@ -3186,7 +3317,7 @@ namespace ILSOS_KS.Sequences
{
Log.I(string.Format($"[IOInterface]Get PLC_WELD_REQEUST True"));
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADWeldingStop), false);
WriteLBADDo(eADOAddress.PCLBADWeldingStop, false);
Log.I(string.Format($"[IOInterface]LBAD {_PageNoRcc} setp --> SET PCWeldingStop false"));
bool[] Page = new bool[4];
@ -3230,7 +3361,7 @@ namespace ILSOS_KS.Sequences
WeldingEnd = true;
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADWeldingStop), false);
WriteLBADDo(eADOAddress.PCLBADWeldingStop, false);
Log.I(string.Format($"[IOInterface]LBAD {_PageNoRcc} setp --> SET PCWeldingStop false"));
DateTime startTime = DateTime.Now;
@ -3577,6 +3708,7 @@ namespace ILSOS_KS.Sequences
private SequenceActionResult Complete(SequenceContext context, string id, SequenceActionResult param)
{
_RccChangeNoticeCts?.Cancel();
RestoreDeviceStatus();
Log.I("AutoRun Complete");
@ -3596,6 +3728,10 @@ namespace ILSOS_KS.Sequences
private bool RestoreDeviceStatus()
{
// RCC 패턴 변경 알림 감시 종료 + B2506 OFF (ACK 전이면 기록하지 않았으므로 다음 오토런에서 다시 알린다)
_RccChangeNoticeCts?.Cancel();
WriteRccPatternChangedBit(false, "AutoRun Stop");
//PLC All BIT OFF
_PLC.WriteBit((int)eBitOutput.PC_WELD_REQEUST_ACK, false);
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false);
@ -3612,9 +3748,9 @@ namespace ILSOS_KS.Sequences
UpdatePLCMemoryAreaCls((int)eWordOutput.STEP1_Arc1_LowerLimit, (int)eWordOutput.WeldingOK_NG);
//LBAD IO All BIT OFF
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADWeldingTrigger), false);
WriteLBADDo(eADOAddress.PCLBADWeldingTrigger, false);
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADReadyStatus), false);
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADWeldingStop), false);
WriteLBADDo(eADOAddress.PCLBADWeldingStop, false);
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADJudgementCompleteAck), false);
Log.I(string.Format("[IOInterface]PC All Bit Off"));
@ -3671,6 +3807,10 @@ namespace ILSOS_KS.Sequences
private void OnSeqActionStop()
{
// RCC 패턴 변경 알림 감시 종료 + B2506 OFF (ACK 전이면 기록하지 않았으므로 다음 오토런에서 다시 알린다)
_RccChangeNoticeCts?.Cancel();
WriteRccPatternChangedBit(false, "AutoRun Stop");
//PLC All BIT OFF
_PLC.WriteBit((int)eBitOutput.PC_WELD_REQEUST_ACK, false);
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false);
@ -3687,9 +3827,9 @@ namespace ILSOS_KS.Sequences
UpdatePLCMemoryAreaCls((int)eWordOutput.STEP1_Arc1_LowerLimit, (int)eWordOutput.WeldingOK_NG);
//LBAD IO All BIT OFF
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADWeldingTrigger), false);
WriteLBADDo(eADOAddress.PCLBADWeldingTrigger, false);
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADReadyStatus), false);
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADWeldingStop), false);
WriteLBADDo(eADOAddress.PCLBADWeldingStop, false);
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADJudgementCompleteAck), false);
Log.I(string.Format("[IOInterface]PC All Bit Off"));
@ -3750,6 +3890,86 @@ namespace ILSOS_KS.Sequences
}
// RCC 패턴 변경 기록 : 패턴 폴더 전체 내용 해시를 레시피별로 DATA_PATH\_config_setting\RccPatternChangeRecord.json 에 기록하고 비교한다.
public static class RccPatternChangeRecord
{
private const string FILE_NAME = "RccPatternChangeRecord.json";
private static readonly object _Lock = new object();
private static string RecordFilePath =>
Path.Combine(ApplicationExt.DataPath, "_config_setting", FILE_NAME);
// 기록 키 : 현재 레시피 폴더 경로
public static string GetCurrentRecipeKey()
{
return Path.GetFullPath(PatternConstants.GetCurrentRecipeFilePath()).TrimEnd('\\').ToLowerInvariant();
}
// 현재 레시피의 패턴 폴더 전체 내용 해시
public static string ComputePatternHash()
{
string patternDir = PatternConstants.GetCurrentRecipePatternRootPath();
var files = new List<string>();
if (Directory.Exists(patternDir))
{
files.AddRange(Directory.GetFiles(patternDir, "*", SearchOption.AllDirectories));
}
using (var sha = SHA256.Create())
{
foreach (var file in files.OrderBy(f => f, StringComparer.OrdinalIgnoreCase))
{
byte[] name = Encoding.UTF8.GetBytes(GetRelativePath(patternDir, file).ToLowerInvariant());
sha.TransformBlock(name, 0, name.Length, null, 0);
byte[] content = File.ReadAllBytes(file);
sha.TransformBlock(content, 0, content.Length, null, 0);
}
sha.TransformFinalBlock(new byte[0], 0, 0);
return BitConverter.ToString(sha.Hash).Replace("-", "");
}
}
// 레시피가 이 해시로 알림 완료됐는지
public static bool IsAcknowledged(string recipeKey, string hash)
{
lock (_Lock)
{
return Load().TryGetValue(recipeKey, out string recorded) && recorded == hash;
}
}
// 레시피의 이 해시를 알림 완료로 기록한다.
public static void Acknowledge(string recipeKey, string hash)
{
lock (_Lock)
{
var records = Load();
records[recipeKey] = hash;
Directory.CreateDirectory(Path.GetDirectoryName(RecordFilePath));
File.WriteAllText(RecordFilePath,
Newtonsoft.Json.JsonConvert.SerializeObject(records, Newtonsoft.Json.Formatting.Indented), Encoding.UTF8);
}
}
private static Dictionary<string, string> Load()
{
if (!File.Exists(RecordFilePath))
{
return new Dictionary<string, string>();
}
return Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(RecordFilePath))
?? new Dictionary<string, string>();
}
private static string GetRelativePath(string baseDir, string fullPath)
{
string root = Path.GetFullPath(baseDir).TrimEnd('\\') + "\\";
string full = Path.GetFullPath(fullPath);
return full.StartsWith(root, StringComparison.OrdinalIgnoreCase) ? full.Substring(root.Length) : full;
}
}
public class ScannerObjectInfo
{
public object Object { get; set; } // Arc, Line, Spline, Dot

View File

@ -219,13 +219,13 @@ namespace ILSOS_KS.Views
NowDateTime = DateTime.Now;
// Sequence Status check
if (_SeqAutoRun.RunState != SequenceRunState.Idle)
if (_SeqAutoRun.RunState == SequenceRunState.Idle || _SeqAutoRun.RunState == SequenceRunState.EmergencyStopHandling)
{
IsSequenceRunning_AutoRun = true;
IsSequenceRunning_AutoRun = false;
}
else
{
IsSequenceRunning_AutoRun = false;
IsSequenceRunning_AutoRun = true;
}
// Alarm Status check