461 lines
15 KiB
C#
461 lines
15 KiB
C#
|
|
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;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|