1618 lines
68 KiB
C#
1618 lines
68 KiB
C#
using Aurora;
|
|
using Aurora.Api;
|
|
using Aurora.View;
|
|
using AuroraLaser.Coherent;
|
|
using AuroraPowerMeter;
|
|
using LiveChartsCore;
|
|
using LiveChartsCore.Defaults;
|
|
using LiveChartsCore.SkiaSharpView;
|
|
using LiveChartsCore.SkiaSharpView.Painting;
|
|
using LiveChartsCore.SkiaSharpView.Painting.Effects;
|
|
using MathNet.Numerics;
|
|
using SkiaSharp;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Configuration;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reactive.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Input;
|
|
using System.Windows.Threading;
|
|
|
|
namespace AuroraLaser.Views
|
|
{
|
|
public class PowerCalibData : NotifyPropertyChangedBase
|
|
{
|
|
public double PowerPerc { get; set; }
|
|
public double PowerWatt { get; set; }
|
|
|
|
private bool _use = true;
|
|
public bool Use {
|
|
get { return _use; }
|
|
set { SetProperty(ref _use, value); }
|
|
} // DataGridRow의 IsEnable과 Binding되는 property
|
|
}
|
|
|
|
public class PlotCheckBox : NotifyPropertyChangedBase
|
|
{
|
|
private bool _isSelected = true;
|
|
public bool IsSelected {
|
|
get => _isSelected;
|
|
set {
|
|
if (_isSelected != value) {
|
|
_plotItem.IsVisible = value; // 여기서 Plot 을 제어하면 될 것 같은데....
|
|
SetProperty(ref _isSelected, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
public string PlotName { get; set; }
|
|
public int Index { get; set; }
|
|
private ISeries _plotItem;
|
|
|
|
public PlotCheckBox(int index, string name, ISeries plotItem)
|
|
{
|
|
Index = index;
|
|
PlotName = name;
|
|
_plotItem = plotItem;
|
|
|
|
IsSelected = true;
|
|
}
|
|
}
|
|
|
|
public class CalibRadioButton : NotifyPropertyChangedBase
|
|
{
|
|
public string Id { get; set; }
|
|
|
|
private bool _isEnabledRadio;
|
|
public bool IsEnabledRadio {
|
|
get { return _isEnabledRadio; }
|
|
set {
|
|
if (_isEnabledRadio != value) {
|
|
_isEnabledRadio = value;
|
|
NotifyPropertyChanged("IsEnabledRadio");
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool _isSelected;
|
|
public bool IsSelected {
|
|
get { return _isSelected; }
|
|
set {
|
|
if (_isSelected != value) {
|
|
_isSelected = value;
|
|
|
|
if (_isSelected) { // 선택된 경우만 레이저 설정을 변경
|
|
// 레이저의 추정모델타입을 변경한다.
|
|
_laser?.SetLaserPowerCalibMethod(Id);
|
|
}
|
|
|
|
NotifyPropertyChanged("IsSelected");
|
|
}
|
|
}
|
|
}
|
|
|
|
// 공용 필드
|
|
public static LaserCompact _laser;
|
|
|
|
public CalibRadioButton(string id)
|
|
{
|
|
Id = id;
|
|
IsEnabledRadio = true;
|
|
IsSelected = false;
|
|
}
|
|
}
|
|
|
|
internal enum ePowerCalibPhase // 실질적으로는 파워 데이터 측정 페이즈임
|
|
{
|
|
Idle = -1, // 대기 상태
|
|
Init = 0,
|
|
Collecting,
|
|
LaserPowerSet,
|
|
Finish,
|
|
}
|
|
|
|
internal enum eInitBy
|
|
{
|
|
LaserActivate = 0,
|
|
LaserChange,
|
|
VisibleChange,
|
|
}
|
|
|
|
public enum ePowerModelCalib
|
|
{
|
|
LUT = 0,
|
|
Lin1Seg,
|
|
Lin2Seg,
|
|
}
|
|
|
|
public struct stPowerModelEnable
|
|
{
|
|
public bool IsLUTEnable { get; set; }
|
|
public bool IsLin1SegEnable { get; set; }
|
|
public bool IsLin2SegEnable { get; set; }
|
|
|
|
public stPowerModelEnable(bool isLUTEnable, bool isLinSegEnalbe, bool isLin2SegEanble)
|
|
{
|
|
IsLUTEnable = isLUTEnable;
|
|
IsLin1SegEnable = isLinSegEnalbe;
|
|
IsLin2SegEnable = isLin2SegEanble;
|
|
}
|
|
}
|
|
|
|
public class UCLaserCompactCalibrationViewModel : NotifyPropertyChangedBase
|
|
{
|
|
private static readonly Logger Log = new Logger("Laser Calib");
|
|
|
|
#region 커맨드
|
|
public ICommand CMD_VisibleChanged { get; }
|
|
public ICommand CMD_CopyFromCurernt2New { get; }
|
|
|
|
//--- 파워미터
|
|
public ICommand CMD_PowerMeterSetZero { get; }
|
|
|
|
//--- Laser 관련
|
|
public ICommand CMD_LaserTargetPowerSet { get; }
|
|
public ICommand CMD_LaserTargetPowerWattSet { get; }
|
|
//==============================================
|
|
//------ For LiveCharts2
|
|
public ICommand CMD_PlotZoomReset { get; }
|
|
public ICommand CMD_AxesSwitch { get; }
|
|
//=================================================
|
|
// --- 파워 측정 for collecting calibration data
|
|
public ICommand CMD_LaserPowerMeasureStart { get; }
|
|
public ICommand CMD_LaserPowerMeasureStop { get; }
|
|
|
|
public ICommand CMD_SaveApply { get; }
|
|
public ICommand CMD_SaveAsCSV { get; }
|
|
//======================================
|
|
//--- 레이저 파워 교정 관련
|
|
public ICommand CMD_SolveModel { get; }
|
|
public ICommand CMD_SetROI { get; }
|
|
#endregion
|
|
|
|
#region 프로퍼티 & 필드
|
|
// 해당 뷰 enable 상태
|
|
private bool _isUCViewEnabled = true;
|
|
public bool IsUCViewEnabled { get => _isUCViewEnabled; set => SetProperty(ref _isUCViewEnabled, value); }
|
|
//--- 전체 UC view enable 제어가 목적이었으나, 부분적으로만 사용됨..
|
|
private bool _isEnabled = false;
|
|
public bool IsEnabled { get => _isEnabled; set => SetProperty(ref _isEnabled, value); }
|
|
//--- 전체 uc view visible 정보
|
|
public bool IsVisible { get; private set; }
|
|
//--- 레이저 장치 선택/변경 enalbe 제어
|
|
private bool _isLaserDeviceSwitchEnabled = true;
|
|
public bool IsLaserDeviceSwitchEnabled { get => _isLaserDeviceSwitchEnabled; set => SetProperty(ref _isLaserDeviceSwitchEnabled, value); }
|
|
|
|
// Laser 0 or Laser1
|
|
List<string> laserIDs;
|
|
|
|
private bool _isLaser1 = false;
|
|
public bool IsLaser1 {
|
|
get => _isLaser1;
|
|
set {
|
|
if (_isLaser1 != value) {
|
|
// 레이저가 변경된 경우, 그에 맞는 초기화 필요!!!
|
|
// 1. 타이머 종료
|
|
if (MonitorTimer.IsEnabled) {
|
|
Log.V($"IsLaser1: {value}, MonitorTimer is enabled");
|
|
MonitorTimer.Stop();
|
|
|
|
// 레이저 변경
|
|
if (value) { // Laser1
|
|
//_laser = _aurora.DeviceManager.GetDevice(CommonDeviceIds.LaserWire1) as WireLaser;
|
|
} else { // Laser0
|
|
_laser = _aurora.DeviceManager.GetDevice(laserIDs[0]) as LaserCompact;
|
|
}
|
|
|
|
InitByLaserChange();
|
|
|
|
MonitorTimer.Start();
|
|
} else {
|
|
Log.V($"IsLaser1: {value}, MonitorTimer is disabled");
|
|
|
|
// 레이저 변경
|
|
if (value) { // Laser1
|
|
//_laser = _aurora.DeviceManager.GetDevice(CommonDeviceIds.LaserWire1) as WireLaser;
|
|
} else { // Laser0
|
|
_laser = _aurora.DeviceManager.GetDevice(laserIDs[0]) as LaserCompact;
|
|
}
|
|
|
|
InitByLaserChange();
|
|
}
|
|
SetProperty(ref _isLaser1, value);
|
|
}
|
|
}
|
|
}
|
|
//------- 파워미터 관련
|
|
private string _powerMeterInfo;
|
|
public string PowerMeterInfo { get => _powerMeterInfo; set => SetProperty(ref _powerMeterInfo, value); }
|
|
private double _powerMeterWatt;
|
|
public double PowerMeterWatt {
|
|
get => _powerMeterWatt;
|
|
set {
|
|
SetProperty(ref _powerMeterWatt, Math.Round(value, precision4));
|
|
}
|
|
}
|
|
|
|
private bool _isPowerMeterZeroEnabled = false;
|
|
public bool IsPowerMeterZeroEnabled { get => _isPowerMeterZeroEnabled; set => SetProperty(ref _isPowerMeterZeroEnabled, value); }
|
|
private bool _setPowerMeterZero = false;
|
|
//===================================
|
|
//----- 레이저 관련
|
|
private double _laserActualPower;
|
|
public double LaserActualPower { get => _laserActualPower; set => SetProperty(ref _laserActualPower, value); }
|
|
//======================
|
|
//------ 레이저 파워교정 세팅
|
|
private int _startDelay;
|
|
public int StartDelay { get => _startDelay; set => SetProperty(ref _startDelay, value); }
|
|
private int _measurementDelay;
|
|
public int MeasurementDelay {
|
|
get => _measurementDelay;
|
|
set {
|
|
// 파워 측정 중에만 타이머 interval 을 변경할 수 있다
|
|
// - 하나의 타이머를 쓰고 있으므로... 일반 상태, 파워 측정 상태로 구분...
|
|
if (IsLaserPowerMeasuring && _measurementDelay != value) {
|
|
// 타이머의 인터벌을 측정 딜레이로 변경하자.
|
|
MonitorTimer.Interval = TimeSpan.FromMilliseconds(value);
|
|
}
|
|
|
|
SetProperty(ref _measurementDelay, value);
|
|
}
|
|
}
|
|
private double _beginPowerPerc;
|
|
public double BeginPowerPerc { get => _beginPowerPerc; set => SetProperty(ref _beginPowerPerc, value); }
|
|
private double _endPowerPerc;
|
|
public double EndPowerPerc { get => _endPowerPerc; set => SetProperty(ref _endPowerPerc, value); }
|
|
private double _measurementStepPerc;
|
|
public double MeasurementStepPerc {
|
|
get => _measurementStepPerc;
|
|
set {
|
|
SetProperty(ref _measurementStepPerc, value);
|
|
}
|
|
}
|
|
//===================
|
|
//----- 레이저 수동 조작
|
|
private double _laserTargetPowerWatt;
|
|
public double LaserTargetPowerWatt { get => _laserTargetPowerWatt; set => SetProperty(ref _laserTargetPowerWatt, value); }
|
|
bool _isLaserTargetPowerWattSetEnabled = true;
|
|
public bool IsLaserTargetPowerWattSetEnabled { get => _isLaserTargetPowerWattSetEnabled; set => SetProperty(ref _isLaserTargetPowerWattSetEnabled, value); }
|
|
private double _laserTargetPower;
|
|
public double LaserTargetPower { get => _laserTargetPower; set => SetProperty(ref _laserTargetPower, value); }
|
|
bool _isLaserTargetPowerSetEnabled = true;
|
|
public bool IsLaserTargetPowerSetEnabled { get => _isLaserTargetPowerSetEnabled; set => SetProperty(ref _isLaserTargetPowerSetEnabled, value); }
|
|
//======================================
|
|
|
|
//=============================
|
|
//---- 파워 교정 관련
|
|
ePowerCalibPhase powerCalibPhase = ePowerCalibPhase.Idle;
|
|
|
|
bool _isLaserPowerMeasureStartEnabled = true;
|
|
public bool IsLaserPowerMeasureStartEnabled { get => _isLaserPowerMeasureStartEnabled; set => SetProperty(ref _isLaserPowerMeasureStartEnabled, value); }
|
|
bool _isLaserPowerMeasureStopEnabled = false;
|
|
public bool IsLaserPowerMeasureStopEnabled { get => _isLaserPowerMeasureStopEnabled; set => SetProperty(ref _isLaserPowerMeasureStopEnabled, value); }
|
|
|
|
double _threshold = 0.1; // [W]
|
|
public double Threshold { get => _threshold; set => SetProperty(ref _threshold, value); }
|
|
private bool _isThresholdSetEnabled;
|
|
public bool IsThresholdSetEnabled { get => _isThresholdSetEnabled; set => SetProperty(ref _isThresholdSetEnabled, value); }
|
|
|
|
bool _isTwoSegment;
|
|
public bool IsTwoSegment {
|
|
get => _isTwoSegment;
|
|
set {
|
|
// 교정 옵션을 변경하면, 교정 결과를 초기화(?)함, 다른 교정 결과들도 초기화해야 할까..?
|
|
IsModelSolved = false; // Save&Apply 교정모델 업데이트 용..
|
|
|
|
if (_isTwoSegment != value) {
|
|
CheckTwoSegmentIsValid();
|
|
|
|
SetProperty(ref _isTwoSegment, value);
|
|
}
|
|
}
|
|
}
|
|
private bool _isSegmentToggleSwitchEnabled = true; // View 정렬 확인 목적 테스트...false 로 바꿔야 함
|
|
public bool IsSegmentToggleSwitchEnabled { get => _isSegmentToggleSwitchEnabled; set => SetProperty(ref _isSegmentToggleSwitchEnabled, value); }
|
|
|
|
bool _canEstimation = false;
|
|
public bool CanEstimation { get => _canEstimation; set => SetProperty(ref _canEstimation, value); }
|
|
public bool IsROIConfigured { get; private set; }
|
|
|
|
string _model1Result = "Y [%] = a * X + b;";
|
|
public string Model1Result { get => _model1Result; set => SetProperty(ref _model1Result, value); }
|
|
string _model2Result = "Y [%] = a * X + b;";
|
|
public string Model2Result { get => _model2Result; set => SetProperty(ref _model2Result, value); }
|
|
|
|
double[] _estModelParamA = new double[2];
|
|
double[] _estModelParamB = new double[2];
|
|
|
|
private bool _isCopyFromCurrent2NewEnabled = true;
|
|
public bool IsCopyFromCurrent2NewEnabled { get => _isCopyFromCurrent2NewEnabled; set => SetProperty(ref _isCopyFromCurrent2NewEnabled, value); }
|
|
|
|
bool _isSaveApplyEnabled = false;
|
|
public bool IsSaveApplyEnabled { get => _isSaveApplyEnabled; set => SetProperty(ref _isSaveApplyEnabled, value); }
|
|
bool _isSaveAsCSVEnabled = false;
|
|
public bool IsSaveAsCSVEnabled { get => _isSaveAsCSVEnabled; set => SetProperty(ref _isSaveAsCSVEnabled, value); }
|
|
|
|
public bool IsModelSolved { get; set; } = false;
|
|
|
|
public bool IsLaserPowerMeasuring { get; set; } = false;
|
|
|
|
// DataGrid 가 clear 되면, 자동으로 바인딩된 값이 -1 이 됨
|
|
public int _newSelectedIndex = -1;
|
|
public int NewSelectedIndex {
|
|
get {
|
|
return _newSelectedIndex;
|
|
}
|
|
set {
|
|
SetProperty(ref _newSelectedIndex, value);
|
|
}
|
|
}
|
|
|
|
//=======================================
|
|
#endregion
|
|
const double _beginPercFor2ndSegmentMethod = 50.0; // 2번째 세그먼트 시작값
|
|
const double _warningPercFor2ndSegmentMethod = 25.0; // 절반 범위 (이보다 작으면 에러 처리)
|
|
|
|
private const double EPS5 = 1.0e-5;
|
|
private const int precision1 = 1;
|
|
private const int precision3 = 3;
|
|
private const int precision4 = 4;
|
|
|
|
const int delayMSForGraph = 200; // LiveCharts2 그래프 처리에 딜레이가 필요해보임 [ms]
|
|
|
|
//--- LiveCharts2 관련
|
|
private bool _isXAxisWatt;
|
|
public bool IsXAxisWatt { get => _isXAxisWatt; set => SetProperty(ref _isXAxisWatt, value); }
|
|
|
|
private bool _isAxesSwitchEnabled = true; // 테스트 : 원래는 false 로...
|
|
public bool IsAxesSwitchEnabled { get => _isAxesSwitchEnabled; set => SetProperty(ref _isAxesSwitchEnabled, value); }
|
|
|
|
// 데이터 그리드 관련
|
|
public ObservableCollection<PowerCalibData> CurrentCalibData { get; set; } = new ObservableCollection<PowerCalibData>();
|
|
public ObservableCollection<PowerCalibData> NewCalibData { get; set; } = new ObservableCollection<PowerCalibData>();
|
|
|
|
// 그래프 인스턴스
|
|
public ObservableCollection<ISeries> Series { get; set; }
|
|
// X axis
|
|
public Axis[] XAxes { get; set; }
|
|
// Y axis
|
|
public Axis[] YAxes { get; set; }
|
|
public SolidColorPaint LegendTextPaint { get; set; } = new SolidColorPaint {
|
|
Color = new SKColor(255, 255, 255)
|
|
};
|
|
|
|
private ObservableCollection<ObservablePoint> observableValuesCurrent;
|
|
private ObservableCollection<ObservablePoint> observableValuesNew;
|
|
private ObservableCollection<ObservablePoint> observableValuesPreProcessing;
|
|
private ObservableCollection<ObservablePoint> observableValuesEstimatedModel;
|
|
// Plot visible 제어용
|
|
public List<PlotCheckBox> PlotList { get; set; } = new List<PlotCheckBox>();
|
|
|
|
const string AxisXLabel = "Power [%]";
|
|
const string AxisYLabel = "Power [W]";
|
|
//======================================================================================
|
|
// 파워 교정 모델 셀렉터
|
|
public List<CalibRadioButton> CalibModelList { get; set; } = new List<CalibRadioButton>();
|
|
private CalibRadioButton _selectedCalibModelItem;
|
|
public CalibRadioButton SelectedCalibModelItem {
|
|
get {
|
|
return _selectedCalibModelItem;
|
|
}
|
|
set {
|
|
_selectedCalibModelItem = value;
|
|
}
|
|
}
|
|
|
|
IAuroraEnvironment _aurora;
|
|
|
|
LaserCompact _laser;
|
|
PowerMeter _pm30; // Wire 솔더링은 레이저는 2개이지만, 파워미터는 하나만 사용하는 것 같다
|
|
|
|
|
|
// 원본: timer_Meter; 해당 타이머의 역할을 포함함
|
|
private readonly DispatcherTimer MonitorTimer;
|
|
int delayForTimer = 50; // 디스패처 타이머 interval
|
|
|
|
Stopwatch _swMonitor = new Stopwatch();
|
|
|
|
Stopwatch _swTest = new Stopwatch();
|
|
bool _isLUKASTEST_TimerInterval = false; // 테스트 필요할 때만 true 로 켜자
|
|
//bool _isLUKASTEST_TimerInterval = true;
|
|
|
|
//===================================================
|
|
#region 생성자
|
|
// 레이저가 몇개 일지 모르니, ID 리스트를 받아야겠는데... 레이저 교체는 콤보박스를 쓰던가..
|
|
|
|
[Obsolete]
|
|
public UCLaserCompactCalibrationViewModel(IAuroraEnvironment aurora, List<string> deviceIdList, string powerMeterId)
|
|
{
|
|
CMD_VisibleChanged = new DelegateCommand(VisibleChanged);
|
|
|
|
CMD_CopyFromCurernt2New = new DelegateCommand(CopyFromCurrent2New);
|
|
|
|
CMD_LaserTargetPowerSet = new DelegateCommand(LaserTargetPowerSet);
|
|
CMD_LaserTargetPowerWattSet = new DelegateCommand(LaserTargetPowerWattSet);
|
|
|
|
CMD_PlotZoomReset = new DelegateCommand(PlotZoomReset);
|
|
CMD_AxesSwitch = new DelegateCommand(PlotAxesSwitch);
|
|
|
|
CMD_SolveModel = new DelegateCommand(SolveModel);
|
|
CMD_SetROI = new DelegateCommand(SetROI);
|
|
|
|
CMD_PowerMeterSetZero = new DelegateCommand(PowerMeterSetZero);
|
|
|
|
// --- 파워 측정
|
|
CMD_LaserPowerMeasureStart = new DelegateCommand(LaserPowerMeasureStart);
|
|
CMD_LaserPowerMeasureStop = new DelegateCommand(LaserPowerMeasureStop);
|
|
|
|
CMD_SaveApply = new DelegateCommand(SaveApply);
|
|
CMD_SaveAsCSV = new DelegateCommand(SaveAsCSV);
|
|
|
|
_aurora = aurora;
|
|
// 기본값은 wire laser 0 번
|
|
laserIDs = deviceIdList;
|
|
_laser = aurora.DeviceManager.GetDevice(laserIDs[0]) as LaserCompact;
|
|
_pm30 = aurora.DeviceManager.GetDevice(powerMeterId) as PowerMeter;
|
|
|
|
Observable.Merge(
|
|
aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_DEVICE_ACTIVATED),
|
|
aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_DEVICE_DEACTIVATED))
|
|
.Select(msg => msg.Param as string)
|
|
.Where(x => x == _laser.DeviceId).ObserveOnDispatcher()
|
|
.Subscribe(x => OnLaserDeviceActiveChanged());
|
|
|
|
Observable.Merge(
|
|
aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_DEVICE_ACTIVATED),
|
|
aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_DEVICE_DEACTIVATED))
|
|
.Select(msg => msg.Param as string)
|
|
.Where(x => x == _pm30.DeviceId).ObserveOnDispatcher()
|
|
.Subscribe(x => OnPowerMeterDeviceActiveChanged());
|
|
|
|
//int stationIndex = 0;
|
|
IsUCViewEnabled = true;
|
|
|
|
//-------------------------------------------------
|
|
observableValuesCurrent = new ObservableCollection<ObservablePoint>();
|
|
observableValuesNew = new ObservableCollection<ObservablePoint>();
|
|
observableValuesPreProcessing = new ObservableCollection<ObservablePoint>();
|
|
observableValuesEstimatedModel = new ObservableCollection<ObservablePoint>();
|
|
|
|
// 그래프 초기화
|
|
Series = new ObservableCollection<ISeries> {
|
|
new LineSeries<ObservablePoint> {
|
|
Name = "Current",
|
|
|
|
Stroke = null,
|
|
Fill = null,
|
|
GeometryFill = new SolidColorPaint(SKColors.Green), // 내부색
|
|
//GeometryStroke = new SolidColorPaint(SKColors.Green) {StrokeThickness = 1}, // 윤곽선
|
|
GeometryStroke = null,
|
|
GeometrySize = 4,
|
|
LineSmoothness = 0,
|
|
|
|
|
|
Values = observableValuesCurrent,
|
|
// Tooltip formatter: 모두 F3 로...: 축 전환시, X 축의 소수 자릿수를 바꿀 방법이 없네...
|
|
YToolTipLabelFormatter = (chartPoint) => $"X: {chartPoint.SecondaryValue}, Y: {chartPoint.PrimaryValue:F3}"
|
|
},
|
|
|
|
new LineSeries<ObservablePoint> {
|
|
Name = "New",
|
|
|
|
//Fill = new SolidColorPaint(SKColors.Tomato),
|
|
//GeometrySize = 4,
|
|
|
|
Stroke = null,
|
|
Fill = null,
|
|
GeometryFill = new SolidColorPaint(SKColors.Gold),
|
|
//GeometryStroke = new SolidColorPaint(SKColors.Tomato) {StrokeThickness = 1},
|
|
GeometryStroke = null,
|
|
GeometrySize = 4,
|
|
LineSmoothness = 0,
|
|
|
|
|
|
Values = observableValuesNew,
|
|
// Tooltip formatter
|
|
YToolTipLabelFormatter = (chartPoint) => $"X: {chartPoint.SecondaryValue}, Y: {chartPoint.PrimaryValue:F3}"
|
|
},
|
|
|
|
new LineSeries<ObservablePoint> {
|
|
Name = "Train",
|
|
|
|
//Fill = new SolidColorPaint(SKColors.Blue),
|
|
//GeometrySize = 4,
|
|
|
|
Stroke = null,
|
|
Fill = null,
|
|
GeometryFill = new SolidColorPaint(SKColors.Blue),
|
|
//GeometryStroke = new SolidColorPaint(SKColors.Blue) {StrokeThickness = 1},
|
|
GeometryStroke = null,
|
|
GeometrySize = 4,
|
|
LineSmoothness = 0,
|
|
|
|
|
|
Values = observableValuesPreProcessing,
|
|
// Tooltip formatter
|
|
YToolTipLabelFormatter = (chartPoint) => $"X: {chartPoint.SecondaryValue}, Y: {chartPoint.PrimaryValue:F3}"
|
|
},
|
|
|
|
// plot for estimated model
|
|
new LineSeries<ObservablePoint> {
|
|
Name = "Estimate",
|
|
Stroke = new SolidColorPaint(SKColors.Orange) { StrokeThickness = 1 },
|
|
//Stroke = new SolidColorPaint(SKColors.Orange),
|
|
Fill = null,
|
|
//Fill = new SolidColorPaint(SKColors.Orange),
|
|
//Stroke = null,
|
|
GeometrySize = 4,
|
|
GeometryStroke = null,
|
|
GeometryFill = new SolidColorPaint(SKColors.Red),
|
|
LineSmoothness = 0,
|
|
Values = observableValuesEstimatedModel,
|
|
// Tooltip formatter
|
|
YToolTipLabelFormatter = (chartPoint) => $"X: {chartPoint.SecondaryValue}, Y: {chartPoint.PrimaryValue:F3}"
|
|
},
|
|
};
|
|
|
|
// X axis
|
|
XAxes = new Axis[] {
|
|
new Axis {
|
|
Name = AxisXLabel,
|
|
NamePaint = new SolidColorPaint(SKColors.White),
|
|
LabelsPaint = new SolidColorPaint(SKColors.White)
|
|
}
|
|
};
|
|
YAxes = new Axis[] {
|
|
new Axis {
|
|
Name = AxisYLabel,
|
|
NamePaint = new SolidColorPaint(SKColors.White),
|
|
LabelsPaint = new SolidColorPaint(SKColors.White),
|
|
// 배경 눈금선
|
|
SeparatorsPaint = new SolidColorPaint(SKColors.LightSlateGray) {
|
|
PathEffect = new DashEffect(new float[] { 3, 3})
|
|
}
|
|
}
|
|
};
|
|
//-----------------------------------------------------
|
|
// 그래프 개수를 Checkbox 리스트에 추가하자.
|
|
var _index = 0;
|
|
foreach (var item in Series) {
|
|
PlotList.Add(new PlotCheckBox(_index, item.Name, item));
|
|
_index++;
|
|
}
|
|
|
|
// 교정모델을 RadioButton 리스트에 추가하자.
|
|
CalibModelList.Add(new CalibRadioButton(eModelType.LUT.ToString()));
|
|
CalibModelList.Add(new CalibRadioButton(eModelType.Lin1Seg.ToString()));
|
|
CalibModelList.Add(new CalibRadioButton(eModelType.Lin2Seg.ToString()));
|
|
CalibRadioButton._laser = _laser;
|
|
|
|
MonitorTimer = new DispatcherTimer();
|
|
MonitorTimer.Tick += OnMonitoring;
|
|
MonitorTimer.Interval = TimeSpan.FromMilliseconds(delayForTimer); // 50 [ms] => 20 [Hz]
|
|
}
|
|
#endregion
|
|
|
|
private void PowerMeterSetZero(object obj)
|
|
{
|
|
_setPowerMeterZero = true;
|
|
}
|
|
|
|
private void CopyFromCurrent2New(object obj)
|
|
{
|
|
Log.V("CopyFromCurrent2New is clicked");
|
|
var res = MessageUtil.ShowYesNoMessageDialog("Test Mode: Copy (⇒ new)", "Would you like to copy Current to New?");
|
|
if (res) { // 복사하기
|
|
// 1. 그래프를 Clear 하자...
|
|
//-----------------------------------------------------
|
|
// Train 과 추정치 그래프를 Off 한다.
|
|
var _index = 0;
|
|
foreach (var item in PlotList) {
|
|
if (_index > 1) {
|
|
item.IsSelected = false;
|
|
}
|
|
_index++;
|
|
}
|
|
// New 그래프도 Clear...
|
|
observableValuesNew.Clear();
|
|
observableValuesPreProcessing.Clear();
|
|
observableValuesEstimatedModel.Clear();
|
|
|
|
// 2. X 축은 Percent 으로!!!
|
|
if (IsXAxisWatt) {
|
|
PlotAxesSwitch(null);
|
|
}
|
|
// 그래프에 적용되도록 시간 지연을 준다.
|
|
Thread.Sleep(delayMSForGraph);
|
|
//==========================================
|
|
|
|
// 새로운 측정데이터 초기화
|
|
NewCalibData.Clear();
|
|
// New 그래프를 표시하자..
|
|
PlotList[1].IsSelected = true;
|
|
// 복사: Curret --> New 데이터 표
|
|
foreach (var item in CurrentCalibData) {
|
|
var _perc = item.PowerPerc;
|
|
double _wattValue = Math.Round(item.PowerWatt, precision3);
|
|
NewCalibData.Add(new PowerCalibData() {
|
|
PowerPerc = _perc,
|
|
PowerWatt = _wattValue,
|
|
Use = true
|
|
}
|
|
);
|
|
observableValuesNew.Add(new ObservablePoint(_perc, _wattValue)); // 그래프에 추가
|
|
}
|
|
// UI 컨트롤 enable 처리
|
|
SetControlEnable();
|
|
|
|
// Calib. Solve 를 disable 함
|
|
CanEstimation = false;
|
|
} else {
|
|
Log.V("CopyFromCurrent2New is cancled");
|
|
}
|
|
}
|
|
|
|
int _fileIndex = 0;
|
|
private void SaveAsCSV(object obj)
|
|
{
|
|
var recipePath = ConfigurationManager.AppSettings["DATA_PATH"];
|
|
string strFilePath = string.Format($"{recipePath}\\External\\Laser\\LaserCalData_{_laser.DeviceId}_{_fileIndex++}.csv");
|
|
|
|
if (File.Exists(strFilePath)) File.Delete(strFilePath);
|
|
string strSave = "Laser Power[%], Power Meter[W]\r\n";
|
|
string[] strSetData = new string[2];
|
|
for (int i = 0; i < NewCalibData.Count; i++) {
|
|
strSetData[0] = NewCalibData[i].PowerPerc.ToString("F1");
|
|
strSetData[1] = NewCalibData[i].PowerWatt.ToString("F3");
|
|
// 1번째 인자 strSave 를 이용해서, 문자열을 이어붙임
|
|
strSave = string.Format("{0}{1},{2}\r\n", strSave, strSetData[0], strSetData[1]);
|
|
}
|
|
File.AppendAllText(strFilePath, strSave);
|
|
|
|
MessageUtil.ShowSaveSuccessDialog();
|
|
Log.V("Save as CSV is success");
|
|
}
|
|
|
|
private void SaveApply(object obj)
|
|
{
|
|
// Power Model Enable 업데이트
|
|
stPowerModelEnable powerModelEnableInfo;
|
|
if (IsModelSolved) {
|
|
// 교정 옵션을 보고 선정...
|
|
if (IsTwoSegment) {
|
|
powerModelEnableInfo = new stPowerModelEnable(true, false, true);
|
|
} else {
|
|
powerModelEnableInfo = new stPowerModelEnable(true, true, false);
|
|
}
|
|
} else {
|
|
powerModelEnableInfo = new stPowerModelEnable(true, false, false);
|
|
}
|
|
|
|
// 다이얼로그에 LUT, ModelType 에 대한 선택지를 추가하자...
|
|
if (MessageUtilLaserCompact.ShowYesNoMessageDialogWireLaserCalib("Save & Apply", "Please select the Power Calib. type.", powerModelEnableInfo) == false) {
|
|
return;
|
|
}
|
|
//var test = MessageUtilWireLaser.ResultCalibType;
|
|
|
|
Stopwatch _sw = new Stopwatch();
|
|
string strTime = "";
|
|
try {
|
|
_sw.Restart();
|
|
|
|
IsSaveApplyEnabled = false;
|
|
|
|
if (NewCalibData.Count < 50) return;
|
|
MonitorTimer.Stop();
|
|
|
|
_laser._rawConfig.StartDelay = StartDelay;
|
|
_laser._rawConfig.MeasureDelay = MeasurementDelay;
|
|
_laser._rawConfig.StartPower = Math.Round(BeginPowerPerc, precision1);
|
|
_laser._rawConfig.EndPower = Math.Round(EndPowerPerc, precision1);
|
|
_laser._rawConfig.StepPerc = Math.Round(MeasurementStepPerc, precision1);
|
|
|
|
// 저장할 모델 선택
|
|
_laser._rawConfig.ModelType = (int)MessageUtilLaserCompact.ResultCalibType;
|
|
_laser._rawConfig.ModelParamA0 = Math.Round(_estModelParamA[0], precision3);
|
|
_laser._rawConfig.ModelParamB0 = Math.Round(_estModelParamB[0], precision3);
|
|
_laser._rawConfig.ModelParamA1 = Math.Round(_estModelParamA[1], precision3);
|
|
_laser._rawConfig.ModelParamB1 = Math.Round(_estModelParamB[1], precision3);
|
|
|
|
strTime = _sw.ElapsedMilliseconds.ToString();
|
|
string[] strSetData = new string[2];
|
|
// Config 의 PowerValue 비우기..
|
|
_laser._rawConfig.PowerValue.Clear();
|
|
foreach (var item in NewCalibData) {
|
|
_laser._rawConfig.PowerValue.Add(item.PowerPerc.ToString("F1"), item.PowerWatt);
|
|
}
|
|
|
|
// Config json 저장
|
|
_aurora.DeviceManager.SaveDeviceConfig(_laser.DeviceId);
|
|
|
|
Log.I($"[{_laser.DeviceId}] [LASER{_laser.Index}] Calibration Data Saved");
|
|
strTime = strTime + "," + _sw.ElapsedMilliseconds.ToString();
|
|
|
|
MessageUtil.ShowSaveSuccessDialog();
|
|
} catch (Exception ex) {
|
|
Log.E($"Exception: {ex.Message}");
|
|
MessageUtil.ShowSaveFailedDialog();
|
|
} finally {
|
|
_laser.LoadLaserCalibrationData(); // 레이저 설정 업데이트
|
|
RefreshCalibrationData(); // 화면 업데이트
|
|
|
|
IsSaveApplyEnabled = true;
|
|
strTime = strTime + "," + _sw.ElapsedMilliseconds.ToString();
|
|
|
|
MonitorTimer.Start();
|
|
Log.V($"Save elasped time: {strTime} [ms]");
|
|
}
|
|
}
|
|
|
|
private void CheckTwoSegmentIsValid()
|
|
{
|
|
// 예외처리...
|
|
if (observableValuesPreProcessing is null || observableValuesPreProcessing.Count == 0) {
|
|
Log.W("No Train data");
|
|
return;
|
|
}
|
|
|
|
if (IsTwoSegment) { // 2 segment
|
|
// 만약 2 segment 옵션 상태라면, Train 데이터의 시작값이 25 [%] 이상인지 검사
|
|
// 만약 넘어서면, 데이터가 충분하지 않으므로, 최적화 버튼을 비활성화한다.
|
|
if (observableValuesPreProcessing[0].Y > _warningPercFor2ndSegmentMethod) {
|
|
// first segment 의 범위가 너무 작다...!!!
|
|
Log.W("ROI for 2nd Segment method is too small");
|
|
CanEstimation = false;
|
|
} else {
|
|
CanEstimation = true;
|
|
}
|
|
} else { // 1 segment
|
|
if (IsROIConfigured) {
|
|
CanEstimation = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SetROI(object obj) // New 측정 데이터 기준, Train 그래프 표시
|
|
{
|
|
// 예외처리...
|
|
// 1. 교정할 측정 데이터가 없는 경우
|
|
if (NewCalibData is null || NewCalibData.Count == 0) {
|
|
Log.W("No New Calibration data");
|
|
return;
|
|
}
|
|
// X 축이 Watt 가 아니면, 축 스위칭한다.
|
|
if (IsXAxisWatt != true) {
|
|
PlotAxesSwitch(null);
|
|
}
|
|
|
|
// Train 그래프를 visible 로!!!
|
|
PlotList[2].IsSelected = true;
|
|
// Plot data clear
|
|
observableValuesPreProcessing.Clear();
|
|
|
|
// 1. Data 전처리
|
|
// 최적화에 사용할 유효한 데이터를 추출하자...
|
|
// ※ 보통 최적화는 train 데이터와 test 데이터로 분류하는데, 여긴 굳이 필요없으니 모든 데이터를 train 데이터로 사용한다.
|
|
var _rawData = NewCalibData;
|
|
var trainDataSet = from item in _rawData
|
|
where item.PowerWatt > Threshold
|
|
select (item.PowerWatt, item.PowerPerc);
|
|
|
|
foreach (var item in trainDataSet) {
|
|
observableValuesPreProcessing.Add(new ObservablePoint(item.PowerWatt, item.PowerPerc));
|
|
}
|
|
|
|
IsROIConfigured = true;
|
|
// 만약 2 segment 옵션 상태라면, 전처리된 데이터가 50% 을 넘어서는지 검사한다.
|
|
// 만약 넘어서면, 최적화 버튼을 비활성화한다.
|
|
CheckTwoSegmentIsValid();
|
|
}
|
|
|
|
private void SolveModel(object obj) // New 측정 데이터 기준
|
|
{
|
|
// 방어 코드 추가... Train 데이터가 없으면 취소
|
|
if (observableValuesPreProcessing.Count == 0) {
|
|
Log.E("No Train Data");
|
|
CanEstimation = false;
|
|
return;
|
|
}
|
|
|
|
// 다른 곳에서 데이터 전처리 완료됨, 여기서는 최적화만 수행한다.
|
|
// 데이터 전처리가 수행되었는지 체크한다..
|
|
if (IsROIConfigured == false) {
|
|
SetROI(null);
|
|
}
|
|
// X 축이 Watt 가 아니면, 축 스위칭한다.
|
|
if (IsXAxisWatt != true) {
|
|
PlotAxesSwitch(null);
|
|
}
|
|
|
|
// Estimate 그래프를 visible 로!!!
|
|
PlotList[3].IsSelected = true;
|
|
|
|
// 1 segment 인지 2 segment 인지 조건에 따라 최적화 수행...
|
|
if (IsTwoSegment != true) { // 1 segment
|
|
var xData = observableValuesPreProcessing.Select(x => x.X).Cast<double>().ToArray();
|
|
var yData = observableValuesPreProcessing.Select(x => x.Y).Cast<double>().ToArray();
|
|
|
|
// Math.NET Numerics 의 Fit 함수는 y = a + b*x 기준임.
|
|
// y = a * x + b 기준에 맞추기 위해서, 리턴값의 순서를 변경함
|
|
(double b, double a) linModel = Fit.Line(xData, yData);
|
|
Model1Result = $"Y [%] = {linModel.a:f3} * X + {linModel.b:f3}";
|
|
_estModelParamA[0] = linModel.a;
|
|
_estModelParamB[0] = linModel.b;
|
|
|
|
observableValuesEstimatedModel.Clear();
|
|
|
|
// 추정에서는 입력으로 [W], 출력으로 [%] 를 사용했다.
|
|
// 그러나, 그래프를 그리기 위해서는 입력은 [%], 출력을 [W] 로 계산하는게 편하다 ???
|
|
// - 왜냐하면, % 의 범위는 정해져있기때문임.... [0, 100] 이고, 0.1 단위임..
|
|
// 추정된 모델은 [%] = a * ([W]) + b 이므로, 역계산을 유도하면,
|
|
// y - b = a * x
|
|
// x = (y - b) / a | x: [W], y: [%]
|
|
int maxIter = 1000;
|
|
for (int i = 0; i <= maxIter; i++) {
|
|
var tempXPerc = i * 0.1;
|
|
double tempYWatt = (tempXPerc - linModel.b) / linModel.a;
|
|
if (tempYWatt < 0) {
|
|
tempYWatt = 0.0;
|
|
}
|
|
// 그래프 데이터에 저장: x 축: [W], y축: [%]
|
|
observableValuesEstimatedModel.Add(new ObservablePoint(Math.Round(tempYWatt, precision3), tempXPerc));
|
|
}
|
|
} else { // 2 segment
|
|
// 전처리된 데이터를 절반으로 나누자...
|
|
// Y 값이 [%] 에 해당한다.
|
|
var xData1 = observableValuesPreProcessing.Where(x => x.Y < _beginPercFor2ndSegmentMethod).Select(x => x.X).Cast<double>().ToArray();
|
|
var xData2 = observableValuesPreProcessing.Where(x => x.Y >= _beginPercFor2ndSegmentMethod).Select(x => x.X).Cast<double>().ToArray();
|
|
|
|
var yData1 = observableValuesPreProcessing.Where(x => x.Y < _beginPercFor2ndSegmentMethod).Select(x => x.Y).Cast<double>().ToArray();
|
|
var yData2 = observableValuesPreProcessing.Where(x => x.Y >= _beginPercFor2ndSegmentMethod).Select(x => x.Y).Cast<double>().ToArray();
|
|
|
|
Log.I($"Total size: {observableValuesPreProcessing.Count}, x1: {xData1.Length}, x2: {xData2.Length}");
|
|
|
|
// 모델 최적화
|
|
// Math.NET Numerics 의 Fit 함수는 y = a + b*x 기준임.
|
|
// y = a * x + b 기준에 맞추기 위해서, 리턴값의 순서를 변경함
|
|
(double b, double a) linModel1 = Fit.Line(xData1, yData1);
|
|
(double b, double a) linModel2 = Fit.Line(xData2, yData2);
|
|
Model1Result = $"Y [%] = {linModel1.a:f3} * X + {linModel1.b:f3}";
|
|
Model2Result = $"Y [%] = {linModel2.a:f3} * X + {linModel2.b:f3}";
|
|
_estModelParamA[0] = linModel1.a;
|
|
_estModelParamB[0] = linModel1.b;
|
|
_estModelParamA[1] = linModel2.a;
|
|
_estModelParamB[1] = linModel2.b;
|
|
|
|
// 그래프 비우기
|
|
observableValuesEstimatedModel.Clear();
|
|
|
|
// 추정에서는 입력으로 [W], 출력으로 [%] 를 사용했다.
|
|
// 그러나, 그래프를 그리기 위해서는 입력은 [%], 출력을 [W] 로 계산하는게 편하다 ???
|
|
// - 왜냐하면, % 의 범위는 정해져있기때문임.... [0, 100] 이고, 0.1 단위임..
|
|
// 추정된 모델은 [%] = a * ([W]) + b 이므로, 역계산을 유도하면,
|
|
// y - b = a * x
|
|
// x = (y - b) / a
|
|
// x: [W], y: [%]
|
|
int maxIter = 1000;
|
|
double tempYWatt = 0.0;
|
|
for (int i = 0; i <= maxIter; i++) {
|
|
var tempXPerc = i * 0.1;
|
|
if (tempXPerc < _beginPercFor2ndSegmentMethod) {
|
|
tempYWatt = (tempXPerc - linModel1.b) / linModel1.a;
|
|
} else {
|
|
tempYWatt = (tempXPerc - linModel2.b) / linModel2.a;
|
|
}
|
|
if (tempYWatt < 0) {
|
|
tempYWatt = 0.0;
|
|
}
|
|
// 그래프 데이터에 저장: x 축: [W], y축: [%]
|
|
observableValuesEstimatedModel.Add(new ObservablePoint(Math.Round(tempYWatt, precision3), tempXPerc));
|
|
}
|
|
}
|
|
IsModelSolved = true;
|
|
}
|
|
|
|
private void PlotAxesSwitch(object obj)
|
|
{
|
|
// X - Y 스위치
|
|
// 1. 데이터 스위칭
|
|
// 2. 축 정보 스위칭
|
|
|
|
// 모든 그래프의 축을 스위칭 !!!
|
|
foreach (var plotItem in Series) {
|
|
if (plotItem is null || plotItem.Values is null) {
|
|
continue;
|
|
}
|
|
foreach (ObservablePoint plotData in plotItem.Values) {
|
|
var _tempX = plotData.X;
|
|
plotData.X = plotData.Y;
|
|
plotData.Y = _tempX;
|
|
}
|
|
}
|
|
|
|
// 축 정보 바꾸기
|
|
IsXAxisWatt = !IsXAxisWatt;
|
|
if (IsXAxisWatt) { // X - Y 스위칭
|
|
XAxes[0].Name = AxisYLabel;
|
|
YAxes[0].Name = AxisXLabel;
|
|
} else { // 복원
|
|
XAxes[0].Name = AxisXLabel;
|
|
YAxes[0].Name = AxisYLabel;
|
|
}
|
|
}
|
|
|
|
private void PlotZoomReset(object obj)
|
|
{
|
|
// X, Y zoom, panning reset
|
|
XAxes[0].MaxLimit = null;
|
|
XAxes[0].MinLimit = null;
|
|
|
|
YAxes[0].MaxLimit = null;
|
|
YAxes[0].MinLimit = null;
|
|
}
|
|
|
|
// 레이저가 변경될 때마다 호출 ???
|
|
private void DrawCalibrationData(eInitBy condition)
|
|
{
|
|
try {
|
|
// LUKAS: Setting 부분
|
|
StartDelay = _laser._rawConfig.StartDelay;
|
|
MeasurementDelay = _laser._rawConfig.MeasureDelay;
|
|
BeginPowerPerc = _laser._rawConfig.StartPower;
|
|
EndPowerPerc = _laser._rawConfig.EndPower;
|
|
MeasurementStepPerc = _laser._rawConfig.StepPerc;
|
|
|
|
// 교정모델 셀렉터 뷰 세팅
|
|
UpdateCalibModelSelector();
|
|
|
|
// 기존 측정표 비우기
|
|
CurrentCalibData.Clear();
|
|
|
|
switch (condition) {
|
|
case eInitBy.LaserActivate: {
|
|
// LUKAS 추가.....New 데이터가 있다면, 그래프 표시하기
|
|
if (NewCalibData.Count > 50) { // 50 개 정도면 되지 않을까?
|
|
// New 그래프를 표시하자..
|
|
PlotList[1].IsSelected = true;
|
|
foreach (var item in NewCalibData) {
|
|
observableValuesNew.Add(new ObservablePoint(item.PowerPerc, item.PowerWatt));
|
|
}
|
|
// New 데이터가 존재한다면, Calibration 이 가능하도록 뷰 컨트롤 제어...
|
|
SetControlEnable();
|
|
}
|
|
|
|
break;
|
|
}
|
|
case eInitBy.LaserChange: {
|
|
// New 측정 데이터를 지운다
|
|
NewCalibData.Clear();
|
|
|
|
// New 데이터가 지워지면, 관련 컨트롤을 disabled 처리한다.
|
|
IsThresholdSetEnabled = CanEstimation = false;
|
|
IsSaveApplyEnabled = IsSaveAsCSVEnabled = false;
|
|
|
|
// 기존 데이터만 읽어와서 표 및 그래프에 표시하자 (아래에 공통으로)
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Current 그래프를 표시하자..
|
|
PlotList[0].IsSelected = true;
|
|
var _powerCalDataArr = _laser.GetPowerCalData();
|
|
foreach (var item in _powerCalDataArr) {
|
|
var _perc = Math.Round(item.perc, precision1);
|
|
double _wattValue = Math.Round(item.watt, precision3); // LUKAS: ini 에서 읽어온 정보, 4자리수 정확도
|
|
CurrentCalibData.Add(new PowerCalibData() {
|
|
PowerPerc = _perc,
|
|
PowerWatt = _wattValue,
|
|
Use = true
|
|
}
|
|
);
|
|
|
|
observableValuesCurrent.Add(new ObservablePoint(_perc, _wattValue)); // 그래프에 추가
|
|
}
|
|
} catch (Exception ex) {
|
|
Log.E($"Exception: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
void UpdateCalibModelSelector()
|
|
{
|
|
// 레이저가 변경된건지 검사
|
|
if (string.Equals(CalibRadioButton._laser.DeviceId, _laser.DeviceId) == false) {
|
|
// 레이저 업데이트
|
|
CalibRadioButton._laser = _laser;
|
|
}
|
|
|
|
string curretPowerCalibType = _laser.GetLaserPowerCalibMethod();
|
|
foreach (var item in CalibModelList) {
|
|
if (string.Equals(item.Id, curretPowerCalibType)) {
|
|
// _1. 이 라디오 버튼 선택 및 활성화
|
|
item.IsEnabledRadio = true;
|
|
item.IsSelected = true;
|
|
|
|
switch (item.Id) {
|
|
case "LUT": {
|
|
// 나머지 라디오버튼 비활성화
|
|
CalibModelList[1].IsEnabledRadio = false;
|
|
CalibModelList[2].IsEnabledRadio = false;
|
|
|
|
break;
|
|
}
|
|
case "Lin1Seg": {
|
|
// LUT 활성화, 다른 하나는 비활성화
|
|
CalibModelList[0].IsEnabledRadio = true;
|
|
CalibModelList[2].IsEnabledRadio = false;
|
|
|
|
break;
|
|
}
|
|
case "Lin2Seg": {
|
|
// LUT 활성화, 다른 하나는 비활성화
|
|
CalibModelList[0].IsEnabledRadio = true;
|
|
CalibModelList[1].IsEnabledRadio = false;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 매칭을 찾았으므로, 나간다.
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// LUKAS: 측정된 데이터를 ini 파일에 저장후 ini 파일 정보로, 화면을 다시 업데이트함
|
|
// 즉, New 데이터가 Current 데이터가 됨..
|
|
private void RefreshCalibrationData()
|
|
{
|
|
try {
|
|
StartDelay = _laser._rawConfig.StartDelay;
|
|
MeasurementDelay = _laser._rawConfig.MeasureDelay;
|
|
BeginPowerPerc = _laser._rawConfig.StartPower;
|
|
EndPowerPerc = _laser._rawConfig.EndPower;
|
|
MeasurementStepPerc = _laser._rawConfig.StepPerc;
|
|
|
|
// 레이저 교정 모델 타입 업데이트
|
|
UpdateCalibModelSelector();
|
|
|
|
// Current 그래프 수정...
|
|
observableValuesCurrent.Clear();
|
|
// Current 데이터 그리드 수정
|
|
CurrentCalibData.Clear();
|
|
|
|
// X 축은 [W] 로...
|
|
if (IsXAxisWatt != true) {
|
|
PlotAxesSwitch(null);
|
|
}
|
|
// 딜레이 주기..
|
|
Thread.Sleep(delayMSForGraph);
|
|
// 흠, New 데이터 그리드 정보를 이용해도 될 것 같긴 하지만...
|
|
var _powerCalDataArr = _laser.GetPowerCalData();
|
|
foreach (var item in _powerCalDataArr) {
|
|
var _perc = item.perc;
|
|
double _wattValue = Math.Round(item.watt, precision3); // LUKAS: ini 에서 읽어온 정보
|
|
CurrentCalibData.Add(new PowerCalibData() {
|
|
PowerPerc = _perc,
|
|
PowerWatt = _wattValue,
|
|
Use = true
|
|
}
|
|
);
|
|
// X 축: [W]
|
|
observableValuesCurrent.Add(new ObservablePoint(_wattValue, _perc));
|
|
}
|
|
} catch (Exception ex) {
|
|
Log.E($"Exception: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void InitByLaserActivate()
|
|
{
|
|
if (_laser.IsActivated) {
|
|
// 기존 및 (있을 경우) New 데이터만 표시
|
|
InitGraph(eInitBy.LaserActivate);
|
|
// 일단 그래프는 다 지웠다.
|
|
|
|
DrawCalibrationData(eInitBy.LaserActivate);
|
|
|
|
if (_pm30.IsActivated != true) {
|
|
_pm30.Activate();
|
|
PowerMeterInfo = "Activated";
|
|
}
|
|
}
|
|
}
|
|
|
|
private void InitByLaserChange()
|
|
{
|
|
if (_laser.IsActivated) {
|
|
// 모든 그래프 Clear
|
|
InitGraph(eInitBy.LaserChange);
|
|
// 기존 측정 데이터만 표시
|
|
DrawCalibrationData(eInitBy.LaserChange);
|
|
|
|
if (_pm30.IsActivated != true) {
|
|
_pm30.Activate();
|
|
PowerMeterInfo = "Activated";
|
|
}
|
|
}
|
|
IsModelSolved = false;
|
|
}
|
|
|
|
private void InitByVisibleChange()
|
|
{
|
|
// 화면 전환인 경우는 그래프는 그대로 두자
|
|
InitGraph(eInitBy.VisibleChange);
|
|
|
|
// 화면이 전환되었을 뿐이니, 다시 그릴 필요는 없을 것 같지만, 장치는 활성화 되어 있는데, 처음 뷰에 진입시의 드로잉은 필요하다!!
|
|
if (CurrentCalibData.Count == 0) { // 만약 기존 데이터그리드가 비어있는 경우, 화면을 그리자 !!!
|
|
DrawCalibrationData(eInitBy.VisibleChange);
|
|
}
|
|
|
|
if (_pm30.IsActivated != true) {
|
|
_pm30.Activate();
|
|
PowerMeterInfo = "Activated";
|
|
}
|
|
}
|
|
|
|
private void InitGraph(eInitBy condition)
|
|
{
|
|
switch (condition) {
|
|
case eInitBy.LaserActivate:
|
|
case eInitBy.LaserChange: {
|
|
// 그래프를 모두 clear 할거면, visible 체크박스도 해제하자...
|
|
foreach (var item in PlotList) {
|
|
item.IsSelected = false;
|
|
}
|
|
// 일단 그래프 모두 지우기 ??
|
|
observableValuesCurrent.Clear();
|
|
observableValuesNew.Clear();
|
|
observableValuesPreProcessing.Clear();
|
|
observableValuesEstimatedModel.Clear();
|
|
|
|
// X 축은 [%] 로...
|
|
if (IsXAxisWatt) {
|
|
PlotAxesSwitch(null);
|
|
}
|
|
|
|
Log.V("Graph is cleared");
|
|
// 딜레이를 줘보자..
|
|
// 파워 교정 후 레이저 변경 시, 그래프가 남아 있는 문제가 있음...LiveCharts2 도 이벤트(?) 방식이라, 대기가 필요한 것 같아보임
|
|
Thread.Sleep(delayMSForGraph);
|
|
|
|
break;
|
|
}
|
|
|
|
case eInitBy.VisibleChange: { // 화면 전환인 경우, 아무런 처리를 하지 않는다.
|
|
// Nothing
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnLaserDeviceActiveChanged()
|
|
{
|
|
if (_laser.IsActivated) {
|
|
IsEnabled = true;
|
|
|
|
if (IsVisible) {
|
|
InitByLaserActivate();
|
|
}
|
|
} else {
|
|
IsEnabled = false;
|
|
|
|
// 메모리 해제를 여기에 넣을까??
|
|
// invisible 상태에서만 메모리를 해제하자!!
|
|
if (IsVisible == false) {
|
|
InitGraph(eInitBy.LaserActivate);
|
|
// 데이터 그리드에 있는 데이터도 해제하자..
|
|
if (NewCalibData.Count != 0) {
|
|
NewCalibData.Clear();
|
|
}
|
|
if (CurrentCalibData.Count != 0) {
|
|
CurrentCalibData.Clear();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnPowerMeterDeviceActiveChanged()
|
|
{
|
|
if (_pm30.IsActivated) {
|
|
PowerMeterInfo = "Activated";
|
|
} else {
|
|
PowerMeterInfo = "Not activated";
|
|
}
|
|
}
|
|
|
|
private void OnMonitoring(object sender, EventArgs e)
|
|
{
|
|
if (!_laser.IsActivated) {
|
|
return;
|
|
}
|
|
|
|
UpdateActualLaserPower();
|
|
UpdatePowerMeter();
|
|
|
|
// 파워 측정 프로세스
|
|
LaserPowerMeasuring();
|
|
|
|
// LUKAS_TEST : 타이머 시간 모니터링
|
|
if (_isLUKASTEST_TimerInterval) {
|
|
Debug.WriteLine($"OnMonitoring: {_swTest.ElapsedMilliseconds} [ms] elapsed");
|
|
_swTest.Restart();
|
|
}
|
|
}
|
|
|
|
private async void LaserPowerMeasureStart(object obj)
|
|
{
|
|
if (!_pm30.IsActivated || PowerMeterInfo.Length < 1) {
|
|
Log.E("PowerMeter Connection Error");
|
|
return;
|
|
}
|
|
|
|
// 파워 측정이 시작되었으므로, 특정 view 컨트롤을 비활성화!!
|
|
SetControlEnable(false);
|
|
|
|
//-----------------------------------------------------
|
|
// 그래프 처리 관련
|
|
// Train 과 추정치 그래프를 Off 한다.
|
|
var _index = 0;
|
|
foreach (var item in PlotList) {
|
|
if (_index > 1) {
|
|
item.IsSelected = false;
|
|
}
|
|
_index++;
|
|
}
|
|
// New 그래프도 초기화...
|
|
observableValuesNew.Clear();
|
|
observableValuesPreProcessing.Clear();
|
|
observableValuesEstimatedModel.Clear();
|
|
|
|
// 2. X 축은 Percent 으로!!!
|
|
if (IsXAxisWatt) {
|
|
PlotAxesSwitch(null);
|
|
}
|
|
|
|
// 타이머의 인터벌을 측정 딜레이로 변경하자.
|
|
MonitorTimer.Interval = TimeSpan.FromMilliseconds(MeasurementDelay);
|
|
|
|
// 그래프가 적용되도록 시간 지연을 준다.
|
|
//Thread.Sleep(delayMSForGraph);
|
|
await Task.Delay(delayMSForGraph); // UI 스레드로 리턴되고, 여기서 대기..
|
|
//==========================================
|
|
// LUKAS: 새로운 측정데이터 초기화
|
|
NewCalibData.Clear();
|
|
|
|
// 차라리 시작 딜레이를 여기에 넣자..!!!
|
|
LaserTargetPower = BeginPowerPerc;
|
|
_laser.SetOutputPower(LaserTargetPower);
|
|
await Task.Delay(StartDelay);
|
|
|
|
// async/await 에 의해서, UI 스레드 동기화가 이뤄짐..
|
|
powerCalibPhase = ePowerCalibPhase.Init; // 시작
|
|
|
|
//--- 메뉴 전환 불가하도록 !!!
|
|
SetMenuEnable(false);
|
|
|
|
IsLaserPowerMeasuring = true;
|
|
}
|
|
|
|
private void SetMenuEnable(bool IsEnabled)
|
|
{
|
|
/*
|
|
if (IsEnabled) {
|
|
_aurora.EventPublisher.Publish(new EventMessage("", AuroraSysEvent.SYS_REQ_MAIN_MENU_ENABLE, IsEnabled));
|
|
} else {
|
|
_aurora.EventPublisher.Publish(new EventMessage("", AuroraSysEvent.SYS_REQ_MAIN_MENU_ENABLE, IsEnabled));
|
|
}
|
|
*/
|
|
_aurora.EventPublisher.Publish(new EventMessage("", AuroraSysEvent.SYS_REQ_MAIN_MENU_ENABLE, IsEnabled));
|
|
}
|
|
|
|
// 여기 UI 스레드인 것 같다.
|
|
// 여기서 Thread.Sleep 를 걸어버리면, DispatcherTimer 도 같이 블럭이 걸리는 문제 발생
|
|
// 일단 async/await 로 대기 사용
|
|
private async void LaserPowerMeasureStop(object obj)
|
|
{
|
|
// 중복으로 못 누르도록 비활성화..
|
|
IsLaserPowerMeasureStopEnabled = false;
|
|
// 파워 측정 버튼의 비동기 로직이 끝날때까지 대기 후, 진행 필요
|
|
// 즉, LaserPowerMeasureStart 메서드의 마지막 코드가 실행될 때까지 대기
|
|
while (IsLaserPowerMeasuring == false) {
|
|
await Task.Delay(delayForTimer);
|
|
}
|
|
if (IsLaserPowerMeasuring) {
|
|
// 타이머에서 파워 측정 종료하도록 flag 세팅
|
|
IsLaserPowerMeasuring = false;
|
|
// 파워 측정이 정상 종료될때까지 대기...
|
|
while (powerCalibPhase != ePowerCalibPhase.Idle) {
|
|
//Thread.Sleep(10);
|
|
await Task.Delay(delayForTimer);
|
|
}
|
|
|
|
// 레이저 파워를 0 으로 초기화...
|
|
LaserTargetPower = 0.0;
|
|
_laser.SetOutputPower(LaserTargetPower);
|
|
|
|
//--- 메뉴 전환 가능하도록 !!!
|
|
SetMenuEnable(true);
|
|
}
|
|
|
|
// 타이머의 인터벌을 원복하자
|
|
MonitorTimer.Interval = TimeSpan.FromMilliseconds(delayForTimer);
|
|
|
|
IsLaserTargetPowerSetEnabled = IsLaserTargetPowerWattSetEnabled = true;
|
|
IsLaserPowerMeasureStartEnabled = IsLaserPowerMeasureStopEnabled = true;
|
|
|
|
// 측정 정지 버튼 비활성화
|
|
IsLaserPowerMeasureStopEnabled = false;
|
|
// 측정 시작에 의해 변경된 뷰 컨트롤 enable 상태 복구
|
|
SetControlEnable();
|
|
|
|
powerCalibPhase = ePowerCalibPhase.Idle;
|
|
}
|
|
|
|
double fPowerMeterValue = 0.0;
|
|
//private async void LaserPowerMeasuring()
|
|
private void LaserPowerMeasuring()
|
|
{
|
|
// 측정 도중에 취소 대응
|
|
// - 파워 측정 도중 취소 버튼이 눌렸을 때!!
|
|
if (IsLaserPowerMeasuring == false) {
|
|
//_laser.SetOutputPower(0.0);
|
|
powerCalibPhase = ePowerCalibPhase.Idle;
|
|
|
|
return;
|
|
}
|
|
|
|
if (_pm30.IsActivated != true) {
|
|
Log.E("PowerMeter is not activated");
|
|
powerCalibPhase = ePowerCalibPhase.Finish;
|
|
} else {
|
|
fPowerMeterValue = Math.Round(PowerMeterWatt, precision3);
|
|
}
|
|
|
|
switch (powerCalibPhase) {
|
|
case ePowerCalibPhase.Idle: // 대기 상태
|
|
|
|
break;
|
|
case ePowerCalibPhase.Init: // Start
|
|
// 파워 측정이 시작되었으므로, 특정 view 컨트롤을 비활성화!! // 시작 버튼으로 이동...
|
|
//SetControlEnable(false);
|
|
|
|
// New 그래프를 표시하자...
|
|
PlotList[1].IsSelected = true;
|
|
|
|
// LUKAS: 윈도우의 시작 Power 값 (설정값)
|
|
LaserTargetPower = BeginPowerPerc;
|
|
_laser.SetOutputPower(LaserTargetPower);
|
|
// 시작 딜레이...
|
|
//Thread.Sleep(StartDelay);
|
|
//await Task.Delay(StartDelay); // 시작 딜레이는 굳이 필요 없을 것 같은데...?
|
|
powerCalibPhase = ePowerCalibPhase.Collecting;
|
|
|
|
break;
|
|
case ePowerCalibPhase.Collecting: // Measure Data Add
|
|
//Thread.Sleep(MeasurementDelay);
|
|
NewCalibData.Add(
|
|
new PowerCalibData() {
|
|
PowerPerc = LaserTargetPower,
|
|
PowerWatt = fPowerMeterValue,
|
|
Use = true
|
|
}
|
|
);
|
|
// 데이터 그리드를 추가한 행을 보이도록 해보자...될까..??
|
|
++NewSelectedIndex;
|
|
|
|
// New 그래프에 추가
|
|
observableValuesNew.Add(new ObservablePoint(LaserTargetPower, fPowerMeterValue));
|
|
|
|
// LUKAS: 종료조건: 교정범위 마지막에 도달했는지 여부
|
|
// 소수점 첫째자리까지만: xx.x
|
|
// 실수 비교이므로, eps 추가..
|
|
if (Math.Round(LaserTargetPower, 1) + EPS5 >= EndPowerPerc) powerCalibPhase = ePowerCalibPhase.Finish;
|
|
else powerCalibPhase = ePowerCalibPhase.LaserPowerSet;
|
|
break;
|
|
case ePowerCalibPhase.LaserPowerSet: // Laser Send
|
|
LaserTargetPower = Math.Round(LaserTargetPower + MeasurementStepPerc, precision1);
|
|
_laser.SetOutputPower(LaserTargetPower);
|
|
powerCalibPhase = ePowerCalibPhase.Collecting;
|
|
// 파워미터값이 switch 문 밖에 있으므로, 측정 딜레이는 여기로 와야 함
|
|
//Thread.Sleep(MeasurementDelay);
|
|
//await Task.Delay(MeasurementDelay); // 타이머 인터벌로 커버해야 할 것 같은데....
|
|
break;
|
|
case ePowerCalibPhase.Finish: // Flow End
|
|
_laser.SetOutputPower(0.0);
|
|
SetControlEnable();
|
|
powerCalibPhase = ePowerCalibPhase.Idle;
|
|
|
|
// 타이머의 인터벌을 원복하자
|
|
MonitorTimer.Interval = TimeSpan.FromMilliseconds(delayForTimer);
|
|
|
|
if (IsLaserPowerMeasuring) {
|
|
//--- 메뉴 전환 가능하도록 !!!
|
|
SetMenuEnable(true);
|
|
|
|
IsLaserPowerMeasuring = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void UpdateActualLaserPower()
|
|
{
|
|
// LUKAS: 팝업 윈도우의 Power Meter 부분: 레이저의 power 값을 가져옴
|
|
if (!_laser.IsNetworkError()) {
|
|
var _laserStatus = _laser.GetLaserStatusInfo();
|
|
LaserActualPower = _laserStatus.PowerValue * 0.1;
|
|
} else {
|
|
LaserActualPower = 0.0;
|
|
}
|
|
}
|
|
|
|
private void UpdatePowerMeter()
|
|
{
|
|
if (!_swMonitor.IsRunning) _swMonitor.Restart();
|
|
|
|
if (_pm30.IsActivated && !_pm30.IsCommError) { // 정상 상태
|
|
// LUKAS: calibration 팝업 윈도우의 Power Meter 부분 : 이거 연결 초기 부분으로 이동시키는게 좋을 것 같긴 한데...
|
|
// Activate 코드 아래에 배치하면 될 것 같음...
|
|
//PowerMeterInfo = string.Format("[ {0} ] {1}", _pm30.ComPort, _pm30.SerialNo);
|
|
IsPowerMeterZeroEnabled = IsLaserPowerMeasuring ? false : true;
|
|
|
|
// Sensor Zero
|
|
if (_setPowerMeterZero) {
|
|
_pm30.SetZero();
|
|
_setPowerMeterZero = false;
|
|
}
|
|
|
|
// Query 현재 파워
|
|
if (_pm30.IsSimul) { // 시뮬 모드
|
|
// 레이저 actual power 를 넣어서, [W] 로 변환..
|
|
// 구현한 모델은 Y [%] = a * [W] + b 이므로,
|
|
var _laserActualPowerWatt = _laser.Power2Watt(LaserActualPower);
|
|
_pm30.SimSetActualPowerWatt(_laserActualPowerWatt);
|
|
}
|
|
|
|
// LUKAS: 파워 미터의 파워값 [W]
|
|
_pm30.Measure(out double measuredPower);
|
|
PowerMeterWatt = measuredPower;
|
|
|
|
_swMonitor.Restart();
|
|
} else { // 파워미터 에러 상태....
|
|
//PowerMeterInfo = "";
|
|
PowerMeterWatt = 0.0;
|
|
IsPowerMeterZeroEnabled = false;
|
|
|
|
if (_pm30.IsActivated == false) {
|
|
PowerMeterInfo = "Not activated";
|
|
} else {
|
|
PowerMeterInfo = "ComPort Error";
|
|
}
|
|
// 1초는 너무 빈번한거 같으니, 4초로 변경...
|
|
if (_swMonitor.ElapsedMilliseconds >= 4000) {
|
|
if (_pm30.IsActivated == false) {
|
|
_pm30.Activate();
|
|
PowerMeterInfo = "Activated";
|
|
}
|
|
// DeviceMonitoring 쪽으로 옮길까 하다가, PowerMeterInfo 업데이트때문에 여기에 처리함...
|
|
else {
|
|
var res = _pm30.Reconnect();
|
|
if (res) {
|
|
PowerMeterInfo = "Activated";
|
|
Log.I("ResetComPortError success");
|
|
} else {
|
|
Log.E("ResetComPortError failed");
|
|
}
|
|
}
|
|
|
|
_swMonitor.Restart();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void VisibleChanged(object obj) // 화면 전환 시 동작하는 곳
|
|
{
|
|
IsVisible = (bool)obj;
|
|
if (IsVisible) {
|
|
Log.D("Visible: True");
|
|
|
|
if (_laser.IsActivated) {
|
|
InitByVisibleChange();
|
|
}
|
|
|
|
// 파워미터 정보
|
|
if (_pm30.IsActivated == false) {
|
|
PowerMeterInfo = "Not activated";
|
|
}
|
|
|
|
// 초기화...
|
|
if (MonitorTimer.IsEnabled == false) {
|
|
MonitorTimer.Start();
|
|
}
|
|
} else {
|
|
Log.D("Visible: False");
|
|
|
|
if (MonitorTimer.IsEnabled) {
|
|
MonitorTimer.Stop();
|
|
}
|
|
|
|
// 화면에 안보이니, 그래프 등 리소스를 없애야 할까???
|
|
if (_swMonitor.IsRunning) {
|
|
_swMonitor.Stop();
|
|
}
|
|
if (_swTest.IsRunning) {
|
|
_swTest.Stop();
|
|
}
|
|
|
|
// 파워 교정이 종료되면, 파워미터를 비활성화
|
|
if (_pm30.IsActivated) {
|
|
_pm30.Deactivate();
|
|
PowerMeterInfo = "Not activated";
|
|
IsPowerMeterZeroEnabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 버튼 조작
|
|
private void LaserTargetPowerWattSet(object obj)
|
|
{
|
|
// [W] --> [%] 변환
|
|
LaserTargetPower = _laser.Watt2Power(LaserTargetPowerWatt);
|
|
_laser.SetOutputPower(LaserTargetPower);
|
|
Log.I($"[{_laser.DeviceId}] : [{LaserTargetPowerWatt:f1} [W] ({LaserTargetPower:f1} [%])]");
|
|
}
|
|
|
|
private void LaserTargetPowerSet(object obj)
|
|
{
|
|
_laser.SetOutputPower(LaserTargetPower);
|
|
Log.I($"[{_laser.DeviceId}] : [{LaserTargetPower:f1}]");
|
|
}
|
|
|
|
// 주로 파워 측정 중
|
|
private void SetControlEnable(bool IsEnable = true)
|
|
{
|
|
if (IsEnable) {
|
|
IsLaserTargetPowerSetEnabled = true;
|
|
IsLaserTargetPowerWattSetEnabled = true;
|
|
IsLaserPowerMeasureStartEnabled = true;
|
|
IsLaserPowerMeasureStopEnabled = false;
|
|
|
|
IsLaserDeviceSwitchEnabled = true;
|
|
|
|
IsSaveApplyEnabled = IsSaveAsCSVEnabled = true;
|
|
IsThresholdSetEnabled = IsSegmentToggleSwitchEnabled = true;
|
|
IsAxesSwitchEnabled = true;
|
|
|
|
IsCopyFromCurrent2NewEnabled = true;
|
|
} else { // 비활성화
|
|
IsLaserTargetPowerSetEnabled = false;
|
|
IsLaserTargetPowerWattSetEnabled = false;
|
|
IsLaserPowerMeasureStartEnabled = false;
|
|
IsLaserPowerMeasureStopEnabled = true;
|
|
|
|
// 레이저 교체 불가
|
|
IsLaserDeviceSwitchEnabled = false;
|
|
|
|
IsSaveApplyEnabled = IsSaveAsCSVEnabled = false;
|
|
|
|
IsThresholdSetEnabled = IsSegmentToggleSwitchEnabled = false;
|
|
// 축 스위칭 불가
|
|
IsAxesSwitchEnabled = false;
|
|
|
|
IsCopyFromCurrent2NewEnabled = false;
|
|
|
|
// Calib Solver 버튼
|
|
CanEstimation = false;
|
|
}
|
|
}
|
|
}
|
|
}
|