添加 MX-PD-盘古 项目文件

将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
Shi.Ji
2026-05-18 11:43:09 +08:00
parent 03632a379d
commit e31d3560bb
739 changed files with 99783 additions and 0 deletions

View File

@@ -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); }
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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()
{
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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>
};
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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>();
}
}
}
}

View File

@@ -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); }
}
}
}

View File

@@ -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()
{
}
}
}

View File

@@ -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()
{
}
}
}

View File

@@ -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()
{
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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); }
}
}
}

View File

@@ -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;
}
}
}
}
}
}
}

View File

@@ -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))
};
}
}
}

View File

@@ -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,
};
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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); }
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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); }
}
}
}

View File

@@ -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()
{
}
}
}

View File

@@ -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()
{
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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();
}
}
}

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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();
}
}
}

View File

@@ -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>

View File

@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace MainShell.Recipe.View
{
/// <summary>
/// PidRecipeEditorView.xaml 的交互逻辑
/// </summary>
public partial class PidRecipeEditorView : UserControl
{
public PidRecipeEditorView()
{
InitializeComponent();
}
}
}

View File

@@ -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>

View File

@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace MainShell.Recipe.View
{
/// <summary>
/// ProcessCompensationView.xaml 的交互逻辑
/// </summary>
public partial class ProcessCompensationView : UserControl
{
public ProcessCompensationView()
{
InitializeComponent();
}
}
}

View File

@@ -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>

View File

@@ -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();
}
}
}

View File

@@ -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="&#xe600;" 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="&#xebdf;" 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="&#xe618;" 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="&#xe630;" 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="&#xe67e;" 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="&#xe60c;" 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="&#xe664;" 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="&#xe667;" 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="&#xec09;" Foreground="#007BFF" FontSize="16" FontFamily="{StaticResource ttfFont}" VerticalAlignment="Center" />
<TextBlock Margin="4,0" Text="{DynamicResource Save}" />
</StackPanel>
</Button>
</StackPanel>
</Border>
</UserControl>

View File

@@ -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();
}
}
}

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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;
}
}
}

View File

@@ -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>

View File

@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace MainShell.Recipe.View
{
public partial class SubstrateHeightMeasureView : UserControl
{
public SubstrateHeightMeasureView()
{
InitializeComponent();
}
}
}

View File

@@ -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>

View File

@@ -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();
}
}
}

View File

@@ -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>

View File

@@ -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();
}
}
}

View File

@@ -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>

View File

@@ -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();
}
}
}

View File

@@ -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>

View File

@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace MainShell.Recipe.View
{
public partial class WaferTeachView : UserControl
{
public WaferTeachView()
{
InitializeComponent();
}
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
}
}

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
}
}