2438 lines
89 KiB
C#
2438 lines
89 KiB
C#
|
|
using Aitex.Core.Common;
|
|||
|
|
using Aitex.Core.RT.DataCenter;
|
|||
|
|
using Aitex.Core.RT.Event;
|
|||
|
|
using Aitex.Core.RT.Key;
|
|||
|
|
using Aitex.Core.RT.Log;
|
|||
|
|
using Aitex.Core.RT.OperationCenter;
|
|||
|
|
using Aitex.Core.RT.Routine;
|
|||
|
|
using Aitex.Core.RT.SCCore;
|
|||
|
|
using Aitex.Core.Util;
|
|||
|
|
using Aitex.Sorter.Common;
|
|||
|
|
using Kxware.Common;
|
|||
|
|
using MECF.Framework.Common.DBCore;
|
|||
|
|
using MECF.Framework.Common.Equipment;
|
|||
|
|
using MECF.Framework.Common.Gem;
|
|||
|
|
using MECF.Framework.Common.Jobs;
|
|||
|
|
using MECF.Framework.Common.Schedulers;
|
|||
|
|
using MECF.Framework.Common.SubstrateTrackings;
|
|||
|
|
using OpenSEMI.Ctrlib.Controls;
|
|||
|
|
using SicRT.Equipments.Schedulers;
|
|||
|
|
using SicRT.Modules.Schedulers;
|
|||
|
|
using SicRT.Scheduler;
|
|||
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Linq;
|
|||
|
|
|
|||
|
|
|
|||
|
|
namespace SicRT.Modules
|
|||
|
|
{
|
|||
|
|
public partial class AutoTransfer : SchedulerModuleFactory
|
|||
|
|
{
|
|||
|
|
private List<ControlJobInfo> _controlJobList = new();
|
|||
|
|
private List<ProcessJobInfo> _processJobList = new();
|
|||
|
|
|
|||
|
|
private int _maxTrayCount = 4; //可以同时运行的石墨盘总数
|
|||
|
|
|
|||
|
|
private const string LogSource = "Scheduler";
|
|||
|
|
private SchedulerDBCallback _dbCallback = new();
|
|||
|
|
|
|||
|
|
private bool _isCycleMode;
|
|||
|
|
private bool _isATMMode;
|
|||
|
|
private int _cycleSetPoint = 0;
|
|||
|
|
private int _cycledCount = 0;
|
|||
|
|
private int _cycledWafer = 0;
|
|||
|
|
private string _curRecipeName = "";
|
|||
|
|
private string _curSequenceName = "";
|
|||
|
|
|
|||
|
|
private bool _isModuleErrorPrevious;
|
|||
|
|
private bool[] _isPMErrorPrevious = new bool[2];
|
|||
|
|
|
|||
|
|
private double _timeBuffer1 { get; set; }
|
|||
|
|
private double _timeBuffer2 { get; set; }
|
|||
|
|
private double _timeBuffer3 { get; set; }
|
|||
|
|
private double _timeLoad { get; set; }
|
|||
|
|
|
|||
|
|
private const int _bufferFloors = 1;
|
|||
|
|
|
|||
|
|
private Dictionary<string, DateTime> _loadWaferInfo = new();
|
|||
|
|
|
|||
|
|
private Dictionary<string, DateTime> _bufferWaferInfo = new();
|
|||
|
|
|
|||
|
|
private Queue<Action> tmRobotActions = new() { };
|
|||
|
|
|
|||
|
|
private R_TRIG _updateAutoJobLocation = new(); //需要向数据库更新Wafer位置
|
|||
|
|
|
|||
|
|
public AutoTransfer()
|
|||
|
|
{
|
|||
|
|
DATA.Subscribe("Scheduler.IsCycleMode", () => _isCycleMode);
|
|||
|
|
DATA.Subscribe("Scheduler.CycledCount", () => _cycledCount);
|
|||
|
|
DATA.Subscribe("Scheduler.CycleSetPoint", () => _cycleSetPoint);
|
|||
|
|
DATA.Subscribe("Scheduler.RecipeName", () => _curRecipeName);
|
|||
|
|
DATA.Subscribe("Scheduler.SequenceName", () => _curSequenceName);
|
|||
|
|
|
|||
|
|
DATA.Subscribe("Scheduler.TimeBuffer1", () => _timeBuffer1);
|
|||
|
|
DATA.Subscribe("Scheduler.TimeBuffer2", () => _timeBuffer2);
|
|||
|
|
DATA.Subscribe("Scheduler.TimeBuffer3", () => _timeBuffer3);
|
|||
|
|
|
|||
|
|
DATA.Subscribe("Scheduler.TimeLoad", () => _timeLoad);
|
|||
|
|
|
|||
|
|
DATA.Subscribe("LoadLock.LocalJobName", () =>
|
|||
|
|
{
|
|||
|
|
var jb = _controlJobList.Find(x => x.Module == "LoadLock");
|
|||
|
|
if (jb != null)
|
|||
|
|
return jb.Name;
|
|||
|
|
return "";
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
DATA.Subscribe("LoadLock.LocalJobStatus", () =>
|
|||
|
|
{
|
|||
|
|
var jb = _controlJobList.Find(x => x.Module == "LoadLock");
|
|||
|
|
if (jb != null)
|
|||
|
|
return jb.State.ToString();
|
|||
|
|
return "";
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
OP.Subscribe($"{ModuleName.TM}.ResetTask", (string cmd, object[] args) =>
|
|||
|
|
{
|
|||
|
|
_tmRobot.ResetTask();
|
|||
|
|
return true;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
OP.Subscribe($"{ModuleName.EFEM}.ResetTask", (string cmd, object[] args) =>
|
|||
|
|
{
|
|||
|
|
_waferRobot.ResetTask();
|
|||
|
|
_trayRobot.ResetTask();
|
|||
|
|
return true;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
OP.Subscribe($"{ModuleName.WaferRobot}.ResetTask", (string cmd, object[] args) =>
|
|||
|
|
{
|
|||
|
|
_waferRobot.ResetTask();
|
|||
|
|
return true;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
OP.Subscribe($"{ModuleName.TrayRobot}.ResetTask", (string cmd, object[] args) =>
|
|||
|
|
{
|
|||
|
|
_tmRobot.ResetTask();
|
|||
|
|
return true;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
tmRobotActions.Enqueue(MonitorTmRobotLoadPlaceTask);
|
|||
|
|
tmRobotActions.Enqueue(MonitorTmRobotLoadPickTask);
|
|||
|
|
tmRobotActions.Enqueue(MonitorTmRobotPMPlaceTask);
|
|||
|
|
tmRobotActions.Enqueue(MonitorTmRobotPMPickTask);
|
|||
|
|
tmRobotActions.Enqueue(MonitorTmRobotBufferPlaceTask);
|
|||
|
|
tmRobotActions.Enqueue(MonitorTmRobotBufferPickTask);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool HasJobRunning
|
|||
|
|
{
|
|||
|
|
get { return _controlJobList.Count > 0; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
public void Clear()
|
|||
|
|
{
|
|||
|
|
_tmRobot.ResetTask();
|
|||
|
|
|
|||
|
|
foreach (var pm in _lstPms)
|
|||
|
|
{
|
|||
|
|
pm.ResetTask();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_load.ResetTask();
|
|||
|
|
_buffer.ResetTask();
|
|||
|
|
|
|||
|
|
_controlJobList.Clear();
|
|||
|
|
_processJobList.Clear();
|
|||
|
|
_loadWaferInfo.Clear();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void ResetTask()
|
|||
|
|
{
|
|||
|
|
if (!_load.IsOnline)
|
|||
|
|
{
|
|||
|
|
_load.ResetTask();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!_buffer.IsOnline)
|
|||
|
|
{
|
|||
|
|
_buffer.ResetTask();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!_tmRobot.IsOnline)
|
|||
|
|
{
|
|||
|
|
_tmRobot.ResetTask();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var pm in _lstPms)
|
|||
|
|
{
|
|||
|
|
if (!pm.IsOnline)
|
|||
|
|
{
|
|||
|
|
pm.ResetTask();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#region Job Management
|
|||
|
|
|
|||
|
|
public bool CreateJob(Dictionary<string, object> param)
|
|||
|
|
{
|
|||
|
|
var reason = "";
|
|||
|
|
var slotSequence = (string[])param["SlotSequence"];
|
|||
|
|
var jobId = (string)param["JobId"];
|
|||
|
|
var module = (string)param["Module"];
|
|||
|
|
var lotId = (string)param["LotId"];
|
|||
|
|
|
|||
|
|
////检查Load腔Lock是否锁定
|
|||
|
|
//if(!_load.CheckLocked())
|
|||
|
|
//{
|
|||
|
|
// EV.PostWarningLog(LogSource, $"The Load is Unlocked,do not start");
|
|||
|
|
// return false;
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
//检查所有Module里Tray的数量
|
|||
|
|
if(GetCurrentTrayCount() >= _maxTrayCount)
|
|||
|
|
{
|
|||
|
|
EV.PostWarningLog(LogSource, $"The machine has more than {_maxTrayCount} Tray");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (slotSequence.Length != 1)
|
|||
|
|
{
|
|||
|
|
reason = $"slot sequence parameter not valid, length is {slotSequence.Length}, should be 1";
|
|||
|
|
EV.PostWarningLog(LogSource, reason);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//拿到Recipe和ModuleName
|
|||
|
|
var recipeName = "";
|
|||
|
|
var pmModule = ModuleName.System;
|
|||
|
|
var seq = SequenceInfoHelper.GetInfo(slotSequence[0]);
|
|||
|
|
for (var i = 0; i < seq.Steps.Count; i++)
|
|||
|
|
{
|
|||
|
|
var stepInfo = seq.Steps[i];
|
|||
|
|
foreach (var m in stepInfo.StepModules)
|
|||
|
|
{
|
|||
|
|
if (ModuleHelper.IsPm(m))
|
|||
|
|
{
|
|||
|
|
recipeName = seq.Steps[i].RecipeName;
|
|||
|
|
pmModule = m;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//每个PM腔不能超过2个对应Wafer
|
|||
|
|
if(pmModule != ModuleName.System)
|
|||
|
|
{
|
|||
|
|
if (GetRunWaferCount(pmModule) >= _maxTrayCount/2)
|
|||
|
|
{
|
|||
|
|
EV.PostWarningLog(LogSource, $"Creating the job failed . There are already {_maxTrayCount / 2} job of the same pm in the system.");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (CheckModuleHaveWaferWithNoJob(out reason))
|
|||
|
|
{
|
|||
|
|
EV.PostWarningLog(LogSource, $"{reason}");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!ValidateSequence(slotSequence, out reason))
|
|||
|
|
{
|
|||
|
|
EV.PostWarningLog(LogSource, $"{reason}");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (string.IsNullOrEmpty(jobId))
|
|||
|
|
{
|
|||
|
|
jobId = "CJ_Local_Load_" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!string.IsNullOrEmpty(lotId))
|
|||
|
|
{
|
|||
|
|
if (_controlJobList.Exists(x => x.LotName == lotId))
|
|||
|
|
{
|
|||
|
|
reason = $"LotID : {lotId} already created";
|
|||
|
|
EV.PostWarningLog(LogSource, reason);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var cj = new ControlJobInfo();
|
|||
|
|
cj.Name = jobId;
|
|||
|
|
cj.Module = module;
|
|||
|
|
cj.LotName = lotId;
|
|||
|
|
cj.LotInnerId = Guid.NewGuid();
|
|||
|
|
cj.LotWafers = new List<WaferInfoRt>();
|
|||
|
|
cj.SetState(EnumControlJobState.WaitingForStart);
|
|||
|
|
|
|||
|
|
List<string> sequenceNameList = new List<string>();
|
|||
|
|
|
|||
|
|
string WaferAssociationInfo = $"WaferAssociationInfo({module}):";
|
|||
|
|
for (int i = 0; i < 1; i++)
|
|||
|
|
{
|
|||
|
|
//检查是否设置Sequence
|
|||
|
|
string sequenceName = slotSequence[i];
|
|||
|
|
if (string.IsNullOrEmpty(sequenceName))
|
|||
|
|
{
|
|||
|
|
reason = $"sequence name is empty";
|
|||
|
|
EV.PostWarningLog(LogSource, reason);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//检查是否有Normal Wafer
|
|||
|
|
if (!WaferManager.Instance.CheckWafer(ModuleHelper.Converter(module), i, WaferStatus.Normal))
|
|||
|
|
{
|
|||
|
|
reason = $"{module} has no normal wafer";
|
|||
|
|
EV.PostWarningLog(LogSource, reason);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
WaferInfoRt wafer = WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i);
|
|||
|
|
|
|||
|
|
if (wafer.ProcessState != WaferProcessStatus.Idle)
|
|||
|
|
{
|
|||
|
|
reason = $"specifies wafer: {module} slot {i + 1} process state is not idle";
|
|||
|
|
EV.PostWarningLog(LogSource, reason);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (wafer.ProcessJob != null)
|
|||
|
|
{
|
|||
|
|
reason = $"{module} wafer has processJob";
|
|||
|
|
EV.PostWarningLog(LogSource, reason);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
WaferAssociationInfo = WaferAssociationInfo + string.Format(" slot{0} -- {1};", i + 1, slotSequence[i]);
|
|||
|
|
|
|||
|
|
wafer.NextSequenceStep = 0;
|
|||
|
|
wafer.PPID = sequenceName;
|
|||
|
|
wafer.LotId = lotId;
|
|||
|
|
cj.LotWafers.Add(wafer);
|
|||
|
|
|
|||
|
|
_isATMMode = SC.GetValue<bool>("System.IsATMMode");
|
|||
|
|
_isCycleMode = SC.GetValue<bool>("System.IsCycleMode");
|
|||
|
|
|
|||
|
|
//没有包含sequenceName,需新建
|
|||
|
|
if (!sequenceNameList.Exists(s => s == sequenceName))
|
|||
|
|
{
|
|||
|
|
sequenceNameList.Add(sequenceName);
|
|||
|
|
|
|||
|
|
ProcessJobInfo pj = new ProcessJobInfo();
|
|||
|
|
pj.Name = jobId + "_" + (i + 1);
|
|||
|
|
pj.ControlJobName = cj.Name;
|
|||
|
|
pj.Sequence = SequenceInfoHelper.GetInfo(sequenceName);
|
|||
|
|
pj.SetState(EnumProcessJobState.Queued);
|
|||
|
|
pj.SlotWafers = [Tuple.Create(ModuleHelper.Converter(module), i)];
|
|||
|
|
|
|||
|
|
//不是CycleMode模式,则CycleCount设为1
|
|||
|
|
pj.CycleCount = _isATMMode && _isCycleMode ? SC.GetValue<int>("System.CycleCount") : 1;
|
|||
|
|
pj.CycleTime = 0;
|
|||
|
|
|
|||
|
|
//检查Sequence对应的PM腔状态
|
|||
|
|
if (!CheckPMState(sequenceName))
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
wafer.ProcessJob = pj;
|
|||
|
|
_processJobList.Add(pj);
|
|||
|
|
cj.ProcessJobNameList.Add(pj.Name);
|
|||
|
|
}
|
|||
|
|
//已包含sequenceName
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
ProcessJobInfo pj = _processJobList.Find(p => p.Sequence.Name == sequenceName);
|
|||
|
|
wafer.ProcessJob = pj;
|
|||
|
|
|
|||
|
|
pj.SlotWafers.Add(Tuple.Create(ModuleHelper.Converter(module), i));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_controlJobList.Add(cj);
|
|||
|
|
|
|||
|
|
int totalWafer = 0;
|
|||
|
|
foreach (string pjName in cj.ProcessJobNameList)
|
|||
|
|
{
|
|||
|
|
ProcessJobInfo tempPJ = _processJobList.First(p => p.Name == pjName);
|
|||
|
|
foreach (var pjSlotWafer in tempPJ.SlotWafers)
|
|||
|
|
{
|
|||
|
|
var wafer = WaferManager.Instance.GetWafer(pjSlotWafer.Item1, pjSlotWafer.Item2);
|
|||
|
|
WaferDataRecorder.SetCjInfo(wafer.WaferInnerID.ToString(), cj.InnerId.ToString());
|
|||
|
|
WaferDataRecorder.SetWaferSequence(wafer.WaferInnerID.ToString(), tempPJ.Sequence.Name);
|
|||
|
|
WaferDataRecorder.SetWaferLotId(wafer.WaferInnerID.ToString(), jobId);
|
|||
|
|
WaferManager.Instance.UpdateWaferLotId(pjSlotWafer.Item1, pjSlotWafer.Item2, jobId);
|
|||
|
|
|
|||
|
|
totalWafer++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
CarrierManager.Instance.DeleteCarrier(cj.Module);
|
|||
|
|
CarrierManager.Instance.CreateCarrier(cj.Module);
|
|||
|
|
CarrierManager.Instance.UpdateCarrierId(cj.Module, $"{cj.Module} {DateTime.Now}");
|
|||
|
|
//此carrier为null
|
|||
|
|
CarrierInfo carrier = CarrierManager.Instance.GetCarrier(cj.Module);
|
|||
|
|
JobDataRecorder.StartCJ(cj.InnerId.ToString(), "", cj.Name, cj.Module, cj.Module, totalWafer);
|
|||
|
|
|
|||
|
|
EV.PostInfoLog(LogSource, WaferAssociationInfo);
|
|||
|
|
|
|||
|
|
|
|||
|
|
//保存信息
|
|||
|
|
var waferInfo = WaferManager.Instance.GetWafer(ModuleName.LoadLock, 0);
|
|||
|
|
if (waferInfo != null)
|
|||
|
|
{
|
|||
|
|
waferInfo.LotId = cj.LotName;
|
|||
|
|
if (recipeName.IndexOf(@"\") > 0)
|
|||
|
|
{
|
|||
|
|
recipeName = recipeName.Substring(recipeName.LastIndexOf(@"\") + 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
AutoJobRecorder.Add(waferInfo.WaferID.ToString(), recipeName, cj.LotName, ModuleName.LoadLock.ToString(), GetWaferStatue(waferInfo), cj.Name);
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void StartJob(string jobName)
|
|||
|
|
{
|
|||
|
|
_maxTrayCount = _lstPms.Count * 2;
|
|||
|
|
|
|||
|
|
var cj = _controlJobList.Find(x => x.Name == jobName);
|
|||
|
|
if (cj == null)
|
|||
|
|
{
|
|||
|
|
EV.PostWarningLog(LogSource, $"start job rejected, not found job with id {jobName}");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (cj.State == EnumControlJobState.WaitingForStart)
|
|||
|
|
{
|
|||
|
|
cj.BeginTime = DateTime.Now;
|
|||
|
|
cj.LotInnerId = Guid.NewGuid();
|
|||
|
|
//_dbCallback.LotCreated(cj);
|
|||
|
|
|
|||
|
|
foreach (var pjName in cj.ProcessJobNameList)
|
|||
|
|
{
|
|||
|
|
var pj = _processJobList.Find(x => x.Name == pjName);
|
|||
|
|
if (pj == null)
|
|||
|
|
{
|
|||
|
|
LOG.Error($"Not find pj named {pjName} in {cj.Name}");
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (pj.State == EnumProcessJobState.Queued)
|
|||
|
|
{
|
|||
|
|
ActiveProcessJob(pj);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cj.SetState(EnumControlJobState.Executing);
|
|||
|
|
|
|||
|
|
string squenceID = cj.LotWafers != null ? cj.LotWafers[0].PPID : string.Empty;
|
|||
|
|
//补全路径
|
|||
|
|
squenceID = $"Sequence\\{squenceID}.seq";
|
|||
|
|
GemManager.Instance.Equipment?.TriggerEvent("JobStart", new string[] { "LotID", "SequenceID" }, new object[] { cj.LotName, squenceID });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private string GetWaferStatue(WaferInfoRt wafer)
|
|||
|
|
{
|
|||
|
|
if (wafer.ProcessState == WaferProcessStatus.InProcess)
|
|||
|
|
{
|
|||
|
|
return "InProcess";
|
|||
|
|
}
|
|||
|
|
else if (wafer.ProcessState == WaferProcessStatus.Completed)
|
|||
|
|
{
|
|||
|
|
return "Completed";
|
|||
|
|
}
|
|||
|
|
else if (wafer.ProcessState == WaferProcessStatus.Abort)
|
|||
|
|
{
|
|||
|
|
return "Aborted";
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
return "Idle";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool ValidateSequence(string[] seqs, out string reason)
|
|||
|
|
{
|
|||
|
|
reason = string.Empty;
|
|||
|
|
//bool isAllSequenceNull = true;
|
|||
|
|
//for (int i = 0; i < seqs.Length; i++)
|
|||
|
|
//{
|
|||
|
|
// if (string.IsNullOrEmpty(seqs[i]))
|
|||
|
|
// continue;
|
|||
|
|
// var sequence = SequenceInfoHelper.GetInfo(seqs[i]);
|
|||
|
|
// if ((sequence == null || sequence.Steps == null || sequence.Steps.Count == 0) && !string.IsNullOrEmpty(sequence.Name))
|
|||
|
|
// {
|
|||
|
|
// reason = $"Invalid sequence {seqs[i]}";
|
|||
|
|
// return false;
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// isAllSequenceNull = false;
|
|||
|
|
|
|||
|
|
// if (sequence.Steps != null)
|
|||
|
|
// {
|
|||
|
|
// int currentIndex = 0;
|
|||
|
|
// if (sequence.Steps[currentIndex] == null || sequence.Steps[currentIndex].StepModules.Count <= 0 || sequence.Steps[currentIndex].StepModules[0] != ModuleName.Aligner)
|
|||
|
|
// {
|
|||
|
|
// reason = $"Invalid sequence {seqs[i]}, {currentIndex + 1}st step should be Aligner";
|
|||
|
|
// return false;
|
|||
|
|
// }
|
|||
|
|
// else
|
|||
|
|
// {
|
|||
|
|
// if (sequence.Steps[currentIndex].AlignAngle < 0 || sequence.Steps[currentIndex].AlignAngle > 360)
|
|||
|
|
// {
|
|||
|
|
// reason = $"Invalid sequence {seqs[i]},{currentIndex + 1}st step Aligner angle parameter is not valid";
|
|||
|
|
// return false;
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// currentIndex++;
|
|||
|
|
// if (sequence.Steps[currentIndex] == null || sequence.Steps[currentIndex].StepModules.Count <= 0 || sequence.Steps[currentIndex].StepModules[0] != ModuleName.Load)
|
|||
|
|
// {
|
|||
|
|
// reason = $"Invalid sequence {seqs[i]}, {currentIndex + 1}st step should be Load";
|
|||
|
|
// return false;
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// currentIndex++;
|
|||
|
|
// if (sequence.Steps[currentIndex] == null || sequence.Steps[currentIndex].StepModules.Count <= 0 || sequence.Steps[currentIndex].StepModules[0] != ModuleName.Buffer)
|
|||
|
|
// {
|
|||
|
|
// reason = $"Invalid sequence {seqs[i]}, {currentIndex + 1}st step should be Buffer";
|
|||
|
|
// return false;
|
|||
|
|
// }
|
|||
|
|
// else
|
|||
|
|
// {
|
|||
|
|
// //if (!sequence.Steps[currentIndex].StepParameter.ContainsKey("SlotSelection")
|
|||
|
|
// // || string.IsNullOrEmpty(sequence.Steps[currentIndex].StepParameter["SlotSelection"].ToString())
|
|||
|
|
// // || sequence.Steps[currentIndex].StepParameter["SlotSelection"].ToString().Contains("3"))
|
|||
|
|
// //{
|
|||
|
|
// // reason = $"Invalid sequence {seqs[i]},{currentIndex + 1}st step Buffer SlotSelection parameter is not valid";
|
|||
|
|
// // return false;
|
|||
|
|
// //}
|
|||
|
|
|
|||
|
|
// if (!sequence.Steps[currentIndex].StepParameter.ContainsKey("BufferType") || !sequence.Steps[currentIndex].StepParameter["BufferType"].ToString().Contains("Heat"))
|
|||
|
|
// {
|
|||
|
|
// reason = $"Invalid sequence {seqs[i]},{currentIndex + 1}st step Buffer BufferType parameter is not valid";
|
|||
|
|
// return false;
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
|
|||
|
|
// currentIndex++;
|
|||
|
|
// if (sequence.Steps[currentIndex] == null || sequence.Steps[currentIndex].StepModules.Count <= 0 || sequence.Steps[currentIndex].StepModules.Any(pm => !ModuleHelper.IsPm(pm)))
|
|||
|
|
// {
|
|||
|
|
// reason = $"Invalid sequence {seqs[i]}, {currentIndex + 1}st step should be PM";
|
|||
|
|
// return false;
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// //if (sequence.Steps.Count == 7)
|
|||
|
|
// //{
|
|||
|
|
// // currentIndex++;
|
|||
|
|
// // if (sequence.Steps[currentIndex] == null || sequence.Steps[currentIndex].StepModules.Count <= 0 || sequence.Steps[currentIndex].StepModules[0] != ModuleName.Buffer)
|
|||
|
|
// // {
|
|||
|
|
// // reason = $"Invalid sequence {seqs[i]}, {currentIndex + 1}st step should be Buffer";
|
|||
|
|
// // return false;
|
|||
|
|
// // }
|
|||
|
|
// // else
|
|||
|
|
// // {
|
|||
|
|
// // if (!sequence.Steps[currentIndex].StepParameter.ContainsKey("SlotSelection")
|
|||
|
|
// // || sequence.Steps[currentIndex].StepParameter["SlotSelection"].ToString() != "3")
|
|||
|
|
// // {
|
|||
|
|
// // reason = $"Invalid sequence {seqs[i]},{currentIndex + 1}st step Buffer SlotSelection parameter is not valid";
|
|||
|
|
// // return false;
|
|||
|
|
// // }
|
|||
|
|
|
|||
|
|
// // if (!sequence.Steps[currentIndex].StepParameter.ContainsKey("BufferType") || !sequence.Steps[currentIndex].StepParameter["BufferType"].ToString().Contains("Cooling"))
|
|||
|
|
// // {
|
|||
|
|
// // reason = $"Invalid sequence {seqs[i]},{currentIndex + 1}st step Buffer BufferType parameter is not valid";
|
|||
|
|
// // return false;
|
|||
|
|
// // }
|
|||
|
|
// // }
|
|||
|
|
|
|||
|
|
// //}
|
|||
|
|
|
|||
|
|
// currentIndex++;
|
|||
|
|
// if (sequence.Steps[currentIndex] == null || sequence.Steps[currentIndex].StepModules.Count <= 0 || sequence.Steps[currentIndex].StepModules[0] != ModuleName.UnLoad)
|
|||
|
|
// {
|
|||
|
|
// reason = $"Invalid sequence {seqs[i]}, {currentIndex + 1}st step should be Unload";
|
|||
|
|
// return false;
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// currentIndex++;
|
|||
|
|
// if (sequence.Steps[currentIndex] == null || sequence.Steps[currentIndex].StepModules.Count <= 0 || sequence.Steps[currentIndex].StepModules[0] != ModuleName.Aligner)
|
|||
|
|
// {
|
|||
|
|
// reason = $"Invalid sequence {seqs[i]}, {currentIndex + 1}st step should be Aligner";
|
|||
|
|
// return false;
|
|||
|
|
// }
|
|||
|
|
// else
|
|||
|
|
// {
|
|||
|
|
// if (sequence.Steps[0].AlignAngle < 0 || sequence.Steps[0].AlignAngle > 360)
|
|||
|
|
// {
|
|||
|
|
// reason = $"Invalid sequence {seqs[i]}, Aligner angle parameter is not valid";
|
|||
|
|
// return false;
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
//if (isAllSequenceNull)
|
|||
|
|
//{
|
|||
|
|
// reason = $"Invalid sequence, sequence are all null ";
|
|||
|
|
// return false;
|
|||
|
|
//}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
internal void StopJob(string jobName)
|
|||
|
|
{
|
|||
|
|
//ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == jobName);
|
|||
|
|
//if (cj == null)
|
|||
|
|
//{
|
|||
|
|
// EV.PostWarningLog(LogSource, $"stop job rejected, not found job with id {jobName}");
|
|||
|
|
// return;
|
|||
|
|
//}
|
|||
|
|
foreach (var cj in _controlJobList)
|
|||
|
|
{
|
|||
|
|
foreach (var pj in _processJobList)
|
|||
|
|
{
|
|||
|
|
if (pj.ControlJobName == cj.Name)
|
|||
|
|
{
|
|||
|
|
pj.SetState(EnumProcessJobState.Stopping);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_cycleSetPoint > 0)
|
|||
|
|
{
|
|||
|
|
_cycleSetPoint = _cycledCount;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Abort()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var wafer = WaferManager.Instance.GetWafer(ModuleName.PM1, 0);
|
|||
|
|
if (wafer != null && !string.IsNullOrEmpty(wafer.WaferID))
|
|||
|
|
{
|
|||
|
|
AutoJobRecorder.UpdatePosition(wafer.WaferID.ToString(), ModuleName.PM1.ToString(), "Aborted");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var wafer1 = WaferManager.Instance.GetWafer(ModuleName.TMRobot, 0);
|
|||
|
|
if (wafer1 != null && !string.IsNullOrEmpty(wafer1.WaferID))
|
|||
|
|
{
|
|||
|
|
AutoJobRecorder.UpdatePosition(wafer1.WaferID.ToString(), ModuleName.TM.ToString(), "Aborted");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var wafer2 = WaferManager.Instance.GetWafer(ModuleName.Buffer, 0);
|
|||
|
|
if (wafer2 != null && !string.IsNullOrEmpty(wafer2.WaferID))
|
|||
|
|
{
|
|||
|
|
AutoJobRecorder.UpdatePosition(wafer2.WaferID.ToString(), ModuleName.Buffer.ToString(), "Aborted");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var wafer3 = WaferManager.Instance.GetWafer(ModuleName.LoadLock, 0);
|
|||
|
|
if (wafer3 != null && !string.IsNullOrEmpty(wafer3.WaferID))
|
|||
|
|
{
|
|||
|
|
AutoJobRecorder.UpdatePosition(wafer3.WaferID.ToString(), ModuleName.LoadLock.ToString(), "Aborted");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var cjList = _controlJobList.FindAll(c => c.State == EnumControlJobState.Executing);
|
|||
|
|
if (cjList != null)
|
|||
|
|
{
|
|||
|
|
foreach (var cj in cjList)
|
|||
|
|
{
|
|||
|
|
string squenceID = cj.LotWafers != null ? cj.LotWafers[0].PPID : string.Empty;
|
|||
|
|
//补全路径
|
|||
|
|
squenceID = $"Sequence\\{squenceID}.seq";
|
|||
|
|
GemManager.Instance.Equipment?.TriggerEvent("JobAbort", new string[] { "LotID", "SequenceID" }, new object[] { cj.LotName, squenceID });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LOG.Error(ex.Message, ex);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
internal void AbortJob(string jobName)
|
|||
|
|
{
|
|||
|
|
var cj = _controlJobList.Find(x => x.Name == jobName);
|
|||
|
|
if (cj == null)
|
|||
|
|
{
|
|||
|
|
EV.PostWarningLog(LogSource, $"abort job rejected, not found job with id {jobName}");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var unprocessed_cj = 0;
|
|||
|
|
var aborted_cj = 0;
|
|||
|
|
|
|||
|
|
var pjAbortList = new List<ProcessJobInfo>();
|
|||
|
|
foreach (var pj in _processJobList)
|
|||
|
|
{
|
|||
|
|
if (pj.ControlJobName == cj.Name)
|
|||
|
|
{
|
|||
|
|
pj.SetState(EnumProcessJobState.Aborting);
|
|||
|
|
pjAbortList.Add(pj);
|
|||
|
|
|
|||
|
|
var unprocessed = 0;
|
|||
|
|
var aborted = 0;
|
|||
|
|
var wafers = WaferManager.Instance.GetWaferByProcessJob(pj.Name);
|
|||
|
|
foreach (var waferInfo in wafers)
|
|||
|
|
{
|
|||
|
|
waferInfo.ProcessJob = null;
|
|||
|
|
waferInfo.NextSequenceStep = 0;
|
|||
|
|
if (waferInfo.ProcessState != WaferProcessStatus.Completed)
|
|||
|
|
{
|
|||
|
|
unprocessed++;
|
|||
|
|
unprocessed_cj++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
JobDataRecorder.EndPJ(pj.InnerId.ToString(), aborted, unprocessed);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var pj in pjAbortList)
|
|||
|
|
{
|
|||
|
|
_processJobList.Remove(pj);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_controlJobList.Remove(cj);
|
|||
|
|
|
|||
|
|
_dbCallback.LotFinished(cj);
|
|||
|
|
JobDataRecorder.EndCJ(cj.InnerId.ToString(), aborted_cj, unprocessed_cj);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void AbortSingleJob(string jobName, string waferID)
|
|||
|
|
{
|
|||
|
|
var cj = _controlJobList.Find(x => x.Name.ToString() == jobName);
|
|||
|
|
if (cj == null)
|
|||
|
|
{
|
|||
|
|
EV.PostWarningLog(LogSource, $"abort job rejected, not found job with id {jobName}");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var pjAbortList = new List<ProcessJobInfo>();
|
|||
|
|
foreach (var pj in _processJobList)
|
|||
|
|
{
|
|||
|
|
if (pj.ControlJobName == cj.Name)
|
|||
|
|
{
|
|||
|
|
var wafers = WaferManager.Instance.GetWaferByProcessJob(pj.Name);
|
|||
|
|
foreach (var waferInfo in wafers)
|
|||
|
|
{
|
|||
|
|
SkipWaferProcessStep(waferInfo);
|
|||
|
|
waferInfo.WaferStatus = WaferStatus.Aborted;
|
|||
|
|
waferInfo.ProcessState = WaferProcessStatus.Abort;
|
|||
|
|
|
|||
|
|
AutoJobRecorder.UpdateStatus(waferInfo.WaferID.ToString(),"Aborted");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void ResumeJob(string jobName)
|
|||
|
|
{
|
|||
|
|
//ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == jobName);
|
|||
|
|
//if (cj == null)
|
|||
|
|
//{
|
|||
|
|
// EV.PostWarningLog(LogSource, $"resume job rejected, not found job with id {jobName}");
|
|||
|
|
// return;
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
foreach (var cj in _controlJobList)
|
|||
|
|
{
|
|||
|
|
if (cj.State == EnumControlJobState.Paused)
|
|||
|
|
{
|
|||
|
|
cj.SetState(EnumControlJobState.Executing);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
internal void PauseJob(string jobName)
|
|||
|
|
{
|
|||
|
|
//ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == jobName);
|
|||
|
|
//if (cj == null)
|
|||
|
|
//{
|
|||
|
|
// EV.PostWarningLog(LogSource, $"pause job rejected, not found job with id {jobName}");
|
|||
|
|
// return;
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
foreach (var cj in _controlJobList)
|
|||
|
|
{
|
|||
|
|
if (cj.State == EnumControlJobState.Executing)
|
|||
|
|
{
|
|||
|
|
cj.SetState(EnumControlJobState.Paused);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
internal bool CheckPMState(string sequence)
|
|||
|
|
{
|
|||
|
|
var seqInfo = SequenceInfoHelper.GetInfo(sequence);
|
|||
|
|
|
|||
|
|
if (seqInfo.Name != "")
|
|||
|
|
{
|
|||
|
|
foreach (var step in seqInfo.Steps)
|
|||
|
|
{
|
|||
|
|
foreach (var moduleName in step.StepModules)
|
|||
|
|
{
|
|||
|
|
if (ModuleHelper.IsPm(moduleName))
|
|||
|
|
{
|
|||
|
|
var module = _lstPms.Find(x => x.Module == moduleName);
|
|||
|
|
|
|||
|
|
if (module != null)
|
|||
|
|
{
|
|||
|
|
if (module.IsError)
|
|||
|
|
{
|
|||
|
|
EV.PostWarningLog("Scheduler", $"{moduleName} is not be error");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!module.IsOnline)
|
|||
|
|
{
|
|||
|
|
EV.PostWarningLog("Scheduler", $"{moduleName} is not be Online");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!module.IsLineHeaterEnable)
|
|||
|
|
{
|
|||
|
|
EV.PostWarningLog("Scheduler", $"{moduleName} LineHeater is not be Open");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
//if (module.IsService)
|
|||
|
|
//{
|
|||
|
|
// EV.PostWarningLog("Scheduler", $"can not start job, {moduleName} is Service Mode");
|
|||
|
|
// return false;
|
|||
|
|
//}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
EV.PostWarningLog("Scheduler", "Sequence PM can not be null!");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
EV.PostWarningLog("Scheduler", "Sequence can not be null!");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Start Auto Transfer
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="objs"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
public Result CheckAutoMode(params object[] objs)
|
|||
|
|
{
|
|||
|
|
if (!_tmRobot.IsOnline || _tmRobot.IsError)
|
|||
|
|
{
|
|||
|
|
EV.PostWarningLog("Scheduler", "can not change to auto mode, TM robot should be online and no error");
|
|||
|
|
return Result.FAIL;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return Result.RUN;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public Result Monitor()
|
|||
|
|
{
|
|||
|
|
MonitorModuleTasks();
|
|||
|
|
|
|||
|
|
MonitorAutoJobStatus();
|
|||
|
|
|
|||
|
|
MonitorModuleError();
|
|||
|
|
|
|||
|
|
MonitorJobTasks();
|
|||
|
|
|
|||
|
|
return Result.RUN;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#region Job task
|
|||
|
|
public Result MonitorJobTasks()
|
|||
|
|
{
|
|||
|
|
UpdateProcessJobStatus();
|
|||
|
|
|
|||
|
|
UpdateControlJobStatus();
|
|||
|
|
|
|||
|
|
return Result.RUN;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 检查PJ中的Wafer信息是否被清空。
|
|||
|
|
/// <para>如果被清空,则说明中间过程中Wafer被人工删除,此时返回true,通知相关Monitor可以改PJ。</para>
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="pj"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
protected bool CheckAllWaferDeleted(ProcessJobInfo pj)
|
|||
|
|
{
|
|||
|
|
var wi = WaferManager.Instance.GetWaferByProcessJob(pj.Name);
|
|||
|
|
return wi == null || wi.Length <= 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected bool CheckAllWaferReturned(ProcessJobInfo pj, bool checkAllProcessed)
|
|||
|
|
{
|
|||
|
|
for (var i = 0; i < pj.SlotWafers.Count; ++i)
|
|||
|
|
{
|
|||
|
|
WaferInfoRt wafer = WaferManager.Instance.GetWafer(pj.SlotWafers[i].Item1, pj.SlotWafers[i].Item2);
|
|||
|
|
if (wafer.IsWaferEmpty)
|
|||
|
|
return false;
|
|||
|
|
if (checkAllProcessed && CheckWaferNeedProcess(pj.SlotWafers[i].Item1, pj.SlotWafers[i].Item2))
|
|||
|
|
return false;
|
|||
|
|
if (wafer.ProcessJob == null || wafer.ProcessJob.InnerId != pj.InnerId)
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected bool CheckAllDummyWaferReturned()
|
|||
|
|
{
|
|||
|
|
foreach (var schedulerPm in _lstPms)
|
|||
|
|
{
|
|||
|
|
if (WaferManager.Instance.CheckWaferIsDummy(schedulerPm.Module, 0))
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (WaferManager.Instance.CheckWaferIsDummy(_tmRobot.Module, 0))
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
if (WaferManager.Instance.CheckWaferIsDummy(_tmRobot.Module, 1))
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected bool CheckAllPmCleaned(ProcessJobInfo pj)
|
|||
|
|
{
|
|||
|
|
foreach (var schedulerPm in _lstPms)
|
|||
|
|
{
|
|||
|
|
if (GetModule(schedulerPm.Module).CheckNeedRunClean(out _, out _))
|
|||
|
|
{
|
|||
|
|
foreach (var sequenceStepInfo in pj.Sequence.Steps)
|
|||
|
|
{
|
|||
|
|
if (sequenceStepInfo.StepModules.Contains(schedulerPm.Module))
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void UpdateProcessJobStatus()
|
|||
|
|
{
|
|||
|
|
foreach (var pj in _processJobList)
|
|||
|
|
{
|
|||
|
|
if (pj.State == EnumProcessJobState.Processing)
|
|||
|
|
{
|
|||
|
|
if (CheckAllWaferReturned(pj, true))
|
|||
|
|
{
|
|||
|
|
if (CheckWaferSequenceStepDone(ModuleName.LoadLock, 0))
|
|||
|
|
{
|
|||
|
|
pj.CycleTime++;
|
|||
|
|
|
|||
|
|
if (pj.CycleTime < pj.CycleCount)
|
|||
|
|
{
|
|||
|
|
//重新设置wafer
|
|||
|
|
for (int i = 0; i < pj.SlotWafers.Count; ++i)
|
|||
|
|
{
|
|||
|
|
var wafer = GetModule(pj.SlotWafers[i].Item1).GetWaferInfo(pj.SlotWafers[i].Item2);
|
|||
|
|
|
|||
|
|
wafer.NextSequenceStep = 0;
|
|||
|
|
wafer.WaferStatus = WaferStatus.Normal;
|
|||
|
|
wafer.ProcessState = WaferProcessStatus.Idle;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
_load.SetJobStatue();
|
|||
|
|
pj.SetState(EnumProcessJobState.ProcessingComplete);
|
|||
|
|
JobDataRecorder.EndPJ(pj.InnerId.ToString(), 0, 0);
|
|||
|
|
AutoJobRecorder.EndJob(WaferManager.Instance.GetWafer(ModuleName.LoadLock, 0).WaferID.ToString());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (CheckAllWaferDeleted(pj))
|
|||
|
|
{
|
|||
|
|
_load.SetJobStatue();
|
|||
|
|
pj.SetState(EnumProcessJobState.Complete);
|
|||
|
|
JobDataRecorder.EndPJ(pj.InnerId.ToString(), 0, 0);
|
|||
|
|
AutoJobRecorder.EndJob(WaferManager.Instance.GetWafer(ModuleName.LoadLock, 0).WaferID.ToString());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (pj.State == EnumProcessJobState.Stopping)
|
|||
|
|
{
|
|||
|
|
if (CheckAllWaferReturned(pj, false))
|
|||
|
|
{
|
|||
|
|
if (CheckWaferSequenceStepDone(ModuleName.LoadLock, 0))
|
|||
|
|
{
|
|||
|
|
_load.SetJobStatue();
|
|||
|
|
pj.SetState(EnumProcessJobState.Complete);
|
|||
|
|
JobDataRecorder.EndPJ(pj.InnerId.ToString(), 0, 0);
|
|||
|
|
AutoJobRecorder.EndJob(WaferManager.Instance.GetWafer(ModuleName.LoadLock, 0).WaferID.ToString());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void UpdateControlJobStatus()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (_controlJobList.Count == 0)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var cj in _controlJobList)
|
|||
|
|
{
|
|||
|
|
if (cj.State == EnumControlJobState.Executing)
|
|||
|
|
{
|
|||
|
|
bool allPjCompleted = true;
|
|||
|
|
foreach (var pjName in cj.ProcessJobNameList)
|
|||
|
|
{
|
|||
|
|
var pj = _processJobList.Find(x => x.Name == pjName);
|
|||
|
|
if (pj != null)
|
|||
|
|
{
|
|||
|
|
if (pj.State != EnumProcessJobState.ProcessingComplete && pj.State != EnumProcessJobState.Complete)
|
|||
|
|
{
|
|||
|
|
allPjCompleted = false;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (allPjCompleted)
|
|||
|
|
{
|
|||
|
|
foreach (var pjName in cj.ProcessJobNameList)
|
|||
|
|
{
|
|||
|
|
var pj = _processJobList.Find(x => x.Name == pjName);
|
|||
|
|
if (pj != null)
|
|||
|
|
{
|
|||
|
|
_processJobList.Remove(pj);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var wafer = cj.LotWafers[0];
|
|||
|
|
if(wafer != null)
|
|||
|
|
{
|
|||
|
|
if(wafer.WaferStatus == WaferStatus.Aborted)
|
|||
|
|
{
|
|||
|
|
cj.SetState(EnumControlJobState.Aborted);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
cj.SetState(EnumControlJobState.Completed);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
AutoJobRecorder.UpdatePosition(wafer.WaferID.ToString(), ModuleName.LoadLock.ToString(), cj.State.ToString());
|
|||
|
|
|
|||
|
|
JobDataRecorder.EndCJ(cj.InnerId.ToString(), 0, 0);
|
|||
|
|
_dbCallback.LotFinished(cj);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (int i = 0; i < _controlJobList.Count; i++)
|
|||
|
|
{
|
|||
|
|
if (_controlJobList[i].State == EnumControlJobState.Completed || _controlJobList[i].State == EnumControlJobState.Aborted)
|
|||
|
|
{
|
|||
|
|
_controlJobList.RemoveAt(i);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LOG.Error(ex.Message,ex);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void MonitorAutoJobStatus()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
_updateAutoJobLocation.CLK = _tmRobot.IsAvailable;
|
|||
|
|
if (_updateAutoJobLocation.Q)
|
|||
|
|
{
|
|||
|
|
WaferInfoRt wafer = WaferManager.Instance.GetWafer(ModuleName.PM1, 0);
|
|||
|
|
if (wafer != null && !string.IsNullOrEmpty(wafer.WaferID))
|
|||
|
|
{
|
|||
|
|
AutoJobRecorder.UpdateWaferPosition(wafer.WaferID.ToString(), ModuleName.PM1.ToString());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
WaferInfoRt wafer1 = WaferManager.Instance.GetWafer(ModuleName.TMRobot, 0);
|
|||
|
|
if (wafer1 != null && !string.IsNullOrEmpty(wafer1.WaferID))
|
|||
|
|
{
|
|||
|
|
AutoJobRecorder.UpdateWaferPosition(wafer1.WaferID.ToString(), ModuleName.TM.ToString());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
WaferInfoRt wafer2 = WaferManager.Instance.GetWafer(ModuleName.Buffer, 0);
|
|||
|
|
if (wafer2 != null && !string.IsNullOrEmpty(wafer2.WaferID))
|
|||
|
|
{
|
|||
|
|
AutoJobRecorder.UpdateWaferPosition(wafer2.WaferID.ToString(), ModuleName.Buffer.ToString());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
WaferInfoRt wafer3 = WaferManager.Instance.GetWafer(ModuleName.LoadLock, 0);
|
|||
|
|
if (wafer3 != null && !string.IsNullOrEmpty(wafer3.WaferID))
|
|||
|
|
{
|
|||
|
|
AutoJobRecorder.UpdateWaferPosition(wafer3.WaferID.ToString(), ModuleName.LoadLock.ToString());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
LOG.Error(ex.Message, ex);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool CheckAllJobDone()
|
|||
|
|
{
|
|||
|
|
//条件满足状态由AutoRunning切换到AutoIdle状态
|
|||
|
|
if (_load.IsAvailable && _tmRobot.IsAvailable && _tmRobot.NoWafer(0) && _tmRobot.NoTray(0) &&
|
|||
|
|
_buffer.NoWafer(0) && _buffer.NoTray(0) && _buffer.NoWafer(1) && _buffer.NoTray(1) && _buffer.NoWafer(2) && _buffer.NoTray(2) &&
|
|||
|
|
_pm1.NoWafer(0) && _pm1.NoTray(0) && _pm2.NoWafer(0) && _pm2.NoTray(0) && !_controlJobList.Exists(c => c.State == EnumControlJobState.Executing))
|
|||
|
|
{
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool ActiveProcessJob(ProcessJobInfo pj)
|
|||
|
|
{
|
|||
|
|
foreach (var pjSlotWafer in pj.SlotWafers)
|
|||
|
|
{
|
|||
|
|
WaferInfoRt wafer = GetModule(pjSlotWafer.Item1).GetWaferInfo(pjSlotWafer.Item2);
|
|||
|
|
wafer.ProcessJob = pj;
|
|||
|
|
wafer.NextSequenceStep = 0;
|
|||
|
|
|
|||
|
|
WaferDataRecorder.SetPjInfo(wafer.WaferInnerID.ToString(), pj.InnerId.ToString());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var cj = _controlJobList.Find(x => x.Name == pj.ControlJobName);
|
|||
|
|
|
|||
|
|
var carrier = CarrierManager.Instance.GetCarrier(cj.Module);
|
|||
|
|
JobDataRecorder.StartPJ(pj.InnerId.ToString(), null, cj.InnerId.ToString(), pj.Name, cj.Module, cj.Module, pj.SlotWafers.Count);
|
|||
|
|
pj.SetState(EnumProcessJobState.Processing);
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region Module task
|
|||
|
|
|
|||
|
|
public Result MonitorModuleTasks()
|
|||
|
|
{
|
|||
|
|
MonitorPMTask();
|
|||
|
|
MonitorBufferTask();
|
|||
|
|
|
|||
|
|
MonitorTmRobotTask();
|
|||
|
|
MonitorLoadTask();
|
|||
|
|
|
|||
|
|
return Result.RUN;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void MonitorBufferTask()
|
|||
|
|
{
|
|||
|
|
if (!_buffer.IsAvailable)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (var i = 0; i < _bufferFloors; i++)
|
|||
|
|
{
|
|||
|
|
var canExcute = _buffer.HasWafer(i) && _buffer.CheckWaferNextStepIsThis(_buffer.Module, i);
|
|||
|
|
if (canExcute)
|
|||
|
|
{
|
|||
|
|
WaferInfoRt bufferWafer = _buffer.GetWaferInfo(i);
|
|||
|
|
|
|||
|
|
if(i == 2 || i ==1)
|
|||
|
|
{
|
|||
|
|
bufferWafer.NextSequenceStep++;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var bufferSetValue = 0;
|
|||
|
|
if (!GetWaferSequenceNextValue(ModuleName.Buffer, i, "Type", out var strBufferType))
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
if (!GetWaferSequenceNextValue(ModuleName.Buffer, i, "SetValue", out var strBufferSetValue))
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
if (!Int32.TryParse(strBufferSetValue, out bufferSetValue))
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//分别判断冷却和加热方式温度是否达到
|
|||
|
|
if (strBufferType == "HeatByTemp")
|
|||
|
|
{
|
|||
|
|
if (_bufferWaferInfo.ContainsKey(bufferWafer.WaferInnerID.ToString()))
|
|||
|
|
{
|
|||
|
|
var dtStartTime = _bufferWaferInfo[bufferWafer.WaferInnerID.ToString()];
|
|||
|
|
var pastTime = (DateTime.Now - dtStartTime).TotalSeconds;
|
|||
|
|
|
|||
|
|
//选择By温度5秒后再判断温度
|
|||
|
|
if (pastTime > 5)
|
|||
|
|
{
|
|||
|
|
if (_buffer.GetTemperature() >= bufferSetValue)
|
|||
|
|
bufferWafer.NextSequenceStep++;
|
|||
|
|
_bufferWaferInfo.Remove(bufferWafer.WaferInnerID.ToString());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
_bufferWaferInfo.Add(bufferWafer.WaferInnerID.ToString(), DateTime.Now);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (strBufferType == "HeatByTime")
|
|||
|
|
{
|
|||
|
|
if (_bufferWaferInfo.ContainsKey(bufferWafer.WaferInnerID.ToString()))
|
|||
|
|
{
|
|||
|
|
var dtStartTime = _bufferWaferInfo[bufferWafer.WaferInnerID.ToString()];
|
|||
|
|
var pastTime = (DateTime.Now - dtStartTime).TotalSeconds;
|
|||
|
|
|
|||
|
|
if (i == 0)
|
|||
|
|
{
|
|||
|
|
_timeBuffer1 = pastTime > bufferSetValue ? 0 : (bufferSetValue - pastTime);
|
|||
|
|
}
|
|||
|
|
else if (i == 1)
|
|||
|
|
{
|
|||
|
|
_timeBuffer2 = pastTime > bufferSetValue ? 0 : (bufferSetValue - pastTime);
|
|||
|
|
}
|
|||
|
|
else if (i == 2)
|
|||
|
|
{
|
|||
|
|
_timeBuffer3 = pastTime > bufferSetValue ? 0 : (bufferSetValue - pastTime);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (pastTime > bufferSetValue)
|
|||
|
|
{
|
|||
|
|
bufferWafer.NextSequenceStep++;
|
|||
|
|
_bufferWaferInfo.Remove(bufferWafer.WaferInnerID.ToString());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
_bufferWaferInfo.Add(bufferWafer.WaferInnerID.ToString(), DateTime.Now);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (strBufferType == "CoolingByTime")
|
|||
|
|
{
|
|||
|
|
if (_bufferWaferInfo.ContainsKey(bufferWafer.WaferInnerID.ToString()))
|
|||
|
|
{
|
|||
|
|
var dtStartTime = _bufferWaferInfo[bufferWafer.WaferInnerID.ToString()];
|
|||
|
|
var pastTime = (DateTime.Now - dtStartTime).TotalSeconds;
|
|||
|
|
|
|||
|
|
if (i == 0)
|
|||
|
|
{
|
|||
|
|
_timeBuffer1 = pastTime > bufferSetValue ? 0 : (bufferSetValue - pastTime);
|
|||
|
|
}
|
|||
|
|
else if (i == 1)
|
|||
|
|
{
|
|||
|
|
_timeBuffer2 = pastTime > bufferSetValue ? 0 : (bufferSetValue - pastTime);
|
|||
|
|
}
|
|||
|
|
else if (i == 2)
|
|||
|
|
{
|
|||
|
|
_timeBuffer3 = pastTime > bufferSetValue ? 0 : (bufferSetValue - pastTime);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (pastTime > bufferSetValue)
|
|||
|
|
{
|
|||
|
|
bufferWafer.NextSequenceStep++;
|
|||
|
|
_bufferWaferInfo.Remove(bufferWafer.WaferInnerID.ToString());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
_bufferWaferInfo.Add(bufferWafer.WaferInnerID.ToString(), DateTime.Now);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//Place和Pick条件都一样
|
|||
|
|
if (!_buffer.IsReadyForPlace(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
_buffer.PrepareTransfer(ModuleName.TMRobot, EnumTransferType.Place, 0);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 检测到LL中的Wafer已完成Job。
|
|||
|
|
/// </summary>
|
|||
|
|
//private readonly R_TRIG _trigLLPjDone = new();
|
|||
|
|
|
|||
|
|
private void MonitorLoadTask()
|
|||
|
|
{
|
|||
|
|
if (!_load.IsAvailable)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_load.FirstDetectWaferArrive(0) || _load.FirstDetectWaferLeave(0))
|
|||
|
|
{
|
|||
|
|
_load.ResetPurged();
|
|||
|
|
//_trigLLPjDone.RST = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var canExcute = _load.HasWafer(0);
|
|||
|
|
if (canExcute)
|
|||
|
|
{
|
|||
|
|
if (_load.GetWaferInfo(0).ProcessJob != null)
|
|||
|
|
{
|
|||
|
|
if (_load.GetWaferInfo(0).ProcessJob.State != EnumProcessJobState.Processing)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
if (!_load.CheckWaferSequenceStepDone(0))
|
|||
|
|
{
|
|||
|
|
if (_load.CheckWaferNextStepIsThis(ModuleName.LoadLock, 0))
|
|||
|
|
{
|
|||
|
|
if (_load.CheckWaferNeedProcess(0))
|
|||
|
|
{
|
|||
|
|
_load.Purge(_load.GetWaferPurgeCount(0), _load.GetWaferPumpDelayTime(0));
|
|||
|
|
|
|||
|
|
_load.GetWaferInfo(0).NextSequenceStep++;
|
|||
|
|
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
if (!GetWaferSequenceNextValue(ModuleName.Load, 0, "Type", out var strType))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!GetWaferSequenceNextValue(ModuleName.Load, 0, "SetValue", out var strSetValue))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!Int32.TryParse(strSetValue, out var setValue))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//Load腔没有测温
|
|||
|
|
//if (strType == "CoolingByTemp")
|
|||
|
|
//{
|
|||
|
|
// if (_load.GetTemperature() <= setValue)
|
|||
|
|
// {
|
|||
|
|
// _load.Purge(_load.GetWaferPurgeCount(0), _load.GetWaferPumpDelayTime(0));
|
|||
|
|
|
|||
|
|
// _load.Vent();
|
|||
|
|
|
|||
|
|
// _load.GetWaferInfo(0).NextSequenceStep++;
|
|||
|
|
// }
|
|||
|
|
//}
|
|||
|
|
if (strType == "CoolingByTime")
|
|||
|
|
{
|
|||
|
|
if (_loadWaferInfo.ContainsKey(_load.GetWaferInfo(0).WaferInnerID.ToString()))
|
|||
|
|
{
|
|||
|
|
var dtStartTime = _loadWaferInfo[_load.GetWaferInfo(0).WaferInnerID.ToString()];
|
|||
|
|
var pastTime = (DateTime.Now - dtStartTime).TotalSeconds;
|
|||
|
|
|
|||
|
|
_timeLoad = pastTime > setValue ? 0 : (setValue - pastTime);
|
|||
|
|
|
|||
|
|
if (pastTime > setValue)
|
|||
|
|
{
|
|||
|
|
if (!_load.CheckPurged())
|
|||
|
|
{
|
|||
|
|
_load.Purge(_load.GetWaferPurgeCount(0), _load.GetWaferPumpDelayTime(0));
|
|||
|
|
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_load.ResetPurged();
|
|||
|
|
|
|||
|
|
_load.Vent();
|
|||
|
|
|
|||
|
|
_load.GetWaferInfo(0).NextSequenceStep++;
|
|||
|
|
|
|||
|
|
_loadWaferInfo.Remove(_load.GetWaferInfo(0).WaferInnerID.ToString());
|
|||
|
|
|
|||
|
|
//补全路径
|
|||
|
|
string squenceID = $"Sequence\\{_load.GetWaferInfo(0).PPID}.seq";
|
|||
|
|
GemManager.Instance.Equipment?.TriggerEvent("JobComplete", new string[] { "LotID", "SequenceID" },
|
|||
|
|
new object[] { _load.GetWaferInfo(0).LotId, squenceID });
|
|||
|
|
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
_loadWaferInfo.Add(_load.GetWaferInfo(0).WaferInnerID.ToString(), DateTime.Now);
|
|||
|
|
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (!_load.IsReadyForPick(ModuleName.TMRobot, 0) && !_tmRobot.IsInPumping)
|
|||
|
|
{
|
|||
|
|
_load.PrepareTransfer(ModuleName.TMRobot, EnumTransferType.Pick, 0);
|
|||
|
|
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/*else
|
|||
|
|
{
|
|||
|
|
// Job Done, 响蜂鸣器
|
|||
|
|
_trigLLPjDone.CLK = true;
|
|||
|
|
if (_trigLLPjDone.Q)
|
|||
|
|
{
|
|||
|
|
OP.DoOperation("System.AlertJobDone", ModuleName.LoadLock.ToString(), 0);
|
|||
|
|
}
|
|||
|
|
}*/
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
if (_load.NoTray(0))
|
|||
|
|
{
|
|||
|
|
if (!_load.IsReadyForPlace(ModuleName.TMRobot, 0) && !_tmRobot.IsInPumping)
|
|||
|
|
{
|
|||
|
|
_load.PrepareTransfer(ModuleName.TMRobot, EnumTransferType.Place, 0);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
private void MonitorPMTask()
|
|||
|
|
{
|
|||
|
|
//if (_pm1.FirstDetectTrayArrive(0))
|
|||
|
|
//{
|
|||
|
|
// _pm1.GetWaferInfo(0).TrayUsedForWhichPM = (int)_pm1.Module;
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
//if (_pm2.FirstDetectTrayArrive(1))
|
|||
|
|
//{
|
|||
|
|
// _pm2.GetWaferInfo(0).TrayUsedForWhichPM = (int)_pm2.Module;
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
foreach (var pm in _lstPms)
|
|||
|
|
{
|
|||
|
|
if (!pm.IsAvailable)
|
|||
|
|
continue;
|
|||
|
|
|
|||
|
|
if (pm.HasWafer(0))
|
|||
|
|
{
|
|||
|
|
if (pm.CheckNeedRunClean(out var withWafer, out var recipe) && withWafer
|
|||
|
|
&& GetModule(pm.Module).GetWaferInfo(0).WaferStatus == WaferStatus.Dummy
|
|||
|
|
&& GetModule(pm.Module).GetWaferInfo(0).ProcessState == WaferProcessStatus.Wait)
|
|||
|
|
{
|
|||
|
|
pm.Process(recipe, true, withWafer);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (GetModule(pm.Module).CheckWaferNeedProcess(0, pm.Module))
|
|||
|
|
{
|
|||
|
|
WaferInfoRt wafer = GetModule(pm.Module).GetWaferInfo(0);
|
|||
|
|
if (pm.Process(wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].RecipeName, false, true))
|
|||
|
|
{
|
|||
|
|
GetModule(pm.Module).GetWaferInfo(0).NextSequenceStep++;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
if (!pm.IsReadyForPick(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
pm.PrepareTransfer(ModuleName.TMRobot, EnumTransferType.Pick, 0);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
if (GetModule(pm.Module).CheckNeedRunClean(out var withWafer, out var recipe) && !withWafer)
|
|||
|
|
{
|
|||
|
|
pm.Process(recipe, true, withWafer);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!pm.IsReadyForPlace(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
pm.PrepareTransfer(ModuleName.TMRobot, EnumTransferType.Place, 0);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void MonitorTmRobotTask()
|
|||
|
|
{
|
|||
|
|
// TMRobot Idle或者Error时,清除被传盘对象的等待状态
|
|||
|
|
if (_tmRobot.CheckTaskDone())
|
|||
|
|
{
|
|||
|
|
foreach (var pm in _lstPms.Where(
|
|||
|
|
pm => pm.IsWaitTransfer(ModuleName.TMRobot) && _tmRobot.CheckTaskDone()))
|
|||
|
|
{
|
|||
|
|
pm.StopWaitTransfer(ModuleName.TMRobot);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_buffer.IsWaitTransfer(ModuleName.TMRobot) && _tmRobot.CheckTaskDone())
|
|||
|
|
_buffer.StopWaitTransfer(ModuleName.TMRobot);
|
|||
|
|
|
|||
|
|
if (_load.IsWaitTransfer(ModuleName.TMRobot) && _tmRobot.CheckTaskDone())
|
|||
|
|
_load.StopWaitTransfer(ModuleName.TMRobot);
|
|||
|
|
|
|||
|
|
//if (_unload.IsWaitTransfer(ModuleName.TMRobot))
|
|||
|
|
// _unload.StopWaitTransfer(ModuleName.TMRobot);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!_tmRobot.IsAvailable)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!_tmRobot.IsAvailable)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
var act = tmRobotActions.Peek();
|
|||
|
|
act.Invoke();
|
|||
|
|
|
|||
|
|
tmRobotActions.Enqueue(tmRobotActions.Dequeue());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
#region TmRobotTask
|
|||
|
|
private void MonitorTmRobotLoadPlaceTask()
|
|||
|
|
{
|
|||
|
|
if (!_load.IsAvailable || !_tmRobot.IsAvailable)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//place Robot有Tray有Wafer,Load无Tray,无Wafer
|
|||
|
|
var canPlace = _tmRobot.HasTray(0) && _load.NoTray(0) && _load.NoWafer(0)
|
|||
|
|
&& !_load.CheckTrayPlaced();
|
|||
|
|
if (canPlace)
|
|||
|
|
{
|
|||
|
|
if(!_tmRobot.CheckWaferNeedProcess(0) || _tmRobot.CheckWaferSequenceStepDone(0) || _load.CheckWaferNextStepIsThis(ModuleName.TMRobot,0))
|
|||
|
|
{
|
|||
|
|
if (_load.IsReadyForPlace(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
if (_tmRobot.Place(_load.Module, 0, Hand.Blade1))
|
|||
|
|
{
|
|||
|
|
_load.WaitTransfer(ModuleName.TMRobot, false, 0);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void MonitorTmRobotLoadPickTask()
|
|||
|
|
{
|
|||
|
|
if (!_load.IsAvailable || !_tmRobot.IsAvailable)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//pick TM无Tray,需要Process
|
|||
|
|
var canPick = _tmRobot.NoTray(0) && _tmRobot.NoWafer(0)
|
|||
|
|
&& _load.HasTray(0) && _load.HasWafer(0)
|
|||
|
|
&& _load.CheckWaferNeedProcess(0)
|
|||
|
|
&& !_load.CheckWaferNextStepIsThis(ModuleName.LoadLock, 0)
|
|||
|
|
&& _load.CheckWaferNextStepModuleNoTray(0);
|
|||
|
|
if (canPick)
|
|||
|
|
{
|
|||
|
|
//下一步如果去PM1,判断PM1是否准备好
|
|||
|
|
if (_pm1.CheckWaferNextStepIsThis(ModuleName.LoadLock, 0))
|
|||
|
|
{
|
|||
|
|
if (!_pm1.IsAvailable || _pm1.HasTray(0) || _pm1.HasWafer(0) || !_pm1.IsReadyForPlace(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if(!_pm1.CheckBufferToPMTemp())
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//下一步如果去PM2,判断PM2是否准备好
|
|||
|
|
if (_pm2.CheckWaferNextStepIsThis(ModuleName.LoadLock, 0))
|
|||
|
|
{
|
|||
|
|
if (!_pm2.IsAvailable || _pm2.HasTray(0) || _pm2.HasWafer(0) || !_pm2.IsReadyForPlace(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!_pm2.CheckBufferToPMTemp())
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_load.IsReadyForPick(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
if (_tmRobot.Pick(_load.Module, 0, Hand.Blade1))
|
|||
|
|
{
|
|||
|
|
_load.WaitTransfer(ModuleName.TMRobot, true, 0);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void MonitorTmRobotBufferPlaceTask()
|
|||
|
|
{
|
|||
|
|
if (!_tmRobot.IsAvailable || !_buffer.IsAvailable)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//place Buffer位置没有Tray,Robot有Wafer,下一步骤是Buffer
|
|||
|
|
var canPalce = _tmRobot.HasTray(0);
|
|||
|
|
if (canPalce)
|
|||
|
|
{
|
|||
|
|
SlotItem bufferEmptySlot = null;
|
|||
|
|
|
|||
|
|
if (_tmRobot.CheckWaferNeedProcess(0, _pm1.Module)
|
|||
|
|
&& _buffer.NoTray(2)
|
|||
|
|
&& _buffer.NoWafer(2)
|
|||
|
|
&& _buffer.CheckWaferNextStepIsThis(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
bufferEmptySlot = new SlotItem(ModuleName.Buffer, 2);
|
|||
|
|
}
|
|||
|
|
else if (_tmRobot.CheckWaferNeedProcess(0, _pm2.Module)
|
|||
|
|
&& _buffer.NoTray(1)
|
|||
|
|
&& _buffer.NoWafer(1)
|
|||
|
|
&& _buffer.CheckWaferNextStepIsThis(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
bufferEmptySlot = new SlotItem(ModuleName.Buffer, 1);
|
|||
|
|
}
|
|||
|
|
//TMRobot的下一步是Buffer3
|
|||
|
|
else if (!_tmRobot.CheckWaferNeedProcess(0)
|
|||
|
|
&& _buffer.NoTray(0)
|
|||
|
|
&& _buffer.NoWafer(0)
|
|||
|
|
&& _buffer.CheckWaferNextStepIsThis(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
bufferEmptySlot = new SlotItem(ModuleName.Buffer, 0);
|
|||
|
|
}
|
|||
|
|
//Load里有Tray
|
|||
|
|
else if (!_tmRobot.CheckWaferNeedProcess(0)
|
|||
|
|
&& _buffer.NoTray(0)
|
|||
|
|
&& _buffer.NoWafer(0)
|
|||
|
|
&& _load.HasTray(0))
|
|||
|
|
{
|
|||
|
|
bufferEmptySlot = new SlotItem(ModuleName.Buffer, 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (bufferEmptySlot == null)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_buffer.IsReadyForPlace(ModuleName.TMRobot, bufferEmptySlot.Slot))
|
|||
|
|
{
|
|||
|
|
if (_tmRobot.Place(_buffer.Module, bufferEmptySlot.Slot, Hand.Blade1))
|
|||
|
|
{
|
|||
|
|
_buffer.WaitTransfer(ModuleName.TMRobot);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void MonitorTmRobotBufferPickTask()
|
|||
|
|
{
|
|||
|
|
if (!_tmRobot.IsAvailable || !_buffer.IsAvailable)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//pick,Buffer有Tray,机械手没有Tray
|
|||
|
|
MonitorTmRobotBuffer1PickTask();
|
|||
|
|
MonitorTmRobotBuffer2PickTask();
|
|||
|
|
MonitorTmRobotBuffer3PickTask();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void MonitorTmRobotBuffer3PickTask()
|
|||
|
|
{
|
|||
|
|
if (!_tmRobot.IsAvailable || !_buffer.IsAvailable)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//pick,Buffer有Tray,机械手没有Tray
|
|||
|
|
var canPick = _tmRobot.NoTray(0);
|
|||
|
|
if (canPick)
|
|||
|
|
{
|
|||
|
|
if (_buffer.HasTray(2) && _buffer.HasWafer(2))
|
|||
|
|
{
|
|||
|
|
if (_buffer.CheckWaferNextStepIsThis(ModuleName.Buffer, 2))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//下一步如果去PM1,判断PM1是否准备好
|
|||
|
|
if (_pm1.CheckWaferNextStepIsThis(ModuleName.Buffer, 2))
|
|||
|
|
{
|
|||
|
|
if (!_pm1.IsAvailable || _pm1.HasTray(0) || _pm1.HasWafer(0) || !_pm1.IsReadyForPlace(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if(!_pm1.CheckBufferToPMTemp())
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_buffer.IsReadyForPick(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
if (_tmRobot.Pick(_buffer.Module, 2, Hand.Blade1))
|
|||
|
|
{
|
|||
|
|
_buffer.WaitTransfer(ModuleName.TMRobot);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void MonitorTmRobotBuffer2PickTask()
|
|||
|
|
{
|
|||
|
|
if (!_tmRobot.IsAvailable || !_buffer.IsAvailable)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//pick,Buffer有Tray,机械手没有Tray
|
|||
|
|
var canPick = _tmRobot.NoTray(0);
|
|||
|
|
if (canPick)
|
|||
|
|
{
|
|||
|
|
if (_buffer.HasTray(1) && _buffer.HasWafer(1))
|
|||
|
|
{
|
|||
|
|
if (_buffer.CheckWaferNextStepIsThis(ModuleName.Buffer, 1))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//下一步如果去PM2,判断PM2是否准备好
|
|||
|
|
if (_pm2.CheckWaferNextStepIsThis(ModuleName.Buffer, 1))
|
|||
|
|
{
|
|||
|
|
if (!_pm2.IsAvailable || _pm2.HasTray(0) || _pm2.HasWafer(0) || !_pm2.IsReadyForPlace(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if(!_pm2.CheckBufferToPMTemp())
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_buffer.CheckWaferNextStepModuleNoTray(1))
|
|||
|
|
{
|
|||
|
|
if (_buffer.IsReadyForPick(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
if (_tmRobot.Pick(_buffer.Module, 1, Hand.Blade1))
|
|||
|
|
{
|
|||
|
|
_buffer.WaitTransfer(ModuleName.TMRobot);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void MonitorTmRobotBuffer1PickTask()
|
|||
|
|
{
|
|||
|
|
if (!_tmRobot.IsAvailable || !_buffer.IsAvailable)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var canPick = _tmRobot.NoTray(0) && _tmRobot.NoWafer(0);
|
|||
|
|
if (canPick)
|
|||
|
|
{
|
|||
|
|
if (_buffer.HasTray(0)
|
|||
|
|
&& _load.NoTray(0) && _load.NoWafer(0)
|
|||
|
|
&& !_buffer.CheckWaferNextStepIsThis(ModuleName.Buffer, 0)
|
|||
|
|
&& _load.IsReadyForPlace(ModuleName.TMRobot,0)
|
|||
|
|
&& _load.IsAvailable
|
|||
|
|
&& _load.IsOnline)
|
|||
|
|
{
|
|||
|
|
if (!_buffer.CheckWaferNeedProcess(0))
|
|||
|
|
{
|
|||
|
|
if (_buffer.IsReadyForPick(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
if (_tmRobot.Pick(_buffer.Module,0, Hand.Blade1))
|
|||
|
|
{
|
|||
|
|
_buffer.WaitTransfer(ModuleName.TMRobot);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void MonitorTmRobotPMPlaceTask()
|
|||
|
|
{
|
|||
|
|
if (!_tmRobot.IsAvailable)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
//place to pm
|
|||
|
|
var blade0HasWaferAndNeedProcess = _tmRobot.HasWafer(0) && _tmRobot.HasTray(0) && _tmRobot.CheckWaferNeedProcess(0);
|
|||
|
|
|
|||
|
|
if (blade0HasWaferAndNeedProcess)
|
|||
|
|
{
|
|||
|
|
foreach (var pm in _lstPms)
|
|||
|
|
{
|
|||
|
|
if (!pm.IsAvailable
|
|||
|
|
|| !GetModule(pm.Module).NoWafer(0)
|
|||
|
|
|| !GetModule(pm.Module).NoTray(0)
|
|||
|
|
|| !pm.IsReadyForPlace(ModuleName.TMRobot, 0))
|
|||
|
|
continue;
|
|||
|
|
|
|||
|
|
var blade0Place = _tmRobot.HasWafer(0) && _tmRobot.CheckWaferNeedProcess(0, pm.Module) &&
|
|||
|
|
pm.CheckWaferNextStepIsThis(ModuleName.TMRobot, 0);
|
|||
|
|
if (blade0Place)
|
|||
|
|
{
|
|||
|
|
if (!pm.CheckBufferToPMTemp())
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_tmRobot.Place(pm.Module, 0, Hand.Blade1))
|
|||
|
|
{
|
|||
|
|
pm.WaitTransfer(ModuleName.TMRobot);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void MonitorTmRobotPMPickTask()
|
|||
|
|
{
|
|||
|
|
if (!_tmRobot.IsAvailable)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
//TMRobot从PM取Wafer,Buffer1中不能有Tray,否则先去Buffer1中的Tray
|
|||
|
|
if (_tmRobot.NoWafer(0) && _tmRobot.NoTray(0))
|
|||
|
|
{
|
|||
|
|
var pickBlade = Hand.Blade1;
|
|||
|
|
|
|||
|
|
var pickPm = ModuleName.System;
|
|||
|
|
foreach (var schedulerPm in _lstPms)
|
|||
|
|
{
|
|||
|
|
//增加温度低于900才能Pick的限制
|
|||
|
|
if (!schedulerPm.IsAvailable
|
|||
|
|
|| GetModule(schedulerPm.Module).NoTray(0)
|
|||
|
|
|| GetModule(schedulerPm.Module).CheckWaferNeedProcess(0, schedulerPm.Module)
|
|||
|
|
|| _tmRobot.HasWafer((int)pickBlade)
|
|||
|
|
|| _tmRobot.HasTray((int)pickBlade))
|
|||
|
|
continue;
|
|||
|
|
|
|||
|
|
//如果下一步是Buffer,只能取出放Buffer1
|
|||
|
|
if (_buffer.CheckWaferNextStepIsThis(schedulerPm.Module, 0))
|
|||
|
|
{
|
|||
|
|
if (_buffer.NoTray(0) && _buffer.NoWafer(0))
|
|||
|
|
{
|
|||
|
|
pickPm = schedulerPm.Module;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if ((_buffer.NoTray(0) && _buffer.NoWafer(0))
|
|||
|
|
|| (_load.NoTray(0) && _load.NoWafer(0)))
|
|||
|
|
{
|
|||
|
|
pickPm = schedulerPm.Module;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (pickPm != ModuleName.System)
|
|||
|
|
{
|
|||
|
|
var pmScheduler = _lstPms.Find(scheduler => scheduler.Module == pickPm);
|
|||
|
|
if (!pmScheduler.CheckPMToTMRobotTemp())
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var pm = GetModule(pickPm.ToString());
|
|||
|
|
if (pm.IsReadyForPick(ModuleName.TMRobot, 0))
|
|||
|
|
{
|
|||
|
|
if (_tmRobot.Pick(pm.Module, 0, pickBlade))
|
|||
|
|
{
|
|||
|
|
pm.WaitTransfer(ModuleName.TMRobot);
|
|||
|
|
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region Logic Check
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获得Buffer的方式和数值
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="module"></param>
|
|||
|
|
/// <param name="slot"></param>
|
|||
|
|
/// <param name="coolingType"></param>
|
|||
|
|
/// <param name="setValue"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private bool GetWaferSequenceNextValue(ModuleName module, int slot, string nodeName, out string nodeValue)
|
|||
|
|
{
|
|||
|
|
nodeValue = "";
|
|||
|
|
|
|||
|
|
if (!GetModule(module).HasWafer(slot))
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
WaferInfoRt wafer = GetModule(module).GetWaferInfo(slot);
|
|||
|
|
|
|||
|
|
if (wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
if (wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
if (!wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules.Contains(module))
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
nodeValue = wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepParameter[nodeName].ToString();
|
|||
|
|
if (String.IsNullOrEmpty(nodeValue))
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool GetWaferSequenceCurrentValue(ModuleName module, int slot, string nodeName, out string nodeValue)
|
|||
|
|
{
|
|||
|
|
nodeValue = "";
|
|||
|
|
|
|||
|
|
if (!GetModule(module).HasWafer(slot))
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
WaferInfoRt wafer = GetModule(module).GetWaferInfo(slot);
|
|||
|
|
|
|||
|
|
if (wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
if (wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
if (!wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep - 1].StepModules.Contains(module))
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
nodeValue = wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep - 1].StepParameter[nodeName].ToString();
|
|||
|
|
if (String.IsNullOrEmpty(nodeValue))
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool CheckBufferWaferHasJob()
|
|||
|
|
{
|
|||
|
|
WaferInfoRt wafer = _buffer.GetWaferInfo(0);
|
|||
|
|
|
|||
|
|
if (wafer.IsWaferEmpty)
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
if (wafer.ProcessJob == null)
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var pj = _processJobList.Find(x => x.InnerId == wafer.ProcessJob.InnerId);
|
|||
|
|
if (pj == null)
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool CheckWaferProcessModuleIsAvailable(ModuleName waferModule, int waferSlot)
|
|||
|
|
{
|
|||
|
|
var wafer = GetModule(waferModule).GetWaferInfo(waferSlot);
|
|||
|
|
|
|||
|
|
if (wafer.IsWaferEmpty)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
if (wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
if (wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
//foreach (var module in wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules)
|
|||
|
|
//{
|
|||
|
|
//if (GetModule(module).NoWafer(0)
|
|||
|
|
// && _lstPms.Find(x => x.Module == module).IsAvailable
|
|||
|
|
// && !CheckNeedRunClean(module, out bool _, out string _))
|
|||
|
|
// return true;
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
foreach (var step in wafer.ProcessJob.Sequence.Steps)
|
|||
|
|
{
|
|||
|
|
foreach (var module in step.StepModules)
|
|||
|
|
{
|
|||
|
|
if (module.ToString().StartsWith("PM")
|
|||
|
|
&& GetModule(module).NoWafer(0)
|
|||
|
|
&& _lstPms.Find(x => x.Module == module).IsAvailable
|
|||
|
|
&& !GetModule(module).CheckNeedRunClean(out var _, out var _))
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool CheckWaferSequenceStepDone(ModuleName waferModule, int waferSlot)
|
|||
|
|
{
|
|||
|
|
var wafer = WaferManager.Instance.GetWafer(waferModule, waferSlot);
|
|||
|
|
|
|||
|
|
if (wafer.IsWaferEmpty)
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
if (wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null)
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count && wafer.ProcessJob.Sequence.Steps.Count > 0)
|
|||
|
|
{
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private SlotItem SortBySerialMode(List<Tuple<ModuleName, int>> processingWafers)
|
|||
|
|
{
|
|||
|
|
foreach (var pjSlotWafer in processingWafers)
|
|||
|
|
{
|
|||
|
|
if (pjSlotWafer.Item1 == ModuleName.CassAL || pjSlotWafer.Item1 == ModuleName.CassAR)
|
|||
|
|
{
|
|||
|
|
if (GetModule(pjSlotWafer.Item1).CheckWaferNeedProcess(pjSlotWafer.Item2, _pm1.Module))
|
|||
|
|
{
|
|||
|
|
if (GetRunWaferCount(_pm1.Module) < 2)
|
|||
|
|
{
|
|||
|
|
return new SlotItem(pjSlotWafer.Item1, pjSlotWafer.Item2);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (GetModule(pjSlotWafer.Item1).CheckWaferNeedProcess(pjSlotWafer.Item2, _pm2.Module))
|
|||
|
|
{
|
|||
|
|
if (GetRunWaferCount(_pm2.Module) < 2)
|
|||
|
|
{
|
|||
|
|
return new SlotItem(pjSlotWafer.Item1, pjSlotWafer.Item2);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private SlotItem SortByParallelMode(List<Tuple<ModuleName, int>> processingWafers)
|
|||
|
|
{
|
|||
|
|
foreach (var pjSlotWafer in processingWafers)
|
|||
|
|
{
|
|||
|
|
if (pjSlotWafer.Item1 == ModuleName.CassAL || pjSlotWafer.Item1 == ModuleName.CassAR)
|
|||
|
|
{
|
|||
|
|
if (GetModule(pjSlotWafer.Item1).CheckWaferNeedProcess(pjSlotWafer.Item2, _pm1.Module))
|
|||
|
|
{
|
|||
|
|
if (GetRunWaferCount(_pm1.Module) < 2 && GetRunWaferCount(_pm1.Module) <= GetRunWaferCount(_pm2.Module))
|
|||
|
|
{
|
|||
|
|
return new SlotItem(pjSlotWafer.Item1, pjSlotWafer.Item2);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (GetModule(pjSlotWafer.Item1).CheckWaferNeedProcess(pjSlotWafer.Item2, _pm2.Module))
|
|||
|
|
{
|
|||
|
|
if (GetRunWaferCount(_pm2.Module) < 2 && GetRunWaferCount(_pm2.Module) <= GetRunWaferCount(_pm1.Module))
|
|||
|
|
{
|
|||
|
|
return new SlotItem(pjSlotWafer.Item1, pjSlotWafer.Item2);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return SortBySerialMode(processingWafers);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取各个模块的Wafer总数量
|
|||
|
|
/// </summary>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private int GetRunWaferCount()
|
|||
|
|
{
|
|||
|
|
var waferCount = 0;
|
|||
|
|
if (_load.HasWafer(0))
|
|||
|
|
{
|
|||
|
|
waferCount++;
|
|||
|
|
}
|
|||
|
|
if (_unload.HasWafer(0))
|
|||
|
|
{
|
|||
|
|
waferCount++;
|
|||
|
|
}
|
|||
|
|
if (_tmRobot.HasWafer(0))
|
|||
|
|
{
|
|||
|
|
waferCount++;
|
|||
|
|
}
|
|||
|
|
foreach (var pm in _lstPms)
|
|||
|
|
{
|
|||
|
|
if (GetModule(pm.Module).HasWafer(0))
|
|||
|
|
{
|
|||
|
|
waferCount++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for (var i = 0; i < _bufferFloors; i++)
|
|||
|
|
{
|
|||
|
|
if (_buffer.HasWafer(i))
|
|||
|
|
{
|
|||
|
|
waferCount++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return waferCount;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private int GetRunWaferCount(ModuleName processIn)
|
|||
|
|
{
|
|||
|
|
var waferCount = 0;
|
|||
|
|
|
|||
|
|
if (_tmRobot.HasWafer(0) && (_tmRobot.CheckWaferNeedProcess(0, processIn) || _tmRobot.GetWaferInfo(0).SubstHists.Select(x => x.locationID).Contains(processIn.ToString())))
|
|||
|
|
{
|
|||
|
|
waferCount++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (GetModule(processIn).HasWafer(0) && (GetModule(processIn).CheckWaferNeedProcess(0, processIn) || GetModule(processIn).GetWaferInfo(0).SubstHists.Select(x => x.locationID).Contains(processIn.ToString())))
|
|||
|
|
{
|
|||
|
|
waferCount++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (var i = 0; i < _bufferFloors; i++)
|
|||
|
|
{
|
|||
|
|
if (_buffer.HasWafer(i) && (_buffer.CheckWaferNeedProcess(i, processIn) || _buffer.GetWaferInfo(i).SubstHists.Select(x => x.locationID).Contains(processIn.ToString())))
|
|||
|
|
{
|
|||
|
|
waferCount++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return waferCount;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取各个模块的石墨盘总数量
|
|||
|
|
/// </summary>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private int GetCurrentTrayCount()
|
|||
|
|
{
|
|||
|
|
var trayCount = 0;
|
|||
|
|
|
|||
|
|
if (_tmRobot.HasTray(0))
|
|||
|
|
{
|
|||
|
|
trayCount++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var pm in _lstPms)
|
|||
|
|
{
|
|||
|
|
if (GetModule(pm.Module).HasTray(0))
|
|||
|
|
{
|
|||
|
|
trayCount++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (var i = 0; i < _bufferFloors; i++)
|
|||
|
|
{
|
|||
|
|
if (_buffer.HasTray(i))
|
|||
|
|
{
|
|||
|
|
trayCount++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return trayCount;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取ProcessJob可以同时运行工艺的Wafer总数
|
|||
|
|
/// </summary>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private int GetCurrentWaferCount()
|
|||
|
|
{
|
|||
|
|
var waferCountDiv = 0;
|
|||
|
|
var cassWaferCount = 0;
|
|||
|
|
foreach (var cj in _controlJobList)
|
|||
|
|
{
|
|||
|
|
if (cj.State == EnumControlJobState.Executing)
|
|||
|
|
{
|
|||
|
|
foreach (var pj in _processJobList)
|
|||
|
|
{
|
|||
|
|
if (pj.ControlJobName == cj.Name && pj.State == EnumProcessJobState.Processing)
|
|||
|
|
{
|
|||
|
|
cassWaferCount += pj.SlotWafers.Count;
|
|||
|
|
foreach (var pjSlotWafer in pj.SlotWafers)
|
|||
|
|
{
|
|||
|
|
var module = GetModule(pjSlotWafer.Item1);
|
|||
|
|
if (module.HasWafer(pjSlotWafer.Item2) && !module.CheckWaferNeedProcess(pjSlotWafer.Item2))
|
|||
|
|
{
|
|||
|
|
waferCountDiv++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return cassWaferCount - waferCountDiv;
|
|||
|
|
|
|||
|
|
//int waferCount = 0;
|
|||
|
|
//if (_load.HasWafer(0))
|
|||
|
|
//{
|
|||
|
|
// waferCount++;
|
|||
|
|
//}
|
|||
|
|
//for (int i = 0; i < 3; i++)
|
|||
|
|
//{
|
|||
|
|
//if (_buffer.HasWafer(i))
|
|||
|
|
// {
|
|||
|
|
// waferCount++;
|
|||
|
|
// }
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
//if (_waferRobot.CheckWaferNeedProcess(0) )
|
|||
|
|
//{
|
|||
|
|
// waferCount++;
|
|||
|
|
//}
|
|||
|
|
//if (_aligner.CheckWaferNeedProcess(0) )
|
|||
|
|
//{
|
|||
|
|
// waferCount++;
|
|||
|
|
//}
|
|||
|
|
|
|||
|
|
//for (int i=0;i<25;i++)
|
|||
|
|
//{
|
|||
|
|
// if (CheckWaferNeedProcess(ModuleName.CassAL, i))
|
|||
|
|
// {
|
|||
|
|
// waferCount++;
|
|||
|
|
// }
|
|||
|
|
// if (CheckWaferNeedProcess(ModuleName.CassAR, i))
|
|||
|
|
// {
|
|||
|
|
// waferCount++;
|
|||
|
|
// }
|
|||
|
|
//}
|
|||
|
|
//return waferCount;
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
#region Module error
|
|||
|
|
|
|||
|
|
public Result MonitorModuleError()
|
|||
|
|
{
|
|||
|
|
var isModuleError = false;
|
|||
|
|
var isPMError = new bool[2];
|
|||
|
|
|
|||
|
|
for (var i = 0; i < isPMError.Length; i++)
|
|||
|
|
{
|
|||
|
|
isPMError[i] = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_tmRobot.IsError)
|
|||
|
|
isModuleError = true;
|
|||
|
|
|
|||
|
|
for (var i = 0; i < _lstPms.Count; i++) // PM出错,不影响其他腔体的传片
|
|||
|
|
{
|
|||
|
|
if (_lstPms[i].IsError)
|
|||
|
|
isPMError[i] = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (isModuleError && !_isModuleErrorPrevious)
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
else if (!isModuleError && _isModuleErrorPrevious)
|
|||
|
|
{
|
|||
|
|
Reset();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (var i = 0; i < _lstPms.Count; i++)
|
|||
|
|
{
|
|||
|
|
if (!isPMError[i] && _isPMErrorPrevious[i])
|
|||
|
|
{
|
|||
|
|
_lstPms[i].ResetTask();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_isPMErrorPrevious[i] = isPMError[i];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_isModuleErrorPrevious = isModuleError;
|
|||
|
|
|
|||
|
|
return Result.RUN;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool CheckModuleHaveWaferWithNoJob(out string reason)
|
|||
|
|
{
|
|||
|
|
reason = "";
|
|||
|
|
if (_controlJobList.Count > 0)
|
|||
|
|
{
|
|||
|
|
reason = "lstControlJobs.Count > 0";
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
if (_buffer.HasWafer(0) || _buffer.HasWafer(1) || _buffer.HasWafer(2))
|
|||
|
|
{
|
|||
|
|
reason = $"Buffer have wafer!";
|
|||
|
|
EV.PostWarningLog(LogSource, reason);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_tmRobot.HasWafer(0))
|
|||
|
|
{
|
|||
|
|
reason = $"TmRobot have wafer!";
|
|||
|
|
EV.PostWarningLog(LogSource, reason);
|
|||
|
|
return true;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!_load.HasWafer(0))
|
|||
|
|
{
|
|||
|
|
reason = $"Load have no wafer!";
|
|||
|
|
EV.PostWarningLog(LogSource, reason);
|
|||
|
|
return true;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!_load.HasTray(0))
|
|||
|
|
{
|
|||
|
|
reason = $"Load have no Tray!";
|
|||
|
|
EV.PostWarningLog(LogSource, reason);
|
|||
|
|
return true;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool CheckWaferNeedProcess(ModuleName module, int waferSlot, ModuleName processIn = ModuleName.System)
|
|||
|
|
{
|
|||
|
|
WaferInfoRt wafer = WaferManager.Instance.GetWafer(module, waferSlot);
|
|||
|
|
|
|||
|
|
if (wafer.IsWaferEmpty)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
if (wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null || wafer.ProcessJob.Sequence.Steps == null)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
if (wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count || wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules == null)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
//if (processIn != ModuleName.System && !wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules.Contains(processIn))
|
|||
|
|
// return false;
|
|||
|
|
|
|||
|
|
var hasPm = false;
|
|||
|
|
for (int i = wafer.NextSequenceStep; i < wafer.ProcessJob.Sequence.Steps.Count; i++)
|
|||
|
|
{
|
|||
|
|
foreach (var stepModule in wafer.ProcessJob.Sequence.Steps[i].StepModules)
|
|||
|
|
{
|
|||
|
|
if (ModuleHelper.IsPm(stepModule))
|
|||
|
|
{
|
|||
|
|
hasPm = true;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (hasPm)
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (processIn == ModuleName.System && !hasPm)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private bool SkipWaferProcessStep(WaferInfoRt wafer)
|
|||
|
|
{
|
|||
|
|
if (wafer.IsWaferEmpty)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
if (wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null || wafer.ProcessJob.Sequence.Steps == null)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
if (wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count || wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules == null)
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
for (int i = wafer.NextSequenceStep; i < wafer.ProcessJob.Sequence.Steps.Count; i++)
|
|||
|
|
{
|
|||
|
|
foreach (var stepModule in wafer.ProcessJob.Sequence.Steps[i].StepModules)
|
|||
|
|
{
|
|||
|
|
if (ModuleHelper.IsPm(stepModule))
|
|||
|
|
{
|
|||
|
|
if(wafer.NextSequenceStep == 0)
|
|||
|
|
{
|
|||
|
|
wafer.NextSequenceStep = wafer.ProcessJob.Sequence.Steps.Count - 1;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
wafer.NextSequenceStep = i + 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|