添加 MX-PD-盘古 项目文件
将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
using MainShell.Common.Display.ViewModel;
|
||||
using MainShell.Models;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public abstract class CameraBaseViewModel : BaseScreen
|
||||
{
|
||||
protected CameraAxisViewModel _cameraAxisViewModel;
|
||||
|
||||
public CameraAxisViewModel CameraAxisViewModel
|
||||
{
|
||||
get { return _cameraAxisViewModel; }
|
||||
set { SetAndNotify(ref _cameraAxisViewModel, value); }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class CarrierInfo : PropertyChangedBase, IParameterItem
|
||||
{
|
||||
private double _width;
|
||||
public double Width
|
||||
{
|
||||
get { return _width; }
|
||||
set { SetAndNotify(ref _width, value); }
|
||||
}
|
||||
private double _height;
|
||||
public double Height
|
||||
{
|
||||
get { return _height; }
|
||||
set { SetAndNotify(ref _height, value); }
|
||||
}
|
||||
private double _thickness;
|
||||
|
||||
public double Thickness
|
||||
{
|
||||
get { return _thickness; }
|
||||
set { SetAndNotify(ref _thickness, value); }
|
||||
}
|
||||
|
||||
|
||||
public IParameterItem Clone()
|
||||
{
|
||||
return this.MemberwiseClone() as IParameterItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class LogisticsParameterItem : PropertyChangedBase, IParameterItem
|
||||
{
|
||||
private double _cv1Width;
|
||||
public double CV1Width
|
||||
{
|
||||
get { return _cv1Width; }
|
||||
set { SetAndNotify(ref _cv1Width, value); }
|
||||
}
|
||||
private double _cv2Width;
|
||||
public double CV2Width
|
||||
{
|
||||
get { return _cv2Width; }
|
||||
set { SetAndNotify(ref _cv2Width, value); }
|
||||
}
|
||||
private double _cv3Width;
|
||||
public double CV3Width
|
||||
{
|
||||
get { return _cv3Width; }
|
||||
set { SetAndNotify(ref _cv3Width, value); }
|
||||
}
|
||||
public IParameterItem Clone()
|
||||
{
|
||||
return this.MemberwiseClone() as IParameterItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class SubstrateSelectInfo : PropertyChangedBase, IParameterItem
|
||||
{
|
||||
private string _substrateName;
|
||||
public string SubstrateName
|
||||
{
|
||||
get { return _substrateName; }
|
||||
set { SetAndNotify(ref _substrateName, value); }
|
||||
}
|
||||
private string _description;
|
||||
public string Description
|
||||
{
|
||||
get { return _description; }
|
||||
set { SetAndNotify(ref _description, value); }
|
||||
}
|
||||
|
||||
|
||||
public IParameterItem Clone()
|
||||
{
|
||||
return MemberwiseClone() as IParameterItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using MwFramework.ManagerService;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public sealed class CarrierRecipe : RecipeBase
|
||||
{
|
||||
public override string Dir => System.IO.Path.Combine(MainShell.Filewritable.Paths.CarrierRecipe, RecipeName);
|
||||
|
||||
private ObservableCollection<SubstrateSelectInfo> _substrateSelectInfos = new ObservableCollection<SubstrateSelectInfo>();
|
||||
public ObservableCollection<SubstrateSelectInfo> SubstrateSelectInfos
|
||||
{
|
||||
get { return _substrateSelectInfos; }
|
||||
set { SetProperty(ref _substrateSelectInfos, value); }
|
||||
}
|
||||
|
||||
private LogisticsParameterItem _logisticsParameterItem = new LogisticsParameterItem();
|
||||
|
||||
public LogisticsParameterItem LogisticsParameterItem
|
||||
{
|
||||
get { return _logisticsParameterItem; }
|
||||
set { SetProperty(ref _logisticsParameterItem, value); }
|
||||
}
|
||||
|
||||
private CarrierInfo _carrierInfo=new CarrierInfo();
|
||||
|
||||
public CarrierInfo CarrierInfo
|
||||
{
|
||||
get { return _carrierInfo; }
|
||||
set { SetProperty(ref _carrierInfo, value); }
|
||||
}
|
||||
|
||||
|
||||
public CarrierRecipe(string name) : base(name)
|
||||
{
|
||||
}
|
||||
public CarrierRecipe():base()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 晶圆信息。
|
||||
/// </summary>
|
||||
public class WaferInfo : PropertyChangedBase, IParameterItem
|
||||
{
|
||||
private string _dieColor;
|
||||
|
||||
/// <summary>
|
||||
/// 芯片颜色。
|
||||
/// </summary>
|
||||
public string DieColor
|
||||
{
|
||||
get { return _dieColor; }
|
||||
set { SetAndNotify(ref _dieColor, value); }
|
||||
}
|
||||
|
||||
private double _dieSizeX;
|
||||
|
||||
/// <summary>
|
||||
/// 芯片 X 尺寸。
|
||||
/// </summary>
|
||||
public double DieSizeX
|
||||
{
|
||||
get { return _dieSizeX; }
|
||||
set { SetAndNotify(ref _dieSizeX, value); }
|
||||
}
|
||||
|
||||
private double _dieSizeY;
|
||||
|
||||
/// <summary>
|
||||
/// 芯片 Y 尺寸。
|
||||
/// </summary>
|
||||
public double DieSizeY
|
||||
{
|
||||
get { return _dieSizeY; }
|
||||
set { SetAndNotify(ref _dieSizeY, value); }
|
||||
}
|
||||
|
||||
private double _diePitchX;
|
||||
|
||||
/// <summary>
|
||||
/// 芯片 X 间距。
|
||||
/// </summary>
|
||||
public double DiePitchX
|
||||
{
|
||||
get { return _diePitchX; }
|
||||
set { SetAndNotify(ref _diePitchX, value); }
|
||||
}
|
||||
|
||||
private double _diePitchY;
|
||||
|
||||
/// <summary>
|
||||
/// 芯片 Y 间距。
|
||||
/// </summary>
|
||||
public double DiePitchY
|
||||
{
|
||||
get { return _diePitchY; }
|
||||
set { SetAndNotify(ref _diePitchY, value); }
|
||||
}
|
||||
|
||||
private double _waferWidth;
|
||||
|
||||
/// <summary>
|
||||
/// 晶圆宽度。
|
||||
/// </summary>
|
||||
public double WaferWidth
|
||||
{
|
||||
get { return _waferWidth; }
|
||||
set { SetAndNotify(ref _waferWidth, value); }
|
||||
}
|
||||
|
||||
private double _waferHeight;
|
||||
|
||||
/// <summary>
|
||||
/// 晶圆高度。
|
||||
/// </summary>
|
||||
public double WaferHeight
|
||||
{
|
||||
get { return _waferHeight; }
|
||||
set { SetAndNotify(ref _waferHeight, value); }
|
||||
}
|
||||
|
||||
private int _waferRowNum;
|
||||
|
||||
/// <summary>
|
||||
/// 晶圆行数。
|
||||
/// </summary>
|
||||
public int WaferRowNum
|
||||
{
|
||||
get { return _waferRowNum; }
|
||||
set { SetAndNotify(ref _waferRowNum, value); }
|
||||
}
|
||||
|
||||
private int _waferColNum;
|
||||
|
||||
/// <summary>
|
||||
/// 晶圆列数。
|
||||
/// </summary>
|
||||
public int WaferColNum
|
||||
{
|
||||
get { return _waferColNum; }
|
||||
set { SetAndNotify(ref _waferColNum, value); }
|
||||
}
|
||||
|
||||
public IParameterItem Clone()
|
||||
{
|
||||
return this.MemberwiseClone() as IParameterItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using MainShell.Common;
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class WaferScanSettings : PropertyChangedBase, IParameterItem
|
||||
{
|
||||
private double _startPointX;
|
||||
|
||||
public double StartPointX
|
||||
{
|
||||
get { return _startPointX; }
|
||||
set { SetAndNotify(ref _startPointX, value); }
|
||||
}
|
||||
|
||||
private double _startPointY;
|
||||
|
||||
public double StartPointY
|
||||
{
|
||||
get { return _startPointY; }
|
||||
set { SetAndNotify(ref _startPointY, value); }
|
||||
}
|
||||
|
||||
private TransPathDirection _scanDirectionX;
|
||||
|
||||
public TransPathDirection ScanDirectionX
|
||||
{
|
||||
get { return _scanDirectionX; }
|
||||
set { SetAndNotify(ref _scanDirectionX, value); }
|
||||
}
|
||||
|
||||
private TransPathDirection _scanDirectionY;
|
||||
|
||||
public TransPathDirection ScanDirectionY
|
||||
{
|
||||
get { return _scanDirectionY; }
|
||||
set { SetAndNotify(ref _scanDirectionY, value); }
|
||||
}
|
||||
|
||||
private double _overlapRateX;
|
||||
|
||||
public double OverlapRateX
|
||||
{
|
||||
get { return _overlapRateX; }
|
||||
set { SetAndNotify(ref _overlapRateX, value); }
|
||||
}
|
||||
|
||||
private double _overlapRateY;
|
||||
|
||||
public double OverlapRateY
|
||||
{
|
||||
get { return _overlapRateY; }
|
||||
set { SetAndNotify(ref _overlapRateY, value); }
|
||||
}
|
||||
|
||||
private double _softLimitOffsetMm;
|
||||
|
||||
public double SoftLimitOffsetMm
|
||||
{
|
||||
get { return _softLimitOffsetMm; }
|
||||
set { SetAndNotify(ref _softLimitOffsetMm, value); }
|
||||
}
|
||||
|
||||
private int _consumerCount = 2;
|
||||
|
||||
public int ConsumerCount
|
||||
{
|
||||
get { return _consumerCount; }
|
||||
set { SetAndNotify(ref _consumerCount, value); }
|
||||
}
|
||||
|
||||
private int _frameChannelCapacity = 8;
|
||||
|
||||
public int FrameChannelCapacity
|
||||
{
|
||||
get { return _frameChannelCapacity; }
|
||||
set { SetAndNotify(ref _frameChannelCapacity, value); }
|
||||
}
|
||||
|
||||
public IParameterItem Clone()
|
||||
{
|
||||
return this.MemberwiseClone() as IParameterItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public interface IRecipe
|
||||
{
|
||||
string RecipeName { get; set; }
|
||||
/// <summary>
|
||||
/// 配方名改变时通知
|
||||
/// </summary>
|
||||
event Action<IRecipe, string, string> OnRenamed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class AxisFilteringParameter : PropertyChangedBase , IParameterItem
|
||||
{
|
||||
private int _axisIndex;
|
||||
/// <summary>
|
||||
/// 轴ID
|
||||
/// </summary>
|
||||
public int AxisIndex
|
||||
{
|
||||
get { return _axisIndex; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _axisIndex, value))
|
||||
{
|
||||
NotifyOfPropertyChange(() => AxisDisplayName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string AxisDisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
string axisName = PidAxisCatalog.GetAxisName(AxisIndex);
|
||||
return string.IsNullOrWhiteSpace(axisName) ? AxisIndex.ToString() : axisName;
|
||||
}
|
||||
}
|
||||
|
||||
private PIDLabel<bool> _isUseNotch = new PIDLabel<bool>() { Name = "PA_SLNothFilth", Value = false };
|
||||
/// <summary>
|
||||
/// 开启陷波滤波
|
||||
/// </summary>
|
||||
public PIDLabel<bool> IsUseNotch
|
||||
{
|
||||
get { return _isUseNotch; }
|
||||
set { SetAndNotify(ref _isUseNotch, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<double> _notchFrequency = new PIDLabel<double>() { Name = "PA_SLVNFRQ", Value = 0 };
|
||||
/// <summary>
|
||||
/// 陷波滤波频率
|
||||
/// </summary>
|
||||
public PIDLabel<double> NotchFrequency
|
||||
{
|
||||
get { return _notchFrequency; }
|
||||
set { SetAndNotify(ref _notchFrequency, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<double> _notchWidth = new PIDLabel<double>() { Name = "PA_SLVNWID", Value = 0 };
|
||||
/// <summary>
|
||||
/// 陷波滤波宽度
|
||||
/// </summary>
|
||||
public PIDLabel<double> NotchWidth
|
||||
{
|
||||
get { return _notchWidth; }
|
||||
set { SetAndNotify(ref _notchWidth, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<double> _notchFalloff = new PIDLabel<double>() { Name = "PA_SLVNATT", Value = 0 };
|
||||
/// <summary>
|
||||
/// 陷波滤波衰减
|
||||
/// </summary>
|
||||
public PIDLabel<double> NotchFalloff
|
||||
{
|
||||
get { return _notchFalloff; }
|
||||
set { SetAndNotify(ref _notchFalloff, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<bool> _isUseFiltering = new PIDLabel<bool>() { Name = "PA_SLLowPass", Value = false };
|
||||
/// <summary>
|
||||
/// 开启低通滤波
|
||||
/// </summary>
|
||||
public PIDLabel<bool> IsUseFiltering
|
||||
{
|
||||
get { return _isUseFiltering; }
|
||||
set { SetAndNotify(ref _isUseFiltering, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<double> _filteringFrequency = new PIDLabel<double>() { Name = "PA_SLVSOF", Value = 0 };
|
||||
/// <summary>
|
||||
/// 低通滤波频率
|
||||
/// </summary>
|
||||
public PIDLabel<double> FilteringFrequency
|
||||
{
|
||||
get { return _filteringFrequency; }
|
||||
set { SetAndNotify(ref _filteringFrequency, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<double> _filteringDamping = new PIDLabel<double>() { Name = "PA_SLVSOFD", Value = 0 };
|
||||
/// <summary>
|
||||
/// 低通滤波阻尼系数
|
||||
/// </summary>
|
||||
public PIDLabel<double> FilteringDamping
|
||||
{
|
||||
get { return _filteringDamping; }
|
||||
set { SetAndNotify(ref _filteringDamping, value); }
|
||||
}
|
||||
|
||||
public IParameterItem Clone()
|
||||
{
|
||||
return new AxisFilteringParameter
|
||||
{
|
||||
AxisIndex = this.AxisIndex,
|
||||
IsUseNotch = this.IsUseNotch.Clone() as PIDLabel<bool>,
|
||||
NotchFrequency = this.NotchFrequency.Clone() as PIDLabel<double>,
|
||||
NotchWidth = this.NotchWidth.Clone() as PIDLabel<double>,
|
||||
NotchFalloff = this.NotchFalloff.Clone() as PIDLabel<double>,
|
||||
IsUseFiltering = this.IsUseFiltering.Clone() as PIDLabel<bool>,
|
||||
FilteringFrequency = this.FilteringFrequency.Clone() as PIDLabel<double>,
|
||||
FilteringDamping = this.FilteringDamping.Clone() as PIDLabel<double>
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class PidAxisOption
|
||||
{
|
||||
public string AxisName { get; set; }
|
||||
|
||||
public int AxisIndex { get; set; }
|
||||
}
|
||||
|
||||
public static class PidAxisCatalog
|
||||
{
|
||||
private static readonly Dictionary<int, string> _axisNameByIndex = new Dictionary<int, string>();
|
||||
private static readonly Dictionary<string, int> _axisIndexByName = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public static void SetAxisOptions(IEnumerable<PidAxisOption> axisOptions)
|
||||
{
|
||||
_axisNameByIndex.Clear();
|
||||
_axisIndexByName.Clear();
|
||||
|
||||
if (axisOptions == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (PidAxisOption axisOption in axisOptions)
|
||||
{
|
||||
if (axisOption == null || string.IsNullOrWhiteSpace(axisOption.AxisName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_axisNameByIndex[axisOption.AxisIndex] = axisOption.AxisName;
|
||||
_axisIndexByName[axisOption.AxisName] = axisOption.AxisIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetAxisName(int axisIndex)
|
||||
{
|
||||
string axisName;
|
||||
return _axisNameByIndex.TryGetValue(axisIndex, out axisName) ? axisName : string.Empty;
|
||||
}
|
||||
|
||||
public static bool TryGetAxisIndex(string axisName, out int axisIndex)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(axisName))
|
||||
{
|
||||
axisIndex = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
return _axisIndexByName.TryGetValue(axisName, out axisIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public class AxisPIDParameter : PropertyChangedBase, IParameterItem
|
||||
{
|
||||
private bool _isSynchronizingAxis;
|
||||
private string _moduleName;
|
||||
|
||||
public string ModuleName
|
||||
{
|
||||
get { return _moduleName; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _moduleName, value))
|
||||
{
|
||||
SyncAxisIndexFromModuleName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _axisIndex;
|
||||
|
||||
public int AxisIndex
|
||||
{
|
||||
get { return _axisIndex; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _axisIndex, value))
|
||||
{
|
||||
SyncModuleNameFromAxisIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
private PIDLabel<double> _pA_SLVKP = new PIDLabel<double>() { Name = "PA_SLVKP_", Value = 0 };
|
||||
/// <summary>
|
||||
/// 速度环KP
|
||||
/// </summary>
|
||||
public PIDLabel<double> PA_SLVKP
|
||||
{
|
||||
get { return _pA_SLVKP; }
|
||||
set { SetAndNotify(ref _pA_SLVKP, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<double> _pA_SLVKI = new PIDLabel<double>() { Name = "PA_SLVKI_", Value = 0 };
|
||||
/// <summary>
|
||||
/// 速度环KI
|
||||
/// </summary>
|
||||
public PIDLabel<double> PA_SLVKI
|
||||
{
|
||||
get { return _pA_SLVKI; }
|
||||
set { SetAndNotify(ref _pA_SLVKI, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<double> _pA_SLVKPSF = new PIDLabel<double>() { Name = "PA_SLVKPSF_", Value = 0 };
|
||||
/// <summary>
|
||||
/// 速度环KP Settle因子
|
||||
/// </summary>
|
||||
public PIDLabel<double> PA_SLVKPSF
|
||||
{
|
||||
get { return _pA_SLVKPSF; }
|
||||
set { SetAndNotify(ref _pA_SLVKPSF, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<double> _pA_SLVKPIF = new PIDLabel<double>() { Name = "PA_SLVKPIF_", Value = 0 };
|
||||
/// <summary>
|
||||
/// 速度环KP Iddle因子
|
||||
/// </summary>
|
||||
public PIDLabel<double> PA_SLVKPIF
|
||||
{
|
||||
get { return _pA_SLVKPIF; }
|
||||
set { SetAndNotify(ref _pA_SLVKPIF, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<double> _pA_SLVKISF = new PIDLabel<double>() { Name = "PA_SLVKISF_", Value = 0 };
|
||||
/// <summary>
|
||||
/// 速度环KI Settle因子
|
||||
/// </summary>
|
||||
public PIDLabel<double> PA_SLVKISF
|
||||
{
|
||||
get { return _pA_SLVKISF; }
|
||||
set { SetAndNotify(ref _pA_SLVKISF, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<double> _pA_SLVKIIF = new PIDLabel<double>() { Name = "PA_SLVKIIF_", Value = 0 };
|
||||
/// <summary>
|
||||
/// 速度环KI Iddle因子
|
||||
/// </summary>
|
||||
public PIDLabel<double> PA_SLVKIIF
|
||||
{
|
||||
get { return _pA_SLVKIIF; }
|
||||
set { SetAndNotify(ref _pA_SLVKIIF, value); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
private PIDLabel<double> _pA_SLPKP = new PIDLabel<double>() { Name = "PA_SLPKP_", Value = 0 };
|
||||
/// <summary>
|
||||
/// 位置环的KP
|
||||
/// </summary>
|
||||
public PIDLabel<double> PA_SLPKP
|
||||
{
|
||||
get { return _pA_SLPKP; }
|
||||
set { SetAndNotify(ref _pA_SLPKP, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<double> _pA_SLPKPIF = new PIDLabel<double>() { Name = "PA_SLPKPIF_", Value = 0 };
|
||||
/// <summary>
|
||||
/// 位置环的KP的Idel因子
|
||||
/// </summary>
|
||||
public PIDLabel<double> PA_SLPKPIF
|
||||
{
|
||||
get { return _pA_SLPKPIF; }
|
||||
set { SetAndNotify(ref _pA_SLPKPIF, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<double> _pA_SLPKPSF = new PIDLabel<double>() { Name = "PA_SLPKPSF_", Value = 0 };
|
||||
/// <summary>
|
||||
/// 位置环的KP的Settle因子
|
||||
/// </summary>
|
||||
public PIDLabel<double> PA_SLPKPSF
|
||||
{
|
||||
get { return _pA_SLPKPSF; }
|
||||
set { SetAndNotify(ref _pA_SLPKPSF, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<double> _pA_SLAFF = new PIDLabel<double>() { Name = "PA_SLAFF_", Value = 0 };
|
||||
/// <summary>
|
||||
/// 加速度前馈
|
||||
/// </summary>
|
||||
public PIDLabel<double> PA_SLAFF
|
||||
{
|
||||
get { return _pA_SLAFF; }
|
||||
set { SetAndNotify(ref _pA_SLAFF, value); }
|
||||
}
|
||||
|
||||
private PIDLabel<double> _pA_SLJFF = new PIDLabel<double>() { Name = "PA_SLJFF_", Value = 0 };
|
||||
/// <summary>
|
||||
/// 加加速度前馈
|
||||
/// </summary>
|
||||
public PIDLabel<double> PA_SLJFF
|
||||
{
|
||||
get { return _pA_SLJFF; }
|
||||
set { SetAndNotify(ref _pA_SLJFF, value); }
|
||||
}
|
||||
|
||||
public AxisPIDParameter()
|
||||
{
|
||||
}
|
||||
public AxisPIDParameter(string moduleName)
|
||||
{
|
||||
ModuleName = moduleName;
|
||||
}
|
||||
|
||||
public IParameterItem Clone()
|
||||
{
|
||||
AxisPIDParameter copy = new AxisPIDParameter
|
||||
{
|
||||
ModuleName = this.ModuleName,
|
||||
AxisIndex = this.AxisIndex,
|
||||
PA_SLVKP = this.PA_SLVKP != null ? (PIDLabel<double>)this.PA_SLVKP.Clone() : new PIDLabel<double>(),
|
||||
PA_SLVKI = this.PA_SLVKI != null ? (PIDLabel<double>)this.PA_SLVKI.Clone() : new PIDLabel<double>(),
|
||||
PA_SLVKPSF = this.PA_SLVKPSF != null ? (PIDLabel<double>)this.PA_SLVKPSF.Clone() : new PIDLabel<double>(),
|
||||
PA_SLVKPIF = this.PA_SLVKPIF != null ? (PIDLabel<double>)this.PA_SLVKPIF.Clone() : new PIDLabel<double>(),
|
||||
PA_SLVKISF = this.PA_SLVKISF != null ? (PIDLabel<double>)this.PA_SLVKISF.Clone() : new PIDLabel<double>(),
|
||||
PA_SLVKIIF = this.PA_SLVKIIF != null ? (PIDLabel<double>)this.PA_SLVKIIF.Clone() : new PIDLabel<double>(),
|
||||
|
||||
PA_SLPKP = this.PA_SLPKP != null ? (PIDLabel<double>)this.PA_SLPKP.Clone() : new PIDLabel<double>(),
|
||||
PA_SLPKPIF = this.PA_SLPKPIF != null ? (PIDLabel<double>)this.PA_SLPKPIF.Clone() : new PIDLabel<double>(),
|
||||
PA_SLPKPSF = this.PA_SLPKPSF != null ? (PIDLabel<double>)this.PA_SLPKPSF.Clone() : new PIDLabel<double>(),
|
||||
PA_SLAFF = this.PA_SLAFF != null ? (PIDLabel<double>)this.PA_SLAFF.Clone() : new PIDLabel<double>(),
|
||||
PA_SLJFF = this.PA_SLJFF != null ? (PIDLabel<double>)this.PA_SLJFF.Clone() : new PIDLabel<double>()
|
||||
};
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
private void SyncAxisIndexFromModuleName()
|
||||
{
|
||||
if (_isSynchronizingAxis)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int axisIndex;
|
||||
if (!PidAxisCatalog.TryGetAxisIndex(ModuleName, out axisIndex))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isSynchronizingAxis = true;
|
||||
try
|
||||
{
|
||||
SetAndNotify(ref _axisIndex, axisIndex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSynchronizingAxis = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncModuleNameFromAxisIndex()
|
||||
{
|
||||
if (_isSynchronizingAxis)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string axisName = PidAxisCatalog.GetAxisName(AxisIndex);
|
||||
if (string.IsNullOrWhiteSpace(axisName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isSynchronizingAxis = true;
|
||||
try
|
||||
{
|
||||
SetAndNotify(ref _moduleName, axisName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSynchronizingAxis = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class PIDLabel<T> : PropertyChangedBase , IParameterItem
|
||||
{
|
||||
private string _name;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return _name; }
|
||||
set { SetAndNotify(ref _name, value); }
|
||||
}
|
||||
|
||||
private T _value;
|
||||
|
||||
public T Value
|
||||
{
|
||||
get { return _value; }
|
||||
set { SetAndNotify(ref _value, value); }
|
||||
}
|
||||
|
||||
public IParameterItem Clone()
|
||||
{
|
||||
return this.MemberwiseClone() as IParameterItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using MwFramework.Device;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class PIDOperater
|
||||
{
|
||||
public void SendPIDParameters(AxisPIDParameter axisPIDParameter)
|
||||
{
|
||||
var controller = GetControler();
|
||||
foreach (var property in axisPIDParameter.GetType().GetProperties())
|
||||
{
|
||||
var value = property.GetValue(axisPIDParameter);
|
||||
if (value is PIDLabel<double> pIDLabel)
|
||||
{
|
||||
controller.WriteVariableBuffer(-1, axisPIDParameter.ModuleName + pIDLabel.Name, pIDLabel.Value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public AxisPIDParameter ReadPIDParametersFromDevice(string moduleName)
|
||||
{
|
||||
var controller = GetControler();
|
||||
|
||||
AxisPIDParameter axisPIDParameter = new AxisPIDParameter(moduleName);
|
||||
var props = axisPIDParameter.GetType()
|
||||
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => p.CanRead && p.CanWrite);
|
||||
foreach (var property in props)
|
||||
{
|
||||
var value = property.GetValue(axisPIDParameter);
|
||||
if (value is PIDLabel<double> pIDLabel)
|
||||
{
|
||||
controller.ReadVariableBuffer(-1, axisPIDParameter.ModuleName + pIDLabel.Name, out var val);
|
||||
if (val == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
pIDLabel.Value = Convert.ToDouble(val);
|
||||
}
|
||||
}
|
||||
return axisPIDParameter;
|
||||
}
|
||||
|
||||
private IMotionController GetControler()
|
||||
{
|
||||
var hw = IoC.Get<Hardware.HardwareManager>();
|
||||
if (hw?.AcsCard?.Controller == null)
|
||||
throw new InvalidOperationException("硬件控制器未初始化 (HardwareManager.AcsCard.Controller is null).");
|
||||
return hw.AcsCard.Controller;
|
||||
}
|
||||
|
||||
public void SendPIDFilteringParameters(AxisFilteringParameter pidFilteringParameter)
|
||||
{
|
||||
var controller = GetControler();
|
||||
foreach (var property in pidFilteringParameter.GetType().GetProperties())
|
||||
{
|
||||
var value = property.GetValue(pidFilteringParameter);
|
||||
if (value is PIDLabel<double> pIDLabel)
|
||||
{
|
||||
controller.WriteVariableBuffer(-1, pIDLabel.Name, pIDLabel.Value);
|
||||
}
|
||||
else if (value is PIDLabel<bool> pIDLabelBool)
|
||||
{
|
||||
var val= pIDLabelBool.Value== true ? 1 : 0;
|
||||
controller.WriteVariableBuffer(-1, pIDLabelBool.Name, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
public AxisFilteringParameter ReadPIDFilteringParametersFromDevice()
|
||||
{
|
||||
var controller = GetControler();
|
||||
var axisFilteringParameter = new AxisFilteringParameter();
|
||||
var props = axisFilteringParameter.GetType()
|
||||
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => p.CanRead && p.CanWrite);
|
||||
foreach (var property in props)
|
||||
{
|
||||
var value = property.GetValue(axisFilteringParameter);
|
||||
if (value is PIDLabel<double> pIDLabel)
|
||||
{
|
||||
controller.ReadVariableBuffer(-1, pIDLabel.Name, out var val);
|
||||
if (val == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
pIDLabel.Value = Convert.ToDouble(val);
|
||||
}
|
||||
else if (value is PIDLabel<bool> pIDLabelBool)
|
||||
{
|
||||
controller.ReadVariableBuffer(-1, pIDLabelBool.Name, out var val);
|
||||
if (val == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
pIDLabelBool.Value = Convert.ToInt32(val) != 0;
|
||||
}
|
||||
}
|
||||
return axisFilteringParameter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models.PID
|
||||
{
|
||||
public class PIDProfile : PropertyChangedBase
|
||||
{
|
||||
private string _name;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return _name; }
|
||||
set { SetAndNotify(ref _name, value); }
|
||||
}
|
||||
private string _description;
|
||||
public string Description
|
||||
{
|
||||
get { return _description; }
|
||||
set { SetAndNotify(ref _description, value); }
|
||||
}
|
||||
|
||||
// 每个 profile 下的轴参数集合
|
||||
public ObservableCollection<AxisPIDParameter> Axes { get; set; }
|
||||
|
||||
private ObservableCollection<AxisFilteringParameter> _filteringParameters;
|
||||
public ObservableCollection<AxisFilteringParameter> FilteringParameters
|
||||
{
|
||||
get { return _filteringParameters; }
|
||||
set { SetAndNotify(ref _filteringParameters, value); }
|
||||
}
|
||||
|
||||
public PIDProfile()
|
||||
{
|
||||
Axes = new ObservableCollection<AxisPIDParameter>();
|
||||
FilteringParameters = new ObservableCollection<AxisFilteringParameter>();
|
||||
}
|
||||
|
||||
public PIDProfile(string name)
|
||||
: this()
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public void EnsureAxes()
|
||||
{
|
||||
if (Axes == null)
|
||||
{
|
||||
Axes = new ObservableCollection<AxisPIDParameter>();
|
||||
NotifyOfPropertyChange(() => Axes);
|
||||
}
|
||||
|
||||
if (FilteringParameters == null)
|
||||
{
|
||||
FilteringParameters = new ObservableCollection<AxisFilteringParameter>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Stylet;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class ProcessAxisCompensationParameter : PropertyChangedBase
|
||||
{
|
||||
private string _axisName;
|
||||
public string AxisName
|
||||
{
|
||||
get { return _axisName; }
|
||||
set { SetAndNotify(ref _axisName, value); }
|
||||
}
|
||||
|
||||
private double _compensationValue;
|
||||
public double CompensationValue
|
||||
{
|
||||
get { return _compensationValue; }
|
||||
set { SetAndNotify(ref _compensationValue, value); }
|
||||
}
|
||||
|
||||
private double _phsX1Compensation;
|
||||
public double PhsX1Compensation
|
||||
{
|
||||
get { return _phsX1Compensation; }
|
||||
set { SetAndNotify(ref _phsX1Compensation, value); }
|
||||
}
|
||||
|
||||
private double _phsY1Compensation;
|
||||
public double PhsY1Compensation
|
||||
{
|
||||
get { return _phsY1Compensation; }
|
||||
set { SetAndNotify(ref _phsY1Compensation, value); }
|
||||
}
|
||||
|
||||
private double _wsXCompensation;
|
||||
public double WsXCompensation
|
||||
{
|
||||
get { return _wsXCompensation; }
|
||||
set { SetAndNotify(ref _wsXCompensation, value); }
|
||||
}
|
||||
|
||||
private double _stageYCompensation;
|
||||
public double StageYCompensation
|
||||
{
|
||||
get { return _stageYCompensation; }
|
||||
set { SetAndNotify(ref _stageYCompensation, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using MainShell.Filewritable;
|
||||
using MainShell.Recipe.Models.PID;
|
||||
using MwFramework.ManagerService;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class ProcessRecipe : RecipeBase
|
||||
{
|
||||
public override string Dir => Path.Combine(Paths.ProcessRecipe, RecipeName);
|
||||
|
||||
private ObservableCollection<PIDProfile> _axisPIDParameters = new ObservableCollection<PIDProfile>();
|
||||
/// <summary>
|
||||
/// PID参数集合
|
||||
/// </summary>
|
||||
public ObservableCollection<PIDProfile> AxisPIDParameters
|
||||
{
|
||||
get { return _axisPIDParameters; }
|
||||
set { SetProperty(ref _axisPIDParameters, value); }
|
||||
}
|
||||
|
||||
private ProcessAxisCompensationParameter _processAxisCompensationParameter = new ProcessAxisCompensationParameter();
|
||||
/// <summary>
|
||||
/// 轴补偿参数
|
||||
/// </summary>
|
||||
public ProcessAxisCompensationParameter ProcessAxisCompensationParameter
|
||||
{
|
||||
get { return _processAxisCompensationParameter; }
|
||||
set { SetProperty(ref _processAxisCompensationParameter, value); }
|
||||
}
|
||||
|
||||
private ObservableCollection<ProcessAxisCompensationParameter> _processCompensationParameters = new ObservableCollection<ProcessAxisCompensationParameter>();
|
||||
/// <summary>
|
||||
/// 工艺轴补偿参数集合
|
||||
/// </summary>
|
||||
public ObservableCollection<ProcessAxisCompensationParameter> ProcessCompensationParameters
|
||||
{
|
||||
get { return _processCompensationParameters; }
|
||||
set { SetProperty(ref _processCompensationParameters, value); }
|
||||
}
|
||||
|
||||
public ProcessRecipe(string name) : base(name)
|
||||
{
|
||||
}
|
||||
|
||||
public ProcessRecipe() : base()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using MainShell.Filewritable;
|
||||
using MwFramework.ManagerService;
|
||||
using MXJM.FileWritable;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public abstract class RecipeBase :JsonFileWritableBase,IRecipe , INotifyPropertyChanged
|
||||
{
|
||||
private string _recipeName;
|
||||
public virtual string RecipeName
|
||||
{
|
||||
get => _recipeName;
|
||||
set
|
||||
{
|
||||
if (_recipeName != value)
|
||||
{
|
||||
string oldName = _recipeName;
|
||||
_recipeName = value;
|
||||
OnRenamed?.Invoke(this, oldName, _recipeName);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<IRecipe, string, string> OnRenamed;
|
||||
#region
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
/// <summary>
|
||||
/// 通用的 SetProperty 辅助方法,派生类在属性 setter 中可使用以简化通知
|
||||
/// 用法: SetProperty(ref _field, value);
|
||||
/// </summary>
|
||||
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
||||
field = value;
|
||||
OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public string CreateSnapshot()
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
Formatting = Formatting.None,
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
||||
ContractResolver = IgnorePropertiesResolver
|
||||
};
|
||||
|
||||
return JsonConvert.SerializeObject(this, settings);
|
||||
}
|
||||
#endregion
|
||||
protected RecipeBase(string name)
|
||||
{
|
||||
_recipeName = name;
|
||||
}
|
||||
protected RecipeBase()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class RecipeEventArgs : EventArgs
|
||||
{
|
||||
public string RecipeName { get; set; }
|
||||
public string RecipeDescription { get; set; } = string.Empty;
|
||||
public RecipeEventArgs(string recipeName, string recipeDescription = "")
|
||||
{
|
||||
RecipeName = recipeName;
|
||||
RecipeDescription = recipeDescription;
|
||||
}
|
||||
public RecipeEventArgs()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class RecipeRenameEventArgs : EventArgs
|
||||
{
|
||||
public string OldRecipeName { get; set; }
|
||||
public string NewRecipeName { get; set; }
|
||||
public RecipeRenameEventArgs(string oldRecipeName, string newRecipeName)
|
||||
{
|
||||
OldRecipeName = oldRecipeName;
|
||||
NewRecipeName = newRecipeName;
|
||||
}
|
||||
public RecipeRenameEventArgs()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
using MainShell.Filewritable;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class RecipeManager
|
||||
{
|
||||
public event EventHandler RecipesChanged;
|
||||
|
||||
// 记录上次加载的基板配方名(放在根目录)
|
||||
private string LastLoadedFile => Path.Combine(Paths.RecipeBasePath, "last_loaded.txt");
|
||||
|
||||
public CarrierRecipe CurrentCarrierRecipe { get; private set; }
|
||||
public SubstrateRecipe CurrentSubstrateRecipe { get; private set; }
|
||||
public WaferRecipe CurrentWaferRecipe { get; private set; }
|
||||
public ProcessRecipe CurrentProcessRecipe { get; private set; }
|
||||
|
||||
public RecipeManager()
|
||||
{
|
||||
var lastLoaded = LoadLastLoadedRecipe();
|
||||
if (!string.IsNullOrWhiteSpace(lastLoaded))
|
||||
{
|
||||
// 构造阶段不主动抛事件,避免初始化期无意义通知
|
||||
SwitchSubstrateRecipeInternal(lastLoaded, cascade: true, notify: false);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveLastLoadedRecipe(string recipeName)
|
||||
{
|
||||
var dir = Path.GetDirectoryName(LastLoadedFile);
|
||||
if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
File.WriteAllText(LastLoadedFile, recipeName ?? string.Empty);
|
||||
}
|
||||
|
||||
public string LoadLastLoadedRecipe()
|
||||
{
|
||||
if (!File.Exists(LastLoadedFile))
|
||||
return string.Empty;
|
||||
|
||||
return File.ReadAllText(LastLoadedFile).Trim();
|
||||
}
|
||||
|
||||
public void SwitchCarrierRecipe(string carrierRecipeName)
|
||||
{
|
||||
SwitchCarrierRecipeInternal(carrierRecipeName, notify: true);
|
||||
}
|
||||
|
||||
public void SwitchSubstrateRecipe(string substrateRecipeName)
|
||||
{
|
||||
// 需求:切换基板时自动切换芯片;切换芯片时自动切换工艺
|
||||
SwitchSubstrateRecipeInternal(substrateRecipeName, cascade: true, notify: true);
|
||||
}
|
||||
|
||||
public void SwitchWaferRecipe(string waferRecipeName)
|
||||
{
|
||||
// 需求:切换芯片时自动切换工艺
|
||||
SwitchWaferRecipeInternal(waferRecipeName, cascade: true, notify: true);
|
||||
}
|
||||
|
||||
public void SwitchProcessRecipe(string processRecipeName)
|
||||
{
|
||||
SwitchProcessRecipeInternal(processRecipeName, notify: true);
|
||||
}
|
||||
|
||||
private void SwitchCarrierRecipeInternal(string carrierRecipeName, bool notify)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(carrierRecipeName))
|
||||
return;
|
||||
|
||||
CurrentCarrierRecipe = CurrentCarrierRecipe ?? new CarrierRecipe();
|
||||
CurrentCarrierRecipe.RecipeName = carrierRecipeName;
|
||||
|
||||
var path = Path.Combine(Paths.CarrierRecipe, carrierRecipeName, CurrentCarrierRecipe.FileName);
|
||||
CurrentCarrierRecipe.Read(path);
|
||||
|
||||
var substrateName = GetFirstSubstrateName(CurrentCarrierRecipe);
|
||||
if (!string.IsNullOrWhiteSpace(substrateName))
|
||||
{
|
||||
// 向下级联,但由最外层统一通知
|
||||
SwitchSubstrateRecipeInternal(substrateName, cascade: true, notify: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearSubstrateRecipeInternal(false);
|
||||
}
|
||||
|
||||
if (notify)
|
||||
RaiseRecipesChanged();
|
||||
}
|
||||
|
||||
private void SwitchSubstrateRecipeInternal(string substrateRecipeName, bool cascade, bool notify)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(substrateRecipeName))
|
||||
return;
|
||||
|
||||
CurrentSubstrateRecipe = CurrentSubstrateRecipe ?? new SubstrateRecipe();
|
||||
CurrentSubstrateRecipe.RecipeName = substrateRecipeName;
|
||||
|
||||
var path = Path.Combine(Paths.SubstrateRecipe, substrateRecipeName, CurrentSubstrateRecipe.FileName);
|
||||
CurrentSubstrateRecipe.Read(path);
|
||||
|
||||
SaveLastLoadedRecipe(substrateRecipeName);
|
||||
|
||||
if (cascade)
|
||||
{
|
||||
var waferName = GetFirstWaferName(CurrentSubstrateRecipe);
|
||||
if (!string.IsNullOrWhiteSpace(waferName))
|
||||
{
|
||||
SwitchWaferRecipeInternal(waferName, cascade: true, notify: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有关联芯片时,清空下游当前引用,避免残留旧状态
|
||||
CurrentWaferRecipe = null;
|
||||
CurrentProcessRecipe = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (notify)
|
||||
RaiseRecipesChanged();
|
||||
}
|
||||
|
||||
private void SwitchWaferRecipeInternal(string waferRecipeName, bool cascade, bool notify)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(waferRecipeName))
|
||||
return;
|
||||
|
||||
CurrentWaferRecipe = CurrentWaferRecipe ?? new WaferRecipe();
|
||||
CurrentWaferRecipe.RecipeName = waferRecipeName;
|
||||
|
||||
var path = Path.Combine(Paths.WaferRecipe, waferRecipeName, CurrentWaferRecipe.FileName);
|
||||
CurrentWaferRecipe.Read(path);
|
||||
|
||||
if (cascade)
|
||||
{
|
||||
var processName = CurrentWaferRecipe.ProcessRecipeName;
|
||||
if (!string.IsNullOrWhiteSpace(processName))
|
||||
{
|
||||
SwitchProcessRecipeInternal(processName, notify: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentProcessRecipe = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (notify)
|
||||
RaiseRecipesChanged();
|
||||
}
|
||||
|
||||
private void SwitchProcessRecipeInternal(string processRecipeName, bool notify)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(processRecipeName))
|
||||
return;
|
||||
|
||||
CurrentProcessRecipe = CurrentProcessRecipe ?? new ProcessRecipe();
|
||||
CurrentProcessRecipe.RecipeName = processRecipeName;
|
||||
|
||||
var path = Path.Combine(Paths.ProcessRecipe, processRecipeName, CurrentProcessRecipe.FileName);
|
||||
CurrentProcessRecipe.Read(path);
|
||||
|
||||
if (notify)
|
||||
RaiseRecipesChanged();
|
||||
}
|
||||
|
||||
private static string GetFirstSubstrateName(CarrierRecipe carrierRecipe)
|
||||
{
|
||||
if (carrierRecipe == null ||
|
||||
carrierRecipe.SubstrateSelectInfos == null ||
|
||||
carrierRecipe.SubstrateSelectInfos.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return carrierRecipe.SubstrateSelectInfos[0].SubstrateName;
|
||||
}
|
||||
|
||||
private static string GetFirstWaferName(SubstrateRecipe substrateRecipe)
|
||||
{
|
||||
if (substrateRecipe == null ||
|
||||
substrateRecipe.WaferSelectInfos == null ||
|
||||
substrateRecipe.WaferSelectInfos.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var inUse = substrateRecipe.WaferSelectInfos
|
||||
.FirstOrDefault(x => x != null && x.IsUse && !string.IsNullOrWhiteSpace(x.WaferRecipeName));
|
||||
if (inUse != null)
|
||||
return inUse.WaferRecipeName;
|
||||
|
||||
var firstValid = substrateRecipe.WaferSelectInfos
|
||||
.FirstOrDefault(x => x != null && !string.IsNullOrWhiteSpace(x.WaferRecipeName));
|
||||
return firstValid != null ? firstValid.WaferRecipeName : string.Empty;
|
||||
}
|
||||
|
||||
private void RaiseRecipesChanged()
|
||||
{
|
||||
RecipesChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void DeleteCarrierRecipe(string recipeName)
|
||||
{
|
||||
DeleteRecipeFolder(Paths.CarrierRecipe, recipeName, out _);
|
||||
}
|
||||
|
||||
public bool DeleteCarrierRecipe(string recipeName, out string error)
|
||||
{
|
||||
return DeleteRecipeFolder(Paths.CarrierRecipe, recipeName, out error);
|
||||
}
|
||||
|
||||
public void DeleteSubstrateRecipe(string recipeName)
|
||||
{
|
||||
DeleteRecipeFolder(Paths.SubstrateRecipe, recipeName, out _);
|
||||
}
|
||||
|
||||
public bool DeleteSubstrateRecipe(string recipeName, out string error)
|
||||
{
|
||||
return DeleteRecipeFolder(Paths.SubstrateRecipe, recipeName, out error);
|
||||
}
|
||||
|
||||
public void DeleteWaferRecipe(string recipeName)
|
||||
{
|
||||
DeleteRecipeFolder(Paths.WaferRecipe, recipeName, out _);
|
||||
}
|
||||
|
||||
public bool DeleteWaferRecipe(string recipeName, out string error)
|
||||
{
|
||||
return DeleteRecipeFolder(Paths.WaferRecipe, recipeName, out error);
|
||||
}
|
||||
|
||||
public void DeleteProcessRecipe(string recipeName)
|
||||
{
|
||||
DeleteRecipeFolder(Paths.ProcessRecipe, recipeName, out _);
|
||||
}
|
||||
|
||||
public bool DeleteProcessRecipe(string recipeName, out string error)
|
||||
{
|
||||
return DeleteRecipeFolder(Paths.ProcessRecipe, recipeName, out error);
|
||||
}
|
||||
|
||||
public bool DeleteRecipeFolder(string baseFolder, string recipeName, out string error)
|
||||
{
|
||||
error = null;
|
||||
if (string.IsNullOrWhiteSpace(baseFolder) || string.IsNullOrWhiteSpace(recipeName))
|
||||
{
|
||||
error = "配方名称不能为空";
|
||||
return false;
|
||||
}
|
||||
|
||||
var path = Path.Combine(baseFolder, recipeName);
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
error = "配方目录不存在";
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Directory.Delete(path, true);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CopyRecipeFolder(string baseFolder, string sourceRecipeName, string targetRecipeName, out string error)
|
||||
{
|
||||
error = null;
|
||||
if (string.IsNullOrWhiteSpace(baseFolder) || string.IsNullOrWhiteSpace(sourceRecipeName) || string.IsNullOrWhiteSpace(targetRecipeName))
|
||||
{
|
||||
error = "配方名称不能为空";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceRecipeName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 ||
|
||||
targetRecipeName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
|
||||
{
|
||||
error = "名称包含非法字符";
|
||||
return false;
|
||||
}
|
||||
|
||||
var sourcePath = Path.Combine(baseFolder, sourceRecipeName);
|
||||
var targetPath = Path.Combine(baseFolder, targetRecipeName);
|
||||
|
||||
if (!Directory.Exists(sourcePath))
|
||||
{
|
||||
error = "源文件夹不存在";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Directory.Exists(targetPath))
|
||||
{
|
||||
error = "目标文件夹已存在";
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CopyDirectory(sourcePath, targetPath);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.Message;
|
||||
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(targetPath))
|
||||
{
|
||||
Directory.Delete(targetPath, true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearCarrierRecipe()
|
||||
{
|
||||
ClearCarrierRecipeInternal(true);
|
||||
}
|
||||
|
||||
public void ClearSubstrateRecipe()
|
||||
{
|
||||
ClearSubstrateRecipeInternal(true);
|
||||
}
|
||||
|
||||
public void ClearWaferRecipe()
|
||||
{
|
||||
ClearWaferRecipeInternal(true);
|
||||
}
|
||||
|
||||
public void ClearProcessRecipe()
|
||||
{
|
||||
ClearProcessRecipeInternal(true);
|
||||
}
|
||||
|
||||
private void ClearCarrierRecipeInternal(bool notify)
|
||||
{
|
||||
CurrentCarrierRecipe = null;
|
||||
|
||||
if (notify)
|
||||
RaiseRecipesChanged();
|
||||
}
|
||||
|
||||
private void ClearSubstrateRecipeInternal(bool notify)
|
||||
{
|
||||
CurrentSubstrateRecipe = null;
|
||||
CurrentWaferRecipe = null;
|
||||
CurrentProcessRecipe = null;
|
||||
SaveLastLoadedRecipe(string.Empty);
|
||||
|
||||
if (notify)
|
||||
RaiseRecipesChanged();
|
||||
}
|
||||
|
||||
private void ClearWaferRecipeInternal(bool notify)
|
||||
{
|
||||
CurrentWaferRecipe = null;
|
||||
CurrentProcessRecipe = null;
|
||||
|
||||
if (notify)
|
||||
RaiseRecipesChanged();
|
||||
}
|
||||
|
||||
private void ClearProcessRecipeInternal(bool notify)
|
||||
{
|
||||
CurrentProcessRecipe = null;
|
||||
|
||||
if (notify)
|
||||
RaiseRecipesChanged();
|
||||
}
|
||||
|
||||
private static void CopyDirectory(string sourcePath, string targetPath)
|
||||
{
|
||||
Directory.CreateDirectory(targetPath);
|
||||
|
||||
foreach (var filePath in Directory.GetFiles(sourcePath))
|
||||
{
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
var targetFilePath = Path.Combine(targetPath, fileName);
|
||||
File.Copy(filePath, targetFilePath, false);
|
||||
}
|
||||
|
||||
foreach (var directoryPath in Directory.GetDirectories(sourcePath))
|
||||
{
|
||||
var directoryName = Path.GetFileName(directoryPath);
|
||||
var targetDirectoryPath = Path.Combine(targetPath, directoryName);
|
||||
CopyDirectory(directoryPath, targetDirectoryPath);
|
||||
}
|
||||
}
|
||||
|
||||
public bool RenameFolder(string baseFolder, string oldName, string newName, out string error)
|
||||
{
|
||||
error = null;
|
||||
if (string.IsNullOrWhiteSpace(oldName) || string.IsNullOrWhiteSpace(newName))
|
||||
{
|
||||
error = "名称不能为空";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (oldName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 ||
|
||||
newName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
|
||||
{
|
||||
error = "名称包含非法字符";
|
||||
return false;
|
||||
}
|
||||
|
||||
var src = Path.Combine(baseFolder, oldName);
|
||||
var dst = Path.Combine(baseFolder, newName);
|
||||
|
||||
if (!Directory.Exists(src))
|
||||
{
|
||||
error = "源文件夹不存在";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Directory.Exists(dst))
|
||||
{
|
||||
error = "目标文件夹已存在";
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (string.Equals(oldName, newName, StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(oldName, newName, StringComparison.Ordinal))
|
||||
{
|
||||
var temp = Path.Combine(baseFolder, oldName + "_tmp_" + Guid.NewGuid().ToString("N"));
|
||||
Directory.Move(src, temp);
|
||||
Directory.Move(temp, dst);
|
||||
}
|
||||
else
|
||||
{
|
||||
Directory.Move(src, dst);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class RecipeWrap : PropertyChangedBase
|
||||
{
|
||||
private string _recipeName;
|
||||
|
||||
public string RecipeName
|
||||
{
|
||||
get { return _recipeName; }
|
||||
set { SetAndNotify(ref _recipeName, value); }
|
||||
}
|
||||
private DateTime _createTime;
|
||||
|
||||
public DateTime CreateTime
|
||||
{
|
||||
get { return _createTime; }
|
||||
set { SetAndNotify(ref _createTime, value); }
|
||||
}
|
||||
private DateTime _modifiedTime;
|
||||
|
||||
public DateTime ModifiedTime
|
||||
{
|
||||
get { return _modifiedTime; }
|
||||
set { SetAndNotify(ref _modifiedTime, value); }
|
||||
}
|
||||
|
||||
private bool _isInUse;
|
||||
|
||||
public bool IsInUse
|
||||
{
|
||||
get { return _isInUse; }
|
||||
set { SetAndNotify(ref _isInUse, value); }
|
||||
}
|
||||
private bool _hasError;
|
||||
|
||||
public bool HasError
|
||||
{
|
||||
get { return _hasError; }
|
||||
set { SetAndNotify(ref _hasError, value); }
|
||||
}
|
||||
private bool _needUpdate;
|
||||
/// <summary>
|
||||
/// 重命名&删除时
|
||||
/// </summary>
|
||||
public bool NeedUpdate
|
||||
{
|
||||
get { return _needUpdate; }
|
||||
set { SetAndNotify(ref _needUpdate, value); }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using MainShell.Filewritable;
|
||||
using MaxwellFramework.Core.Attributes;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
[Singleton]
|
||||
public class RecipeWrapManager : PropertyChangedBase , IHandle<SubstrateNameChangedEventArgs>
|
||||
{
|
||||
private ObservableCollection<RecipeWrap> _carrierRecipeWraps;
|
||||
|
||||
public ObservableCollection<RecipeWrap> CarrierRecipeWraps
|
||||
{
|
||||
get { return _carrierRecipeWraps; }
|
||||
set { SetAndNotify(ref _carrierRecipeWraps, value); }
|
||||
}
|
||||
|
||||
private ObservableCollection<RecipeWrap> _substrateRecipeWraps;
|
||||
|
||||
public ObservableCollection<RecipeWrap> SubstrateRecipeWraps
|
||||
{
|
||||
get { return _substrateRecipeWraps; }
|
||||
set { SetAndNotify(ref _substrateRecipeWraps, value); }
|
||||
}
|
||||
private ObservableCollection<RecipeWrap> _waferRecipeWraps;
|
||||
|
||||
public ObservableCollection<RecipeWrap> WaferRecipeWraps
|
||||
{
|
||||
get { return _waferRecipeWraps; }
|
||||
set { SetAndNotify(ref _waferRecipeWraps, value); }
|
||||
}
|
||||
private ObservableCollection<RecipeWrap> _processRecipeWraps;
|
||||
|
||||
public ObservableCollection<RecipeWrap> ProcessRecipeWraps
|
||||
{
|
||||
get { return _processRecipeWraps; }
|
||||
set { SetAndNotify(ref _processRecipeWraps, value); }
|
||||
}
|
||||
|
||||
public RecipeWrapManager(IEventAggregator eventAggregator)
|
||||
{
|
||||
CarrierRecipeWraps = new ObservableCollection<RecipeWrap>(LoadRecipes(Paths.CarrierRecipe));
|
||||
SubstrateRecipeWraps = new ObservableCollection<RecipeWrap>(LoadRecipes(Paths.SubstrateRecipe));
|
||||
WaferRecipeWraps = new ObservableCollection<RecipeWrap>(LoadRecipes(Paths.WaferRecipe));
|
||||
ProcessRecipeWraps = new ObservableCollection<RecipeWrap>(LoadRecipes(Paths.ProcessRecipe));
|
||||
eventAggregator.Unsubscribe(this);
|
||||
eventAggregator.Subscribe(this);
|
||||
}
|
||||
private IEnumerable<RecipeWrap> LoadRecipes(string dirPath)
|
||||
{
|
||||
if (!Directory.Exists(dirPath))
|
||||
Directory.CreateDirectory(dirPath);
|
||||
foreach (var dir in Directory.EnumerateDirectories(dirPath))
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension(dir);
|
||||
yield return new RecipeWrap
|
||||
{
|
||||
RecipeName = name,
|
||||
CreateTime = Directory.GetCreationTime(dir),
|
||||
ModifiedTime = Directory.GetLastWriteTime(dir),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(SubstrateNameChangedEventArgs message)
|
||||
{
|
||||
CarrierRecipe carrierRecipe = new CarrierRecipe();
|
||||
foreach (var carrierWrap in CarrierRecipeWraps)
|
||||
{
|
||||
carrierRecipe.RecipeName= carrierWrap.RecipeName;
|
||||
carrierRecipe.Read();
|
||||
if(carrierRecipe.SubstrateSelectInfos!=null)
|
||||
{
|
||||
foreach (var item in carrierRecipe.SubstrateSelectInfos)
|
||||
{
|
||||
if(item.SubstrateName==message.OldName)
|
||||
{
|
||||
item.SubstrateName= message.NewName;
|
||||
carrierRecipe.Write();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace MainShell.Recipe.Models.SubstrateParameter
|
||||
{
|
||||
public class MarkCoordinateGenerationState : PropertyChangedBase, IParameterItem
|
||||
{
|
||||
private int _rows;
|
||||
public int Rows
|
||||
{
|
||||
get { return _rows; }
|
||||
set { SetAndNotify(ref _rows, value); }
|
||||
}
|
||||
|
||||
private int _cols;
|
||||
public int Cols
|
||||
{
|
||||
get { return _cols; }
|
||||
set { SetAndNotify(ref _cols, value); }
|
||||
}
|
||||
|
||||
private double _pitchX;
|
||||
public double PitchX
|
||||
{
|
||||
get { return _pitchX; }
|
||||
set { SetAndNotify(ref _pitchX, value); }
|
||||
}
|
||||
|
||||
private double _pitchY;
|
||||
public double PitchY
|
||||
{
|
||||
get { return _pitchY; }
|
||||
set { SetAndNotify(ref _pitchY, value); }
|
||||
}
|
||||
|
||||
private ObservableCollection<MarkCoordinatePoint> _points = new ObservableCollection<MarkCoordinatePoint>();
|
||||
public ObservableCollection<MarkCoordinatePoint> Points
|
||||
{
|
||||
get { return _points; }
|
||||
set { SetAndNotify(ref _points, value); }
|
||||
}
|
||||
|
||||
public IParameterItem Clone()
|
||||
{
|
||||
return new MarkCoordinateGenerationState
|
||||
{
|
||||
Rows = Rows,
|
||||
Cols = Cols,
|
||||
PitchX = PitchX,
|
||||
PitchY = PitchY,
|
||||
Points = new ObservableCollection<MarkCoordinatePoint>(Points.Select(point => point.Clone() as MarkCoordinatePoint))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
|
||||
namespace MainShell.Recipe.Models.SubstrateParameter
|
||||
{
|
||||
public class MarkCoordinatePoint : PropertyChangedBase, IParameterItem
|
||||
{
|
||||
private int _index;
|
||||
public int Index
|
||||
{
|
||||
get { return _index; }
|
||||
set { SetAndNotify(ref _index, value); }
|
||||
}
|
||||
|
||||
private string _pointName;
|
||||
public string PointName
|
||||
{
|
||||
get { return _pointName; }
|
||||
set { SetAndNotify(ref _pointName, value); }
|
||||
}
|
||||
|
||||
private int _row;
|
||||
public int Row
|
||||
{
|
||||
get { return _row; }
|
||||
set { SetAndNotify(ref _row, value); }
|
||||
}
|
||||
|
||||
private int _col;
|
||||
public int Col
|
||||
{
|
||||
get { return _col; }
|
||||
set { SetAndNotify(ref _col, value); }
|
||||
}
|
||||
|
||||
private double _theoryX;
|
||||
public double TheoryX
|
||||
{
|
||||
get { return _theoryX; }
|
||||
set { SetAndNotify(ref _theoryX, value); }
|
||||
}
|
||||
|
||||
private double _theoryY;
|
||||
public double TheoryY
|
||||
{
|
||||
get { return _theoryY; }
|
||||
set { SetAndNotify(ref _theoryY, value); }
|
||||
}
|
||||
|
||||
public IParameterItem Clone()
|
||||
{
|
||||
return new MarkCoordinatePoint
|
||||
{
|
||||
Index = Index,
|
||||
PointName = PointName,
|
||||
Row = Row,
|
||||
Col = Col,
|
||||
TheoryX = TheoryX,
|
||||
TheoryY = TheoryY,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using MainShell.Models;
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models.SubstrateParameter
|
||||
{
|
||||
public class MarkData : PropertyChangedBase, IParameterItem
|
||||
{
|
||||
private string _templateName;
|
||||
private bool _isEnabled = true;
|
||||
|
||||
public string TemplateName
|
||||
{
|
||||
get { return _templateName; }
|
||||
set { SetAndNotify(ref _templateName, value); }
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get { return _isEnabled; }
|
||||
set { SetAndNotify(ref _isEnabled, value); }
|
||||
}
|
||||
|
||||
private MPoint _basePos;
|
||||
public MPoint BasePos
|
||||
{
|
||||
get { return _basePos; }
|
||||
set { SetAndNotify(ref _basePos, value); }
|
||||
}
|
||||
|
||||
private MPoint _cameraPos;
|
||||
|
||||
public MPoint CameraPos
|
||||
{
|
||||
get { return _cameraPos; }
|
||||
set { SetAndNotify(ref _cameraPos, value); }
|
||||
}
|
||||
|
||||
|
||||
public IParameterItem Clone()
|
||||
{
|
||||
return new MarkData
|
||||
{
|
||||
IsEnabled = this.IsEnabled,
|
||||
TemplateName = this.TemplateName,
|
||||
BasePos = this.BasePos?.Clone(),
|
||||
CameraPos = this.CameraPos?.Clone()
|
||||
} as IParameterItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using MainShell.Models;
|
||||
using Stylet;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace MainShell.Recipe.Models.SubstrateParameter
|
||||
{
|
||||
public enum SubstrateHeightMeasureMode
|
||||
{
|
||||
[Description("标准示教位置")]
|
||||
StandardTeachPosition = 0,
|
||||
|
||||
[Description("行列坐标+偏移补偿")]
|
||||
RowColumnOffset = 1,
|
||||
}
|
||||
|
||||
public class SubstrateHeightMeasurePoint : PropertyChangedBase
|
||||
{
|
||||
private string _pointName;
|
||||
public string PointName
|
||||
{
|
||||
get { return _pointName; }
|
||||
set { SetAndNotify(ref _pointName, value); }
|
||||
}
|
||||
|
||||
private MPoint _teachPosition = new MPoint();
|
||||
public MPoint TeachPosition
|
||||
{
|
||||
get { return _teachPosition; }
|
||||
set { SetAndNotify(ref _teachPosition, value); }
|
||||
}
|
||||
|
||||
private int _rowIndex;
|
||||
public int RowIndex
|
||||
{
|
||||
get { return _rowIndex; }
|
||||
set { SetAndNotify(ref _rowIndex, value); }
|
||||
}
|
||||
|
||||
private int _columnIndex;
|
||||
public int ColumnIndex
|
||||
{
|
||||
get { return _columnIndex; }
|
||||
set { SetAndNotify(ref _columnIndex, value); }
|
||||
}
|
||||
|
||||
private MPoint _offsetCompensation = new MPoint();
|
||||
public MPoint OffsetCompensation
|
||||
{
|
||||
get { return _offsetCompensation; }
|
||||
set { SetAndNotify(ref _offsetCompensation, value); }
|
||||
}
|
||||
}
|
||||
|
||||
public class SubstrateHeightMeasureSetting : PropertyChangedBase
|
||||
{
|
||||
private SubstrateHeightMeasureMode _mode = SubstrateHeightMeasureMode.StandardTeachPosition;
|
||||
public SubstrateHeightMeasureMode Mode
|
||||
{
|
||||
get { return _mode; }
|
||||
set { SetAndNotify(ref _mode, value); }
|
||||
}
|
||||
|
||||
private MPoint _commonOffsetCompensation = new MPoint();
|
||||
public MPoint CommonOffsetCompensation
|
||||
{
|
||||
get { return _commonOffsetCompensation; }
|
||||
set { SetAndNotify(ref _commonOffsetCompensation, value); }
|
||||
}
|
||||
|
||||
private ObservableCollection<SubstrateHeightMeasurePoint> _points = new ObservableCollection<SubstrateHeightMeasurePoint>();
|
||||
public ObservableCollection<SubstrateHeightMeasurePoint> Points
|
||||
{
|
||||
get { return _points; }
|
||||
set { SetAndNotify(ref _points, value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using MainShell.Models;
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class SubstrateInfo : PropertyChangedBase, IParameterItem
|
||||
{
|
||||
private MxSize _substrateSize = new MxSize();
|
||||
|
||||
public MxSize SubstrateSize
|
||||
{
|
||||
get { return _substrateSize; }
|
||||
set { SetAndNotify(ref _substrateSize, value); }
|
||||
}
|
||||
|
||||
private MxSize _padSize;
|
||||
|
||||
public MxSize PadSize
|
||||
{
|
||||
get { return _padSize; }
|
||||
set { SetAndNotify(ref _padSize, value); }
|
||||
}
|
||||
|
||||
|
||||
private double _thickness;
|
||||
|
||||
public double ThickNess
|
||||
{
|
||||
get { return _thickness; }
|
||||
set { SetAndNotify(ref _thickness, value); }
|
||||
}
|
||||
private double _pitchX;
|
||||
|
||||
public double PitchX
|
||||
{
|
||||
get { return _pitchX; }
|
||||
set { SetAndNotify(ref _pitchX, value); }
|
||||
}
|
||||
private double _pitchY;
|
||||
|
||||
public double PitchY
|
||||
{
|
||||
get { return _pitchY; }
|
||||
set { SetAndNotify(ref _pitchY, value); }
|
||||
}
|
||||
private int _rowNumber;
|
||||
|
||||
public int RowNumber
|
||||
{
|
||||
get { return _rowNumber; }
|
||||
set { SetAndNotify(ref _rowNumber, value); }
|
||||
}
|
||||
private int _colNumber;
|
||||
|
||||
public int ColNumber
|
||||
{
|
||||
get { return _colNumber; }
|
||||
set { SetAndNotify(ref _colNumber, value); }
|
||||
}
|
||||
|
||||
private double _recheckPitch;
|
||||
|
||||
public double RecheckPitch
|
||||
{
|
||||
get { return _recheckPitch; }
|
||||
set { SetAndNotify(ref _recheckPitch, value); }
|
||||
}
|
||||
|
||||
public IParameterItem Clone()
|
||||
{
|
||||
// 1. 首先执行浅拷贝,复制所有值类型字段(如 Thickness, PitchX 等)
|
||||
var clone = this.MemberwiseClone() as SubstrateInfo;
|
||||
|
||||
// 2. 手动深拷贝引用类型对象 (MxSize)
|
||||
// 如果不这样做,clone 和原对象将指向内存中的同一个 MxSize 实例
|
||||
if (this.SubstrateSize != null)
|
||||
{
|
||||
clone.SubstrateSize = new MxSize
|
||||
{
|
||||
Width = this.SubstrateSize.Width,
|
||||
Height = this.SubstrateSize.Height
|
||||
};
|
||||
}
|
||||
|
||||
if (this.PadSize != null)
|
||||
{
|
||||
clone.PadSize = new MxSize
|
||||
{
|
||||
Width = this.PadSize.Width,
|
||||
Height = this.PadSize.Height
|
||||
};
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using MainShell.Models;
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models.SubstrateParameter
|
||||
{
|
||||
public class SubtrateMarkPars : PropertyChangedBase, IParameterItem
|
||||
{
|
||||
public CameraConfig MarkVisionConfig { get; set; } = new CameraConfig();
|
||||
public UpCamLightConfig MarkLightConfig { get; set; } = new UpCamLightConfig();
|
||||
public ObservableCollection<MarkData> MarkDatas { get; set; } = new ObservableCollection<MarkData>();
|
||||
public MarkCoordinateGenerationState CoordinateGenerationState { get; set; } = new MarkCoordinateGenerationState();
|
||||
public IParameterItem Clone()
|
||||
{
|
||||
var clone = this.MemberwiseClone() as SubtrateMarkPars;
|
||||
if (clone != null)
|
||||
{
|
||||
clone.MarkVisionConfig = this.MarkVisionConfig.Clone() as CameraConfig;
|
||||
clone.MarkLightConfig = this.MarkLightConfig.Clone() as UpCamLightConfig;
|
||||
clone.MarkDatas = new ObservableCollection<MarkData>(this.MarkDatas.Select(md => md.Clone() as MarkData));
|
||||
clone.CoordinateGenerationState = this.CoordinateGenerationState?.Clone() as MarkCoordinateGenerationState ?? new MarkCoordinateGenerationState();
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models.SubstrateParameter
|
||||
{
|
||||
public class WaferSelectInfo : PropertyChangedBase
|
||||
{
|
||||
private string _waferRecipeName;
|
||||
|
||||
public string WaferRecipeName
|
||||
{
|
||||
get { return _waferRecipeName; }
|
||||
set { SetAndNotify(ref _waferRecipeName, value); }
|
||||
}
|
||||
private double _offsetX;
|
||||
|
||||
public double OffsetX
|
||||
{
|
||||
get { return _offsetX; }
|
||||
set { SetAndNotify(ref _offsetX, value); }
|
||||
}
|
||||
private double _offsetY;
|
||||
|
||||
public double OffsetY
|
||||
{
|
||||
get { return _offsetY; }
|
||||
set { SetAndNotify(ref _offsetY, value); }
|
||||
}
|
||||
|
||||
private bool _isUse;
|
||||
|
||||
public bool IsUse
|
||||
{
|
||||
get { return _isUse; }
|
||||
set { SetAndNotify(ref _isUse, value); }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models.SubstrateParameter;
|
||||
using MwFramework.ManagerService;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class SubstrateRecipe : RecipeBase
|
||||
{
|
||||
public override string Dir => System.IO.Path.Combine(Filewritable.Paths.SubstrateRecipe, RecipeName);
|
||||
|
||||
private ObservableCollection<WaferSelectInfo> _waferSelectInfos = new ObservableCollection<WaferSelectInfo>();
|
||||
public ObservableCollection<WaferSelectInfo> WaferSelectInfos
|
||||
{
|
||||
get { return _waferSelectInfos; }
|
||||
set { SetProperty(ref _waferSelectInfos, value); }
|
||||
}
|
||||
private SubstrateInfo _substrateInfo = new SubstrateInfo();
|
||||
public SubstrateInfo SubstrateInfo
|
||||
{
|
||||
get { return _substrateInfo; }
|
||||
set { SetProperty(ref _substrateInfo, value); }
|
||||
}
|
||||
private SubtrateMarkPars _subtrateMarkParameterInfo = new SubtrateMarkPars();
|
||||
|
||||
public SubtrateMarkPars SubtrateMarkParameterInfo
|
||||
{
|
||||
get { return _subtrateMarkParameterInfo; }
|
||||
set { SetProperty(ref _subtrateMarkParameterInfo, value); }
|
||||
}
|
||||
|
||||
private SubstrateHeightMeasureSetting _heightMeasureSetting = new SubstrateHeightMeasureSetting();
|
||||
public SubstrateHeightMeasureSetting HeightMeasureSetting
|
||||
{
|
||||
get { return _heightMeasureSetting; }
|
||||
set { SetProperty(ref _heightMeasureSetting, value); }
|
||||
}
|
||||
|
||||
public SubstratePoint[,] SubstratePoints { get; set; }
|
||||
|
||||
public SubstrateRecipe(string name) : base(name)
|
||||
{
|
||||
}
|
||||
public SubstrateRecipe() : base()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using MainShell.Filewritable;
|
||||
using MwFramework.ManagerService;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Models
|
||||
{
|
||||
public class WaferRecipe : RecipeBase
|
||||
{
|
||||
public override string Dir => Path.Combine(Paths.WaferRecipe, RecipeName);
|
||||
|
||||
private WaferInfo _waferInfo = new WaferInfo();
|
||||
|
||||
/// <summary>
|
||||
/// 晶圆信息
|
||||
/// </summary>
|
||||
public WaferInfo WaferInfo
|
||||
{
|
||||
get { return _waferInfo; }
|
||||
set { SetProperty(ref _waferInfo, value); }
|
||||
}
|
||||
|
||||
private WaferScanSettings _scanSettings = new WaferScanSettings();
|
||||
|
||||
public WaferScanSettings ScanSettings
|
||||
{
|
||||
get { return _scanSettings; }
|
||||
set { SetProperty(ref _scanSettings, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 工艺参数配方名称
|
||||
/// </summary>
|
||||
private string _processRecipeName;
|
||||
|
||||
public string ProcessRecipeName
|
||||
{
|
||||
get { return _processRecipeName; }
|
||||
set { SetProperty(ref _processRecipeName, value); }
|
||||
}
|
||||
|
||||
public WaferRecipe(string name) : base(name)
|
||||
{
|
||||
}
|
||||
public WaferRecipe() : base()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.Services
|
||||
{
|
||||
public interface IActiveRecipeService<T> : INotifyPropertyChanged
|
||||
{
|
||||
T Active { get; set; }
|
||||
}
|
||||
|
||||
public class ActiveRecipeService<T> : PropertyChangedBase, IActiveRecipeService<T>
|
||||
{
|
||||
private T _active;
|
||||
public T Active
|
||||
{
|
||||
get => _active;
|
||||
set => SetAndNotify(ref _active, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<UserControl x:Class="MainShell.Recipe.View.CarrierRecipeView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:MainShell.Recipe.View"
|
||||
xmlns:mw="http://www.maxwell-gp.com/"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="850" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="300"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<GroupBox DataContext="{Binding CarrierRecipe.CarrierInfo}" Header="{DynamicResource CarrierInfo}">
|
||||
<UniformGrid Rows="7" Columns="1">
|
||||
<UniformGrid.Resources>
|
||||
<Style TargetType="TextBlock" BasedOn="{StaticResource TextBlockStyle}">
|
||||
|
||||
</Style>
|
||||
|
||||
</UniformGrid.Resources>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Style="{StaticResource TextBlockStyle}" Text="{DynamicResource CarrierWidth}"/>
|
||||
<mw:NumberBox Width="120" Grid.Column="1" Value="{Binding Width}" Maximum="300" Minimum="50" mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120" mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Style="{StaticResource TextBlockStyle}" Text="{DynamicResource CarrierHeight}"/>
|
||||
<mw:NumberBox Width="120" Grid.Column="1" Value="{Binding Height}" Maximum="300" Minimum="50" mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120" mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Style="{StaticResource TextBlockStyle}" Text="{DynamicResource CarrierThickness}"/>
|
||||
<mw:NumberBox Width="120" Grid.Column="1" Value="{Binding Thickness}" Maximum="300" Minimum="50" mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
</Grid>
|
||||
</UniformGrid>
|
||||
</GroupBox>
|
||||
<GroupBox Grid.Column="1" Header="{DynamicResource SubstrateSelect}">
|
||||
<Grid>
|
||||
<Border BorderThickness="0,0,0,0.5" BorderBrush="{DynamicResource BorderBrush}" >
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<DataGrid x:Name="selectSubstrateInfo"
|
||||
ItemsSource="{Binding CarrierRecipe.SubstrateSelectInfos}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
SelectionMode="Single">
|
||||
<DataGrid.Columns>
|
||||
<DataGridComboBoxColumn Width="*" Header="{DynamicResource SubstrateRecipe}" TextBinding="{Binding SubstrateName}">
|
||||
<DataGridComboBoxColumn.EditingElementStyle>
|
||||
<Style TargetType="ComboBox">
|
||||
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=GroupBox}, Path=DataContext.SubstrateRecipes}" />
|
||||
<Setter Property="DisplayMemberPath" Value="RecipeName"/>
|
||||
<Setter Property="SelectedIndex" Value="0"/>
|
||||
</Style>
|
||||
</DataGridComboBoxColumn.EditingElementStyle>
|
||||
</DataGridComboBoxColumn>
|
||||
<DataGridTextColumn Width="*" Header="{DynamicResource Description}" Binding="{Binding Description}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,10">
|
||||
<Button Content="{DynamicResource Add}" Style="{StaticResource StartButtonStyle}" Click="{mw:Action AddSubstrate}"/>
|
||||
<Button Content="{DynamicResource Delete}" Style="{StaticResource CloseButtonStyle}"
|
||||
Command="{Binding RemoveSubstrateCommand}"
|
||||
CommandParameter="{Binding ElementName=selectSubstrateInfo, Path=SelectedItem}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
<GroupBox Grid.Row="1" Header="{DynamicResource LogisticsInfo}">
|
||||
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
/// <summary>
|
||||
/// CarrierRecipeView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class CarrierRecipeView : UserControl
|
||||
{
|
||||
public CarrierRecipeView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Window x:Class="MainShell.Recipe.View.MarkCoordinatePreviewWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Title="{DynamicResource MarkCoordinatePreviewWindowTitle}"
|
||||
Width="940"
|
||||
Height="720"
|
||||
MinWidth="860"
|
||||
MinHeight="620"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Background="#F3F6FB">
|
||||
<Window.Resources>
|
||||
<SolidColorBrush x:Key="PageBackgroundBrush" Color="#F3F6FB"/>
|
||||
<SolidColorBrush x:Key="PanelBorderBrush" Color="#D8E0EC"/>
|
||||
<SolidColorBrush x:Key="PanelHeaderBrush" Color="#F7FAFD"/>
|
||||
<SolidColorBrush x:Key="PrimaryBrush" Color="#2F5DAA"/>
|
||||
<SolidColorBrush x:Key="PrimaryDarkBrush" Color="#244C8D"/>
|
||||
<SolidColorBrush x:Key="PrimaryLightBrush" Color="#EAF1FF"/>
|
||||
<SolidColorBrush x:Key="BodyTextBrush" Color="#26415E"/>
|
||||
<SolidColorBrush x:Key="MutedTextBrush" Color="#6E7F96"/>
|
||||
<SolidColorBrush x:Key="DangerBrush" Color="#D95B69"/>
|
||||
<SolidColorBrush x:Key="DangerPanelBrush" Color="#FFF8F8"/>
|
||||
<SolidColorBrush x:Key="DangerBorderBrush" Color="#FFD8D8"/>
|
||||
|
||||
<Style x:Key="CardBorderStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource PanelBorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CornerRadius" Value="3"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="HeaderTitleStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="22"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="HeaderSubtitleStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource MutedTextBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SectionTitleStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="16"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryBrush}"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MetricLabelStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource MutedTextBrush}"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MetricValueStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="28"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PrimaryActionButtonStyle" TargetType="Button">
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource PrimaryBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="18,8"/>
|
||||
<Setter Property="Height" Value="40"/>
|
||||
<Setter Property="MinWidth" Value="112"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="3"
|
||||
SnapsToDevicePixels="True">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryDarkBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource PrimaryDarkBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#1D3F74"/>
|
||||
<Setter Property="BorderBrush" Value="#1D3F74"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.55"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GhostActionButtonStyle" TargetType="Button" BasedOn="{StaticResource PrimaryActionButtonStyle}">
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryBrush}"/>
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource PanelBorderBrush}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="3"
|
||||
SnapsToDevicePixels="True">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryLightBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource PrimaryBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#DDE9FF"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.55"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGrid">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource PanelBorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource BodyTextBrush}"/>
|
||||
<Setter Property="RowBackground" Value="White"/>
|
||||
<Setter Property="AlternatingRowBackground" Value="#FAFCFF"/>
|
||||
<Setter Property="GridLinesVisibility" Value="Horizontal"/>
|
||||
<Setter Property="HorizontalGridLinesBrush" Value="#E4E9F2"/>
|
||||
<Setter Property="VerticalGridLinesBrush" Value="#E4E9F2"/>
|
||||
<Setter Property="HeadersVisibility" Value="Column"/>
|
||||
<Setter Property="CanUserAddRows" Value="False"/>
|
||||
<Setter Property="CanUserDeleteRows" Value="False"/>
|
||||
<Setter Property="CanUserResizeRows" Value="False"/>
|
||||
<Setter Property="CanUserReorderColumns" Value="False"/>
|
||||
<Setter Property="CanUserSortColumns" Value="False"/>
|
||||
<Setter Property="IsReadOnly" Value="True"/>
|
||||
<Setter Property="RowHeaderWidth" Value="0"/>
|
||||
<Setter Property="AutoGenerateColumns" Value="False"/>
|
||||
<Setter Property="SelectionMode" Value="Single"/>
|
||||
<Setter Property="SelectionUnit" Value="FullRow"/>
|
||||
<Setter Property="RowHeight" Value="42"/>
|
||||
<Setter Property="ColumnHeaderHeight" Value="46"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGridColumnHeader">
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryBrush}"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="Background" Value="{StaticResource PanelHeaderBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource PanelBorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="0,0,0,1"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGridCell">
|
||||
<Setter Property="Padding" Value="8,4"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="TextBlock.TextAlignment" Value="Center"/>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid Background="{StaticResource PageBackgroundBrush}" Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0" Style="{StaticResource CardBorderStyle}" Padding="18,16">
|
||||
<DockPanel LastChildFill="True">
|
||||
<Button DockPanel.Dock="Right"
|
||||
Content="{DynamicResource MarkCoordinatePreviewClose}"
|
||||
Style="{StaticResource GhostActionButtonStyle}"
|
||||
Command="{Binding CloseCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>
|
||||
|
||||
<StackPanel>
|
||||
<TextBlock Text="{DynamicResource MarkCoordinatePreviewWindowTitle}"
|
||||
Style="{StaticResource HeaderTitleStyle}"/>
|
||||
<TextBlock Text="Preview"
|
||||
Style="{StaticResource HeaderSubtitleStyle}"
|
||||
Margin="0,4,0,0"/>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="1" Margin="0,14,0,14">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="260"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0" Style="{StaticResource CardBorderStyle}" Padding="18,16">
|
||||
<StackPanel>
|
||||
<TextBlock Text="{DynamicResource MarkCoordinatePreviewPointCountLabel}"
|
||||
Style="{StaticResource MetricLabelStyle}"/>
|
||||
<TextBlock Text="{Binding PointCount}"
|
||||
Style="{StaticResource MetricValueStyle}"
|
||||
Margin="0,6,0,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Column="2" Style="{StaticResource CardBorderStyle}" Padding="18,16">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Read-only data view"
|
||||
Style="{StaticResource SectionTitleStyle}"/>
|
||||
<TextBlock Text="The table below mirrors the generated or imported coordinate set and does not edit source data."
|
||||
Foreground="{StaticResource BodyTextBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0,8,0,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Row="2" Style="{StaticResource CardBorderStyle}" Padding="0">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<DockPanel Grid.Row="0" Background="{StaticResource PanelHeaderBrush}" LastChildFill="False">
|
||||
<TextBlock Margin="18,12"
|
||||
Text="{DynamicResource MarkCoordinatePreviewWindowTitle}"
|
||||
Style="{StaticResource SectionTitleStyle}"/>
|
||||
<TextBlock Margin="0,12,18,12"
|
||||
DockPanel.Dock="Right"
|
||||
Foreground="{StaticResource MutedTextBrush}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding PointCount, StringFormat=Total: {0}}"/>
|
||||
</DockPanel>
|
||||
|
||||
<DataGrid Grid.Row="1"
|
||||
Margin="16"
|
||||
ItemsSource="{Binding Points}"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkCoordinatePreviewIndex}" Width="90" Binding="{Binding Index}"/>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkCoordinatePreviewPointName}" Width="2*" Binding="{Binding PointName}"/>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkCoordinatePreviewRow}" Width="*" Binding="{Binding Row}"/>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkCoordinatePreviewCol}" Width="*" Binding="{Binding Col}"/>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkCoordinatePreviewTheoryX}" Width="*" Binding="{Binding TheoryX, StringFormat={}{0:F4}}"/>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkCoordinatePreviewTheoryY}" Width="*" Binding="{Binding TheoryY, StringFormat={}{0:F4}}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="3"
|
||||
Margin="0,14,0,0"
|
||||
Padding="14,10"
|
||||
Background="{StaticResource DangerPanelBrush}"
|
||||
BorderBrush="{StaticResource DangerBorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="3">
|
||||
<DockPanel LastChildFill="True">
|
||||
<Button DockPanel.Dock="Right"
|
||||
Content="{DynamicResource MarkCoordinatePreviewClose}"
|
||||
Style="{StaticResource GhostActionButtonStyle}"
|
||||
Margin="12,0,0,0"
|
||||
Command="{Binding CloseCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>
|
||||
<TextBlock Text="{DynamicResource MarkCoordinatePreviewReadOnlyTip}"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{StaticResource DangerBrush}"
|
||||
TextWrapping="Wrap"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Windows;
|
||||
using MainShell.Recipe.ViewModel;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
public partial class MarkCoordinatePreviewWindow : Window
|
||||
{
|
||||
public MarkCoordinatePreviewWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnClosed(System.EventArgs e)
|
||||
{
|
||||
if (DataContext is MarkCoordinatePreviewWindowModel viewModel)
|
||||
{
|
||||
viewModel.Cleanup();
|
||||
}
|
||||
|
||||
base.OnClosed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
<UserControl x:Class="MainShell.Recipe.View.MarkTeachView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:behavior="clr-namespace:MainShell.Common.ControlAttribute"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
|
||||
<UserControl.Resources>
|
||||
<SolidColorBrush x:Key="MarkTeachPageBackgroundBrush" Color="#F4F7FB"/>
|
||||
<SolidColorBrush x:Key="MarkTeachCardBackgroundBrush" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="MarkTeachCardBorderBrush" Color="#D6E0EA"/>
|
||||
<SolidColorBrush x:Key="MarkTeachHeaderBackgroundBrush" Color="#EEF3F8"/>
|
||||
<SolidColorBrush x:Key="MarkTeachHeaderForegroundBrush" Color="#2E5FA7"/>
|
||||
<SolidColorBrush x:Key="MarkTeachBodyTextBrush" Color="#425466"/>
|
||||
<SolidColorBrush x:Key="MarkTeachMutedTextBrush" Color="#6B7B8D"/>
|
||||
<SolidColorBrush x:Key="MarkTeachNumberBrush" Color="#264B86"/>
|
||||
<SolidColorBrush x:Key="MarkTeachTabSelectedBrush" Color="#E8F0FC"/>
|
||||
<SolidColorBrush x:Key="MarkTeachTabHoverBrush" Color="#F4F8FE"/>
|
||||
<SolidColorBrush x:Key="MarkTeachTabNormalBrush" Color="#D6DDE7"/>
|
||||
|
||||
<Style x:Key="MarkTeachSectionGroupStyle" TargetType="GroupBox">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource MarkTeachCardBorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Background" Value="{StaticResource MarkTeachCardBackgroundBrush}"/>
|
||||
<Setter Property="Margin" Value="0,0,0,12"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GroupBox">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Border Background="{StaticResource MarkTeachHeaderBackgroundBrush}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="0,0,0,1"
|
||||
Padding="12,8">
|
||||
<ContentPresenter ContentSource="Header" HorizontalAlignment="Center"/>
|
||||
</Border>
|
||||
<ContentPresenter Grid.Row="1"
|
||||
Margin="12,12,12,12"
|
||||
ContentSource="Content"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}"
|
||||
FontSize="15"
|
||||
FontWeight="Bold"
|
||||
Foreground="{StaticResource MarkTeachHeaderForegroundBrush}"/>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MarkTeachFieldLabelStyle" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource MarkTeachBodyTextBrush}"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Margin" Value="0,0,0,6"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MarkTeachReadOnlyTextBoxStyle" TargetType="TextBox">
|
||||
<Setter Property="Height" Value="34"/>
|
||||
<Setter Property="Padding" Value="12,0"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource MarkTeachCardBorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="Foreground" Value="#2F3E4E"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MarkTeachInputTextBoxStyle" TargetType="TextBox" BasedOn="{StaticResource MarkTeachReadOnlyTextBoxStyle}">
|
||||
<Setter Property="Height" Value="38"/>
|
||||
<Setter Property="MinWidth" Value="96"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MarkTeachPrimaryActionButtonStyle" TargetType="Button" BasedOn="{StaticResource MarkPrimaryActionButtonStyle}">
|
||||
|
||||
<Setter Property="Height" Value="38"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MarkTeachOutlineActionButtonStyle" TargetType="Button" BasedOn="{StaticResource MarkOutlineActionButtonStyle}">
|
||||
|
||||
<Setter Property="Height" Value="38"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MarkTeachNeutralActionButtonStyle" TargetType="Button" BasedOn="{StaticResource MarkNeutralActionButtonStyle}">
|
||||
|
||||
<Setter Property="Height" Value="38"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MarkTeachDangerOutlineButtonStyle" TargetType="Button" BasedOn="{StaticResource MarkDangerOutlineButtonStyle}">
|
||||
|
||||
<Setter Property="Height" Value="38"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MarkTeachHintTextStyle" TargetType="TextBlock">
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource MarkTeachBodyTextBrush}"/>
|
||||
<Setter Property="LineHeight" Value="22"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MarkTeachCaptionStyle" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource MarkTeachMutedTextBrush}"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Margin" Value="0,0,0,10"/>
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MarkTeachInlineCaptionStyle" TargetType="TextBlock" BasedOn="{StaticResource MarkTeachCaptionStyle}">
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MarkTeachStatTitleStyle" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource MarkTeachMutedTextBrush}"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MarkTeachStatValueStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="24"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource MarkTeachNumberBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="MarkTeachTabItemStyle" TargetType="TabItem">
|
||||
<Setter Property="Padding" Value="34,12"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource MarkTeachBodyTextBrush}"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Background" Value="{StaticResource MarkTeachTabNormalBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource MarkTeachCardBorderBrush}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TabItem">
|
||||
<Border x:Name="TabBorder"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="0,2,1,0"
|
||||
Margin="0,0,1,0"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ContentSource="Header"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="TabBorder" Property="Background" Value="White"/>
|
||||
<Setter TargetName="TabBorder" Property="BorderBrush" Value="{StaticResource MarkTeachHeaderForegroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource MarkTeachHeaderForegroundBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="TabBorder" Property="Background" Value="{StaticResource MarkTeachTabHoverBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Background="{StaticResource MarkTeachPageBackgroundBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Border Grid.Row="1"
|
||||
Margin="12"
|
||||
Background="White"
|
||||
BorderBrush="{StaticResource MarkTeachCardBorderBrush}"
|
||||
BorderThickness="1">
|
||||
<TabControl Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Padding="0"
|
||||
ItemContainerStyle="{StaticResource MarkTeachTabItemStyle}">
|
||||
<TabItem Header="{DynamicResource MarkTeachTabTeach}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<Grid Margin="20,16,20,12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox Grid.Row="0"
|
||||
Style="{StaticResource MarkTeachSectionGroupStyle}"
|
||||
Header="{DynamicResource MakeModel}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="{DynamicResource MarkTeachTeachSectionHint}"
|
||||
Style="{StaticResource MarkTeachCaptionStyle}"/>
|
||||
<WrapPanel Orientation="Horizontal">
|
||||
<Button Style="{StaticResource MarkTeachPrimaryActionButtonStyle}"
|
||||
Content="{DynamicResource VisionParSet}"
|
||||
Command="{Binding VisionParSetCmd}"
|
||||
Margin="0,0,10,0"/>
|
||||
<Button Style="{StaticResource MarkTeachOutlineActionButtonStyle}"
|
||||
Content="{DynamicResource MarkModel}"
|
||||
Command="{Binding MarkModelCmd}"
|
||||
Margin="0"/>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Grid.Row="1"
|
||||
Style="{StaticResource MarkTeachSectionGroupStyle}"
|
||||
Header="{DynamicResource MarkTeachOperationsHeader}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<WrapPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,12">
|
||||
<Button Content="{DynamicResource AddMark}"
|
||||
Style="{StaticResource MarkTeachPrimaryActionButtonStyle}"
|
||||
Command="{Binding AddMarkCmd}"
|
||||
Margin="0,0,10,10"/>
|
||||
<Button Content="{DynamicResource DeleteMark}"
|
||||
Style="{StaticResource MarkTeachDangerOutlineButtonStyle}"
|
||||
Command="{Binding DeleteMarkCmd}"
|
||||
Margin="0,0,10,10"/>
|
||||
<Button Content="{DynamicResource TeachPos}"
|
||||
Style="{StaticResource MarkTeachNeutralActionButtonStyle}"
|
||||
Command="{Binding TeachMarkCmd}"
|
||||
Margin="0,0,10,10"/>
|
||||
<Button Content="{DynamicResource MoveToCurrentPos}"
|
||||
Style="{StaticResource MarkTeachNeutralActionButtonStyle}"
|
||||
Command="{Binding MoveToMarkCmd}"
|
||||
Margin="0,0,10,10"/>
|
||||
<CheckBox Content="{DynamicResource MarkTeachReferenceTeach}"
|
||||
IsChecked="{Binding IsTeachPose, UpdateSourceTrigger=PropertyChanged}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="8,0,2,10"/>
|
||||
</WrapPanel>
|
||||
|
||||
<DataGrid Grid.Row="1"
|
||||
Margin="0"
|
||||
MinHeight="180"
|
||||
ItemsSource="{Binding MarkDatas}"
|
||||
SelectedItem="{Binding SelectedMarkData}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False">
|
||||
<DataGrid.Resources>
|
||||
<Style x:Key="NumericOnlyEditingTextBoxStyle" TargetType="TextBox">
|
||||
<Setter Property="behavior:ControlBehavior.IsNumericOnly" Value="True"/>
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="{DynamicResource InUse}" Width="58">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding IsEnabled, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkTeachBaseX}" Width="*" Binding="{Binding BasePos.X}" EditingElementStyle="{StaticResource NumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkTeachBaseY}" Width="*" Binding="{Binding BasePos.Y}" EditingElementStyle="{StaticResource NumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkTeachCameraX}" Width="*" Binding="{Binding CameraPos.X}" EditingElementStyle="{StaticResource NumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkTeachCameraY}" Width="*" Binding="{Binding CameraPos.Y}" EditingElementStyle="{StaticResource NumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridComboBoxColumn Width="2*" TextBinding="{Binding TemplateName}" Header="{DynamicResource MarkTeachTemplateName}">
|
||||
<DataGridComboBoxColumn.EditingElementStyle>
|
||||
<Style TargetType="ComboBox">
|
||||
<Setter Property="ItemsSource" Value="{Binding DataContext.AvailableTemplates, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
<Setter Property="DisplayMemberPath" Value="TemplateName"/>
|
||||
<Setter Property="SelectedIndex" Value="0"/>
|
||||
</Style>
|
||||
</DataGridComboBoxColumn.EditingElementStyle>
|
||||
</DataGridComboBoxColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<Border Grid.Row="2"
|
||||
Padding="12,10"
|
||||
Background="#F8FBFF"
|
||||
BorderBrush="{StaticResource MarkTeachCardBorderBrush}"
|
||||
BorderThickness="1">
|
||||
<TextBlock Text="{DynamicResource MarkTeachTeachBottomHint}"
|
||||
Style="{StaticResource MarkTeachHintTextStyle}"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="{DynamicResource MarkTeachTabCoordinate}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<Grid Margin="20,16,20,12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox Grid.Row="0"
|
||||
Style="{StaticResource MarkTeachSectionGroupStyle}"
|
||||
Header="{DynamicResource MarkTeachCoordinateSourceHeader}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="130"/>
|
||||
<ColumnDefinition Width="14"/>
|
||||
<ColumnDefinition Width="130"/>
|
||||
<ColumnDefinition Width="14"/>
|
||||
<ColumnDefinition Width="96"/>
|
||||
<ColumnDefinition Width="14"/>
|
||||
<ColumnDefinition Width="96"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0"
|
||||
Grid.ColumnSpan="8"
|
||||
Text="{DynamicResource MarkTeachCoordinateActionHint}"
|
||||
Style="{StaticResource MarkTeachCaptionStyle}"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="{DynamicResource MarkTeachPitchX}" Style="{StaticResource MarkTeachFieldLabelStyle}"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="2" Text="{DynamicResource MarkTeachPitchY}" Style="{StaticResource MarkTeachFieldLabelStyle}"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="4" Text="{DynamicResource MarkTeachRows}" Style="{StaticResource MarkTeachFieldLabelStyle}"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="6" Text="{DynamicResource MarkTeachCols}" Style="{StaticResource MarkTeachFieldLabelStyle}"/>
|
||||
|
||||
<TextBox Grid.Row="2" Grid.Column="0"
|
||||
Style="{StaticResource MarkTeachInputTextBoxStyle}"
|
||||
Text="{Binding CoordinateGenerationState.PitchX, UpdateSourceTrigger=PropertyChanged}"
|
||||
behavior:ControlBehavior.IsNumericOnly="True"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="2"
|
||||
Style="{StaticResource MarkTeachInputTextBoxStyle}"
|
||||
Text="{Binding CoordinateGenerationState.PitchY, UpdateSourceTrigger=PropertyChanged}"
|
||||
behavior:ControlBehavior.IsNumericOnly="True"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="4"
|
||||
Style="{StaticResource MarkTeachInputTextBoxStyle}"
|
||||
Text="{Binding CoordinateGenerationState.Rows, UpdateSourceTrigger=PropertyChanged}"
|
||||
behavior:ControlBehavior.IsNumericOnly="True"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="6"
|
||||
Style="{StaticResource MarkTeachInputTextBoxStyle}"
|
||||
Text="{Binding CoordinateGenerationState.Cols, UpdateSourceTrigger=PropertyChanged}"
|
||||
behavior:ControlBehavior.IsNumericOnly="True"/>
|
||||
|
||||
<StackPanel Grid.Row="3"
|
||||
Grid.ColumnSpan="8"
|
||||
Margin="0,14,0,0">
|
||||
<TextBlock Text="{DynamicResource MarkTeachCoordinateActionHeader}"
|
||||
Style="{StaticResource MarkTeachFieldLabelStyle}"
|
||||
Margin="0,0,0,8"/>
|
||||
<WrapPanel HorizontalAlignment="Left"
|
||||
Orientation="Horizontal">
|
||||
<Button Content="{DynamicResource MarkTeachGenerateCoordinates}"
|
||||
Style="{StaticResource MarkTeachPrimaryActionButtonStyle}"
|
||||
Command="{Binding GenerateCoordinatesCmd}"
|
||||
Margin="0,0,10,10"/>
|
||||
<Button Content="{DynamicResource MarkTeachImportCoordinates}"
|
||||
Style="{StaticResource MarkTeachOutlineActionButtonStyle}"
|
||||
Command="{Binding ImportCoordinatesCmd}"
|
||||
Margin="0,0,10,10"/>
|
||||
<Button Content="{DynamicResource MarkCoordinatePreviewOpen}"
|
||||
Style="{StaticResource MarkTeachOutlineActionButtonStyle}"
|
||||
Command="{Binding ShowCoordinatePreviewCmd}"
|
||||
Margin="0,0,10,10"/>
|
||||
<Button Content="{DynamicResource MarkTeachClearCoordinates}"
|
||||
Style="{StaticResource MarkTeachDangerOutlineButtonStyle}"
|
||||
Command="{Binding ClearCoordinatesCmd}"
|
||||
Margin="0,0,0,10"/>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Grid.Row="1"
|
||||
Style="{StaticResource MarkTeachSectionGroupStyle}"
|
||||
Header="{DynamicResource MarkTeachCoordinateDetailsHeader}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0"
|
||||
Margin="0,0,0,12"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<TextBlock Text="{DynamicResource MarkTeachCoordinateCurrentDisplayCount}"
|
||||
Style="{StaticResource MarkTeachInlineCaptionStyle}"/>
|
||||
<TextBlock Text="{Binding CoordinatePreviewPoints.Count}"
|
||||
Foreground="{StaticResource MarkTeachHeaderForegroundBrush}"
|
||||
FontWeight="Bold"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<DataGrid Margin="0"
|
||||
MinHeight="320"
|
||||
ItemsSource="{Binding CoordinatePreviewPoints}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
IsReadOnly="True">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkTeachCoordinateIndex}" Width="78" Binding="{Binding Index}"/>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkTeachCoordinatePointName}" Width="2*" Binding="{Binding PointName}"/>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkTeachCoordinateRowHeader}" Width="*" Binding="{Binding Row}"/>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkTeachCoordinateColHeader}" Width="*" Binding="{Binding Col}"/>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkTeachCoordinateTheoryX}" Width="*" Binding="{Binding TheoryX, StringFormat={}{0:0.####}}"/>
|
||||
<DataGridTextColumn Header="{DynamicResource MarkTeachCoordinateTheoryY}" Width="*" Binding="{Binding TheoryY, StringFormat={}{0:0.####}}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{DynamicResource MarkTeachCoordinateEmptyHint}"
|
||||
Foreground="#C1C8D2"
|
||||
FontSize="18"
|
||||
IsHitTestVisible="False">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding CoordinatePreviewPoints.Count}" Value="0">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
/// <summary>
|
||||
/// MarkTeachView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class MarkTeachView : UserControl
|
||||
{
|
||||
public MarkTeachView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
<UserControl x:Class="MainShell.Recipe.View.PidRecipeEditorView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:behavior="clr-namespace:MainShell.Common.ControlAttribute"
|
||||
xmlns:mw="http://www.maxwell-gp.com/"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="700"
|
||||
d:DesignWidth="1180">
|
||||
<UserControl.Resources>
|
||||
<SolidColorBrush x:Key="PidPageBackgroundBrush" Color="#F4F7FB"/>
|
||||
<SolidColorBrush x:Key="PidCardBackgroundBrush" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="PidCardBorderBrush" Color="#D6E0EA"/>
|
||||
<SolidColorBrush x:Key="PidHeaderBackgroundBrush" Color="#EEF3F8"/>
|
||||
<SolidColorBrush x:Key="PidHeaderForegroundBrush" Color="#2E5FA7"/>
|
||||
<SolidColorBrush x:Key="PidBodyTextBrush" Color="#425466"/>
|
||||
<SolidColorBrush x:Key="PidMutedTextBrush" Color="#6B7B8D"/>
|
||||
<SolidColorBrush x:Key="PidValueBrush" Color="#264B86"/>
|
||||
|
||||
<Style x:Key="PidSectionBorderStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="{StaticResource PidCardBackgroundBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource PidCardBorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CornerRadius" Value="4"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PidSectionTitleStyle" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource PidHeaderForegroundBrush}"/>
|
||||
<Setter Property="FontSize" Value="15"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PidHintTextStyle" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource PidMutedTextBrush}"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PidFieldLabelStyle" TargetType="TextBlock" BasedOn="{StaticResource TextBlockStyle}">
|
||||
<Setter Property="Foreground" Value="{StaticResource PidBodyTextBrush}"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Margin" Value="0,6,10,6"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PidNumericOnlyEditingTextBoxStyle" TargetType="TextBox">
|
||||
<Setter Property="behavior:ControlBehavior.IsNumericOnly" Value="True"/>
|
||||
<Setter Property="Padding" Value="6,0"/>
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="SelectionBrush" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
|
||||
<Setter Property="SelectionTextBrush" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PidNumericDisplayTextStyle" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="TextAlignment" Value="Right"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PidNumericOnlyTextBoxStyle" TargetType="TextBox">
|
||||
<Setter Property="behavior:ControlBehavior.IsNumericOnly" Value="True"/>
|
||||
<Setter Property="Margin" Value="4,2"/>
|
||||
<Setter Property="MinWidth" Value="120"/>
|
||||
<Setter Property="Height" Value="32"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PidPrimaryButtonStyle" TargetType="Button" BasedOn="{StaticResource StartButtonStyle}">
|
||||
<Setter Property="MinWidth" Value="108"/>
|
||||
<Setter Property="Height" Value="34"/>
|
||||
<Setter Property="Margin" Value="0,0,8,0"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PidDangerButtonStyle" TargetType="Button" BasedOn="{StaticResource CloseButtonStyle}">
|
||||
<Setter Property="MinWidth" Value="108"/>
|
||||
<Setter Property="Height" Value="34"/>
|
||||
<Setter Property="Margin" Value="0,0,8,0"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PidFilterToggleStyle" TargetType="CheckBox" BasedOn="{StaticResource ModernSwitchStyle}">
|
||||
<Setter Property="Margin" Value="4,2"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PidAxisComboBoxStyle" TargetType="ComboBox">
|
||||
<Setter Property="Margin" Value="4,2"/>
|
||||
<Setter Property="MinWidth" Value="120"/>
|
||||
<Setter Property="Height" Value="32"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PidNumberBoxStyle" TargetType="mw:NumberBox">
|
||||
<Setter Property="Margin" Value="4,2"/>
|
||||
<Setter Property="MinWidth" Value="120"/>
|
||||
<Setter Property="Height" Value="32"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="mw:NumericKeypadAttach.IsEnabled" Value="True"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="PidTabItemStyle" TargetType="TabItem">
|
||||
<Setter Property="Padding" Value="20,8"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PidBodyTextBrush}"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TabItem">
|
||||
<Border x:Name="TabBorder"
|
||||
Background="#E5E9EF"
|
||||
BorderBrush="{StaticResource PidCardBorderBrush}"
|
||||
BorderThickness="1,1,1,0"
|
||||
Margin="0,0,2,0"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ContentSource="Header"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="TabBorder" Property="Background" Value="#F4F8FE"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="TabBorder" Property="Background" Value="White"/>
|
||||
<Setter TargetName="TabBorder" Property="BorderBrush" Value="{StaticResource PidHeaderForegroundBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource PidHeaderForegroundBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Background="{StaticResource PidPageBackgroundBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0"
|
||||
Margin="12,12,12,8"
|
||||
Padding="12,8"
|
||||
Background="White"
|
||||
BorderBrush="{StaticResource PidCardBorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="220"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="选择配置组:"
|
||||
Style="{StaticResource PidFieldLabelStyle}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"/>
|
||||
|
||||
<ComboBox Grid.Column="1"
|
||||
Margin="0"
|
||||
Style="{StaticResource ProcessComboBoxStyle}"
|
||||
ItemsSource="{Binding AxisPIDParameters}"
|
||||
DisplayMemberPath="Name"
|
||||
SelectedItem="{Binding SelectedPIDProfile, Mode=TwoWay}"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<Button Grid.Column="3"
|
||||
Content="读取"
|
||||
Style="{StaticResource PidPrimaryButtonStyle}"
|
||||
Margin="0"
|
||||
Click="{mw:Action ReadParameters}"/>
|
||||
|
||||
<Button Grid.Column="5"
|
||||
Content="写入"
|
||||
Style="{StaticResource PidPrimaryButtonStyle}"
|
||||
Margin="0"
|
||||
Click="{mw:Action WriteParameters}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1"
|
||||
Margin="12,0,12,8"
|
||||
Background="White"
|
||||
BorderBrush="{StaticResource PidCardBorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<TabControl Background="Transparent"
|
||||
BorderThickness="0"
|
||||
SelectedIndex="{Binding SelectedParameterTabIndex, Mode=TwoWay}"
|
||||
ItemContainerStyle="{StaticResource PidTabItemStyle}">
|
||||
<TabItem Header="PID 参数">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0"
|
||||
Margin="12,8,12,6"
|
||||
Text="每个行对应一个轴的速度与位置环参数。"
|
||||
Style="{StaticResource PidHintTextStyle}"
|
||||
Foreground="{StaticResource PidHeaderForegroundBrush}"/>
|
||||
|
||||
<ScrollViewer Grid.Row="1"
|
||||
Margin="12,0,12,8"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<DataGrid ItemsSource="{Binding SelectedAxes}"
|
||||
SelectedItem="{Binding SelectedAxisParameter, Mode=TwoWay}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
MinHeight="420"
|
||||
MinWidth="1100"
|
||||
ScrollViewer.CanContentScroll="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Disabled">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="轴 (Axis)" Width="180">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding ModuleName}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.AxisOptions, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
DisplayMemberPath="AxisName"
|
||||
SelectedValuePath="AxisName"
|
||||
SelectedValue="{Binding ModuleName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource PidAxisComboBoxStyle}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn Header="SLVKP" Width="100"
|
||||
Binding="{Binding PA_SLVKP.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
ElementStyle="{StaticResource PidNumericDisplayTextStyle}"
|
||||
EditingElementStyle="{StaticResource PidNumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="SLVKI" Width="100"
|
||||
Binding="{Binding PA_SLVKI.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
ElementStyle="{StaticResource PidNumericDisplayTextStyle}"
|
||||
EditingElementStyle="{StaticResource PidNumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="SLVKPSF" Width="110"
|
||||
Binding="{Binding PA_SLVKPSF.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
ElementStyle="{StaticResource PidNumericDisplayTextStyle}"
|
||||
EditingElementStyle="{StaticResource PidNumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="SLVKPIF" Width="110"
|
||||
Binding="{Binding PA_SLVKPIF.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
ElementStyle="{StaticResource PidNumericDisplayTextStyle}"
|
||||
EditingElementStyle="{StaticResource PidNumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="SLVKISF" Width="110"
|
||||
Binding="{Binding PA_SLVKISF.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
ElementStyle="{StaticResource PidNumericDisplayTextStyle}"
|
||||
EditingElementStyle="{StaticResource PidNumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="SLVKIIF" Width="110"
|
||||
Binding="{Binding PA_SLVKIIF.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
ElementStyle="{StaticResource PidNumericDisplayTextStyle}"
|
||||
EditingElementStyle="{StaticResource PidNumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="SLPKP" Width="100"
|
||||
Binding="{Binding PA_SLPKP.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
ElementStyle="{StaticResource PidNumericDisplayTextStyle}"
|
||||
EditingElementStyle="{StaticResource PidNumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="SLPKPIF" Width="110"
|
||||
Binding="{Binding PA_SLPKPIF.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
ElementStyle="{StaticResource PidNumericDisplayTextStyle}"
|
||||
EditingElementStyle="{StaticResource PidNumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="SLPKPSF" Width="110"
|
||||
Binding="{Binding PA_SLPKPSF.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
ElementStyle="{StaticResource PidNumericDisplayTextStyle}"
|
||||
EditingElementStyle="{StaticResource PidNumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="SLAFF" Width="100"
|
||||
Binding="{Binding PA_SLAFF.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
ElementStyle="{StaticResource PidNumericDisplayTextStyle}"
|
||||
EditingElementStyle="{StaticResource PidNumericOnlyEditingTextBoxStyle}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</ScrollViewer>
|
||||
|
||||
<WrapPanel Grid.Row="2" Margin="12,0,12,12">
|
||||
<Button Content="新增轴参数"
|
||||
Style="{StaticResource PidPrimaryButtonStyle}"
|
||||
Click="{mw:Action AddAxis}"/>
|
||||
<Button Content="删除轴参数"
|
||||
Style="{StaticResource PidDangerButtonStyle}"
|
||||
Click="{mw:Action DeleteAxis}"/>
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="滤波参数">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="8"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0"
|
||||
Margin="12,8,12,6"
|
||||
Text="每个行对应一个轴的滤波参数设置,请根据机械共振频率调整滤波参数。"
|
||||
Style="{StaticResource PidHintTextStyle}"
|
||||
Foreground="{StaticResource PidHeaderForegroundBrush}"/>
|
||||
|
||||
<Grid Grid.Row="1" Margin="12,0,12,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Margin="0,0,0,6"
|
||||
Text="陷波参数"
|
||||
Style="{StaticResource PidSectionTitleStyle}"/>
|
||||
|
||||
<ScrollViewer Grid.Row="1"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<DataGrid ItemsSource="{Binding SelectedFilteringParameters}"
|
||||
SelectedItem="{Binding SelectedNotchFilteringParameter, Mode=TwoWay}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
MinHeight="200"
|
||||
MinWidth="900"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Disabled">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="轴名称" Width="180">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding AxisDisplayName}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.AxisOptions, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
DisplayMemberPath="AxisName"
|
||||
SelectedValuePath="AxisIndex"
|
||||
SelectedValue="{Binding AxisIndex, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource PidAxisComboBoxStyle}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridCheckBoxColumn Header="开启陷波" Width="90"
|
||||
Binding="{Binding IsUseNotch.Value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<DataGridTemplateColumn Header="陷波频率 (Hz)" Width="120">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding NotchFrequency.Value}" Style="{StaticResource PidNumericDisplayTextStyle}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<mw:NumberBox Value="{Binding NotchFrequency.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource PidNumberBoxStyle}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Header="陷波宽度" Width="110">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding NotchWidth.Value}" Style="{StaticResource PidNumericDisplayTextStyle}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<mw:NumberBox Value="{Binding NotchWidth.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource PidNumberBoxStyle}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Header="衰减" Width="110">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding NotchFalloff.Value}" Style="{StaticResource PidNumericDisplayTextStyle}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<mw:NumberBox Value="{Binding NotchFalloff.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource PidNumberBoxStyle}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="3" Margin="12,0,12,8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Margin="0,0,0,6"
|
||||
Text="低通参数"
|
||||
Style="{StaticResource PidSectionTitleStyle}"/>
|
||||
|
||||
<ScrollViewer Grid.Row="1"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<DataGrid ItemsSource="{Binding SelectedFilteringParameters}"
|
||||
SelectedItem="{Binding SelectedLowPassFilteringParameter, Mode=TwoWay}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
MinHeight="200"
|
||||
MinWidth="760"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Disabled">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="轴名称" Width="180">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding AxisDisplayName}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.AxisOptions, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
DisplayMemberPath="AxisName"
|
||||
SelectedValuePath="AxisIndex"
|
||||
SelectedValue="{Binding AxisIndex, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource PidAxisComboBoxStyle}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridCheckBoxColumn Header="开启低通" Width="90"
|
||||
Binding="{Binding IsUseFiltering.Value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<DataGridTemplateColumn Header="低通频率 (Hz)" Width="130">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding FilteringFrequency.Value}" Style="{StaticResource PidNumericDisplayTextStyle}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<mw:NumberBox Value="{Binding FilteringFrequency.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource PidNumberBoxStyle}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Header="阻尼系数" Width="130">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding FilteringDamping.Value}" Style="{StaticResource PidNumericDisplayTextStyle}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<mw:NumberBox Value="{Binding FilteringDamping.Value, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource PidNumberBoxStyle}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
<WrapPanel Grid.Row="4" Margin="12,0,12,12">
|
||||
<Button Content="新增滤波参数"
|
||||
Style="{StaticResource PidPrimaryButtonStyle}"
|
||||
Click="{mw:Action AddFilteringParameter}"/>
|
||||
<Button Content="删除滤波参数"
|
||||
Style="{StaticResource PidDangerButtonStyle}"
|
||||
Click="{mw:Action DeleteFilteringParameter}"/>
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="2"
|
||||
Margin="12,0,12,12"
|
||||
Padding="12,8"
|
||||
Background="White"
|
||||
BorderBrush="{StaticResource PidCardBorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<TextBlock Text="说明:PID 配置组由程序初始化;每个配置组对应一组 PID 参数和滤波参数。"
|
||||
Style="{StaticResource PidHintTextStyle}"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
/// <summary>
|
||||
/// PidRecipeEditorView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class PidRecipeEditorView : UserControl
|
||||
{
|
||||
public PidRecipeEditorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<UserControl x:Class="MainShell.Recipe.View.ProcessCompensationView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mw="http://www.maxwell-gp.com/"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="500"
|
||||
d:DesignWidth="960">
|
||||
<Grid Background="#F4F7FB">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Margin="12,12,12,8"
|
||||
Padding="12,10"
|
||||
Background="White"
|
||||
BorderBrush="#D6E0EA"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<StackPanel>
|
||||
<TextBlock Text="补偿参数"
|
||||
Foreground="#2E5FA7"
|
||||
FontSize="15"
|
||||
FontWeight="Bold"/>
|
||||
<TextBlock Margin="0,4,0,0"
|
||||
Text="当前工艺配方支持对 PHS-X1、PHS-Y1、WS-X、Stage-Y 四个轴分别设置补偿值。"
|
||||
Foreground="#6B7B8D"
|
||||
FontSize="12"
|
||||
TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1"
|
||||
Margin="12,0,12,12"
|
||||
Background="White"
|
||||
BorderBrush="#D6E0EA"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Padding="14,10"
|
||||
Background="#EEF3F8"
|
||||
BorderBrush="#D6E0EA"
|
||||
BorderThickness="0,0,0,1"
|
||||
CornerRadius="4,4,0,0">
|
||||
<TextBlock Text="轴补偿设置"
|
||||
Foreground="#2E5FA7"
|
||||
FontSize="15"
|
||||
FontWeight="Bold"/>
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="1" Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="180"/>
|
||||
<ColumnDefinition Width="180"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#2E5FA7"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Text="PHS-X1"/>
|
||||
<mw:NumberBox Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ProcessLargeNumberBoxStyle}"
|
||||
Value="{Binding PhsX1Compensation, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
DecimalPlaces="4"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"
|
||||
ShowUpDownButton="True"/>
|
||||
<TextBlock Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#6B7B8D"
|
||||
Text="mm"/>
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="0,12,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#2E5FA7"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Text="PHS-Y1"/>
|
||||
<mw:NumberBox Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="8,12,0,0"
|
||||
Style="{StaticResource ProcessLargeNumberBoxStyle}"
|
||||
Value="{Binding PhsY1Compensation, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
DecimalPlaces="4"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"
|
||||
ShowUpDownButton="True"/>
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Margin="8,12,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#6B7B8D"
|
||||
Text="mm"/>
|
||||
|
||||
<TextBlock Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="0,12,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#2E5FA7"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Text="WS-X"/>
|
||||
<mw:NumberBox Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="8,12,0,0"
|
||||
Style="{StaticResource ProcessLargeNumberBoxStyle}"
|
||||
Value="{Binding WsXCompensation, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
DecimalPlaces="4"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"
|
||||
ShowUpDownButton="True"/>
|
||||
<TextBlock Grid.Row="2"
|
||||
Grid.Column="2"
|
||||
Margin="8,12,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#6B7B8D"
|
||||
Text="mm"/>
|
||||
|
||||
<TextBlock Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Margin="0,12,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#2E5FA7"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Text="Stage-Y"/>
|
||||
<mw:NumberBox Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Margin="8,12,0,0"
|
||||
Style="{StaticResource ProcessLargeNumberBoxStyle}"
|
||||
Value="{Binding StageYCompensation, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
DecimalPlaces="4"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"
|
||||
ShowUpDownButton="True"/>
|
||||
<TextBlock Grid.Row="3"
|
||||
Grid.Column="2"
|
||||
Margin="8,12,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#6B7B8D"
|
||||
Text="mm"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
/// <summary>
|
||||
/// ProcessCompensationView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ProcessCompensationView : UserControl
|
||||
{
|
||||
public ProcessCompensationView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<UserControl x:Class="MainShell.Recipe.View.ProcessRecipeView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:MainShell.Recipe.View"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="720" d:DesignWidth="1180">
|
||||
<UserControl.Resources>
|
||||
<SolidColorBrush x:Key="ProcessRecipePageBackgroundBrush" Color="#F4F7FB"/>
|
||||
<SolidColorBrush x:Key="ProcessRecipeTabBorderBrush" Color="#D6E0EA"/>
|
||||
<SolidColorBrush x:Key="ProcessRecipeTabHeaderBrush" Color="#425466"/>
|
||||
<SolidColorBrush x:Key="ProcessRecipeTabHoverBrush" Color="#F4F8FE"/>
|
||||
<SolidColorBrush x:Key="ProcessRecipeTabSelectedBrush" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="ProcessRecipeTabAccentBrush" Color="#2E5FA7"/>
|
||||
|
||||
<Style x:Key="ProcessRecipeTabItemStyle" TargetType="TabItem">
|
||||
<Setter Property="Padding" Value="28,12"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource ProcessRecipeTabHeaderBrush}"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TabItem">
|
||||
<Border x:Name="TabBorder"
|
||||
Background="#E9EEF4"
|
||||
BorderBrush="{StaticResource ProcessRecipeTabBorderBrush}"
|
||||
BorderThickness="1,1,1,0"
|
||||
Margin="0,0,6,0"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ContentSource="Header"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="TabBorder" Property="Background" Value="{StaticResource ProcessRecipeTabHoverBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="TabBorder" Property="Background" Value="{StaticResource ProcessRecipeTabSelectedBrush}"/>
|
||||
<Setter TargetName="TabBorder" Property="BorderBrush" Value="{StaticResource ProcessRecipeTabAccentBrush}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource ProcessRecipeTabAccentBrush}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
<Grid Background="{StaticResource ProcessRecipePageBackgroundBrush}">
|
||||
<Border Margin="12"
|
||||
Background="White"
|
||||
BorderBrush="{StaticResource ProcessRecipeTabBorderBrush}"
|
||||
BorderThickness="1">
|
||||
<TabControl Background="Transparent"
|
||||
BorderThickness="0"
|
||||
ItemContainerStyle="{StaticResource ProcessRecipeTabItemStyle}">
|
||||
<TabItem Header="PID参数">
|
||||
<local:PidRecipeEditorView/>
|
||||
</TabItem>
|
||||
<TabItem Header="补偿参数">
|
||||
<local:ProcessCompensationView DataContext="{Binding ProcessCompensationViewModel}"/>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
/// <summary>
|
||||
/// ProcessRecipeView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ProcessRecipeView : UserControl
|
||||
{
|
||||
public ProcessRecipeView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<UserControl x:Class="MainShell.Recipe.View.RecipeButtonGroupView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:MainShell.Recipe.View"
|
||||
mc:Ignorable="d"
|
||||
>
|
||||
|
||||
<Border Background="#EBEEF3">
|
||||
<StackPanel>
|
||||
<UniformGrid Margin="2" Columns="2" Rows="4">
|
||||
<Button Command="{Binding RecipeButtonGroupViewModel.CreateNewCmd}" CommandParameter="{Binding}" ToolTip="{DynamicResource New}" Style="{StaticResource iconButton}">
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
<TextBlock Text="" Foreground="Blue" FontFamily="{StaticResource ttfFont}" VerticalAlignment="Center" />
|
||||
<TextBlock Margin="4,0" Text="{DynamicResource New}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Command="{Binding RecipeButtonGroupViewModel.ApplyCmd}" CommandParameter="{Binding}" ToolTip="{DynamicResource Apply}" Style="{StaticResource iconButton}">
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
<TextBlock Text="" Foreground="Blue" FontFamily="{StaticResource ttfFont}" VerticalAlignment="Center" />
|
||||
<TextBlock Margin="4,0" Text="{DynamicResource Apply}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Command="{Binding RecipeButtonGroupViewModel.ReNameCmd}" CommandParameter="{Binding }" ToolTip="{DynamicResource ReName}" Style="{StaticResource iconButton}">
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
<TextBlock Text="" Foreground="#7CEAB8" FontFamily="{StaticResource ttfFont}" VerticalAlignment="Center" />
|
||||
<TextBlock Margin="4,0" Text="{DynamicResource ReName}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Command="{Binding RecipeButtonGroupViewModel.DeleteCmd}" CommandParameter="{Binding}" ToolTip="{DynamicResource Delete}" Style="{StaticResource iconButton}">
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
<TextBlock Text="" Foreground="Red" FontFamily="{StaticResource ttfFont}" VerticalAlignment="Center" />
|
||||
<TextBlock Margin="4,0" Text="{DynamicResource Delete}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Command="{Binding RecipeButtonGroupViewModel.CopyCmd}" CommandParameter="{Binding }" ToolTip="{DynamicResource Copy}" Style="{StaticResource iconButton}" IsEnabled="{Binding CanCopyRecipe}">
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
<TextBlock Text="" Foreground="Blue" FontFamily="{StaticResource ttfFont}" VerticalAlignment="Center" />
|
||||
<TextBlock Margin="4,0" Text="{DynamicResource Copy}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Command="{Binding RecipeButtonGroupViewModel.ClearCmd}" CommandParameter="{Binding }" ToolTip="{DynamicResource Clear}" Style="{StaticResource iconButton}">
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
<TextBlock Text="" Foreground="Red" FontFamily="{StaticResource ttfFont}" VerticalAlignment="Center" />
|
||||
<TextBlock Margin="4,0" Text="{DynamicResource Clear}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Command="{Binding RecipeButtonGroupViewModel.ImportCmd}" CommandParameter="{Binding }" ToolTip="{DynamicResource Import}" Style="{StaticResource iconButton}" IsEnabled="{Binding CanImportRecipe}">
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
<TextBlock Text="" Foreground="#5462E6" FontFamily="{StaticResource ttfFont}" VerticalAlignment="Center" />
|
||||
<TextBlock Margin="4,0" Text="{DynamicResource Import}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Command="{Binding RecipeButtonGroupViewModel.ExportCmd}" CommandParameter="{Binding }" ToolTip="{DynamicResource Export}" Style="{StaticResource iconButton}" IsEnabled="{Binding CanExportRecipe}">
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
<TextBlock Text="" Foreground="#5462E6" FontFamily="{StaticResource ttfFont}" VerticalAlignment="Center" />
|
||||
<TextBlock Margin="4,0" Text="{DynamicResource Export}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</UniformGrid>
|
||||
<Button Command="{Binding RecipeButtonGroupViewModel.SaveCmd}" CommandParameter="{Binding }" Width="140" ToolTip="{DynamicResource Save}" Style="{StaticResource iconButton}">
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
<TextBlock Text="" Foreground="#007BFF" FontSize="16" FontFamily="{StaticResource ttfFont}" VerticalAlignment="Center" />
|
||||
<TextBlock Margin="4,0" Text="{DynamicResource Save}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
/// <summary>
|
||||
/// RecipeButtonGroupView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class RecipeButtonGroupView : UserControl
|
||||
{
|
||||
public RecipeButtonGroupView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<Window x:Class="MainShell.Recipe.View.RecipeNameWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:MainShell.Recipe.View"
|
||||
xmlns:common="clr-namespace:MainShell.Common"
|
||||
mc:Ignorable="d"
|
||||
common:WindowService.DialogResult="{Binding DialogResult}"
|
||||
Title="RecipeNameWindow"
|
||||
ResizeMode="NoResize"
|
||||
AllowsTransparency="True"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
WindowStyle="None"
|
||||
Background="Transparent"
|
||||
Height="180" Width="380"
|
||||
Loaded="Window_Loaded">
|
||||
<Border
|
||||
Background="#F8F8F8"
|
||||
CornerRadius="10"
|
||||
BorderBrush="#E8E8E8"
|
||||
BorderThickness="1"
|
||||
MouseDown="Window_MouseDown">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Style="{StaticResource LabelStyle}"
|
||||
Text="{DynamicResource CreateRecipe}"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="18"
|
||||
FontFamily="Segoe UI"
|
||||
FontWeight="Bold"
|
||||
Foreground="#424A4D"
|
||||
Margin="10,10,0,0"/>
|
||||
<StackPanel Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="请输入新配方的名称:" FontSize="14" VerticalAlignment="Center" Foreground="#555555" Margin="0,0,0,8"/>
|
||||
<TextBox x:Name="RecipeNameTextBox" Width="260" Height="34" Padding="10,0" FontSize="15"
|
||||
VerticalContentAlignment="Center" BorderThickness="1" BorderBrush="#CCCCCC"
|
||||
Background="White"
|
||||
Text="{Binding RecipeName, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<TextBlock
|
||||
Text="{Binding ValidationMessage}"
|
||||
Foreground="Red"
|
||||
Margin="4,2,0,0"
|
||||
FontSize="12"
|
||||
TextWrapping="Wrap">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
<Style.Triggers>
|
||||
<!-- 当 Text 为空时折叠 -->
|
||||
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Self}}" Value="">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Self}}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="20,0,20,20">
|
||||
|
||||
<Button Content="确认" IsDefault="True" Style="{StaticResource StartButtonStyle}"
|
||||
Command="{Binding OkCommand}"/>
|
||||
<Button Content="取消" IsCancel="True" Style="{StaticResource CloseButtonStyle}" Margin="0,0,10,0"
|
||||
Command="{Binding CancelCommand}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
/// <summary>
|
||||
/// RecipeNameWindow.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class RecipeNameWindow : Window
|
||||
{
|
||||
public RecipeNameWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
this.DragMove();
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 使用 Dispatcher 确保控件已可交互,然后设置键盘焦点并全选文本
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
RecipeNameTextBox.Focus();
|
||||
Keyboard.Focus(RecipeNameTextBox);
|
||||
RecipeNameTextBox.SelectAll();
|
||||
}), DispatcherPriority.Input);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<Window x:Class="MainShell.Recipe.View.RecipeReNameWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:MainShell.Recipe.View"
|
||||
xmlns:common="clr-namespace:MainShell.Common"
|
||||
mc:Ignorable="d"
|
||||
common:WindowService.DialogResult="{Binding DialogResult}"
|
||||
Title="RecipeNameWindow"
|
||||
ResizeMode="NoResize"
|
||||
AllowsTransparency="True"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
WindowStyle="None"
|
||||
Background="Transparent"
|
||||
Height="240" Width="380"
|
||||
Loaded="Window_Loaded"
|
||||
>
|
||||
<Border
|
||||
Background="#F8F8F8"
|
||||
CornerRadius="10"
|
||||
BorderBrush="#E8E8E8"
|
||||
BorderThickness="1"
|
||||
MouseDown="Window_MouseDown">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Style="{StaticResource LabelStyle}"
|
||||
Text="{DynamicResource ReNameRecipe}"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="18"
|
||||
FontFamily="Segoe UI"
|
||||
FontWeight="Bold"
|
||||
Foreground="#424A4D"
|
||||
Margin="10,10,0,0"/>
|
||||
<StackPanel Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="配方旧名称:" FontSize="14" VerticalAlignment="Center" Foreground="#555555" Margin="0,0,0,8"/>
|
||||
<TextBox x:Name="OldRecipeNameTextBox" Width="260" Height="34" Padding="10,0" FontSize="15"
|
||||
VerticalContentAlignment="Center" BorderThickness="1" BorderBrush="#CCCCCC"
|
||||
Background="White"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding OldRecipeName}"/>
|
||||
<TextBlock Text="请输入新名称:" FontSize="14" VerticalAlignment="Center" Foreground="#555555" Margin="0,0,0,8"/>
|
||||
<TextBox x:Name="RecipeNameTextBox" Width="260" Height="34" Padding="10,0" FontSize="15"
|
||||
VerticalContentAlignment="Center" BorderThickness="1" BorderBrush="#CCCCCC"
|
||||
Background="White"
|
||||
Text="{Binding RecipeName, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<TextBlock
|
||||
Text="{Binding ValidationMessage}"
|
||||
Foreground="Red"
|
||||
Margin="4,2,0,0"
|
||||
FontSize="12"
|
||||
TextWrapping="Wrap">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
<Style.Triggers>
|
||||
<!-- 当 Text 为空时折叠 -->
|
||||
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Self}}" Value="">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Self}}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="20,0,20,20">
|
||||
|
||||
<Button Content="确认" IsDefault="True" Style="{StaticResource StartButtonStyle}"
|
||||
Command="{Binding OkCommand}"/>
|
||||
<Button Content="取消" IsCancel="True" Style="{StaticResource CloseButtonStyle}" Margin="0,0,10,0"
|
||||
Command="{Binding CancelCommand}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
/// <summary>
|
||||
/// RecipeReNameWindow.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class RecipeReNameWindow : Window
|
||||
{
|
||||
public RecipeReNameWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
this.DragMove();
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 使用 Dispatcher 确保控件已可交互,然后设置键盘焦点并全选文本
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
RecipeNameTextBox.Focus();
|
||||
Keyboard.Focus(RecipeNameTextBox);
|
||||
RecipeNameTextBox.SelectAll();
|
||||
}), DispatcherPriority.Input);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<UserControl x:Class="MainShell.Recipe.View.RecipeView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:MainShell.Recipe.View"
|
||||
xmlns:mw="http://www.maxwell-gp.com/"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Margin="-40,0,0,0" Grid.Column="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<mw:SideMenu ItemsSource="{Binding MenuItemWraps}"
|
||||
SelectedItem="{Binding SelectedMenuItem, Mode=TwoWay}"
|
||||
PreviewMouseLeftButtonDown="SideMenu_PreviewMouseLeftButtonDown"
|
||||
Grid.Row ="0" Width="150"
|
||||
ExpandMode="Accordion"
|
||||
AutoSelect="True"
|
||||
>
|
||||
<mw:SideMenu.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type mw:SideMenuItem}" BasedOn="{StaticResource SideMenuItemHeaderAccordionBaseStyle}">
|
||||
<Setter Property="Header" Value="{Binding Header}" />
|
||||
<Setter Property="Tag" Value="{Binding Tag}" />
|
||||
</Style>
|
||||
</mw:SideMenu.ItemContainerStyle>
|
||||
</mw:SideMenu>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Column="1">
|
||||
<GroupBox Width="200" x:Name="groupList" Header="{DynamicResource RecipeList}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox x:Name="recipeList" SelectedItem="{Binding CurrentScreen.SelectedRecipeWrap, Mode=TwoWay}" Style="{StaticResource recipeListBoxStyle}"
|
||||
PreviewMouseLeftButtonDown="RecipeList_PreviewMouseLeftButtonDown"
|
||||
ItemsSource="{Binding CurrentScreen.RecipeWraps}"
|
||||
d:ItemsSource="{d:SampleData ItemCount=5}"/>
|
||||
<Border DataContext="{Binding ElementName=recipeList, Path=SelectedItem}" BorderThickness="1" BorderBrush="AliceBlue" Grid.Row="1">
|
||||
<ScrollViewer>
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="创建时间:"/>
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding CreateTime, Converter={StaticResource DateTimeToStringConverter}}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="修改时间:"/>
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding ModifiedTime, Converter={StaticResource DateTimeToStringConverter}}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="描述:"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
<Border Grid.Row="2">
|
||||
<local:RecipeButtonGroupView DataContext="{Binding CurrentScreen}" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
<ToggleButton x:Name="toggleBtn" HorizontalAlignment="Right" Style="{StaticResource ToggleBtnSideBarStyle}"
|
||||
IsChecked="{Binding IsRecipePanelHidden, Mode=TwoWay}">
|
||||
|
||||
<ToggleButton.Triggers>
|
||||
<EventTrigger RoutedEvent="ToggleButton.Checked">
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="groupList" Storyboard.TargetProperty="(FrameworkElement.Width)" To="0" Duration="0:0:0.2"/>
|
||||
<ThicknessAnimation Storyboard.TargetName="toggleBtn"
|
||||
Storyboard.TargetProperty="(FrameworkElement.Margin)"
|
||||
To="-30,0,0,0" Duration="0:0:0.2"/>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
<EventTrigger RoutedEvent="ToggleButton.Unchecked">
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="groupList" Storyboard.TargetProperty="(FrameworkElement.Width)" To="200" Duration="0:0:0.2"/>
|
||||
<ThicknessAnimation Storyboard.TargetName="toggleBtn"
|
||||
Storyboard.TargetProperty="(FrameworkElement.Margin)"
|
||||
To="0,0,0,0" Duration="0:0:0.2"/>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</ToggleButton.Triggers>
|
||||
</ToggleButton>
|
||||
</Grid>
|
||||
<ContentControl Margin="5,0" Grid.Column="2" mw:View.Model="{Binding CurrentScreen}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using MainShell.Common;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.ViewModel;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
/// <summary>
|
||||
/// RecipeView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class RecipeView : UserControl
|
||||
{
|
||||
public RecipeView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void SideMenu_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var viewModel = DataContext as RecipeViewModel;
|
||||
if (viewModel == null)
|
||||
return;
|
||||
|
||||
FocusedEditorCommitHelper.CommitFocusedEditorChanges();
|
||||
|
||||
var targetMenuItem = FindMenuItemWrap(e.OriginalSource as DependencyObject);
|
||||
if (targetMenuItem == null)
|
||||
return;
|
||||
|
||||
e.Handled = true;
|
||||
viewModel.TrySelectMenuItem(targetMenuItem);
|
||||
}
|
||||
|
||||
private void RecipeList_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var viewModel = DataContext as RecipeViewModel;
|
||||
if (viewModel == null)
|
||||
return;
|
||||
|
||||
FocusedEditorCommitHelper.CommitFocusedEditorChanges();
|
||||
|
||||
var targetRecipeWrap = FindDataContext<RecipeWrap>(e.OriginalSource as DependencyObject);
|
||||
if (targetRecipeWrap == null)
|
||||
return;
|
||||
|
||||
e.Handled = true;
|
||||
viewModel.TrySelectRecipeWrap(targetRecipeWrap);
|
||||
}
|
||||
|
||||
private static MenuItemWrap FindMenuItemWrap(DependencyObject dependencyObject)
|
||||
{
|
||||
return FindDataContext<MenuItemWrap>(dependencyObject);
|
||||
}
|
||||
|
||||
private static T FindDataContext<T>(DependencyObject dependencyObject) where T : class
|
||||
{
|
||||
var current = dependencyObject;
|
||||
while (current != null)
|
||||
{
|
||||
if (current is FrameworkElement frameworkElement && frameworkElement.DataContext is T dataContext)
|
||||
{
|
||||
return dataContext;
|
||||
}
|
||||
|
||||
if (current is FrameworkContentElement frameworkContentElement && frameworkContentElement.DataContext is T contentDataContext)
|
||||
{
|
||||
return contentDataContext;
|
||||
}
|
||||
|
||||
current = GetParent(current);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static DependencyObject GetParent(DependencyObject dependencyObject)
|
||||
{
|
||||
if (dependencyObject is FrameworkElement frameworkElement)
|
||||
{
|
||||
return frameworkElement.Parent ?? VisualTreeHelper.GetParent(frameworkElement);
|
||||
}
|
||||
|
||||
if (dependencyObject is FrameworkContentElement frameworkContentElement)
|
||||
{
|
||||
return frameworkContentElement.Parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
<UserControl x:Class="MainShell.Recipe.View.SubstrateHeightMeasureView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mw="http://www.maxwell-gp.com/"
|
||||
xmlns:behavior="clr-namespace:MainShell.Common.ControlAttribute"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="900">
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="NumericOnlyEditingTextBoxStyle" TargetType="TextBox">
|
||||
<Setter Property="behavior:ControlBehavior.IsNumericOnly" Value="True"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="Padding" Value="6,2"/>
|
||||
</Style>
|
||||
<Style x:Key="DataGridRowStyle" TargetType="DataGridRow">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="AlternationIndex" Value="1">
|
||||
<Setter Property="Background" Value="#F7FBFF"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox Grid.Row="0"
|
||||
Margin="8,8,8,4"
|
||||
Header="测高模式"
|
||||
Style="{StaticResource GroupBoxSecondary}">
|
||||
<Border Margin="10"
|
||||
Padding="12"
|
||||
Background="#F7FBFF"
|
||||
BorderBrush="#D7E6F5"
|
||||
BorderThickness="1"
|
||||
CornerRadius="6">
|
||||
<StackPanel>
|
||||
<TextBlock Text="支持标准示教位置与行列偏移两种测高点配置方式"
|
||||
Foreground="#4A6178"
|
||||
Margin="0,0,0,12"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<RadioButton Content="标准示教位置"
|
||||
IsChecked="{Binding Mode, Mode=TwoWay, Converter={StaticResource SubstrateHeightMeasureModeToBoolConverter}, ConverterParameter=0}"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Margin="0,0,24,0"/>
|
||||
<RadioButton Content="行列坐标 + 偏移补偿"
|
||||
IsChecked="{Binding Mode, Mode=TwoWay, Converter={StaticResource SubstrateHeightMeasureModeToBoolConverter}, ConverterParameter=1}"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="SemiBold"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Grid.Row="1"
|
||||
Margin="8,4,8,8"
|
||||
Header="测高点设置"
|
||||
Style="{StaticResource GroupBoxSecondary}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<DockPanel Grid.Row="0" Margin="8,8,8,4" LastChildFill="False">
|
||||
<StackPanel DockPanel.Dock="Left">
|
||||
<TextBlock Text="测高点列表"
|
||||
FontSize="16"
|
||||
FontWeight="Bold"
|
||||
Foreground="#24415C"/>
|
||||
<TextBlock Text="当前模式下可维护测高点并编辑对应参数"
|
||||
Margin="0,4,0,0"
|
||||
Foreground="#6B7F92"/>
|
||||
</StackPanel>
|
||||
<WrapPanel DockPanel.Dock="Right" Orientation="Horizontal" Margin="12,0,0,0">
|
||||
<Button Content="添加测高点"
|
||||
Style="{StaticResource StartButtonStyle}"
|
||||
Command="{Binding AddPointCmd}"
|
||||
Margin="4"/>
|
||||
<Button Content="删除测高点"
|
||||
Style="{StaticResource CloseButtonStyle}"
|
||||
Command="{Binding DeletePointCmd}"
|
||||
Margin="4"/>
|
||||
<Button Content="示教当前位置"
|
||||
Style="{StaticResource TeachButtonStyle}"
|
||||
Command="{Binding TeachPointCmd}"
|
||||
IsEnabled="{Binding IsTeachMode}"
|
||||
Margin="4"/>
|
||||
</WrapPanel>
|
||||
</DockPanel>
|
||||
|
||||
<Border Grid.Row="1"
|
||||
Margin="8,4,8,4"
|
||||
Padding="10,8"
|
||||
Background="#F7FBFF"
|
||||
BorderBrush="#D7E6F5"
|
||||
BorderThickness="1"
|
||||
CornerRadius="6"
|
||||
Visibility="{Binding IsRowColumnMode, Converter={StaticResource BoolToVisibleConverter}}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="统一偏移补偿:"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,8,0"
|
||||
Foreground="#4A6178"/>
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="X"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,4,0"/>
|
||||
<mw:NumberBox Grid.Column="2"
|
||||
Width="100"
|
||||
Margin="0,0,10,0"
|
||||
VerticalContentAlignment="Center"
|
||||
Value="{Binding CommonOffsetCompensation.X, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<TextBlock Grid.Column="3"
|
||||
Text="Y"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,4,0"/>
|
||||
<mw:NumberBox Grid.Column="4"
|
||||
Width="100"
|
||||
Margin="0,0,10,0"
|
||||
VerticalContentAlignment="Center"
|
||||
Value="{Binding CommonOffsetCompensation.Y, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<TextBlock Grid.Column="5"
|
||||
Text="可先统一写入,再对个别点单独修正"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,12,0"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="#6B7F92"/>
|
||||
<Button Grid.Column="6"
|
||||
Content="一键写入全部点位"
|
||||
Width="120"
|
||||
Style="{StaticResource VisionButtonStyle}"
|
||||
Command="{Binding ApplyOffsetToAllCmd}"
|
||||
Margin="12,0,0,0"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="2" Margin="8,4,8,8">
|
||||
<Grid Visibility="{Binding IsTeachMode, Converter={StaticResource BoolToVisibleConverter}}">
|
||||
<DataGrid ItemsSource="{Binding Points}"
|
||||
SelectedItem="{Binding SelectedPoint}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
MinHeight="140"
|
||||
AlternationCount="2"
|
||||
RowStyle="{StaticResource DataGridRowStyle}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="点位名称" Width="1.4*" Binding="{Binding PointName, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<DataGridTextColumn Header="标准位置 X" Width="*" Binding="{Binding TeachPosition.X, UpdateSourceTrigger=PropertyChanged, StringFormat=F4}" EditingElementStyle="{StaticResource NumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="标准位置 Y" Width="*" Binding="{Binding TeachPosition.Y, UpdateSourceTrigger=PropertyChanged, StringFormat=F4}" EditingElementStyle="{StaticResource NumericOnlyEditingTextBoxStyle}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
|
||||
<Grid Visibility="{Binding IsRowColumnMode, Converter={StaticResource BoolToVisibleConverter}}">
|
||||
<DataGrid ItemsSource="{Binding Points}"
|
||||
SelectedItem="{Binding SelectedPoint}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
MinHeight="140"
|
||||
AlternationCount="2"
|
||||
RowStyle="{StaticResource DataGridRowStyle}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="点位名称" Width="1.4*" Binding="{Binding PointName, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<DataGridTextColumn Header="行坐标" Width="*" Binding="{Binding RowIndex, UpdateSourceTrigger=PropertyChanged}" EditingElementStyle="{StaticResource NumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="列坐标" Width="*" Binding="{Binding ColumnIndex, UpdateSourceTrigger=PropertyChanged}" EditingElementStyle="{StaticResource NumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="偏移补偿 X" Width="*" Binding="{Binding OffsetCompensation.X, UpdateSourceTrigger=PropertyChanged, StringFormat=F4}" EditingElementStyle="{StaticResource NumericOnlyEditingTextBoxStyle}"/>
|
||||
<DataGridTextColumn Header="偏移补偿 Y" Width="*" Binding="{Binding OffsetCompensation.Y, UpdateSourceTrigger=PropertyChanged, StringFormat=F4}" EditingElementStyle="{StaticResource NumericOnlyEditingTextBoxStyle}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
public partial class SubstrateHeightMeasureView : UserControl
|
||||
{
|
||||
public SubstrateHeightMeasureView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<UserControl x:Class="MainShell.Recipe.View.SubstrateRecipeView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:MainShell.Recipe.View"
|
||||
xmlns:mw="http://www.maxwell-gp.com/"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="700" d:DesignWidth="800">
|
||||
<TabControl mw:TabControlAttach.HeaderWidth="Auto"
|
||||
SelectedIndex="{Binding SelectedTabIndex, Mode=TwoWay}">
|
||||
<TabItem Header="{DynamicResource BaseSetting}" Style="{StaticResource TabItemHorizontalStyle}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="240"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<GroupBox Header="{DynamicResource SubstrateInfo}" Style="{StaticResource GroupBoxSecondary}">
|
||||
<GroupBox.Resources>
|
||||
<Style TargetType="TextBlock" BasedOn="{StaticResource TextBlockStyle}"></Style>
|
||||
</GroupBox.Resources>
|
||||
<UniformGrid DataContext="{Binding SubstrateRecipe.SubstrateInfo}" Columns="2" Rows="14">
|
||||
<TextBlock Text="{DynamicResource SubstrateWidth}"/>
|
||||
<mw:NumberBox Value="{Binding SubstrateSize.Width,UpdateSourceTrigger=PropertyChanged}" mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource SubstrateHeight}"/>
|
||||
<mw:NumberBox Value="{Binding SubstrateSize.Height,UpdateSourceTrigger=PropertyChanged}" mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource SubstrateThickness}"/>
|
||||
<mw:NumberBox Value="{Binding ThickNess,UpdateSourceTrigger=PropertyChanged}" mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource SubstratePiexlWidth}"/>
|
||||
<mw:NumberBox Value="{Binding PadSize.Width,UpdateSourceTrigger=PropertyChanged}" mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource SubstratePiexlHeight}"/>
|
||||
<mw:NumberBox Value="{Binding PadSize.Height,UpdateSourceTrigger=PropertyChanged}" mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource SubstratePitchX}"/>
|
||||
<mw:NumberBox Value="{Binding PitchX,UpdateSourceTrigger=PropertyChanged}" mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource SubstratePitchY}"/>
|
||||
<mw:NumberBox Value="{Binding PitchY,UpdateSourceTrigger=PropertyChanged}" mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource SubstrateRowNum}"/>
|
||||
<mw:NumberBox Value="{Binding RowNumber,UpdateSourceTrigger=PropertyChanged}" mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource SubstrateColNum}"/>
|
||||
<mw:NumberBox Value="{Binding ColNumber,UpdateSourceTrigger=PropertyChanged}" mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
</UniformGrid>
|
||||
</GroupBox>
|
||||
<Grid Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<GroupBox BorderThickness="0,1,0,1"
|
||||
Style="{StaticResource GroupBoxSecondary}"
|
||||
Header="{DynamicResource WaferRecipesSelect}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<DataGrid x:Name="waferInfo"
|
||||
AutoGenerateColumns="False"
|
||||
SelectionMode="Single"
|
||||
ItemsSource="{Binding SubstrateRecipe.WaferSelectInfos}"
|
||||
mw:DataGridAttach.ShowRowNumber="True">
|
||||
<DataGrid.Columns>
|
||||
<DataGridComboBoxColumn Width="*" TextBinding="{Binding WaferRecipeName}" Header="{DynamicResource WaferRecipe}">
|
||||
<DataGridComboBoxColumn.EditingElementStyle>
|
||||
<Style TargetType="ComboBox">
|
||||
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=GroupBox}, Path=DataContext.WaferRecipes}" />
|
||||
<Setter Property="DisplayMemberPath" Value="RecipeName"/>
|
||||
<Setter Property="SelectedIndex" Value="0"/>
|
||||
</Style>
|
||||
</DataGridComboBoxColumn.EditingElementStyle>
|
||||
</DataGridComboBoxColumn>
|
||||
<DataGridTemplateColumn Width="100" Header="{DynamicResource SubstrateOffsetX}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<mw:NumberBox BorderBrush="Transparent"
|
||||
Background="Transparent"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
Value="{Binding OffsetX}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Width="100" Header="{DynamicResource SubstrateOffsetY}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<mw:NumberBox BorderBrush="Transparent"
|
||||
Background="Transparent"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
Value="{Binding OffsetY}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Width="100"
|
||||
Header="{DynamicResource InUse}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox Style="{StaticResource ModernCheckBoxStyle}" IsChecked="{Binding IsUse, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
<Border Grid.Row="1" Background="#F0F6F7">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button Style="{StaticResource StartButtonStyle}" Click="{mw:Action AddWaferSelectInfo}" Content="{DynamicResource Add}" Margin="4,2"/>
|
||||
<Button Style="{StaticResource CloseButtonStyle}"
|
||||
Command="{Binding DeleteSelectWaferInfoCmd}"
|
||||
CommandParameter="{Binding ElementName=waferInfo, Path=SelectedItem}"
|
||||
Content="{DynamicResource Delete}" Margin="4,2"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</GroupBox>
|
||||
<GroupBox Grid.Row="1" Header="{DynamicResource LogisticsInfo}">
|
||||
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="参数示教">
|
||||
<ContentControl mw:View.Model="{Binding SubstrateTeachViewModel}"/>
|
||||
</TabItem>
|
||||
<TabItem Header="测高设置">
|
||||
<local:SubstrateHeightMeasureView DataContext="{Binding SubstrateHeightMeasureViewModel}"/>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
/// <summary>
|
||||
/// SubstrateRecipeView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class SubstrateRecipeView : UserControl
|
||||
{
|
||||
public SubstrateRecipeView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<UserControl x:Class="MainShell.Recipe.View.SubstrateTeachView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:MainShell.Recipe.View"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="900">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentControl Content="{Binding CameraAxisViewModel}" />
|
||||
|
||||
<local:MarkTeachView Grid.Column="1" DataContext="{Binding MarkTeachViewModel}"/>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
/// <summary>
|
||||
/// SubstrateTeachView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class SubstrateTeachView : UserControl
|
||||
{
|
||||
public SubstrateTeachView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<UserControl x:Class="MainShell.Recipe.View.WaferRecipeView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:MainShell.Recipe.View" xmlns:mw="http://www.maxwell-gp.com/"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="750" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<TabControl mw:TabControlAttach.HeaderWidth="Auto"
|
||||
SelectedIndex="{Binding SelectedTabIndex, Mode=TwoWay}">
|
||||
<TabItem Header="{DynamicResource BaseSetting}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="240"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<GroupBox Header="{DynamicResource WaferInfo}" Style="{StaticResource GroupBoxSecondary}">
|
||||
<GroupBox.Resources>
|
||||
<Style TargetType="TextBlock" BasedOn="{StaticResource TextBlockStyle}">
|
||||
<Setter Property="HorizontalAlignment" Value="Right"/>
|
||||
</Style>
|
||||
</GroupBox.Resources>
|
||||
<UniformGrid Rows="14" Columns="2">
|
||||
<TextBlock Text="{DynamicResource DieColor}"/>
|
||||
<ComboBox IsEditable="True"
|
||||
Text="{Binding WaferRecipe.WaferInfo.DieColor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<TextBlock Text="{DynamicResource DieSizeX}"/>
|
||||
<mw:NumberBox Value="{Binding WaferRecipe.WaferInfo.DieSizeX, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource DieSizeY}"/>
|
||||
<mw:NumberBox Value="{Binding WaferRecipe.WaferInfo.DieSizeY, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource DiePitchX}"/>
|
||||
<mw:NumberBox Value="{Binding WaferRecipe.WaferInfo.DiePitchX, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource DiePitchY}"/>
|
||||
<mw:NumberBox Value="{Binding WaferRecipe.WaferInfo.DiePitchY, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource WaferWidth}"/>
|
||||
<mw:NumberBox Value="{Binding WaferRecipe.WaferInfo.WaferWidth, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource WaferHeight}"/>
|
||||
<mw:NumberBox Value="{Binding WaferRecipe.WaferInfo.WaferHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource WaferRowNum}"/>
|
||||
<mw:NumberBox Value="{Binding WaferRecipe.WaferInfo.WaferRowNum, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
<TextBlock Text="{DynamicResource WaferColNum}"/>
|
||||
<mw:NumberBox Value="{Binding WaferRecipe.WaferInfo.WaferColNum, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
</UniformGrid>
|
||||
</GroupBox>
|
||||
<Grid Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<GroupBox Grid.Row="0" Header="工艺参数选择" Style="{StaticResource GroupBoxSecondary}">
|
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<TextBlock Text="工艺参数配方: " Style="{StaticResource TextBlockStyle}" VerticalAlignment="Center"/>
|
||||
<ComboBox MinWidth="160"
|
||||
ItemsSource="{Binding ProcessRecipes}"
|
||||
DisplayMemberPath="RecipeName"
|
||||
SelectedItem="{Binding SelectedProcessRecipeWrap, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="参数示教">
|
||||
<local:WaferTeachView DataContext="{Binding WaferTeachViewModel}"/>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
/// <summary>
|
||||
/// WaferRecipeView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class WaferRecipeView : UserControl
|
||||
{
|
||||
public WaferRecipeView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
<UserControl x:Class="MainShell.Recipe.View.WaferTeachView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mw="http://www.maxwell-gp.com/"
|
||||
xmlns:conv="clr-namespace:MainShell.Converter"
|
||||
xmlns:common="clr-namespace:MainShell.Common"
|
||||
mc:Ignorable="d"
|
||||
>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Margin="0,0,8,0">
|
||||
<ContentControl Content="{Binding CameraAxisViewModel}" />
|
||||
</Border>
|
||||
|
||||
<ScrollViewer Grid.Column="1"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<Grid Margin="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GroupBox Header="模版制作"
|
||||
Style="{StaticResource ProcessWideCardGroupBoxStyle}">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="0,4,0,0">
|
||||
<Button Content="视觉参数设置"
|
||||
Click="{mw:Action OpenVisionParameterSetting}"
|
||||
Style="{StaticResource VisionButtonStyle}"
|
||||
mw:View.ActionTarget="{Binding }"
|
||||
Width="105"
|
||||
Height="40"
|
||||
Margin="0,0,10,0"/>
|
||||
<Button Content="Mark模版"
|
||||
Click="{mw:Action MakeTemplate}"
|
||||
mw:View.ActionTarget="{Binding }"
|
||||
Style="{StaticResource TemplateButtonStyle}"
|
||||
Width="105"
|
||||
Height="40"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
<Grid Grid.Row="1" DataContext="{Binding WaferRecipe.ScanSettings}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<GroupBox Grid.Row="0"
|
||||
Header="参数示教"
|
||||
Style="{StaticResource ProcessWideCardGroupBoxStyle}"
|
||||
Margin="0,0,0,10">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="起始X" Style="{StaticResource ProcessLabelStyle}"/>
|
||||
<mw:NumberBox Grid.Row="0" Grid.Column="1"
|
||||
Width="Auto"
|
||||
MinWidth="120"
|
||||
Style="{StaticResource ProcessLargeNumberBoxStyle}"
|
||||
Value="{Binding StartPointX, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="2" Content="起始Y" Style="{StaticResource ProcessLabelStyle}"/>
|
||||
<mw:NumberBox Grid.Row="0" Grid.Column="3"
|
||||
Width="Auto"
|
||||
MinWidth="120"
|
||||
Style="{StaticResource ProcessLargeNumberBoxStyle}"
|
||||
Value="{Binding StartPointY, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
|
||||
<StackPanel Grid.Row="1"
|
||||
Grid.ColumnSpan="4"
|
||||
Margin="0,12,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Button Content="示教"
|
||||
Click="{mw:Action TeachStartPoint}"
|
||||
mw:View.ActionTarget="{Binding DataContext, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Style="{StaticResource TeachButtonStyle}"
|
||||
Margin="0,0,10,0"/>
|
||||
<Button Content="移动到"
|
||||
Click="{mw:Action MoveToStartPoint}"
|
||||
mw:View.ActionTarget="{Binding DataContext, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Style="{StaticResource AxisMoveButtonStyle}"
|
||||
/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Grid.Row="1"
|
||||
Header="扫描参数"
|
||||
Style="{StaticResource ProcessWideCardGroupBoxStyle}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="X扫描方向" Style="{StaticResource ProcessLabelStyle}"/>
|
||||
<ComboBox Grid.Row="0" Grid.Column="1"
|
||||
Width="Auto"
|
||||
MinWidth="100"
|
||||
Style="{StaticResource ProcessLargeComboBoxStyle}"
|
||||
SelectedValue="{Binding ScanDirectionX, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
SelectedValuePath="Value"
|
||||
DisplayMemberPath="Description"
|
||||
ItemsSource="{conv:EnumBindingSourceExtension UseDescription=True, EnumType={x:Type common:TransPathDirection}}"/>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="2" Content="Y扫描方向" Style="{StaticResource ProcessLabelStyle}"/>
|
||||
<ComboBox Grid.Row="0" Grid.Column="3"
|
||||
Width="Auto"
|
||||
MinWidth="100"
|
||||
Style="{StaticResource ProcessLargeComboBoxStyle}"
|
||||
SelectedValue="{Binding ScanDirectionY, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
SelectedValuePath="Value"
|
||||
DisplayMemberPath="Description"
|
||||
ItemsSource="{conv:EnumBindingSourceExtension UseDescription=True, EnumType={x:Type common:TransPathDirection}}"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="X重叠率" Style="{StaticResource ProcessLabelStyle}"/>
|
||||
<mw:NumberBox Grid.Row="1" Grid.Column="1"
|
||||
Width="Auto"
|
||||
MinWidth="100"
|
||||
Style="{StaticResource ProcessLargeNumberBoxStyle}"
|
||||
Value="{Binding OverlapRateX, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="2" Content="Y重叠率" Style="{StaticResource ProcessLabelStyle}"/>
|
||||
<mw:NumberBox Grid.Row="1" Grid.Column="3"
|
||||
Width="Auto"
|
||||
MinWidth="100"
|
||||
Style="{StaticResource ProcessLargeNumberBoxStyle}"
|
||||
Value="{Binding OverlapRateY, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="偏移(mm)" Style="{StaticResource ProcessLabelStyle}"/>
|
||||
<mw:NumberBox Grid.Row="2" Grid.Column="1"
|
||||
Width="Auto"
|
||||
MinWidth="100"
|
||||
Style="{StaticResource ProcessLargeNumberBoxStyle}"
|
||||
Value="{Binding SoftLimitOffsetMm, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="2" Content="处理数量" Style="{StaticResource ProcessLabelStyle}"/>
|
||||
<mw:IntNumberBox Grid.Row="2" Grid.Column="3"
|
||||
Width="Auto"
|
||||
MinWidth="100"
|
||||
Style="{StaticResource ProcessLargeIntNumberBoxStyle}"
|
||||
Value="{Binding ConsumerCount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="处理线程数" Style="{StaticResource ProcessLabelStyle}"/>
|
||||
<mw:IntNumberBox Grid.Row="3" Grid.Column="1"
|
||||
Width="Auto"
|
||||
MinWidth="100"
|
||||
Style="{StaticResource ProcessLargeIntNumberBoxStyle}"
|
||||
Value="{Binding FrameChannelCapacity, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
mw:NumericKeypadAttach.IsEnabled="True"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace MainShell.Recipe.View
|
||||
{
|
||||
public partial class WaferTeachView : UserControl
|
||||
{
|
||||
public WaferTeachView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Filewritable;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MwFramework.Controls.Components;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class CarrierRecipeViewModel : RecipeViewModelBase<CarrierRecipe>,
|
||||
IHandle<SubstrateNameChangedEventArgs>, IHandle<SubstrateDeletedEventArgs>
|
||||
{
|
||||
#region //Command
|
||||
public ICommand RemoveSubstrateCommand { get; private set; }
|
||||
#endregion
|
||||
|
||||
private CarrierRecipe _carrierRecipe;
|
||||
public CarrierRecipe CarrierRecipe
|
||||
{
|
||||
get { return _carrierRecipe; }
|
||||
set { SetAndNotify(ref _carrierRecipe, value); }
|
||||
}
|
||||
|
||||
protected override CarrierRecipe CurrentRecipe
|
||||
{
|
||||
get => CarrierRecipe;
|
||||
set => CarrierRecipe = value;
|
||||
}
|
||||
|
||||
protected override string RecipeFolderPath => Paths.CarrierRecipe;
|
||||
protected override string EmptyRecipeDisplayName => "当前载具配方";
|
||||
|
||||
protected override string GetActiveRecipeName()
|
||||
{
|
||||
return RecipeManager.CurrentCarrierRecipe?.RecipeName;
|
||||
}
|
||||
|
||||
protected override void SwitchActiveRecipe(string recipeName)
|
||||
{
|
||||
RecipeManager.SwitchCarrierRecipe(recipeName);
|
||||
}
|
||||
|
||||
protected override void ClearActiveRecipe()
|
||||
{
|
||||
RecipeManager.ClearCarrierRecipe();
|
||||
}
|
||||
|
||||
public ObservableCollection<RecipeWrap> SubstrateRecipes => _wrapManager.SubstrateRecipeWraps;
|
||||
|
||||
private readonly RecipeWrapManager _wrapManager;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
public CarrierRecipeViewModel(RecipeWrapManager wrapManager, IEventAggregator eventAggregator, RecipeManager recipeManager)
|
||||
: base(recipeManager)
|
||||
{
|
||||
_wrapManager = wrapManager;
|
||||
_eventAggregator = eventAggregator;
|
||||
_eventAggregator.Unsubscribe(this);
|
||||
_eventAggregator.Subscribe(this);
|
||||
|
||||
RegisterButtonGroupEvents();
|
||||
RecipeWraps = wrapManager.CarrierRecipeWraps;
|
||||
RemoveSubstrateCommand = new DelegateCommand<object>(RemoveSubstrate);
|
||||
}
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
InitializeSelection(RecipeManager.CurrentCarrierRecipe?.RecipeName);
|
||||
}
|
||||
|
||||
protected override void OnRecipeLoaded(CarrierRecipe recipe, RecipeWrap recipeWrap)
|
||||
{
|
||||
foreach (var wrap in RecipeWraps)
|
||||
{
|
||||
wrap.IsInUse = ReferenceEquals(wrap, recipeWrap);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnCopyRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
base.OnCopyRecipe(sender, eventArgs);
|
||||
}
|
||||
|
||||
public override void OnImportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSaveRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs)
|
||||
{
|
||||
SaveCurrentRecipe(true);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnExportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnReNameRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeRenameEventArgs args)
|
||||
{
|
||||
TryRenameRecipe(args);
|
||||
}
|
||||
}
|
||||
|
||||
#region //Methods
|
||||
public void AddSubstrate()
|
||||
{
|
||||
if (CarrierRecipe == null)
|
||||
{
|
||||
MwMessageBox.Show("请先选择或创建载具配方");
|
||||
return;
|
||||
}
|
||||
CarrierRecipe.SubstrateSelectInfos.Add(new SubstrateSelectInfo());
|
||||
}
|
||||
|
||||
public void RemoveSubstrate(object pars)
|
||||
{
|
||||
if (pars is SubstrateSelectInfo selectInfo)
|
||||
{
|
||||
CarrierRecipe.SubstrateSelectInfos.Remove(selectInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(SubstrateNameChangedEventArgs message)
|
||||
{
|
||||
if (CarrierRecipe != null)
|
||||
{
|
||||
if (CarrierRecipe.SubstrateSelectInfos != null)
|
||||
{
|
||||
foreach (var substrateInfo in CarrierRecipe.SubstrateSelectInfos)
|
||||
{
|
||||
if (substrateInfo.SubstrateName == message.OldName)
|
||||
{
|
||||
substrateInfo.SubstrateName = message.NewName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(SubstrateDeletedEventArgs message)
|
||||
{
|
||||
if (CarrierRecipe == null || CarrierRecipe.SubstrateSelectInfos == null)
|
||||
return;
|
||||
|
||||
var list = CarrierRecipe.SubstrateSelectInfos;
|
||||
var targetName = message?.DeletedName;
|
||||
|
||||
CommonUti.RunOnUi(() => RemoveByNameFromCollection(list, targetName));
|
||||
}
|
||||
|
||||
private void RemoveByNameFromCollection(ObservableCollection<SubstrateSelectInfo> list, string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) return;
|
||||
|
||||
for (int i = list.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (list[i]?.SubstrateName == name)
|
||||
list.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using MainShell.Recipe.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public interface IRecipeViewModel
|
||||
{
|
||||
RecipeManager RecipeManager { get; }
|
||||
ObservableCollection<RecipeWrap> RecipeWraps { get; }
|
||||
RecipeWrap SelectedRecipeWrap { get; set; }
|
||||
void SyncSelectionFromActiveRecipe();
|
||||
RecipeButtonGroupViewModel RecipeButtonGroupViewModel { get;}
|
||||
bool HideRecipePanel { get; set; }
|
||||
bool CanCopyRecipe { get; }
|
||||
bool CanImportRecipe { get; }
|
||||
bool CanExportRecipe { get; }
|
||||
bool HasUnsavedChanges { get; }
|
||||
string CurrentRecipeDisplayName { get; }
|
||||
bool TryLeaveCurrentRecipeContext();
|
||||
void SaveCurrentRecipeSilently();
|
||||
void OnCreateNewRecipe(object sender, EventArgs eventArgs);
|
||||
void OnDeleteRecipe(object sender, EventArgs eventArgs);
|
||||
void OnCopyRecipe(object sender, EventArgs eventArgs);
|
||||
void OnApplyRecipe(object sender, EventArgs eventArgs);
|
||||
void OnImportRecipe(object sender, EventArgs eventArgs);
|
||||
void OnSaveRecipe(object sender, EventArgs eventArgs);
|
||||
void OnExportRecipe(object sender, EventArgs eventArgs);
|
||||
void OnReNameRecipe(object sender, EventArgs eventArgs);
|
||||
void OnClearRecipe(object sender, EventArgs eventArgs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using MainShell.Recipe.Models.SubstrateParameter;
|
||||
using MaxwellFramework.Core.Common.Command;
|
||||
using Stylet;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class MarkCoordinatePreviewWindowModel : PropertyChangedBase
|
||||
{
|
||||
private readonly MarkTeachViewModel _markTeachViewModel;
|
||||
private ObservableCollection<MarkCoordinatePoint> _observedPoints;
|
||||
|
||||
public ObservableCollection<MarkCoordinatePoint> Points => _markTeachViewModel?.CoordinatePreviewPoints;
|
||||
|
||||
public int PointCount => Points?.Count ?? 0;
|
||||
|
||||
public ICommand CloseCommand { get; }
|
||||
|
||||
public MarkCoordinatePreviewWindowModel(MarkTeachViewModel markTeachViewModel)
|
||||
{
|
||||
_markTeachViewModel = markTeachViewModel;
|
||||
CloseCommand = new DelegateCommand(OnClose);
|
||||
AttachToMarkTeachViewModel();
|
||||
}
|
||||
|
||||
private void OnClose(object obj)
|
||||
{
|
||||
if (obj is Window window)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshViewState()
|
||||
{
|
||||
NotifyOfPropertyChange(() => Points);
|
||||
NotifyOfPropertyChange(() => PointCount);
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
if (_markTeachViewModel != null)
|
||||
{
|
||||
_markTeachViewModel.PropertyChanged -= OnMarkTeachViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
DetachPointCollection();
|
||||
}
|
||||
|
||||
private void AttachToMarkTeachViewModel()
|
||||
{
|
||||
if (_markTeachViewModel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_markTeachViewModel.PropertyChanged += OnMarkTeachViewModelPropertyChanged;
|
||||
AttachPointCollection(_markTeachViewModel.CoordinatePreviewPoints);
|
||||
}
|
||||
|
||||
private void OnMarkTeachViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(e.PropertyName)
|
||||
|| e.PropertyName == nameof(MarkTeachViewModel.CoordinatePreviewPoints))
|
||||
{
|
||||
DetachPointCollection();
|
||||
AttachPointCollection(_markTeachViewModel.CoordinatePreviewPoints);
|
||||
RefreshViewState();
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachPointCollection(ObservableCollection<MarkCoordinatePoint> points)
|
||||
{
|
||||
_observedPoints = points;
|
||||
if (_observedPoints != null)
|
||||
{
|
||||
_observedPoints.CollectionChanged += OnPointsCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void DetachPointCollection()
|
||||
{
|
||||
if (_observedPoints != null)
|
||||
{
|
||||
_observedPoints.CollectionChanged -= OnPointsCollectionChanged;
|
||||
_observedPoints = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPointsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
NotifyOfPropertyChange(() => PointCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,718 @@
|
||||
using MainShell.Common.ViewModel;
|
||||
using MainShell.Common;
|
||||
using MainShell.Filewritable;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.Models.SubstrateParameter;
|
||||
using MainShell.Recipe.View;
|
||||
using MaxwellFramework.Core.Common.Command;
|
||||
using Microsoft.Win32;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class MarkTeachViewModel : PropertyChangedBase
|
||||
{
|
||||
private const double PitchTolerance = 0.001d;
|
||||
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly IWindowManager _windowManager;
|
||||
private SubstrateInfo _observedSubstrateInfo;
|
||||
private MarkCoordinatePreviewWindow _coordinatePreviewWindow;
|
||||
private MarkCoordinatePreviewWindowModel _coordinatePreviewWindowModel;
|
||||
|
||||
public MarkTeachViewModel(HardwareManager hardwareManager,IWindowManager windowManager)
|
||||
{
|
||||
_hardwareManager = hardwareManager;
|
||||
_windowManager = windowManager;
|
||||
AddMarkCmd = new DelegateCommand(AddMark);
|
||||
DeleteMarkCmd = new DelegateCommand(DeleteMark);
|
||||
TeachMarkCmd = new DelegateCommand(TeachMark);
|
||||
MoveToMarkCmd = new DelegateCommand(MoveToMark);
|
||||
MarkModelCmd = new DelegateCommand(MarkModel);
|
||||
VisionParSetCmd = new DelegateCommand(VisionParSet);
|
||||
GenerateCoordinatesCmd = new DelegateCommand(GenerateCoordinates);
|
||||
ImportCoordinatesCmd = new DelegateCommand(ImportCoordinates);
|
||||
ClearCoordinatesCmd = new DelegateCommand(ClearCoordinates);
|
||||
ShowCoordinatePreviewCmd = new DelegateCommand(ShowCoordinatePreview);
|
||||
RefreshTemplates();
|
||||
}
|
||||
|
||||
public ObservableCollection<string> AvailableTemplates { get; set; } = new ObservableCollection<string>();
|
||||
|
||||
private void RefreshTemplates()
|
||||
{
|
||||
AvailableTemplates.Clear();
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(Paths.TemplateBasePath))
|
||||
{
|
||||
var files = Directory.GetFiles(Paths.TemplateBasePath);
|
||||
foreach (var file in files)
|
||||
{
|
||||
AvailableTemplates.Add(Path.GetFileName(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Handle or log exception
|
||||
}
|
||||
}
|
||||
|
||||
private SubstrateRecipe _substrateRecipe;
|
||||
public SubstrateRecipe SubstrateRecipe
|
||||
{
|
||||
get => _substrateRecipe;
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _substrateRecipe, value))
|
||||
{
|
||||
EnsureCoordinateGenerationState();
|
||||
ObserveSubstrateInfo(value?.SubstrateInfo);
|
||||
NotifyOfPropertyChange(() => MarkPars);
|
||||
NotifyOfPropertyChange(() => MarkDatas);
|
||||
NotifyOfPropertyChange(() => CoordinateGenerationState);
|
||||
NotifyOfPropertyChange(() => CoordinatePreviewPoints);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SubtrateMarkPars MarkPars => SubstrateRecipe?.SubtrateMarkParameterInfo;
|
||||
public ObservableCollection<MarkData> MarkDatas => MarkPars?.MarkDatas;
|
||||
public MarkCoordinateGenerationState CoordinateGenerationState => MarkPars?.CoordinateGenerationState;
|
||||
public ObservableCollection<MarkCoordinatePoint> CoordinatePreviewPoints => CoordinateGenerationState?.Points;
|
||||
|
||||
private MarkData _selectedMarkData;
|
||||
public MarkData SelectedMarkData
|
||||
{
|
||||
get => _selectedMarkData;
|
||||
set => SetAndNotify(ref _selectedMarkData, value);
|
||||
}
|
||||
private bool _isTeachPose;
|
||||
|
||||
public bool IsTeachPose
|
||||
{
|
||||
get { return _isTeachPose; }
|
||||
set { SetAndNotify(ref _isTeachPose, value); }
|
||||
}
|
||||
|
||||
#region Commands
|
||||
|
||||
public ICommand AddMarkCmd { get; }
|
||||
public ICommand DeleteMarkCmd { get; }
|
||||
public ICommand TeachMarkCmd { get; }
|
||||
public ICommand MoveToMarkCmd { get; }
|
||||
public ICommand MarkModelCmd { get; }
|
||||
public ICommand VisionParSetCmd { get; }
|
||||
public ICommand GenerateCoordinatesCmd { get; }
|
||||
public ICommand ImportCoordinatesCmd { get; }
|
||||
public ICommand ClearCoordinatesCmd { get; }
|
||||
public ICommand ShowCoordinatePreviewCmd { get; }
|
||||
|
||||
private void ObserveSubstrateInfo(SubstrateInfo substrateInfo)
|
||||
{
|
||||
if (_observedSubstrateInfo != null)
|
||||
{
|
||||
_observedSubstrateInfo.PropertyChanged -= OnSubstrateInfoChanged;
|
||||
}
|
||||
|
||||
_observedSubstrateInfo = substrateInfo;
|
||||
|
||||
if (_observedSubstrateInfo != null)
|
||||
{
|
||||
_observedSubstrateInfo.PropertyChanged += OnSubstrateInfoChanged;
|
||||
}
|
||||
|
||||
SyncCoordinateGenerationSource();
|
||||
}
|
||||
|
||||
private void OnSubstrateInfoChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(e.PropertyName)
|
||||
|| e.PropertyName == nameof(SubstrateInfo.RowNumber)
|
||||
|| e.PropertyName == nameof(SubstrateInfo.ColNumber)
|
||||
|| e.PropertyName == nameof(SubstrateInfo.PitchX)
|
||||
|| e.PropertyName == nameof(SubstrateInfo.PitchY))
|
||||
{
|
||||
SyncCoordinateGenerationSource();
|
||||
}
|
||||
}
|
||||
|
||||
private bool EnsureCoordinateGenerationState()
|
||||
{
|
||||
if (SubstrateRecipe == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SubstrateRecipe.SubtrateMarkParameterInfo == null)
|
||||
{
|
||||
SubstrateRecipe.SubtrateMarkParameterInfo = new SubtrateMarkPars();
|
||||
}
|
||||
|
||||
if (SubstrateRecipe.SubtrateMarkParameterInfo.CoordinateGenerationState == null)
|
||||
{
|
||||
SubstrateRecipe.SubtrateMarkParameterInfo.CoordinateGenerationState = new MarkCoordinateGenerationState();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SyncCoordinateGenerationSource()
|
||||
{
|
||||
if (!EnsureCoordinateGenerationState())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var substrateInfo = SubstrateRecipe?.SubstrateInfo;
|
||||
var coordinateState = CoordinateGenerationState;
|
||||
if (substrateInfo == null || coordinateState == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
coordinateState.Rows = substrateInfo.RowNumber;
|
||||
coordinateState.Cols = substrateInfo.ColNumber;
|
||||
coordinateState.PitchX = substrateInfo.PitchX;
|
||||
coordinateState.PitchY = substrateInfo.PitchY;
|
||||
|
||||
NotifyOfPropertyChange(() => CoordinateGenerationState);
|
||||
NotifyOfPropertyChange(() => CoordinatePreviewPoints);
|
||||
}
|
||||
|
||||
private bool EnsureRecipeSelected()
|
||||
{
|
||||
if (SubstrateRecipe != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
MwMessageBox.Show("请先选择或创建基板配方", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void AddMark(object obj)
|
||||
{
|
||||
if (MarkDatas == null) return;
|
||||
var newMark = new MarkData
|
||||
{
|
||||
TemplateName = $"Mark{MarkDatas.Count + 1}",
|
||||
BasePos = new MPoint(),
|
||||
CameraPos = new MPoint()
|
||||
};
|
||||
MarkDatas.Add(newMark);
|
||||
SelectedMarkData = newMark;
|
||||
}
|
||||
|
||||
private void DeleteMark(object obj)
|
||||
{
|
||||
if (SelectedMarkData != null && MarkDatas != null)
|
||||
{
|
||||
MarkDatas.Remove(SelectedMarkData);
|
||||
}
|
||||
}
|
||||
|
||||
private void TeachMark(object obj)
|
||||
{
|
||||
if (SelectedMarkData == null) return;
|
||||
|
||||
var device = _hardwareManager.CameraAxisManager.TopCameraAxisDevices.FirstOrDefault();
|
||||
if (device != null)
|
||||
{
|
||||
if (SelectedMarkData.BasePos == null) SelectedMarkData.BasePos = new MPoint();
|
||||
if (SelectedMarkData.CameraPos == null) SelectedMarkData.CameraPos = new MPoint();
|
||||
|
||||
if (device.AxisX != null) SelectedMarkData.BasePos.X = device.AxisX.State.ActualPos;
|
||||
if (device.AxisY != null) SelectedMarkData.BasePos.Y = device.AxisY.State.ActualPos;
|
||||
|
||||
if (device.AxisX != null) SelectedMarkData.CameraPos.X = device.AxisX.State.ActualPos;
|
||||
if (device.AxisY != null) SelectedMarkData.CameraPos.Y = device.AxisY.State.ActualPos;
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveToMark(object obj)
|
||||
{
|
||||
if (SelectedMarkData == null) return;
|
||||
var device = _hardwareManager.CameraAxisManager.TopCameraAxisDevices.FirstOrDefault();
|
||||
if (device != null)
|
||||
{
|
||||
// TODO: Implement Safe Move logic using AxisMotion or similar
|
||||
}
|
||||
}
|
||||
|
||||
private void MarkModel(object obj)
|
||||
{
|
||||
// TODO: Open Mark Model Dialog
|
||||
// Refresh templates after returning from model dialog as new templates might have been created
|
||||
RefreshTemplates();
|
||||
}
|
||||
|
||||
private void VisionParSet(object obj)
|
||||
{
|
||||
// TODO: Open Vision Parameter Settings
|
||||
CameraSettingsViewModel settingsViewModel=new CameraSettingsViewModel(_windowManager,_hardwareManager);
|
||||
settingsViewModel.Initialize(_substrateRecipe.SubtrateMarkParameterInfo.MarkVisionConfig,
|
||||
_substrateRecipe.SubtrateMarkParameterInfo.MarkLightConfig, Common.CameraType.TopPositionCamera);
|
||||
_windowManager.ShowDialog(settingsViewModel);
|
||||
}
|
||||
|
||||
private void GenerateCoordinates(object obj)
|
||||
{
|
||||
if (!EnsureRecipeSelected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SyncCoordinateGenerationSource();
|
||||
var coordinateState = CoordinateGenerationState;
|
||||
if (coordinateState == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (coordinateState.Rows <= 0 || coordinateState.Cols <= 0)
|
||||
{
|
||||
MwMessageBox.Show("基板信息中的行列数必须大于 0。", "生成失败", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (coordinateState.Cols > 1 && Math.Abs(coordinateState.PitchX) <= double.Epsilon)
|
||||
{
|
||||
MwMessageBox.Show("基板信息中的 Pitch X 无效,无法生成点位。", "生成失败", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (coordinateState.Rows > 1 && Math.Abs(coordinateState.PitchY) <= double.Epsilon)
|
||||
{
|
||||
MwMessageBox.Show("基板信息中的 Pitch Y 无效,无法生成点位。", "生成失败", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var generatedPoints = new ObservableCollection<MarkCoordinatePoint>();
|
||||
var index = 1;
|
||||
for (var row = 1; row <= coordinateState.Rows; row++)
|
||||
{
|
||||
for (var col = 1; col <= coordinateState.Cols; col++)
|
||||
{
|
||||
generatedPoints.Add(new MarkCoordinatePoint
|
||||
{
|
||||
Index = index++,
|
||||
PointName = CreatePointName(row, col),
|
||||
Row = row,
|
||||
Col = col,
|
||||
TheoryX = (col - 1) * coordinateState.PitchX,
|
||||
TheoryY = (row - 1) * coordinateState.PitchY,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
coordinateState.Points = generatedPoints;
|
||||
NotifyOfPropertyChange(() => CoordinatePreviewPoints);
|
||||
}
|
||||
|
||||
private void ImportCoordinates(object obj)
|
||||
{
|
||||
if (!EnsureRecipeSelected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Title = "导入坐标文件",
|
||||
Filter = "CSV Files (*.csv)|*.csv|All Files (*.*)|*.*",
|
||||
CheckFileExists = true,
|
||||
Multiselect = false,
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<MarkCoordinatePoint> importedPoints;
|
||||
string errorMessage;
|
||||
if (!TryReadCoordinateFile(dialog.FileName, out importedPoints, out errorMessage))
|
||||
{
|
||||
MwMessageBox.Show(errorMessage, "导入失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (importedPoints.Count == 0)
|
||||
{
|
||||
MwMessageBox.Show("导入文件中没有可用的坐标数据。", "导入失败", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
SyncCoordinateGenerationSource();
|
||||
var validationMessage = BuildImportValidationMessage(importedPoints);
|
||||
if (!string.IsNullOrWhiteSpace(validationMessage))
|
||||
{
|
||||
var confirmResult = MwMessageBox.Show(
|
||||
validationMessage + Environment.NewLine + Environment.NewLine + "是否继续导入?",
|
||||
"导入校验",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Warning,
|
||||
MessageBoxResult.No);
|
||||
|
||||
if (confirmResult != MessageBoxResult.Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CoordinateGenerationState.Points = new ObservableCollection<MarkCoordinatePoint>(importedPoints);
|
||||
NotifyOfPropertyChange(() => CoordinatePreviewPoints);
|
||||
}
|
||||
|
||||
private void ClearCoordinates(object obj)
|
||||
{
|
||||
if (!EnsureRecipeSelected() || CoordinateGenerationState == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CoordinateGenerationState.Points.Clear();
|
||||
NotifyOfPropertyChange(() => CoordinatePreviewPoints);
|
||||
}
|
||||
|
||||
private void ShowCoordinatePreview(object obj)
|
||||
{
|
||||
if (!EnsureRecipeSelected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_coordinatePreviewWindow != null)
|
||||
{
|
||||
_coordinatePreviewWindowModel?.RefreshViewState();
|
||||
|
||||
if (_coordinatePreviewWindow.WindowState == WindowState.Minimized)
|
||||
{
|
||||
_coordinatePreviewWindow.WindowState = WindowState.Normal;
|
||||
}
|
||||
|
||||
_coordinatePreviewWindow.Activate();
|
||||
_coordinatePreviewWindow.Focus();
|
||||
return;
|
||||
}
|
||||
|
||||
_coordinatePreviewWindowModel = new MarkCoordinatePreviewWindowModel(this);
|
||||
_coordinatePreviewWindow = new MarkCoordinatePreviewWindow
|
||||
{
|
||||
DataContext = _coordinatePreviewWindowModel
|
||||
};
|
||||
|
||||
var ownerWindow = Application.Current?.Windows
|
||||
.OfType<Window>()
|
||||
.FirstOrDefault(window => window.IsActive && window != _coordinatePreviewWindow);
|
||||
if (ownerWindow != null)
|
||||
{
|
||||
_coordinatePreviewWindow.Owner = ownerWindow;
|
||||
}
|
||||
|
||||
_coordinatePreviewWindow.Closed += OnCoordinatePreviewWindowClosed;
|
||||
_coordinatePreviewWindow.Show();
|
||||
_coordinatePreviewWindow.Activate();
|
||||
}
|
||||
|
||||
private void OnCoordinatePreviewWindowClosed(object sender, EventArgs e)
|
||||
{
|
||||
if (_coordinatePreviewWindow != null)
|
||||
{
|
||||
_coordinatePreviewWindow.Closed -= OnCoordinatePreviewWindowClosed;
|
||||
}
|
||||
|
||||
_coordinatePreviewWindow = null;
|
||||
_coordinatePreviewWindowModel = null;
|
||||
}
|
||||
|
||||
private string BuildImportValidationMessage(IReadOnlyList<MarkCoordinatePoint> importedPoints)
|
||||
{
|
||||
var coordinateState = CoordinateGenerationState;
|
||||
if (coordinateState == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var importedRows = importedPoints.Select(point => point.Row).Distinct().Count();
|
||||
var importedCols = importedPoints.Select(point => point.Col).Distinct().Count();
|
||||
var importedPitchX = CalculatePitch(importedPoints, true);
|
||||
var importedPitchY = CalculatePitch(importedPoints, false);
|
||||
|
||||
var mismatchMessages = new List<string>();
|
||||
if (coordinateState.Rows > 0 && importedRows != coordinateState.Rows)
|
||||
{
|
||||
mismatchMessages.Add($"行数不匹配:基板信息为 {coordinateState.Rows},导入数据为 {importedRows}。");
|
||||
}
|
||||
|
||||
if (coordinateState.Cols > 0 && importedCols != coordinateState.Cols)
|
||||
{
|
||||
mismatchMessages.Add($"列数不匹配:基板信息为 {coordinateState.Cols},导入数据为 {importedCols}。");
|
||||
}
|
||||
|
||||
if (coordinateState.Cols > 1 && importedPitchX.HasValue && Math.Abs(importedPitchX.Value - coordinateState.PitchX) > PitchTolerance)
|
||||
{
|
||||
mismatchMessages.Add($"Pitch X 不匹配:基板信息为 {coordinateState.PitchX:F4},导入数据为 {importedPitchX.Value:F4}。");
|
||||
}
|
||||
|
||||
if (coordinateState.Rows > 1 && importedPitchY.HasValue && Math.Abs(importedPitchY.Value - coordinateState.PitchY) > PitchTolerance)
|
||||
{
|
||||
mismatchMessages.Add($"Pitch Y 不匹配:基板信息为 {coordinateState.PitchY:F4},导入数据为 {importedPitchY.Value:F4}。");
|
||||
}
|
||||
|
||||
return mismatchMessages.Count == 0 ? string.Empty : string.Join(Environment.NewLine, mismatchMessages);
|
||||
}
|
||||
|
||||
private static double? CalculatePitch(IReadOnlyList<MarkCoordinatePoint> points, bool isXAxis)
|
||||
{
|
||||
var pitchSamples = new List<double>();
|
||||
if (points == null || points.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isXAxis)
|
||||
{
|
||||
foreach (var rowGroup in points.GroupBy(point => point.Row))
|
||||
{
|
||||
var ordered = rowGroup.OrderBy(point => point.Col).ToList();
|
||||
for (var i = 1; i < ordered.Count; i++)
|
||||
{
|
||||
var colDelta = ordered[i].Col - ordered[i - 1].Col;
|
||||
if (colDelta <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var pitch = Math.Abs((ordered[i].TheoryX - ordered[i - 1].TheoryX) / colDelta);
|
||||
if (pitch > double.Epsilon)
|
||||
{
|
||||
pitchSamples.Add(pitch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var colGroup in points.GroupBy(point => point.Col))
|
||||
{
|
||||
var ordered = colGroup.OrderBy(point => point.Row).ToList();
|
||||
for (var i = 1; i < ordered.Count; i++)
|
||||
{
|
||||
var rowDelta = ordered[i].Row - ordered[i - 1].Row;
|
||||
if (rowDelta <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var pitch = Math.Abs((ordered[i].TheoryY - ordered[i - 1].TheoryY) / rowDelta);
|
||||
if (pitch > double.Epsilon)
|
||||
{
|
||||
pitchSamples.Add(pitch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pitchSamples.Count == 0 ? (double?)null : pitchSamples.Average();
|
||||
}
|
||||
|
||||
private static string CreatePointName(int row, int col)
|
||||
{
|
||||
return $"R{row:D2}C{col:D2}";
|
||||
}
|
||||
|
||||
private static bool TryReadCoordinateFile(string filePath, out List<MarkCoordinatePoint> importedPoints, out string errorMessage)
|
||||
{
|
||||
importedPoints = new List<MarkCoordinatePoint>();
|
||||
errorMessage = string.Empty;
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
errorMessage = "坐标文件不存在。";
|
||||
return false;
|
||||
}
|
||||
|
||||
var rawLines = File.ReadAllLines(filePath, Encoding.UTF8)
|
||||
.Where(line => !string.IsNullOrWhiteSpace(line))
|
||||
.ToList();
|
||||
|
||||
if (rawLines.Count == 0)
|
||||
{
|
||||
errorMessage = "坐标文件为空。";
|
||||
return false;
|
||||
}
|
||||
|
||||
var headers = SplitCsvLine(rawLines[0]);
|
||||
var nameIndex = FindColumnIndex(headers, "pointname", "name", "点位名称", "点位名", "point");
|
||||
var rowIndex = FindColumnIndex(headers, "row", "行");
|
||||
var colIndex = FindColumnIndex(headers, "col", "column", "列");
|
||||
var xIndex = FindColumnIndex(headers, "theoryx", "x", "理论x");
|
||||
var yIndex = FindColumnIndex(headers, "theoryy", "y", "理论y");
|
||||
|
||||
if (rowIndex < 0 || colIndex < 0 || xIndex < 0 || yIndex < 0)
|
||||
{
|
||||
errorMessage = "CSV 需至少包含 Row、Col、TheoryX、TheoryY 列。";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var lineIndex = 1; lineIndex < rawLines.Count; lineIndex++)
|
||||
{
|
||||
var cells = SplitCsvLine(rawLines[lineIndex]);
|
||||
if (cells.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int row;
|
||||
int col;
|
||||
double theoryX;
|
||||
double theoryY;
|
||||
if (!TryParseInt(GetCell(cells, rowIndex), out row)
|
||||
|| !TryParseInt(GetCell(cells, colIndex), out col)
|
||||
|| !TryParseDouble(GetCell(cells, xIndex), out theoryX)
|
||||
|| !TryParseDouble(GetCell(cells, yIndex), out theoryY))
|
||||
{
|
||||
errorMessage = $"第 {lineIndex + 1} 行数据格式无效,请检查 Row、Col、TheoryX、TheoryY。";
|
||||
return false;
|
||||
}
|
||||
|
||||
var pointName = nameIndex >= 0 ? GetCell(cells, nameIndex) : string.Empty;
|
||||
importedPoints.Add(new MarkCoordinatePoint
|
||||
{
|
||||
PointName = string.IsNullOrWhiteSpace(pointName) ? CreatePointName(row, col) : pointName,
|
||||
Row = row,
|
||||
Col = col,
|
||||
TheoryX = theoryX,
|
||||
TheoryY = theoryY,
|
||||
});
|
||||
}
|
||||
|
||||
importedPoints = importedPoints
|
||||
.OrderBy(point => point.Row)
|
||||
.ThenBy(point => point.Col)
|
||||
.ToList();
|
||||
|
||||
for (var i = 0; i < importedPoints.Count; i++)
|
||||
{
|
||||
importedPoints[i].Index = i + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int FindColumnIndex(IReadOnlyList<string> headers, params string[] candidates)
|
||||
{
|
||||
for (var index = 0; index < headers.Count; index++)
|
||||
{
|
||||
var normalizedHeader = NormalizeHeader(headers[index]);
|
||||
if (candidates.Any(candidate => normalizedHeader == NormalizeHeader(candidate)))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static string NormalizeHeader(string value)
|
||||
{
|
||||
return (value ?? string.Empty)
|
||||
.Trim()
|
||||
.Trim('\uFEFF')
|
||||
.Replace(" ", string.Empty)
|
||||
.Replace("_", string.Empty)
|
||||
.Replace("-", string.Empty)
|
||||
.ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static string GetCell(IReadOnlyList<string> cells, int index)
|
||||
{
|
||||
if (index < 0 || index >= cells.Count)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return cells[index]?.Trim() ?? string.Empty;
|
||||
}
|
||||
|
||||
private static bool TryParseInt(string value, out int result)
|
||||
{
|
||||
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return int.TryParse(value, NumberStyles.Integer, CultureInfo.CurrentCulture, out result);
|
||||
}
|
||||
|
||||
private static bool TryParseDouble(string value, out double result)
|
||||
{
|
||||
if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.CurrentCulture, out result);
|
||||
}
|
||||
|
||||
private static List<string> SplitCsvLine(string line)
|
||||
{
|
||||
var cells = new List<string>();
|
||||
if (line == null)
|
||||
{
|
||||
return cells;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
var inQuotes = false;
|
||||
for (var i = 0; i < line.Length; i++)
|
||||
{
|
||||
var current = line[i];
|
||||
if (current == '"')
|
||||
{
|
||||
if (inQuotes && i + 1 < line.Length && line[i + 1] == '"')
|
||||
{
|
||||
builder.Append('"');
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
inQuotes = !inQuotes;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current == ',' && !inQuotes)
|
||||
{
|
||||
cells.Add(builder.ToString());
|
||||
builder.Clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append(current);
|
||||
}
|
||||
|
||||
cells.Add(builder.ToString());
|
||||
return cells;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using MainShell.Recipe.Models;
|
||||
using Stylet;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class ProcessCompensationViewModel : PropertyChangedBase
|
||||
{
|
||||
private ProcessRecipe _processRecipe;
|
||||
public ProcessRecipe ProcessRecipe
|
||||
{
|
||||
get { return _processRecipe; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _processRecipe, value))
|
||||
{
|
||||
NotifyCompensationPropertiesChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double PhsX1Compensation
|
||||
{
|
||||
get { return GetCompensationParameter().PhsX1Compensation; }
|
||||
set
|
||||
{
|
||||
ProcessAxisCompensationParameter parameter = GetCompensationParameter();
|
||||
if (parameter.PhsX1Compensation == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
parameter.PhsX1Compensation = value;
|
||||
NotifyOfPropertyChange(nameof(PhsX1Compensation));
|
||||
}
|
||||
}
|
||||
|
||||
public double PhsY1Compensation
|
||||
{
|
||||
get { return GetCompensationParameter().PhsY1Compensation; }
|
||||
set
|
||||
{
|
||||
ProcessAxisCompensationParameter parameter = GetCompensationParameter();
|
||||
if (parameter.PhsY1Compensation == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
parameter.PhsY1Compensation = value;
|
||||
NotifyOfPropertyChange(nameof(PhsY1Compensation));
|
||||
}
|
||||
}
|
||||
|
||||
public double WsXCompensation
|
||||
{
|
||||
get { return GetCompensationParameter().WsXCompensation; }
|
||||
set
|
||||
{
|
||||
ProcessAxisCompensationParameter parameter = GetCompensationParameter();
|
||||
if (parameter.WsXCompensation == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
parameter.WsXCompensation = value;
|
||||
NotifyOfPropertyChange(nameof(WsXCompensation));
|
||||
}
|
||||
}
|
||||
|
||||
public double StageYCompensation
|
||||
{
|
||||
get { return GetCompensationParameter().StageYCompensation; }
|
||||
set
|
||||
{
|
||||
ProcessAxisCompensationParameter parameter = GetCompensationParameter();
|
||||
if (parameter.StageYCompensation == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
parameter.StageYCompensation = value;
|
||||
NotifyOfPropertyChange(nameof(StageYCompensation));
|
||||
}
|
||||
}
|
||||
|
||||
private ProcessAxisCompensationParameter GetCompensationParameter()
|
||||
{
|
||||
if (ProcessRecipe == null)
|
||||
{
|
||||
return new ProcessAxisCompensationParameter();
|
||||
}
|
||||
|
||||
if (ProcessRecipe.ProcessAxisCompensationParameter == null)
|
||||
{
|
||||
ProcessRecipe.ProcessAxisCompensationParameter = new ProcessAxisCompensationParameter();
|
||||
}
|
||||
|
||||
return ProcessRecipe.ProcessAxisCompensationParameter;
|
||||
}
|
||||
|
||||
private void NotifyCompensationPropertiesChanged()
|
||||
{
|
||||
NotifyOfPropertyChange(nameof(PhsX1Compensation));
|
||||
NotifyOfPropertyChange(nameof(PhsY1Compensation));
|
||||
NotifyOfPropertyChange(nameof(WsXCompensation));
|
||||
NotifyOfPropertyChange(nameof(StageYCompensation));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,684 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Filewritable;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Log;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.Models.PID;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using StyletIoC;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class ProcessRecipeViewModel : RecipeViewModelBase<ProcessRecipe>
|
||||
{
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly PIDOperater _pidOperater;
|
||||
|
||||
private static readonly string[] _fixedProfileNames = new string[]
|
||||
{
|
||||
"默认PID组"
|
||||
};
|
||||
|
||||
private static readonly string[] _fixedCompensationAxisNames = new string[]
|
||||
{
|
||||
"PHS-X1",
|
||||
"PHS-Y1",
|
||||
"WS-X",
|
||||
"Stage-Y"
|
||||
};
|
||||
|
||||
private ProcessRecipe _processRecipe;
|
||||
public ProcessRecipe ProcessRecipe
|
||||
{
|
||||
get { return _processRecipe; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _processRecipe, value))
|
||||
{
|
||||
if (ProcessCompensationViewModel != null)
|
||||
{
|
||||
ProcessCompensationViewModel.ProcessRecipe = value;
|
||||
}
|
||||
|
||||
EnsureProcessRecipeState();
|
||||
SyncProfileSelection();
|
||||
NotifyOfPropertyChange(() => AxisPIDParameters);
|
||||
NotifyOfPropertyChange(() => AxisOptions);
|
||||
NotifyOfPropertyChange(() => SelectedAxes);
|
||||
NotifyOfPropertyChange(() => SelectedFilteringParameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override ProcessRecipe CurrentRecipe
|
||||
{
|
||||
get { return ProcessRecipe; }
|
||||
set { ProcessRecipe = value; }
|
||||
}
|
||||
|
||||
protected override string RecipeFolderPath => Paths.ProcessRecipe;
|
||||
protected override string EmptyRecipeDisplayName => "当前工艺配方";
|
||||
|
||||
protected override string GetActiveRecipeName()
|
||||
{
|
||||
return RecipeManager.CurrentProcessRecipe != null ? RecipeManager.CurrentProcessRecipe.RecipeName : null;
|
||||
}
|
||||
|
||||
public ObservableCollection<PIDProfile> AxisPIDParameters => ProcessRecipe != null ? ProcessRecipe.AxisPIDParameters : null;
|
||||
|
||||
private PIDProfile _selectedPIDProfile;
|
||||
public PIDProfile SelectedPIDProfile
|
||||
{
|
||||
get { return _selectedPIDProfile; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _selectedPIDProfile, value))
|
||||
{
|
||||
EnsureProfileState(_selectedPIDProfile);
|
||||
NotifyOfPropertyChange(() => SelectedAxes);
|
||||
NotifyOfPropertyChange(() => SelectedFilteringParameters);
|
||||
SyncFilteringSelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<AxisPIDParameter> SelectedAxes => SelectedPIDProfile != null ? SelectedPIDProfile.Axes : null;
|
||||
|
||||
public ObservableCollection<AxisFilteringParameter> SelectedFilteringParameters => SelectedPIDProfile != null ? SelectedPIDProfile.FilteringParameters : null;
|
||||
|
||||
private ObservableCollection<PidAxisOption> _axisOptions = new ObservableCollection<PidAxisOption>();
|
||||
public ObservableCollection<PidAxisOption> AxisOptions
|
||||
{
|
||||
get { return _axisOptions; }
|
||||
set { SetAndNotify(ref _axisOptions, value); }
|
||||
}
|
||||
|
||||
private AxisPIDParameter _selectedAxisParameter;
|
||||
public AxisPIDParameter SelectedAxisParameter
|
||||
{
|
||||
get { return _selectedAxisParameter; }
|
||||
set { SetAndNotify(ref _selectedAxisParameter, value); }
|
||||
}
|
||||
|
||||
private AxisFilteringParameter _selectedFilteringParameter;
|
||||
public AxisFilteringParameter SelectedFilteringParameter
|
||||
{
|
||||
get { return _selectedFilteringParameter; }
|
||||
set { SetAndNotify(ref _selectedFilteringParameter, value); }
|
||||
}
|
||||
|
||||
private AxisFilteringParameter _selectedNotchFilteringParameter;
|
||||
public AxisFilteringParameter SelectedNotchFilteringParameter
|
||||
{
|
||||
get { return _selectedNotchFilteringParameter; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _selectedNotchFilteringParameter, value) && value != null)
|
||||
{
|
||||
SelectedFilteringParameter = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AxisFilteringParameter _selectedLowPassFilteringParameter;
|
||||
public AxisFilteringParameter SelectedLowPassFilteringParameter
|
||||
{
|
||||
get { return _selectedLowPassFilteringParameter; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _selectedLowPassFilteringParameter, value) && value != null)
|
||||
{
|
||||
SelectedFilteringParameter = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _selectedParameterTabIndex;
|
||||
public int SelectedParameterTabIndex
|
||||
{
|
||||
get { return _selectedParameterTabIndex; }
|
||||
set { SetAndNotify(ref _selectedParameterTabIndex, value); }
|
||||
}
|
||||
|
||||
private ProcessCompensationViewModel _processCompensationViewModel;
|
||||
[Inject]
|
||||
public ProcessCompensationViewModel ProcessCompensationViewModel
|
||||
{
|
||||
get { return _processCompensationViewModel; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _processCompensationViewModel, value) && value != null)
|
||||
{
|
||||
value.ProcessRecipe = ProcessRecipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ProcessRecipeViewModel(RecipeWrapManager wrapManager, RecipeManager recipeManager, HardwareManager hardwareManager, PIDOperater pidOperater)
|
||||
: base(recipeManager)
|
||||
{
|
||||
_hardwareManager = hardwareManager;
|
||||
_pidOperater = pidOperater;
|
||||
RecipeWraps = wrapManager.ProcessRecipeWraps;
|
||||
RegisterButtonGroupEvents();
|
||||
AxisOptions = BuildAxisOptions();
|
||||
PidAxisCatalog.SetAxisOptions(AxisOptions);
|
||||
}
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
|
||||
AxisOptions = BuildAxisOptions();
|
||||
PidAxisCatalog.SetAxisOptions(AxisOptions);
|
||||
InitializeSelection(RecipeManager.CurrentProcessRecipe != null ? RecipeManager.CurrentProcessRecipe.RecipeName : null);
|
||||
}
|
||||
|
||||
protected override void SwitchActiveRecipe(string recipeName)
|
||||
{
|
||||
RecipeManager.SwitchProcessRecipe(recipeName);
|
||||
}
|
||||
|
||||
protected override void ClearActiveRecipe()
|
||||
{
|
||||
RecipeManager.ClearProcessRecipe();
|
||||
}
|
||||
|
||||
protected override void OnRecipeLoaded(ProcessRecipe recipe, RecipeWrap recipeWrap)
|
||||
{
|
||||
EnsureProcessRecipeState();
|
||||
SyncProfileSelection();
|
||||
}
|
||||
|
||||
public override void OnCopyRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
base.OnCopyRecipe(sender, eventArgs);
|
||||
}
|
||||
|
||||
public override void OnImportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnExportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnReNameRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
RecipeRenameEventArgs args = eventArgs as RecipeRenameEventArgs;
|
||||
if (args != null)
|
||||
{
|
||||
TryRenameRecipe(args);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddAxis()
|
||||
{
|
||||
if (SelectedPIDProfile == null)
|
||||
{
|
||||
MwMessageBox.Show("请先选择一个 PID 配置组");
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureProfileState(SelectedPIDProfile);
|
||||
|
||||
PidAxisOption axisOption = GetNextAxisOption(SelectedPIDProfile.Axes.Select(item => item.AxisIndex));
|
||||
AxisPIDParameter axis = new AxisPIDParameter();
|
||||
if (axisOption != null)
|
||||
{
|
||||
axis.ModuleName = axisOption.AxisName;
|
||||
}
|
||||
else
|
||||
{
|
||||
axis.ModuleName = string.Format("Axis{0}", SelectedPIDProfile.Axes.Count + 1);
|
||||
axis.AxisIndex = SelectedPIDProfile.Axes.Count;
|
||||
}
|
||||
|
||||
SelectedPIDProfile.Axes.Add(axis);
|
||||
SelectedAxisParameter = axis;
|
||||
NotifyOfPropertyChange(() => SelectedAxes);
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
}
|
||||
|
||||
public void DeleteAxis()
|
||||
{
|
||||
if (SelectedPIDProfile == null || SelectedAxisParameter == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureProfileState(SelectedPIDProfile);
|
||||
|
||||
int index = SelectedPIDProfile.Axes.IndexOf(SelectedAxisParameter);
|
||||
SelectedPIDProfile.Axes.Remove(SelectedAxisParameter);
|
||||
SelectedAxisParameter = SelectedPIDProfile.Axes.Count == 0
|
||||
? null
|
||||
: SelectedPIDProfile.Axes[Math.Min(index, SelectedPIDProfile.Axes.Count - 1)];
|
||||
NotifyOfPropertyChange(() => SelectedAxes);
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
}
|
||||
|
||||
public void AddFilteringParameter()
|
||||
{
|
||||
if (SelectedPIDProfile == null)
|
||||
{
|
||||
MwMessageBox.Show("请先选择一个 PID 配置组");
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureProfileState(SelectedPIDProfile);
|
||||
|
||||
PidAxisOption axisOption = GetNextAxisOption(SelectedPIDProfile.FilteringParameters.Select(item => item.AxisIndex));
|
||||
AxisFilteringParameter filteringParameter = new AxisFilteringParameter
|
||||
{
|
||||
AxisIndex = axisOption != null ? axisOption.AxisIndex : GetDefaultAxisIndex()
|
||||
};
|
||||
|
||||
SelectedPIDProfile.FilteringParameters.Add(filteringParameter);
|
||||
SelectedFilteringParameter = filteringParameter;
|
||||
NotifyOfPropertyChange(() => SelectedFilteringParameters);
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
}
|
||||
|
||||
public void ReadParameters()
|
||||
{
|
||||
if (SelectedPIDProfile == null)
|
||||
{
|
||||
LocalizedMessageBox.Show(MessageKey.PidNoProfileSelected, MessageKey.TitleWarning);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (SelectedParameterTabIndex == 1)
|
||||
{
|
||||
ReadFilteringParameters();
|
||||
}
|
||||
else
|
||||
{
|
||||
ReadPidAxisParameters();
|
||||
}
|
||||
|
||||
NotifyOfPropertyChange(() => SelectedAxes);
|
||||
NotifyOfPropertyChange(() => SelectedFilteringParameters);
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
LocalizedMessageBox.Show(MessageKey.PidReadSucceeded, MessageKey.TitleInfo);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
$"PID参数读取失败: {exception}".LogSysError();
|
||||
LocalizedMessageBox.Show(MessageKey.PidReadFailed, MessageKey.TitleError);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteParameters()
|
||||
{
|
||||
if (SelectedPIDProfile == null)
|
||||
{
|
||||
LocalizedMessageBox.Show(MessageKey.PidNoProfileSelected, MessageKey.TitleWarning);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (SelectedParameterTabIndex == 1)
|
||||
{
|
||||
WriteFilteringParameters();
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePidAxisParameters();
|
||||
}
|
||||
|
||||
LocalizedMessageBox.Show(MessageKey.PidWriteSucceeded, MessageKey.TitleInfo);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
$"PID参数写入失败: {exception}".LogSysError();
|
||||
LocalizedMessageBox.Show(MessageKey.PidWriteFailed, MessageKey.TitleError);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteFilteringParameter()
|
||||
{
|
||||
if (SelectedPIDProfile == null || SelectedFilteringParameter == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureProfileState(SelectedPIDProfile);
|
||||
|
||||
int index = SelectedPIDProfile.FilteringParameters.IndexOf(SelectedFilteringParameter);
|
||||
SelectedPIDProfile.FilteringParameters.Remove(SelectedFilteringParameter);
|
||||
SelectedFilteringParameter = SelectedPIDProfile.FilteringParameters.Count == 0
|
||||
? null
|
||||
: SelectedPIDProfile.FilteringParameters[Math.Min(index, SelectedPIDProfile.FilteringParameters.Count - 1)];
|
||||
NotifyOfPropertyChange(() => SelectedFilteringParameters);
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
}
|
||||
|
||||
private void EnsureProcessRecipeState()
|
||||
{
|
||||
if (ProcessRecipe == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ProcessRecipe.AxisPIDParameters == null)
|
||||
{
|
||||
ProcessRecipe.AxisPIDParameters = new ObservableCollection<PIDProfile>();
|
||||
}
|
||||
|
||||
if (ProcessRecipe.ProcessCompensationParameters == null)
|
||||
{
|
||||
ProcessRecipe.ProcessCompensationParameters = new ObservableCollection<ProcessAxisCompensationParameter>();
|
||||
}
|
||||
|
||||
EnsureFixedProfiles();
|
||||
EnsureFixedCompensationParameters();
|
||||
|
||||
foreach (PIDProfile profile in ProcessRecipe.AxisPIDParameters)
|
||||
{
|
||||
EnsureProfileState(profile);
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncProfileSelection()
|
||||
{
|
||||
if (AxisPIDParameters == null || AxisPIDParameters.Count == 0)
|
||||
{
|
||||
SelectedPIDProfile = null;
|
||||
SelectedAxisParameter = null;
|
||||
SelectedFilteringParameter = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedPIDProfile == null || !AxisPIDParameters.Contains(SelectedPIDProfile))
|
||||
{
|
||||
SelectedPIDProfile = AxisPIDParameters[0];
|
||||
}
|
||||
|
||||
EnsureProfileState(SelectedPIDProfile);
|
||||
|
||||
if (SelectedAxes == null || SelectedAxes.Count == 0)
|
||||
{
|
||||
SelectedAxisParameter = null;
|
||||
}
|
||||
else if (SelectedAxisParameter == null || !SelectedAxes.Contains(SelectedAxisParameter))
|
||||
{
|
||||
SelectedAxisParameter = SelectedAxes[0];
|
||||
}
|
||||
|
||||
SyncFilteringSelection();
|
||||
}
|
||||
|
||||
private void EnsureProfileState(PIDProfile profile)
|
||||
{
|
||||
if (profile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
profile.EnsureAxes();
|
||||
|
||||
if (profile.FilteringParameters == null)
|
||||
{
|
||||
profile.FilteringParameters = new ObservableCollection<AxisFilteringParameter>();
|
||||
}
|
||||
|
||||
if (profile.FilteringParameters.Count == 0)
|
||||
{
|
||||
AxisFilteringParameter filteringParameter = new AxisFilteringParameter();
|
||||
filteringParameter.AxisIndex = GetDefaultAxisIndex();
|
||||
profile.FilteringParameters.Add(filteringParameter);
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncFilteringSelection()
|
||||
{
|
||||
if (SelectedFilteringParameters == null || SelectedFilteringParameters.Count == 0)
|
||||
{
|
||||
SelectedFilteringParameter = null;
|
||||
SelectedNotchFilteringParameter = null;
|
||||
SelectedLowPassFilteringParameter = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedFilteringParameter == null || !SelectedFilteringParameters.Contains(SelectedFilteringParameter))
|
||||
{
|
||||
SelectedFilteringParameter = SelectedFilteringParameters[0];
|
||||
}
|
||||
|
||||
if (SelectedNotchFilteringParameter == null || !SelectedFilteringParameters.Contains(SelectedNotchFilteringParameter))
|
||||
{
|
||||
SelectedNotchFilteringParameter = SelectedFilteringParameter;
|
||||
}
|
||||
|
||||
if (SelectedLowPassFilteringParameter == null || !SelectedFilteringParameters.Contains(SelectedLowPassFilteringParameter))
|
||||
{
|
||||
SelectedLowPassFilteringParameter = SelectedFilteringParameter;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureFixedProfiles()
|
||||
{
|
||||
foreach (string profileName in _fixedProfileNames)
|
||||
{
|
||||
PIDProfile profile = AxisPIDParameters.FirstOrDefault(item => string.Equals(item.Name, profileName, StringComparison.OrdinalIgnoreCase));
|
||||
if (profile == null)
|
||||
{
|
||||
profile = new PIDProfile(profileName);
|
||||
profile.Description = "程序初始化固定配置组";
|
||||
AxisPIDParameters.Add(profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureFixedCompensationParameters()
|
||||
{
|
||||
foreach (string axisName in _fixedCompensationAxisNames)
|
||||
{
|
||||
ProcessAxisCompensationParameter parameter = ProcessRecipe.ProcessCompensationParameters
|
||||
.FirstOrDefault(item => string.Equals(item.AxisName, axisName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (parameter == null)
|
||||
{
|
||||
parameter = new ProcessAxisCompensationParameter();
|
||||
parameter.AxisName = axisName;
|
||||
parameter.CompensationValue = 0d;
|
||||
ProcessRecipe.ProcessCompensationParameters.Add(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadPidAxisParameters()
|
||||
{
|
||||
if (SelectedAxes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (AxisPIDParameter axisParameter in SelectedAxes)
|
||||
{
|
||||
AxisPIDParameter deviceParameter = _pidOperater.ReadPIDParametersFromDevice(axisParameter.ModuleName);
|
||||
CopyAxisParameterValues(deviceParameter, axisParameter);
|
||||
}
|
||||
}
|
||||
|
||||
private void WritePidAxisParameters()
|
||||
{
|
||||
if (SelectedAxes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (AxisPIDParameter axisParameter in SelectedAxes)
|
||||
{
|
||||
_pidOperater.SendPIDParameters(axisParameter);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadFilteringParameters()
|
||||
{
|
||||
if (SelectedFilteringParameter == null)
|
||||
{
|
||||
LocalizedMessageBox.Show(MessageKey.PidNoFilteringParameterSelected, MessageKey.TitleWarning);
|
||||
return;
|
||||
}
|
||||
|
||||
AxisFilteringParameter deviceParameter = _pidOperater.ReadPIDFilteringParametersFromDevice();
|
||||
CopyFilteringParameterValues(deviceParameter, SelectedFilteringParameter);
|
||||
}
|
||||
|
||||
private void WriteFilteringParameters()
|
||||
{
|
||||
if (SelectedFilteringParameter == null)
|
||||
{
|
||||
LocalizedMessageBox.Show(MessageKey.PidNoFilteringParameterSelected, MessageKey.TitleWarning);
|
||||
return;
|
||||
}
|
||||
|
||||
_pidOperater.SendPIDFilteringParameters(SelectedFilteringParameter);
|
||||
}
|
||||
|
||||
private void CopyAxisParameterValues(AxisPIDParameter source, AxisPIDParameter target)
|
||||
{
|
||||
if (source == null || target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
target.PA_SLVKP.Value = source.PA_SLVKP.Value;
|
||||
target.PA_SLVKI.Value = source.PA_SLVKI.Value;
|
||||
target.PA_SLVKPSF.Value = source.PA_SLVKPSF.Value;
|
||||
target.PA_SLVKPIF.Value = source.PA_SLVKPIF.Value;
|
||||
target.PA_SLVKISF.Value = source.PA_SLVKISF.Value;
|
||||
target.PA_SLVKIIF.Value = source.PA_SLVKIIF.Value;
|
||||
target.PA_SLPKP.Value = source.PA_SLPKP.Value;
|
||||
target.PA_SLPKPIF.Value = source.PA_SLPKPIF.Value;
|
||||
target.PA_SLPKPSF.Value = source.PA_SLPKPSF.Value;
|
||||
target.PA_SLAFF.Value = source.PA_SLAFF.Value;
|
||||
target.PA_SLJFF.Value = source.PA_SLJFF.Value;
|
||||
}
|
||||
|
||||
private void CopyFilteringParameterValues(AxisFilteringParameter source, AxisFilteringParameter target)
|
||||
{
|
||||
if (source == null || target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
target.IsUseNotch.Value = source.IsUseNotch.Value;
|
||||
target.NotchFrequency.Value = source.NotchFrequency.Value;
|
||||
target.NotchWidth.Value = source.NotchWidth.Value;
|
||||
target.NotchFalloff.Value = source.NotchFalloff.Value;
|
||||
target.IsUseFiltering.Value = source.IsUseFiltering.Value;
|
||||
target.FilteringFrequency.Value = source.FilteringFrequency.Value;
|
||||
target.FilteringDamping.Value = source.FilteringDamping.Value;
|
||||
}
|
||||
|
||||
private PidAxisOption GetNextAxisOption(IEnumerable<int> usedAxisIndices)
|
||||
{
|
||||
if (AxisOptions == null || AxisOptions.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<int> usedAxisSet = new HashSet<int>();
|
||||
if (usedAxisIndices != null)
|
||||
{
|
||||
foreach (int axisIndex in usedAxisIndices)
|
||||
{
|
||||
usedAxisSet.Add(axisIndex);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (PidAxisOption axisOption in AxisOptions)
|
||||
{
|
||||
if (!usedAxisSet.Contains(axisOption.AxisIndex))
|
||||
{
|
||||
return axisOption;
|
||||
}
|
||||
}
|
||||
|
||||
return AxisOptions[0];
|
||||
}
|
||||
|
||||
private int GetDefaultAxisIndex()
|
||||
{
|
||||
if (AxisOptions == null || AxisOptions.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return AxisOptions[0].AxisIndex;
|
||||
}
|
||||
|
||||
private ObservableCollection<PidAxisOption> BuildAxisOptions()
|
||||
{
|
||||
ObservableCollection<PidAxisOption> axisOptions = new ObservableCollection<PidAxisOption>();
|
||||
|
||||
if (_hardwareManager != null && _hardwareManager.AxesDic != null && _hardwareManager.AxesDic.Count > 0)
|
||||
{
|
||||
int order = 0;
|
||||
foreach (string axisName in _hardwareManager.AxesDic.Keys.OrderBy(item => item))
|
||||
{
|
||||
axisOptions.Add(new PidAxisOption
|
||||
{
|
||||
AxisName = axisName,
|
||||
AxisIndex = ExtractAxisIndex(axisName, order)
|
||||
});
|
||||
order++;
|
||||
}
|
||||
}
|
||||
|
||||
if (axisOptions.Count > 0)
|
||||
{
|
||||
return axisOptions;
|
||||
}
|
||||
|
||||
FieldInfo[] fields = typeof(AxisName).GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||
int fallbackOrder = 0;
|
||||
foreach (FieldInfo fieldInfo in fields.OrderBy(item => item.Name))
|
||||
{
|
||||
object value = fieldInfo.GetValue(null);
|
||||
string axisName = value as string;
|
||||
if (string.IsNullOrWhiteSpace(axisName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
axisOptions.Add(new PidAxisOption
|
||||
{
|
||||
AxisName = axisName,
|
||||
AxisIndex = ExtractAxisIndex(axisName, fallbackOrder)
|
||||
});
|
||||
fallbackOrder++;
|
||||
}
|
||||
|
||||
return axisOptions;
|
||||
}
|
||||
|
||||
private int ExtractAxisIndex(string axisName, int fallbackValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(axisName))
|
||||
{
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
string digits = new string(axisName.Reverse().TakeWhile(char.IsDigit).Reverse().ToArray());
|
||||
int axisIndex;
|
||||
if (int.TryParse(digits, out axisIndex))
|
||||
{
|
||||
return axisIndex;
|
||||
}
|
||||
|
||||
return fallbackValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.View;
|
||||
using MaxwellControl.Tools;
|
||||
using MaxwellFramework.Core.Common.Command;
|
||||
using MwFramework.ManagerService;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class RecipeButtonGroupViewModel :BaseScreen
|
||||
{
|
||||
public ICommand CreateNewCmd { get;private set; }
|
||||
public ICommand DeleteCmd { get; private set; }
|
||||
public ICommand CopyCmd { get; private set; }
|
||||
public ICommand ApplyCmd { get; private set; }
|
||||
public ICommand ImportCmd { get; private set; }
|
||||
public ICommand SaveCmd { get; private set; }
|
||||
public ICommand ExportCmd { get; private set; }
|
||||
public ICommand ReNameCmd { get; private set; }
|
||||
public ICommand ClearCmd { get; private set; }
|
||||
|
||||
// 新增事件 —— 发布者(按钮组)向外广播意图
|
||||
public event EventHandler CreateNewRequested;
|
||||
public event EventHandler DeleteRequested;
|
||||
public event EventHandler CopyRequested;
|
||||
public event EventHandler ApplyRequested;
|
||||
public event EventHandler ImportRequested;
|
||||
public event EventHandler SaveRequested;
|
||||
public event EventHandler ExportRequested;
|
||||
public event EventHandler ReNameRequested;
|
||||
public event EventHandler ClearRequested;
|
||||
|
||||
public RecipeButtonGroupViewModel()
|
||||
{
|
||||
CreateNewCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
var existingNames = recipeViewModel.RecipeWraps.Select(r => r.RecipeName).ToList();
|
||||
var recipeNameWindow = new RecipeNameWindowModel
|
||||
{
|
||||
ExistingRecipeNames = existingNames
|
||||
};
|
||||
ShowRecipeWindow<RecipeNameWindow,RecipeNameWindowModel>(recipeNameWindow, out bool? result);
|
||||
if (result == true)
|
||||
{
|
||||
CreateNewRequested?.Invoke(this, new RecipeEventArgs
|
||||
{
|
||||
RecipeName = recipeNameWindow.RecipeName
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
DeleteCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
bool flowControl = CheckOperate(recipeViewModel);
|
||||
if (!flowControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var result = MwMessageBox.Show($"确认删除当前选中配方:{recipeViewModel.SelectedRecipeWrap.RecipeName}?", "确认删除", MessageBoxButton.YesNo, MessageBoxImage.Warning);
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
DeleteRequested?.Invoke(this, new RecipeEventArgs
|
||||
{
|
||||
RecipeName = recipeViewModel.SelectedRecipeWrap.RecipeName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
CopyCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
if (!EnsureActionSupported(recipeViewModel.CanCopyRecipe, "复制"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool flowControl = CheckOperate(recipeViewModel);
|
||||
if (!flowControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var existingNames = recipeViewModel.RecipeWraps.Select(r => r.RecipeName).ToList();
|
||||
var recipeNameWindow = new RecipeNameWindowModel
|
||||
{
|
||||
ExistingRecipeNames = existingNames,
|
||||
RecipeName = recipeViewModel.SelectedRecipeWrap.RecipeName + "_Copy"
|
||||
};
|
||||
ShowRecipeWindow<RecipeNameWindow, RecipeNameWindowModel>(recipeNameWindow, out bool? result);
|
||||
if (result == true)
|
||||
{
|
||||
CopyRequested?.Invoke(this, new RecipeEventArgs
|
||||
{
|
||||
RecipeName = recipeNameWindow.RecipeName
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
ApplyCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
bool flowControl = CheckOperate(recipeViewModel);
|
||||
if (!flowControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ApplyRequested?.Invoke(this, new RecipeEventArgs
|
||||
{
|
||||
RecipeName = recipeViewModel.SelectedRecipeWrap.RecipeName
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ImportCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
if (!EnsureActionSupported(recipeViewModel.CanImportRecipe, "导入"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool flowControl = CheckOperate(recipeViewModel);
|
||||
if (!flowControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ImportRequested?.Invoke(this, new RecipeEventArgs
|
||||
{
|
||||
RecipeName = recipeViewModel.SelectedRecipeWrap.RecipeName
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
SaveCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
bool flowControl = CheckOperate(recipeViewModel);
|
||||
if (!flowControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SaveRequested?.Invoke(this, new RecipeEventArgs
|
||||
{
|
||||
RecipeName = recipeViewModel.SelectedRecipeWrap.RecipeName
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ExportCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
if (!EnsureActionSupported(recipeViewModel.CanExportRecipe, "导出"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool flowControl = CheckOperate(recipeViewModel);
|
||||
if (!flowControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ExportRequested?.Invoke(this, new RecipeEventArgs
|
||||
{
|
||||
RecipeName = recipeViewModel.SelectedRecipeWrap.RecipeName
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ReNameCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
if (obj != null && obj is IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
bool flowControl = CheckOperate(recipeViewModel);
|
||||
if (!flowControl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var existingNames = recipeViewModel.RecipeWraps.Select(r => r.RecipeName).ToList();
|
||||
var recipeNameWindow = new RecipeReNameWindowModel
|
||||
{
|
||||
ExistingRecipeNames = existingNames,
|
||||
OldRecipeName = recipeViewModel.SelectedRecipeWrap.RecipeName
|
||||
};
|
||||
ShowRecipeWindow<RecipeReNameWindow,RecipeReNameWindowModel>( recipeNameWindow, out bool? result);
|
||||
if (result == true)
|
||||
{
|
||||
ReNameRequested?.Invoke(this, new RecipeRenameEventArgs
|
||||
{
|
||||
NewRecipeName = recipeNameWindow.RecipeName,
|
||||
OldRecipeName = recipeNameWindow.OldRecipeName
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
ClearCmd = new DelegateCommand(obj =>
|
||||
{
|
||||
ClearRequested?.Invoke(this, EventArgs.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
private static bool CheckOperate(IRecipeViewModel recipeViewModel)
|
||||
{
|
||||
if (recipeViewModel.RecipeWraps.Count == 0)
|
||||
{
|
||||
MwMessageBox.Show("当前无可操作的配方,请先创建或导入配方。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return false;
|
||||
}
|
||||
else if (recipeViewModel.SelectedRecipeWrap == null)
|
||||
{
|
||||
MwMessageBox.Show("请先选择一个配方再进行操作。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool EnsureActionSupported(bool isSupported, string actionName)
|
||||
{
|
||||
if (isSupported)
|
||||
return true;
|
||||
|
||||
MwMessageBox.Show($"当前版本暂不支持{actionName}功能。");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ShowRecipeWindow<TWindow, TViewModel>(TViewModel viewModel, out bool? result)
|
||||
where TWindow : Window, new()
|
||||
{
|
||||
result = null;
|
||||
var owner = WindowHelper.GetActiveWindow();
|
||||
|
||||
try
|
||||
{
|
||||
// 确保在 UI 线程创建/显示窗口
|
||||
if (Application.Current == null || Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
var window = new TWindow
|
||||
{
|
||||
DataContext = viewModel,
|
||||
Owner = owner,
|
||||
ShowInTaskbar = owner == null,
|
||||
WindowStartupLocation = owner != null ? WindowStartupLocation.CenterOwner : WindowStartupLocation.CenterScreen
|
||||
};
|
||||
|
||||
// 如果确实需要置顶,可由调用方或窗口自身控制;这里不强制 Topmost
|
||||
result = window.ShowDialog();
|
||||
}
|
||||
else
|
||||
{
|
||||
var windowResult= default(bool?);
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var window = new TWindow
|
||||
{
|
||||
DataContext = viewModel,
|
||||
Owner = owner,
|
||||
ShowInTaskbar = owner == null,
|
||||
WindowStartupLocation = owner != null ? WindowStartupLocation.CenterOwner : WindowStartupLocation.CenterScreen
|
||||
};
|
||||
windowResult = window.ShowDialog();
|
||||
});
|
||||
result = windowResult;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 避免抛出异常到调用方(视需求可记录日志)
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using MaxwellFramework.Core.Common.Command;
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class RecipeNameWindowModel : PropertyChangedBase
|
||||
{
|
||||
private bool? _dialogResult;
|
||||
public bool? DialogResult
|
||||
{
|
||||
get => _dialogResult;
|
||||
set => SetAndNotify(ref _dialogResult, value);
|
||||
}
|
||||
|
||||
private string _recipeName;
|
||||
public string RecipeName
|
||||
{
|
||||
get => _recipeName;
|
||||
set
|
||||
{
|
||||
if(SetAndNotify(ref _recipeName, value))
|
||||
{
|
||||
UpdateValidationMessage();
|
||||
((DelegateCommand)OkCommand).RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
private string _validationMessage;
|
||||
/// <summary>
|
||||
/// 在界面上显示的验证信息(为空时不显示)
|
||||
/// </summary>
|
||||
public string ValidationMessage
|
||||
{
|
||||
get => _validationMessage;
|
||||
set => SetAndNotify(ref _validationMessage, value);
|
||||
}
|
||||
public IEnumerable<string> ExistingRecipeNames { get; set; }
|
||||
public ICommand OkCommand { get; }
|
||||
public ICommand CancelCommand { get; }
|
||||
public RecipeNameWindowModel()
|
||||
{
|
||||
OkCommand = new DelegateCommand(OnOk, CanOk);
|
||||
CancelCommand = new DelegateCommand(OnCancel);
|
||||
}
|
||||
private void OnOk(object obj)
|
||||
{
|
||||
DialogResult = true;
|
||||
}
|
||||
private bool CanOk(object obj)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(RecipeName))
|
||||
return false;
|
||||
if (ExistingRecipeNames != null && ExistingRecipeNames.Contains(RecipeName, StringComparer.OrdinalIgnoreCase))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
private void OnCancel(object obj)
|
||||
{
|
||||
DialogResult = false;
|
||||
}
|
||||
private void UpdateValidationMessage()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(RecipeName))
|
||||
{
|
||||
ValidationMessage = "名称不能为空";
|
||||
return;
|
||||
}
|
||||
|
||||
if (ExistingRecipeNames != null && ExistingRecipeNames.Contains(RecipeName, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
ValidationMessage = "名称已存在";
|
||||
return;
|
||||
}
|
||||
|
||||
ValidationMessage = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using MaxwellFramework.Core.Common.Command;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class RecipeReNameWindowModel : PropertyChangedBase
|
||||
{
|
||||
private bool? _dialogResult;
|
||||
public bool? DialogResult
|
||||
{
|
||||
get => _dialogResult;
|
||||
set => SetAndNotify(ref _dialogResult, value);
|
||||
}
|
||||
|
||||
private string _recipeName;
|
||||
public string RecipeName
|
||||
{
|
||||
get => _recipeName;
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _recipeName, value))
|
||||
{
|
||||
UpdateValidationMessage();
|
||||
((DelegateCommand)OkCommand).RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
private string _oldRecipeName;
|
||||
|
||||
public string OldRecipeName
|
||||
{
|
||||
get { return _oldRecipeName; }
|
||||
set { SetAndNotify(ref _oldRecipeName, value); }
|
||||
}
|
||||
|
||||
private string _validationMessage;
|
||||
/// <summary>
|
||||
/// 在界面上显示的验证信息(为空时不显示)
|
||||
/// </summary>
|
||||
public string ValidationMessage
|
||||
{
|
||||
get => _validationMessage;
|
||||
set => SetAndNotify(ref _validationMessage, value);
|
||||
}
|
||||
|
||||
public IEnumerable<string> ExistingRecipeNames { get; set; }
|
||||
public ICommand OkCommand { get; }
|
||||
public ICommand CancelCommand { get; }
|
||||
public RecipeReNameWindowModel()
|
||||
{
|
||||
OkCommand = new DelegateCommand(OnOk, CanOk);
|
||||
CancelCommand = new DelegateCommand(OnCancel);
|
||||
}
|
||||
private void OnOk(object obj)
|
||||
{
|
||||
DialogResult = true;
|
||||
}
|
||||
private bool CanOk(object obj)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(RecipeName))
|
||||
return false;
|
||||
if (ExistingRecipeNames != null && ExistingRecipeNames.Contains(RecipeName, StringComparer.OrdinalIgnoreCase))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
private void OnCancel(object obj)
|
||||
{
|
||||
DialogResult = false;
|
||||
}
|
||||
private void UpdateValidationMessage()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(RecipeName))
|
||||
{
|
||||
ValidationMessage = "名称不能为空";
|
||||
return;
|
||||
}
|
||||
|
||||
if (ExistingRecipeNames != null && ExistingRecipeNames.Contains(RecipeName, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
ValidationMessage = "名称已存在";
|
||||
return;
|
||||
}
|
||||
|
||||
ValidationMessage = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MaxwellFramework.Core.Interfaces;
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using StyletIoC;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class RecipeViewModel :BaseScreen ,IPage
|
||||
{
|
||||
private bool _isReactivatingAfterBlockedLeave;
|
||||
|
||||
public string Name => "Recipe";
|
||||
private const string CARRIER = "载具配方";
|
||||
private const string SUBSTRATE = "基板配方";
|
||||
private const string WAFER = "芯片配方";
|
||||
private const string PROCESS = "工艺配方";
|
||||
private readonly Dictionary<string, BaseScreen> _viewModelDict = new Dictionary<string, BaseScreen>();
|
||||
public ObservableCollection<MenuItemWrap> MenuItemWraps { get; private set; }
|
||||
|
||||
private MenuItemWrap _selectedMenuItem;
|
||||
|
||||
public MenuItemWrap SelectedMenuItem
|
||||
{
|
||||
get { return _selectedMenuItem; }
|
||||
set
|
||||
{
|
||||
if (_selectedMenuItem == value)
|
||||
return;
|
||||
|
||||
if (_selectedMenuItem != null && value != null && !TryLeaveCurrentScreen())
|
||||
{
|
||||
NotifyOfPropertyChange(() => SelectedMenuItem);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SetAndNotify(ref _selectedMenuItem, value))
|
||||
{
|
||||
CurrentScreen = value != null ? _viewModelDict[value.Header] : null;
|
||||
SyncCurrentScreenSelectionFromActiveRecipe();
|
||||
}
|
||||
}
|
||||
}
|
||||
private BaseScreen _currentScreen;
|
||||
|
||||
public BaseScreen CurrentScreen
|
||||
{
|
||||
get { return _currentScreen; }
|
||||
set
|
||||
{
|
||||
if (_currentScreen == value)
|
||||
return;
|
||||
|
||||
UnsubscribeScreenState(_currentScreen as INotifyPropertyChanged);
|
||||
|
||||
if (SetAndNotify(ref _currentScreen, value))
|
||||
{
|
||||
SubscribeScreenState(value as INotifyPropertyChanged);
|
||||
NotifyOfPropertyChange(() => IsRecipePanelHidden);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRecipePanelHidden
|
||||
{
|
||||
get
|
||||
{
|
||||
var recipeScreen = CurrentScreen as IRecipeViewModel;
|
||||
return recipeScreen != null && recipeScreen.HideRecipePanel;
|
||||
}
|
||||
set
|
||||
{
|
||||
var recipeScreen = CurrentScreen as IRecipeViewModel;
|
||||
if (recipeScreen == null || recipeScreen.HideRecipePanel == value)
|
||||
return;
|
||||
|
||||
recipeScreen.HideRecipePanel = value;
|
||||
NotifyOfPropertyChange(() => IsRecipePanelHidden);
|
||||
}
|
||||
}
|
||||
|
||||
private CarrierRecipeViewModel _carrierRecipeViewModel;
|
||||
[Inject]
|
||||
public CarrierRecipeViewModel CarrierRecipeViewModel
|
||||
{
|
||||
get { return _carrierRecipeViewModel; }
|
||||
set { _carrierRecipeViewModel = value; }
|
||||
}
|
||||
private SubstrateRecipeViewModel _substrateRecipeViewModel;
|
||||
[Inject]
|
||||
public SubstrateRecipeViewModel SubstrateRecipeViewModel
|
||||
{
|
||||
get { return _substrateRecipeViewModel; }
|
||||
set { _substrateRecipeViewModel = value; }
|
||||
}
|
||||
private WaferRecipeViewModel _waferRecipeViewModel;
|
||||
[Inject]
|
||||
public WaferRecipeViewModel WaferRecipeViewModel
|
||||
{
|
||||
get { return _waferRecipeViewModel; }
|
||||
set { _waferRecipeViewModel = value; }
|
||||
}
|
||||
private ProcessRecipeViewModel _processRecipeViewModel;
|
||||
[Inject]
|
||||
public ProcessRecipeViewModel ProcessRecipeViewModel
|
||||
{
|
||||
get { return _processRecipeViewModel; }
|
||||
set { _processRecipeViewModel = value; }
|
||||
}
|
||||
|
||||
|
||||
public RecipeViewModel()
|
||||
{
|
||||
InitMenuItems();
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
EnsureViewModelDict();
|
||||
if(SelectedMenuItem==null)
|
||||
SelectedMenuItem = MenuItemWraps[0];
|
||||
|
||||
SyncCurrentScreenSelectionFromActiveRecipe();
|
||||
}
|
||||
private void InitMenuItems()
|
||||
{
|
||||
MenuItemWraps = new ObservableCollection<MenuItemWrap>
|
||||
{
|
||||
//new MenuItemWrap() { Header=CARRIER,Tag=CARRIER },
|
||||
new MenuItemWrap() { Header=SUBSTRATE,Tag=SUBSTRATE },
|
||||
new MenuItemWrap() { Header=WAFER,Tag=WAFER },
|
||||
new MenuItemWrap() { Header=PROCESS,Tag=PROCESS },
|
||||
};
|
||||
}
|
||||
private void InitViewModelDict()
|
||||
{
|
||||
_viewModelDict.Clear();
|
||||
_viewModelDict.Add(CARRIER, CarrierRecipeViewModel);
|
||||
_viewModelDict.Add(SUBSTRATE, SubstrateRecipeViewModel);
|
||||
_viewModelDict.Add(WAFER, WaferRecipeViewModel);
|
||||
_viewModelDict.Add(PROCESS, ProcessRecipeViewModel);
|
||||
}
|
||||
|
||||
private void EnsureViewModelDict()
|
||||
{
|
||||
if (_viewModelDict.Count == 0)
|
||||
{
|
||||
InitViewModelDict();
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncCurrentScreenSelectionFromActiveRecipe()
|
||||
{
|
||||
var recipeScreen = CurrentScreen as IRecipeViewModel;
|
||||
recipeScreen?.SyncSelectionFromActiveRecipe();
|
||||
}
|
||||
|
||||
private bool TryLeaveCurrentScreen()
|
||||
{
|
||||
var guard = CurrentScreen as IRecipeViewModel;
|
||||
if (guard == null)
|
||||
return true;
|
||||
|
||||
return guard.TryLeaveCurrentRecipeContext();
|
||||
}
|
||||
|
||||
public bool TrySelectMenuItem(MenuItemWrap targetMenuItem)
|
||||
{
|
||||
if (targetMenuItem == null)
|
||||
return false;
|
||||
|
||||
SelectedMenuItem = targetMenuItem;
|
||||
return SelectedMenuItem == targetMenuItem;
|
||||
}
|
||||
|
||||
public bool TrySelectRecipeWrap(RecipeWrap targetRecipeWrap)
|
||||
{
|
||||
var guard = CurrentScreen as IRecipeViewModel;
|
||||
if (guard == null || targetRecipeWrap == null)
|
||||
return false;
|
||||
|
||||
if (guard.SelectedRecipeWrap == targetRecipeWrap)
|
||||
return true;
|
||||
|
||||
guard.SelectedRecipeWrap = targetRecipeWrap;
|
||||
return guard.SelectedRecipeWrap == targetRecipeWrap;
|
||||
}
|
||||
|
||||
private void SubscribeScreenState(INotifyPropertyChanged screen)
|
||||
{
|
||||
if (screen != null)
|
||||
{
|
||||
screen.PropertyChanged += OnCurrentScreenPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnsubscribeScreenState(INotifyPropertyChanged screen)
|
||||
{
|
||||
if (screen != null)
|
||||
{
|
||||
screen.PropertyChanged -= OnCurrentScreenPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCurrentScreenPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(e.PropertyName) || string.Equals(e.PropertyName, nameof(BaseScreen.HideRecipePanel), StringComparison.Ordinal))
|
||||
{
|
||||
NotifyOfPropertyChange(() => IsRecipePanelHidden);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnStateChanged(ScreenState previousState, ScreenState newState)
|
||||
{
|
||||
base.OnStateChanged(previousState, newState);
|
||||
|
||||
if (_isReactivatingAfterBlockedLeave)
|
||||
return;
|
||||
|
||||
if (newState == ScreenState.Active)
|
||||
{
|
||||
EnsureViewModelDict();
|
||||
if (SelectedMenuItem == null && MenuItemWraps != null && MenuItemWraps.Count > 0)
|
||||
{
|
||||
SelectedMenuItem = MenuItemWraps[0];
|
||||
}
|
||||
|
||||
SyncCurrentScreenSelectionFromActiveRecipe();
|
||||
}
|
||||
|
||||
if (previousState == ScreenState.Active && newState == ScreenState.Deactivated && !TryLeaveCurrentScreen())
|
||||
{
|
||||
try
|
||||
{
|
||||
_isReactivatingAfterBlockedLeave = true;
|
||||
Execute.OnUIThread(() => ((IScreenState)this).Activate());
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isReactivatingAfterBlockedLeave = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDeactivate()
|
||||
{
|
||||
UnsubscribeScreenState(_currentScreen as INotifyPropertyChanged);
|
||||
base.OnDeactivate();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public abstract class RecipeViewModelBase<TRecipe> : BaseScreen, IRecipeViewModel
|
||||
where TRecipe : RecipeBase, new()
|
||||
{
|
||||
private string _lastSavedSnapshot = string.Empty;
|
||||
protected string LastSavedSnapshot => _lastSavedSnapshot;
|
||||
private bool _skipLeaveCheck;
|
||||
private RecipeWrap _selectedRecipeWrap;
|
||||
|
||||
public ObservableCollection<RecipeWrap> RecipeWraps { get; protected set; } = new ObservableCollection<RecipeWrap>();
|
||||
|
||||
public RecipeWrap SelectedRecipeWrap
|
||||
{
|
||||
get { return _selectedRecipeWrap; }
|
||||
set
|
||||
{
|
||||
if (_selectedRecipeWrap == value)
|
||||
return;
|
||||
|
||||
if (!_skipLeaveCheck && _selectedRecipeWrap != null && value != null && !TryLeaveCurrentRecipeContext())
|
||||
{
|
||||
NotifyOfPropertyChange(() => SelectedRecipeWrap);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SetAndNotify(ref _selectedRecipeWrap, value))
|
||||
{
|
||||
LoadRecipeFromWrap(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RecipeButtonGroupViewModel RecipeButtonGroupViewModel { get; protected set; }
|
||||
public virtual bool CanCopyRecipe => true;
|
||||
public virtual bool CanImportRecipe => false;
|
||||
public virtual bool CanExportRecipe => false;
|
||||
public bool HasUnsavedChanges => IsRecipeDirty();
|
||||
public string CurrentRecipeDisplayName => CurrentRecipe != null ? CurrentRecipe.RecipeName : EmptyRecipeDisplayName;
|
||||
public RecipeManager RecipeManager { get; }
|
||||
|
||||
protected RecipeViewModelBase(RecipeManager recipeManager)
|
||||
{
|
||||
RecipeManager = recipeManager;
|
||||
}
|
||||
|
||||
protected abstract TRecipe CurrentRecipe { get; set; }
|
||||
protected abstract string RecipeFolderPath { get; }
|
||||
protected abstract string EmptyRecipeDisplayName { get; }
|
||||
protected abstract string GetActiveRecipeName();
|
||||
protected abstract void SwitchActiveRecipe(string recipeName);
|
||||
protected abstract void ClearActiveRecipe();
|
||||
protected virtual void OnRecipeLoaded(TRecipe recipe, RecipeWrap recipeWrap) { }
|
||||
protected virtual string GetUnsavedChangesMessage() => $"<22>䷽<EFBFBD><E4B7BD>{CurrentRecipeDisplayName}<7D><><EFBFBD><EFBFBD>δ<EFBFBD><CEB4><EFBFBD><EFBFBD><EFBFBD>ģ<DEB8><C4A3>Ƿ<EFBFBD><C7B7>ȱ<EFBFBD><C8B1>棿";
|
||||
protected virtual void AfterDeleteCurrentRecipe(string deletedRecipeName) { }
|
||||
protected virtual void AfterRecipeRenamed(string oldRecipeName, string newRecipeName) { }
|
||||
|
||||
public virtual bool TryLeaveCurrentRecipeContext()
|
||||
{
|
||||
if (!HasUnsavedChanges)
|
||||
return true;
|
||||
|
||||
var result = MwMessageBox.Show(GetUnsavedChangesMessage(), "δ<><CEB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ", System.Windows.MessageBoxButton.YesNoCancel, System.Windows.MessageBoxImage.Warning);
|
||||
if (result == System.Windows.MessageBoxResult.Cancel)
|
||||
return false;
|
||||
|
||||
if (result == System.Windows.MessageBoxResult.Yes)
|
||||
SaveCurrentRecipe(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SaveCurrentRecipeSilently()
|
||||
{
|
||||
SaveCurrentRecipe(false);
|
||||
}
|
||||
|
||||
protected void RegisterButtonGroupEvents()
|
||||
{
|
||||
RecipeButtonGroupViewModel = new RecipeButtonGroupViewModel();
|
||||
RecipeButtonGroupViewModel.CreateNewRequested -= OnCreateNewRecipe;
|
||||
RecipeButtonGroupViewModel.ApplyRequested -= OnApplyRecipe;
|
||||
RecipeButtonGroupViewModel.DeleteRequested -= OnDeleteRecipe;
|
||||
RecipeButtonGroupViewModel.CopyRequested -= OnCopyRecipe;
|
||||
RecipeButtonGroupViewModel.ImportRequested -= OnImportRecipe;
|
||||
RecipeButtonGroupViewModel.SaveRequested -= OnSaveRecipe;
|
||||
RecipeButtonGroupViewModel.ExportRequested -= OnExportRecipe;
|
||||
RecipeButtonGroupViewModel.ReNameRequested -= OnReNameRecipe;
|
||||
RecipeButtonGroupViewModel.ClearRequested -= OnClearRecipe;
|
||||
RecipeButtonGroupViewModel.CreateNewRequested += OnCreateNewRecipe;
|
||||
RecipeButtonGroupViewModel.ApplyRequested += OnApplyRecipe;
|
||||
RecipeButtonGroupViewModel.DeleteRequested += OnDeleteRecipe;
|
||||
RecipeButtonGroupViewModel.CopyRequested += OnCopyRecipe;
|
||||
RecipeButtonGroupViewModel.ImportRequested += OnImportRecipe;
|
||||
RecipeButtonGroupViewModel.SaveRequested += OnSaveRecipe;
|
||||
RecipeButtonGroupViewModel.ExportRequested += OnExportRecipe;
|
||||
RecipeButtonGroupViewModel.ReNameRequested += OnReNameRecipe;
|
||||
RecipeButtonGroupViewModel.ClearRequested += OnClearRecipe;
|
||||
}
|
||||
|
||||
public virtual void OnCreateNewRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
if (!TryLeaveCurrentRecipeContext())
|
||||
return;
|
||||
|
||||
var recipeWrap = new RecipeWrap
|
||||
{
|
||||
RecipeName = args.RecipeName,
|
||||
CreateTime = DateTime.UtcNow,
|
||||
ModifiedTime = DateTime.UtcNow,
|
||||
};
|
||||
|
||||
RecipeWraps.Add(recipeWrap);
|
||||
SelectRecipeWrapInternal(recipeWrap);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnDeleteRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (SelectedRecipeWrap == null)
|
||||
return;
|
||||
|
||||
if (!TryLeaveCurrentRecipeContext())
|
||||
return;
|
||||
|
||||
var deletedRecipeName = SelectedRecipeWrap.RecipeName;
|
||||
if (!RecipeManager.DeleteRecipeFolder(RecipeFolderPath, deletedRecipeName, out var error))
|
||||
{
|
||||
MwMessageBox.Show(error);
|
||||
return;
|
||||
}
|
||||
|
||||
RecipeWraps.Remove(SelectedRecipeWrap);
|
||||
AfterDeleteCurrentRecipe(deletedRecipeName);
|
||||
|
||||
if (RecipeWraps.Count > 0)
|
||||
{
|
||||
SelectRecipeWrapInternal(RecipeWraps[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedRecipeWrap = null;
|
||||
CurrentRecipe = null;
|
||||
ClearActiveRecipe();
|
||||
CaptureSavedSnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnCopyRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (!(eventArgs is RecipeEventArgs args) || SelectedRecipeWrap == null)
|
||||
return;
|
||||
|
||||
var sourceRecipeName = SelectedRecipeWrap.RecipeName;
|
||||
if (string.IsNullOrWhiteSpace(sourceRecipeName) || string.IsNullOrWhiteSpace(args.RecipeName))
|
||||
return;
|
||||
|
||||
if (CurrentRecipe != null &&
|
||||
string.Equals(CurrentRecipe.RecipeName, sourceRecipeName, StringComparison.Ordinal) &&
|
||||
HasUnsavedChanges)
|
||||
{
|
||||
SaveCurrentRecipe(false);
|
||||
}
|
||||
|
||||
if (!RecipeManager.CopyRecipeFolder(RecipeFolderPath, sourceRecipeName, args.RecipeName, out var error))
|
||||
{
|
||||
MwMessageBox.Show(error);
|
||||
return;
|
||||
}
|
||||
|
||||
var copiedRecipeWrap = new RecipeWrap
|
||||
{
|
||||
RecipeName = args.RecipeName,
|
||||
CreateTime = DateTime.UtcNow,
|
||||
ModifiedTime = DateTime.UtcNow,
|
||||
};
|
||||
|
||||
RecipeWraps.Add(copiedRecipeWrap);
|
||||
SelectRecipeWrapInternal(copiedRecipeWrap);
|
||||
}
|
||||
|
||||
public virtual void OnApplyRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
var recipeWrap = RecipeWraps.FirstOrDefault(p => p.RecipeName == args.RecipeName);
|
||||
if (recipeWrap == null)
|
||||
return;
|
||||
|
||||
if (!ReferenceEquals(SelectedRecipeWrap, recipeWrap))
|
||||
{
|
||||
if (!TryLeaveCurrentRecipeContext())
|
||||
return;
|
||||
|
||||
SelectRecipeWrapInternal(recipeWrap);
|
||||
}
|
||||
|
||||
if (SelectedRecipeWrap == null)
|
||||
return;
|
||||
|
||||
if (CurrentRecipe != null && IsRecipeDirty())
|
||||
{
|
||||
SaveCurrentRecipe(false);
|
||||
}
|
||||
|
||||
SwitchActiveRecipe(SelectedRecipeWrap.RecipeName);
|
||||
UpdateInUseState(SelectedRecipeWrap);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void OnImportRecipe(object sender, EventArgs eventArgs);
|
||||
|
||||
public virtual void OnSaveRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
CommonUti.RunOnUi(() =>
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs)
|
||||
{
|
||||
SaveCurrentRecipe(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public abstract void OnExportRecipe(object sender, EventArgs eventArgs);
|
||||
public abstract void OnReNameRecipe(object sender, EventArgs eventArgs);
|
||||
|
||||
public virtual void OnClearRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (!TryLeaveCurrentRecipeContext())
|
||||
return;
|
||||
|
||||
if (SelectedRecipeWrap == null)
|
||||
return;
|
||||
|
||||
var clearedRecipe = new TRecipe
|
||||
{
|
||||
RecipeName = SelectedRecipeWrap.RecipeName
|
||||
};
|
||||
|
||||
CurrentRecipe = clearedRecipe;
|
||||
OnRecipeLoaded(clearedRecipe, SelectedRecipeWrap);
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
}
|
||||
|
||||
protected bool IsRecipeDirty()
|
||||
{
|
||||
if (CurrentRecipe == null)
|
||||
return false;
|
||||
|
||||
var path = Path.Combine(CurrentRecipe.Dir ?? string.Empty, CurrentRecipe.FileName ?? string.Empty);
|
||||
if (!File.Exists(path))
|
||||
return true;
|
||||
|
||||
return !AreSnapshotsEquivalent(_lastSavedSnapshot, CurrentRecipe.CreateSnapshot());
|
||||
}
|
||||
|
||||
protected void CaptureSavedSnapshot()
|
||||
{
|
||||
_lastSavedSnapshot = CurrentRecipe != null ? CurrentRecipe.CreateSnapshot() : string.Empty;
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
NotifyOfPropertyChange(() => CurrentRecipeDisplayName);
|
||||
}
|
||||
|
||||
protected bool TryRenameRecipe(RecipeRenameEventArgs args)
|
||||
{
|
||||
if (args == null)
|
||||
return false;
|
||||
|
||||
var recipeWrap = RecipeWraps.FirstOrDefault(p => p.RecipeName == args.OldRecipeName);
|
||||
if (recipeWrap == null)
|
||||
return false;
|
||||
|
||||
if (!RecipeManager.RenameFolder(RecipeFolderPath, args.OldRecipeName, args.NewRecipeName, out var error))
|
||||
{
|
||||
MwMessageBox.Show(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
recipeWrap.RecipeName = args.NewRecipeName;
|
||||
recipeWrap.ModifiedTime = DateTime.UtcNow;
|
||||
|
||||
if (CurrentRecipe != null && string.Equals(CurrentRecipe.RecipeName, args.OldRecipeName, StringComparison.Ordinal))
|
||||
{
|
||||
CurrentRecipe.RecipeName = args.NewRecipeName;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(recipeWrap, SelectedRecipeWrap))
|
||||
{
|
||||
SwitchActiveRecipe(args.NewRecipeName);
|
||||
}
|
||||
|
||||
CaptureSavedSnapshot();
|
||||
AfterRecipeRenamed(args.OldRecipeName, args.NewRecipeName);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void SaveCurrentRecipe(bool showSuccessMessage)
|
||||
{
|
||||
if (CurrentRecipe == null)
|
||||
return;
|
||||
|
||||
CurrentRecipe.Write();
|
||||
CaptureSavedSnapshot();
|
||||
|
||||
if (SelectedRecipeWrap != null)
|
||||
{
|
||||
SelectedRecipeWrap.ModifiedTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if (showSuccessMessage)
|
||||
{
|
||||
MwMessageBox.Show("<22><><EFBFBD><EFBFBD><EFBFBD>ɹ<EFBFBD>");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AreSnapshotsEquivalent(string leftSnapshot, string rightSnapshot)
|
||||
{
|
||||
var left = string.IsNullOrWhiteSpace(leftSnapshot) ? null : ParseSnapshotToken(leftSnapshot);
|
||||
var right = string.IsNullOrWhiteSpace(rightSnapshot) ? null : ParseSnapshotToken(rightSnapshot);
|
||||
|
||||
if (left == null || right == null)
|
||||
return false;
|
||||
|
||||
return JToken.DeepEquals(left, right);
|
||||
}
|
||||
|
||||
private static JToken ParseSnapshotToken(string snapshot)
|
||||
{
|
||||
try
|
||||
{
|
||||
return JToken.Parse(snapshot);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void SelectRecipeWrapInternal(RecipeWrap recipeWrap)
|
||||
{
|
||||
try
|
||||
{
|
||||
_skipLeaveCheck = true;
|
||||
SelectedRecipeWrap = recipeWrap;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_skipLeaveCheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void InitializeSelection(string activeRecipeName)
|
||||
{
|
||||
SyncSelection(activeRecipeName, true);
|
||||
}
|
||||
|
||||
protected virtual void UpdateInUseState(RecipeWrap activeRecipeWrap)
|
||||
{
|
||||
foreach (var wrap in RecipeWraps)
|
||||
{
|
||||
wrap.IsInUse = ReferenceEquals(wrap, activeRecipeWrap);
|
||||
}
|
||||
}
|
||||
|
||||
public void SyncSelectionFromActiveRecipe()
|
||||
{
|
||||
SyncSelection(GetActiveRecipeName(), false);
|
||||
}
|
||||
|
||||
private void SyncSelection(string recipeName, bool fallbackToFirst)
|
||||
{
|
||||
if (RecipeWraps == null || RecipeWraps.Count == 0)
|
||||
{
|
||||
UpdateInUseState(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var target = string.IsNullOrWhiteSpace(recipeName)
|
||||
? null
|
||||
: RecipeWraps.FirstOrDefault(p => string.Equals(p.RecipeName, recipeName, StringComparison.Ordinal));
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
UpdateInUseState(null);
|
||||
if (!fallbackToFirst)
|
||||
return;
|
||||
|
||||
target = RecipeWraps[0];
|
||||
}
|
||||
|
||||
if (ReferenceEquals(SelectedRecipeWrap, target))
|
||||
{
|
||||
UpdateInUseState(target);
|
||||
return;
|
||||
}
|
||||
|
||||
SelectRecipeWrapInternal(target);
|
||||
}
|
||||
|
||||
private void LoadRecipeFromWrap(RecipeWrap recipeWrap)
|
||||
{
|
||||
if (recipeWrap == null)
|
||||
{
|
||||
UpdateInUseStateByActiveRecipe();
|
||||
return;
|
||||
}
|
||||
|
||||
var newRecipe = new TRecipe
|
||||
{
|
||||
RecipeName = recipeWrap.RecipeName
|
||||
};
|
||||
newRecipe.Read();
|
||||
|
||||
CurrentRecipe = newRecipe;
|
||||
CaptureSavedSnapshot();
|
||||
UpdateInUseStateByActiveRecipe();
|
||||
OnRecipeLoaded(newRecipe, recipeWrap);
|
||||
}
|
||||
|
||||
private void UpdateInUseStateByActiveRecipe()
|
||||
{
|
||||
var activeRecipeName = GetActiveRecipeName();
|
||||
var activeRecipeWrap = RecipeWraps == null || string.IsNullOrWhiteSpace(activeRecipeName)
|
||||
? null
|
||||
: RecipeWraps.FirstOrDefault(p => string.Equals(p.RecipeName, activeRecipeName, StringComparison.Ordinal));
|
||||
|
||||
UpdateInUseState(activeRecipeWrap);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.Models.SubstrateParameter;
|
||||
using MaxwellFramework.Core.Common.Command;
|
||||
using Stylet;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class SubstrateHeightMeasureViewModel : PropertyChangedBase
|
||||
{
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
|
||||
public SubstrateHeightMeasureViewModel(HardwareManager hardwareManager)
|
||||
{
|
||||
_hardwareManager = hardwareManager;
|
||||
AddPointCmd = new DelegateCommand(AddPoint);
|
||||
DeletePointCmd = new DelegateCommand(DeletePoint);
|
||||
TeachPointCmd = new DelegateCommand(TeachPoint);
|
||||
ApplyOffsetToAllCmd = new DelegateCommand(ApplyOffsetToAll);
|
||||
}
|
||||
|
||||
private SubstrateRecipe _substrateRecipe;
|
||||
public SubstrateRecipe SubstrateRecipe
|
||||
{
|
||||
get { return _substrateRecipe; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _substrateRecipe, value))
|
||||
{
|
||||
NotifyOfPropertyChange(() => HeightMeasureSetting);
|
||||
NotifyOfPropertyChange(() => CommonOffsetCompensation);
|
||||
NotifyOfPropertyChange(() => Points);
|
||||
NotifyOfPropertyChange(() => Mode);
|
||||
NotifyOfPropertyChange(() => IsTeachMode);
|
||||
NotifyOfPropertyChange(() => IsRowColumnMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SubstrateHeightMeasureSetting HeightMeasureSetting => SubstrateRecipe?.HeightMeasureSetting;
|
||||
|
||||
public SubstrateHeightMeasureMode Mode
|
||||
{
|
||||
get { return HeightMeasureSetting == null ? SubstrateHeightMeasureMode.StandardTeachPosition : HeightMeasureSetting.Mode; }
|
||||
set
|
||||
{
|
||||
if (HeightMeasureSetting == null || HeightMeasureSetting.Mode == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HeightMeasureSetting.Mode = value;
|
||||
NotifyOfPropertyChange(() => Mode);
|
||||
NotifyOfPropertyChange(() => IsTeachMode);
|
||||
NotifyOfPropertyChange(() => IsRowColumnMode);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTeachMode
|
||||
{
|
||||
get { return Mode == SubstrateHeightMeasureMode.StandardTeachPosition; }
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
Mode = SubstrateHeightMeasureMode.StandardTeachPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRowColumnMode
|
||||
{
|
||||
get { return Mode == SubstrateHeightMeasureMode.RowColumnOffset; }
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
Mode = SubstrateHeightMeasureMode.RowColumnOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public System.Collections.ObjectModel.ObservableCollection<SubstrateHeightMeasurePoint> Points => HeightMeasureSetting?.Points;
|
||||
public MPoint CommonOffsetCompensation => HeightMeasureSetting?.CommonOffsetCompensation;
|
||||
|
||||
private SubstrateHeightMeasurePoint _selectedPoint;
|
||||
public SubstrateHeightMeasurePoint SelectedPoint
|
||||
{
|
||||
get { return _selectedPoint; }
|
||||
set { SetAndNotify(ref _selectedPoint, value); }
|
||||
}
|
||||
|
||||
public ICommand AddPointCmd { get; }
|
||||
public ICommand DeletePointCmd { get; }
|
||||
public ICommand TeachPointCmd { get; }
|
||||
public ICommand ApplyOffsetToAllCmd { get; }
|
||||
|
||||
private void ApplyOffsetToAll(object obj)
|
||||
{
|
||||
if (Points == null || CommonOffsetCompensation == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var point in Points)
|
||||
{
|
||||
if (point.OffsetCompensation == null)
|
||||
{
|
||||
point.OffsetCompensation = new MPoint();
|
||||
}
|
||||
|
||||
point.OffsetCompensation.X = CommonOffsetCompensation.X;
|
||||
point.OffsetCompensation.Y = CommonOffsetCompensation.Y;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPoint(object obj)
|
||||
{
|
||||
if (Points == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var point = new SubstrateHeightMeasurePoint
|
||||
{
|
||||
PointName = $"Point{Points.Count + 1}",
|
||||
TeachPosition = new MPoint(),
|
||||
OffsetCompensation = new MPoint()
|
||||
};
|
||||
Points.Add(point);
|
||||
SelectedPoint = point;
|
||||
}
|
||||
|
||||
private void DeletePoint(object obj)
|
||||
{
|
||||
if (Points == null || SelectedPoint == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Points.Remove(SelectedPoint);
|
||||
SelectedPoint = Points.LastOrDefault();
|
||||
}
|
||||
|
||||
private void TeachPoint(object obj)
|
||||
{
|
||||
if (SelectedPoint == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var device = _hardwareManager.CameraAxisManager.TopCameraAxisDevices.FirstOrDefault();
|
||||
if (device == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (SelectedPoint.TeachPosition == null)
|
||||
{
|
||||
SelectedPoint.TeachPosition = new MPoint();
|
||||
}
|
||||
|
||||
if (device.AxisX != null)
|
||||
{
|
||||
SelectedPoint.TeachPosition.X = device.AxisX.State.ActualPos;
|
||||
}
|
||||
|
||||
if (device.AxisY != null)
|
||||
{
|
||||
SelectedPoint.TeachPosition.Y = device.AxisY.State.ActualPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Filewritable;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.Services;
|
||||
using MaxwellFramework.Core.Common.Command;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stylet;
|
||||
using StyletIoC;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class SubstrateRecipeViewModel : RecipeViewModelBase<SubstrateRecipe>, IHandle<WaferNameChangedEventArgs>
|
||||
{
|
||||
private readonly RecipeWrapManager _wrapManager;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
private SubstrateRecipe _substrateRecipe;
|
||||
public SubstrateRecipe SubstrateRecipe
|
||||
{
|
||||
get { return _substrateRecipe; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _substrateRecipe, value))
|
||||
{
|
||||
if (SubstrateTeachViewModel != null)
|
||||
{
|
||||
SubstrateTeachViewModel.SubstrateRecipe = value;
|
||||
}
|
||||
|
||||
if (SubstrateHeightMeasureViewModel != null)
|
||||
{
|
||||
SubstrateHeightMeasureViewModel.SubstrateRecipe = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override SubstrateRecipe CurrentRecipe
|
||||
{
|
||||
get => SubstrateRecipe;
|
||||
set => SubstrateRecipe = value;
|
||||
}
|
||||
|
||||
protected override string RecipeFolderPath => Paths.SubstrateRecipe;
|
||||
protected override string EmptyRecipeDisplayName => "当前基板配方";
|
||||
|
||||
protected override string GetActiveRecipeName()
|
||||
{
|
||||
return RecipeManager.CurrentSubstrateRecipe?.RecipeName;
|
||||
}
|
||||
|
||||
protected override void SwitchActiveRecipe(string recipeName)
|
||||
{
|
||||
RecipeManager.SwitchSubstrateRecipe(recipeName);
|
||||
}
|
||||
|
||||
protected override void ClearActiveRecipe()
|
||||
{
|
||||
RecipeManager.ClearSubstrateRecipe();
|
||||
}
|
||||
|
||||
private int _selectedTabIndex;
|
||||
/// <summary>
|
||||
/// 当前选中的 Tab 索引:0=基础设置(显示配方列表),1=参数示教(隐藏配方列表)
|
||||
/// </summary>
|
||||
public int SelectedTabIndex
|
||||
{
|
||||
get => _selectedTabIndex;
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _selectedTabIndex, value))
|
||||
HideRecipePanel = value != 0;
|
||||
}
|
||||
}
|
||||
|
||||
private SubstrateTeachViewModel _substrateTeachViewModel;
|
||||
[Inject]
|
||||
public SubstrateTeachViewModel SubstrateTeachViewModel
|
||||
{
|
||||
get { return _substrateTeachViewModel; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _substrateTeachViewModel, value) && value != null)
|
||||
{
|
||||
value.SubstrateRecipe = SubstrateRecipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SubstrateHeightMeasureViewModel _substrateHeightMeasureViewModel;
|
||||
[Inject]
|
||||
public SubstrateHeightMeasureViewModel SubstrateHeightMeasureViewModel
|
||||
{
|
||||
get { return _substrateHeightMeasureViewModel; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _substrateHeightMeasureViewModel, value) && value != null)
|
||||
{
|
||||
value.SubstrateRecipe = SubstrateRecipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<RecipeWrap> WaferRecipes => _wrapManager.WaferRecipeWraps;
|
||||
|
||||
public SubstrateRecipeViewModel(RecipeWrapManager wrapManager, IEventAggregator eventAggregator, RecipeManager recipeManager)
|
||||
: base(recipeManager)
|
||||
{
|
||||
_wrapManager = wrapManager;
|
||||
RecipeWraps = wrapManager.SubstrateRecipeWraps;
|
||||
RegisterButtonGroupEvents();
|
||||
_eventAggregator = eventAggregator;
|
||||
_eventAggregator.Unsubscribe(this);
|
||||
_eventAggregator.Subscribe(this);
|
||||
|
||||
DeleteSelectWaferInfoCmd = new DelegateCommand(DeleteSelectWaferInfo);
|
||||
}
|
||||
|
||||
#region//Commands
|
||||
public ICommand DeleteSelectWaferInfoCmd { get; private set; }
|
||||
#endregion
|
||||
|
||||
protected override string GetUnsavedChangesMessage()
|
||||
{
|
||||
var differenceSummary = GetRecipeDifferenceSummary();
|
||||
return string.IsNullOrWhiteSpace(differenceSummary)
|
||||
? $"配方“{CurrentRecipeDisplayName}”有未保存修改,是否先保存?"
|
||||
: $"配方“{CurrentRecipeDisplayName}”有未保存修改:\r\n{differenceSummary}\r\n是否先保存?";
|
||||
}
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
InitializeSelection(RecipeManager.CurrentSubstrateRecipe?.RecipeName);
|
||||
}
|
||||
|
||||
protected override void OnRecipeLoaded(SubstrateRecipe recipe, RecipeWrap recipeWrap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void AfterDeleteCurrentRecipe(string deletedRecipeName)
|
||||
{
|
||||
RecipeManager.DeleteSubstrateRecipe(deletedRecipeName);
|
||||
_eventAggregator.Publish(new SubstrateDeletedEventArgs
|
||||
{
|
||||
DeletedName = deletedRecipeName,
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnCopyRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
base.OnCopyRecipe(sender, eventArgs);
|
||||
}
|
||||
|
||||
public override void OnImportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnExportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnReNameRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeRenameEventArgs args)
|
||||
{
|
||||
if (TryRenameRecipe(args))
|
||||
{
|
||||
_eventAggregator.Publish(new SubstrateNameChangedEventArgs
|
||||
{
|
||||
OldName = args.OldRecipeName,
|
||||
NewName = args.NewRecipeName,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(WaferNameChangedEventArgs message)
|
||||
{
|
||||
}
|
||||
|
||||
private string GetRecipeDifferenceSummary()
|
||||
{
|
||||
if (SubstrateRecipe == null)
|
||||
return string.Empty;
|
||||
|
||||
var path = System.IO.Path.Combine(SubstrateRecipe.Dir ?? string.Empty, SubstrateRecipe.FileName ?? string.Empty);
|
||||
if (!System.IO.File.Exists(path))
|
||||
return "1. 当前配方文件尚未保存到磁盘";
|
||||
|
||||
var currentSnapshot = SubstrateRecipe.CreateSnapshot();
|
||||
if (string.Equals(LastSavedSnapshot ?? string.Empty, currentSnapshot, StringComparison.Ordinal))
|
||||
return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var originalToken = JToken.Parse(LastSavedSnapshot ?? string.Empty);
|
||||
var currentToken = JToken.Parse(currentSnapshot);
|
||||
var differences = new List<string>();
|
||||
CollectDifferences(originalToken, currentToken, string.Empty, differences, 6);
|
||||
|
||||
if (differences.Count == 0)
|
||||
return "1. 当前配方内容与上次保存快照不一致";
|
||||
|
||||
return string.Join("\r\n", differences);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "1. 当前配方内容与上次保存快照不一致";
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectDifferences(JToken originalToken, JToken currentToken, string path, IList<string> differences, int maxCount)
|
||||
{
|
||||
if (differences.Count >= maxCount)
|
||||
return;
|
||||
|
||||
if (JToken.DeepEquals(originalToken, currentToken))
|
||||
return;
|
||||
|
||||
if (originalToken == null || currentToken == null)
|
||||
{
|
||||
differences.Add($"{differences.Count + 1}. {FormatDifference(path, originalToken, currentToken)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (originalToken.Type == JTokenType.Object && currentToken.Type == JTokenType.Object)
|
||||
{
|
||||
var originalObject = (JObject)originalToken;
|
||||
var currentObject = (JObject)currentToken;
|
||||
var propertyNames = originalObject.Properties().Select(p => p.Name)
|
||||
.Union(currentObject.Properties().Select(p => p.Name))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
foreach (var propertyName in propertyNames)
|
||||
{
|
||||
var nextPath = string.IsNullOrWhiteSpace(path) ? propertyName : $"{path}.{propertyName}";
|
||||
CollectDifferences(originalObject[propertyName], currentObject[propertyName], nextPath, differences, maxCount);
|
||||
if (differences.Count >= maxCount)
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (originalToken.Type == JTokenType.Array && currentToken.Type == JTokenType.Array)
|
||||
{
|
||||
var originalArray = (JArray)originalToken;
|
||||
var currentArray = (JArray)currentToken;
|
||||
var maxLength = Math.Max(originalArray.Count, currentArray.Count);
|
||||
|
||||
for (var i = 0; i < maxLength; i++)
|
||||
{
|
||||
var nextPath = $"{path}[{i}]";
|
||||
var originalItem = i < originalArray.Count ? originalArray[i] : null;
|
||||
var currentItem = i < currentArray.Count ? currentArray[i] : null;
|
||||
CollectDifferences(originalItem, currentItem, nextPath, differences, maxCount);
|
||||
if (differences.Count >= maxCount)
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
differences.Add($"{differences.Count + 1}. {FormatDifference(path, originalToken, currentToken)}");
|
||||
}
|
||||
|
||||
private string FormatDifference(string path, JToken originalToken, JToken currentToken)
|
||||
{
|
||||
var displayPath = string.IsNullOrWhiteSpace(path) ? "配方根节点" : path;
|
||||
return $"{displayPath}:{FormatTokenValue(originalToken)} -> {FormatTokenValue(currentToken)}";
|
||||
}
|
||||
|
||||
private string FormatTokenValue(JToken token)
|
||||
{
|
||||
if (token == null || token.Type == JTokenType.Null || token.Type == JTokenType.Undefined)
|
||||
return "空";
|
||||
|
||||
var value = token.ToString(Newtonsoft.Json.Formatting.None);
|
||||
if (value.Length > 80)
|
||||
return value.Substring(0, 80) + "...";
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
#region//Methods
|
||||
public void AddWaferSelectInfo()
|
||||
{
|
||||
if (SubstrateRecipe == null)
|
||||
{
|
||||
MwMessageBox.Show("请先选择或创建基板配方");
|
||||
return;
|
||||
}
|
||||
SubstrateRecipe.WaferSelectInfos.Add(new Models.SubstrateParameter.WaferSelectInfo());
|
||||
}
|
||||
|
||||
private void DeleteSelectWaferInfo(object obj)
|
||||
{
|
||||
if (obj is Models.SubstrateParameter.WaferSelectInfo info)
|
||||
{
|
||||
SubstrateRecipe.WaferSelectInfos.Remove(info);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Recipe.Models;
|
||||
using MainShell.Recipe.Services;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class SubstrateTeachViewModel : CameraBaseViewModel
|
||||
{
|
||||
|
||||
private SubstrateRecipe _substrateRecipe;
|
||||
public SubstrateRecipe SubstrateRecipe
|
||||
{
|
||||
get { return _substrateRecipe; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _substrateRecipe, value))
|
||||
{
|
||||
if (MarkTeachViewModel != null)
|
||||
{
|
||||
MarkTeachViewModel.SubstrateRecipe = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MarkTeachViewModel _markTeachViewModel;
|
||||
public MarkTeachViewModel MarkTeachViewModel
|
||||
{
|
||||
get { return _markTeachViewModel; }
|
||||
set { SetAndNotify(ref _markTeachViewModel, value); }
|
||||
}
|
||||
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly IWindowManager _windowManager;
|
||||
public SubstrateTeachViewModel(HardwareManager hardwareManager, IWindowManager windowManager)
|
||||
{
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
_windowManager = windowManager ?? throw new ArgumentNullException(nameof(windowManager));
|
||||
_cameraAxisViewModel = IoC.Get<Common.Display.ViewModel.CameraAxisViewModel>();
|
||||
MarkTeachViewModel = new MarkTeachViewModel(hardwareManager, _windowManager);
|
||||
_cameraAxisViewModel.CameraAxisDevices.HardwareDeviceList = hardwareManager.CameraAxisManager.TopCameraAxisDevices;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
using MainShell.Filewritable;
|
||||
using MainShell.Models;
|
||||
using MainShell.Recipe.Models;
|
||||
using Stylet;
|
||||
using StyletIoC;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class WaferRecipeViewModel : RecipeViewModelBase<WaferRecipe>, IHandle<ProcessNameChangedEventArgs>
|
||||
{
|
||||
private WaferRecipe _waferRecipe;
|
||||
public WaferRecipe WaferRecipe
|
||||
{
|
||||
get { return _waferRecipe; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _waferRecipe, value))
|
||||
{
|
||||
if (WaferTeachViewModel != null)
|
||||
{
|
||||
WaferTeachViewModel.WaferRecipe = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override WaferRecipe CurrentRecipe
|
||||
{
|
||||
get => WaferRecipe;
|
||||
set => WaferRecipe = value;
|
||||
}
|
||||
|
||||
protected override string RecipeFolderPath => Paths.WaferRecipe;
|
||||
protected override string EmptyRecipeDisplayName => "当前芯片配方";
|
||||
|
||||
protected override string GetActiveRecipeName()
|
||||
{
|
||||
return RecipeManager.CurrentWaferRecipe?.RecipeName;
|
||||
}
|
||||
|
||||
private bool _skipProcessSelectionWriteBack;
|
||||
private int _selectedTabIndex;
|
||||
|
||||
public int SelectedTabIndex
|
||||
{
|
||||
get { return _selectedTabIndex; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _selectedTabIndex, value))
|
||||
{
|
||||
HideRecipePanel = value != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private WaferTeachViewModel _waferTeachViewModel;
|
||||
[Inject]
|
||||
public WaferTeachViewModel WaferTeachViewModel
|
||||
{
|
||||
get { return _waferTeachViewModel; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _waferTeachViewModel, value) && value != null)
|
||||
{
|
||||
value.WaferRecipe = WaferRecipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RecipeWrap _selectedProcessRecipeWrap;
|
||||
public RecipeWrap SelectedProcessRecipeWrap
|
||||
{
|
||||
get { return _selectedProcessRecipeWrap; }
|
||||
set
|
||||
{
|
||||
if (SetAndNotify(ref _selectedProcessRecipeWrap, value))
|
||||
{
|
||||
if (!_skipProcessSelectionWriteBack && WaferRecipe != null)
|
||||
{
|
||||
WaferRecipe.ProcessRecipeName = value != null ? value.RecipeName : string.Empty;
|
||||
NotifyOfPropertyChange(() => HasUnsavedChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<RecipeWrap> ProcessRecipes => _wrapManager.ProcessRecipeWraps;
|
||||
|
||||
private readonly RecipeWrapManager _wrapManager;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
|
||||
public WaferRecipeViewModel(RecipeWrapManager wrapManager, IEventAggregator eventAggregator, RecipeManager recipeManager)
|
||||
: base(recipeManager)
|
||||
{
|
||||
_wrapManager = wrapManager;
|
||||
_eventAggregator = eventAggregator;
|
||||
RegisterButtonGroupEvents();
|
||||
RecipeWraps = wrapManager.WaferRecipeWraps;
|
||||
|
||||
_eventAggregator.Unsubscribe(this);
|
||||
_eventAggregator.Subscribe(this);
|
||||
}
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
InitializeSelection(RecipeManager.CurrentWaferRecipe?.RecipeName);
|
||||
SelectedTabIndex = HideRecipePanel ? 1 : 0;
|
||||
}
|
||||
|
||||
protected override void SwitchActiveRecipe(string recipeName)
|
||||
{
|
||||
RecipeManager.SwitchWaferRecipe(recipeName);
|
||||
}
|
||||
|
||||
protected override void ClearActiveRecipe()
|
||||
{
|
||||
RecipeManager.ClearWaferRecipe();
|
||||
}
|
||||
|
||||
protected override void OnRecipeLoaded(WaferRecipe recipe, RecipeWrap recipeWrap)
|
||||
{
|
||||
SyncProcessSelectionFromWaferRecipe();
|
||||
}
|
||||
|
||||
protected override void AfterDeleteCurrentRecipe(string deletedRecipeName)
|
||||
{
|
||||
_eventAggregator.Publish(new WaferDeletedEventArgs
|
||||
{
|
||||
DeletedName = deletedRecipeName,
|
||||
});
|
||||
}
|
||||
|
||||
protected override void AfterRecipeRenamed(string oldRecipeName, string newRecipeName)
|
||||
{
|
||||
_eventAggregator.Publish(new WaferNameChangedEventArgs
|
||||
{
|
||||
OldName = oldRecipeName,
|
||||
NewName = newRecipeName,
|
||||
});
|
||||
|
||||
SyncProcessSelectionFromWaferRecipe();
|
||||
}
|
||||
|
||||
public override void OnCopyRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
base.OnCopyRecipe(sender, eventArgs);
|
||||
}
|
||||
|
||||
public override void OnImportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnExportRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeEventArgs args)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnReNameRecipe(object sender, EventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs is RecipeRenameEventArgs args)
|
||||
{
|
||||
TryRenameRecipe(args);
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(ProcessNameChangedEventArgs message)
|
||||
{
|
||||
if (message == null || string.IsNullOrWhiteSpace(message.OldName) || string.IsNullOrWhiteSpace(message.NewName))
|
||||
return;
|
||||
|
||||
foreach (RecipeWrap wrap in RecipeWraps)
|
||||
{
|
||||
WaferRecipe recipe = new WaferRecipe
|
||||
{
|
||||
RecipeName = wrap.RecipeName
|
||||
};
|
||||
recipe.Read();
|
||||
|
||||
if (string.Equals(recipe.ProcessRecipeName, message.OldName, StringComparison.Ordinal))
|
||||
{
|
||||
recipe.ProcessRecipeName = message.NewName;
|
||||
recipe.Write();
|
||||
}
|
||||
}
|
||||
|
||||
if (WaferRecipe != null && string.Equals(WaferRecipe.ProcessRecipeName, message.OldName, StringComparison.Ordinal))
|
||||
{
|
||||
WaferRecipe.ProcessRecipeName = message.NewName;
|
||||
CaptureSavedSnapshot();
|
||||
}
|
||||
|
||||
SyncProcessSelectionFromWaferRecipe();
|
||||
}
|
||||
|
||||
private void SyncProcessSelectionFromWaferRecipe()
|
||||
{
|
||||
if (WaferRecipe == null || ProcessRecipes == null)
|
||||
{
|
||||
SetSelectedProcessRecipeWrapInternal(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(WaferRecipe.ProcessRecipeName))
|
||||
{
|
||||
SetSelectedProcessRecipeWrapInternal(null);
|
||||
return;
|
||||
}
|
||||
|
||||
RecipeWrap processWrap = ProcessRecipes.FirstOrDefault(p => p.RecipeName == WaferRecipe.ProcessRecipeName);
|
||||
if (processWrap != null)
|
||||
{
|
||||
SetSelectedProcessRecipeWrapInternal(processWrap);
|
||||
return;
|
||||
}
|
||||
|
||||
SetSelectedProcessRecipeWrapInternal(null);
|
||||
}
|
||||
|
||||
private void SetSelectedProcessRecipeWrapInternal(RecipeWrap recipeWrap)
|
||||
{
|
||||
try
|
||||
{
|
||||
_skipProcessSelectionWriteBack = true;
|
||||
SelectedProcessRecipeWrap = recipeWrap;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_skipProcessSelectionWriteBack = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void MakeTemplate()
|
||||
{
|
||||
if (WaferTeachViewModel != null)
|
||||
{
|
||||
WaferTeachViewModel.MakeTemplate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using MainShell.Common;
|
||||
using MainShell.Common.Display.ViewModel;
|
||||
using MainShell.Common.ViewModel;
|
||||
using MainShell.Common.VisionTemple.ViewModel;
|
||||
using MainShell.Hardware;
|
||||
using MainShell.Log;
|
||||
using MainShell.Models;
|
||||
using MainShell.Motion;
|
||||
using MainShell.Recipe.Models;
|
||||
using MwFramework.Device;
|
||||
using MwFramework.ManagerService;
|
||||
using Stylet;
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace MainShell.Recipe.ViewModel
|
||||
{
|
||||
public class WaferTeachViewModel : CameraBaseViewModel
|
||||
{
|
||||
private WaferRecipe _waferRecipe;
|
||||
private readonly HardwareManager _hardwareManager;
|
||||
private readonly IWindowManager _windowManager;
|
||||
private readonly SafeAxisMotion _safeAxisMotion;
|
||||
private readonly VisionTempleWindowViewModel _visionTempleWindowViewModel;
|
||||
|
||||
public WaferRecipe WaferRecipe
|
||||
{
|
||||
get { return _waferRecipe; }
|
||||
set { SetAndNotify(ref _waferRecipe, value); }
|
||||
}
|
||||
|
||||
public WaferTeachViewModel(
|
||||
HardwareManager hardwareManager,
|
||||
IWindowManager windowManager,
|
||||
SafeAxisMotion safeAxisMotion,
|
||||
VisionTempleWindowViewModel visionTempleWindowViewModel,
|
||||
CameraAxisViewModel cameraAxisViewModel)
|
||||
{
|
||||
_hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
|
||||
_windowManager = windowManager ?? throw new ArgumentNullException(nameof(windowManager));
|
||||
_safeAxisMotion = safeAxisMotion ?? throw new ArgumentNullException(nameof(safeAxisMotion));
|
||||
_visionTempleWindowViewModel = visionTempleWindowViewModel ?? throw new ArgumentNullException(nameof(visionTempleWindowViewModel));
|
||||
CameraAxisViewModel = cameraAxisViewModel ?? throw new ArgumentNullException(nameof(cameraAxisViewModel));
|
||||
CameraAxisViewModel.CameraAxisDevices.HardwareDeviceList = _hardwareManager.CameraAxisManager.TopCameraWsAxisDevices;
|
||||
}
|
||||
|
||||
public void TeachStartPoint()
|
||||
{
|
||||
try
|
||||
{
|
||||
IAxis axisX;
|
||||
IAxis axisY;
|
||||
if (!TryGetScanAxes(out axisX, out axisY))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WaferRecipe.ScanSettings.StartPointX = GetAxisPosition(axisX);
|
||||
WaferRecipe.ScanSettings.StartPointY = GetAxisPosition(axisY);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.LogSysError(ex);
|
||||
ShowOperationFailed(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveToStartPoint()
|
||||
{
|
||||
try
|
||||
{
|
||||
IAxis axisX;
|
||||
IAxis axisY;
|
||||
if (!TryGetScanAxes(out axisX, out axisY))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_safeAxisMotion.SafeMove(
|
||||
MotionMoveRequest.ForAxis(axisX, WaferRecipe.ScanSettings.StartPointX),
|
||||
MotionMoveRequest.ForAxis(axisY, WaferRecipe.ScanSettings.StartPointY));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.LogSysError(ex);
|
||||
ShowOperationFailed(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenVisionParameterSetting()
|
||||
{
|
||||
CameraConfig cameraConfig = new CameraConfig();
|
||||
UpCamLightConfig lightConfig = new UpCamLightConfig();
|
||||
CameraSettingsViewModel settingsViewModel = new CameraSettingsViewModel(_windowManager, _hardwareManager);
|
||||
settingsViewModel.Initialize(cameraConfig, lightConfig, CameraType.TopWsCamera);
|
||||
_windowManager.ShowDialog(settingsViewModel);
|
||||
}
|
||||
|
||||
public void MakeTemplate()
|
||||
{
|
||||
try
|
||||
{
|
||||
_visionTempleWindowViewModel.ShowWindow();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.LogSysError(ex);
|
||||
LocalizedMessageBox.ShowFormat(
|
||||
MessageKey.OriginCalibOpenVisionTemplateFailed,
|
||||
MessageKey.TitleError,
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error,
|
||||
ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetScanAxes(out IAxis axisX, out IAxis axisY)
|
||||
{
|
||||
axisX = null;
|
||||
axisY = null;
|
||||
|
||||
if (WaferRecipe == null || WaferRecipe.ScanSettings == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CameraAxisViewModel == null || CameraAxisViewModel.CameraAxisDevices == null || CameraAxisViewModel.CameraAxisDevices.SelectedHardwareDevice == null)
|
||||
{
|
||||
LocalizedMessageBox.Show(MessageKey.DeviceNotInitialized, MessageKey.TitleWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
HardwareDevice selectedHardwareDevice = CameraAxisViewModel.CameraAxisDevices.SelectedHardwareDevice;
|
||||
axisX = selectedHardwareDevice.AxisX;
|
||||
axisY = selectedHardwareDevice.AxisY;
|
||||
if (axisX == null || axisY == null)
|
||||
{
|
||||
LocalizedMessageBox.Show(MessageKey.DeviceNotInitialized, MessageKey.TitleWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static double GetAxisPosition(IAxis axis)
|
||||
{
|
||||
if (axis == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(axis));
|
||||
}
|
||||
|
||||
return axis.State != null ? axis.State.ActualPos : axis.GetPositionImmediate();
|
||||
}
|
||||
|
||||
private static void ShowOperationFailed(string message)
|
||||
{
|
||||
string title = LanguageResourceHelper.GetString(MessageKey.TitleError);
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
MwMessageBox.Show(LanguageResourceHelper.GetString(MessageKey.CommonOperationFailed), title, MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
MwMessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user