SETMCycle.cs 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145
  1. using Aitex.Core.Common;
  2. using Aitex.Core.RT.DataCenter;
  3. using Aitex.Core.RT.Fsm;
  4. using Aitex.Core.RT.Log;
  5. using Aitex.Core.RT.RecipeCenter;
  6. using Aitex.Core.RT.Routine;
  7. using Aitex.Core.RT.SCCore;
  8. using Aitex.Core.Util;
  9. using Aitex.Sorter.Common;
  10. using MECF.Framework.Common.Equipment;
  11. using MECF.Framework.Common.Jobs;
  12. using MECF.Framework.Common.Schedulers;
  13. using MECF.Framework.Common.SubstrateTrackings;
  14. using MECF.Framework.RT.EquipmentLibrary.HardwareUnits.Robot;
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Diagnostics;
  18. using System.Linq;
  19. using System.Text;
  20. using System.Threading.Tasks;
  21. using Venus_Core;
  22. using Venus_RT.Modules.Schedulers;
  23. using Venus_RT.Scheduler;
  24. using static Aitex.Core.Util.SubscriptionAttribute;
  25. namespace Venus_RT.Modules
  26. {
  27. public class SETMCycle : ModuleRoutineBase, IRoutine
  28. {
  29. enum TMCycleStep
  30. {
  31. Start,
  32. ReturnBack,
  33. Cycling,
  34. End,
  35. }
  36. private List<ControlJobInfo> _lstControlJobs = new List<ControlJobInfo>();
  37. private List<ProcessJobInfo> _lstProcessJobs = new List<ProcessJobInfo>();
  38. private Dictionary<ModuleName, SchedulerModule> dictSchedulers = new Dictionary<ModuleName, SchedulerModule>();
  39. private List<ModuleName> tmCycleRoutine = new List<ModuleName>()
  40. {
  41. ModuleName.VCE1,
  42. ModuleName.PMA,
  43. ModuleName.PMB,
  44. ModuleName.PMC,
  45. ModuleName.VCE1
  46. };
  47. private int CycleNum = 0;
  48. private bool _isCycleMode;
  49. private ModuleName _sourceModule = ModuleName.VCE1;
  50. private ModuleName _destinationModule = ModuleName.VCE1;
  51. private int _sourceSlotNumber = 25;
  52. //private int _destinationSlotNumber = 25;
  53. private SchedulerSETMRobot _TMRobot = (SchedulerSETMRobot)Singleton<TransferModule>.Instance.GetScheduler(ModuleName.SETM);
  54. private SchedulerFACallback _faCallback;
  55. private SchedulerDBCallback _dbCallback;
  56. //private readonly int INVALID_SLOT = -1;
  57. private Queue<MoveItem> _runningItems = new Queue<MoveItem>();
  58. private Queue<MoveItem> _CycleWafers = new Queue<MoveItem>();
  59. private RState _cycleState = RState.Init;
  60. private Stopwatch _cycleWatch = new Stopwatch();
  61. private List<MoveItem> _moveQueue = new List<MoveItem>();
  62. private List<MovingStatus> movingStatus = new List<MovingStatus>();
  63. private double _throughput = 0;
  64. private DateTime _starttime;
  65. public int? CycleIndex;
  66. private int CycledWafer => _currentWafer + _pastWafer;
  67. private int _currentWafer;
  68. private int _pastWafer;
  69. private bool _IsFirstLot = true;
  70. private bool IsSequenceCycle = false; //判断是否跑货
  71. #region 构造函数
  72. public SETMCycle(ModuleName module) : base(module)
  73. {
  74. Name = "TM Cycle";
  75. void _initMoudle(ModuleName name, SchedulerModule sche)
  76. {
  77. if (ModuleHelper.IsInstalled(name))
  78. {
  79. dictSchedulers[name] = sche;
  80. }
  81. }
  82. _initMoudle(ModuleName.VCE1, new SchedulerVCE(ModuleName.VCE1));
  83. _initMoudle(ModuleName.PMA, new SchedulerPM(ModuleName.PMA));
  84. _initMoudle(ModuleName.PMB, new SchedulerPM(ModuleName.PMB));
  85. _initMoudle(ModuleName.PMC, new SchedulerPM(ModuleName.PMC));
  86. _initMoudle(ModuleName.PMD, new SchedulerPM(ModuleName.PMD));
  87. _faCallback = new SchedulerFACallback();
  88. _dbCallback = new SchedulerDBCallback();
  89. _currentWafer = 0;
  90. _pastWafer = 0;
  91. DATA.Subscribe("SEScheduler.CycledWafer", ()=> CycledWafer );
  92. DATA.Subscribe("SEScheduler.CycleSetPoint", ()=> CycleNum);
  93. DATA.Subscribe("SEScheduler.CycleCount", ()=> CycleIndex);
  94. DATA.Subscribe("SEScheduler.ThroughPut", ()=> _throughput);
  95. }
  96. #endregion
  97. #region Cycle
  98. //run货模式
  99. public RState StartJob(string jobName)
  100. {
  101. if (_TMRobot.IsVCESlitDoorClosed)
  102. {
  103. LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"VCE SlitDoor is Close cannot run!");
  104. return RState.Failed;
  105. }
  106. //sequenceCycle
  107. CycleIndex = 0;
  108. _pastWafer = 0;
  109. _currentWafer = 0;
  110. CycleNum = SC.GetValue<int>("System.CycleCount");
  111. _CycleWafers.Clear();
  112. IsSequenceCycle = true;
  113. ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == jobName);
  114. if (cj == null)
  115. {
  116. LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"start job rejected, not found job with id {jobName}");
  117. return RState.Failed;
  118. }
  119. if (cj.State == EnumControlJobState.WaitingForStart)
  120. {
  121. cj.SetState(EnumControlJobState.Executing);
  122. //PreJobClean(cj);
  123. cj.JetState = EnumJetCtrlJobState.Quequed;
  124. cj.StartTime = DateTime.Now;
  125. if (_IsFirstLot)
  126. {
  127. _dbCallback.LotCreated(cj);
  128. _IsFirstLot = false;
  129. }
  130. _faCallback.JobStarted(cj, GetFirstProcessJob(cj));
  131. int maxslot = 0;
  132. cj.LotWafers.ForEach(x => maxslot = x.OriginSlot > maxslot ? x.OriginSlot : maxslot);
  133. //waiting status means no use
  134. movingStatus = new List<MovingStatus>();
  135. movingStatus.AddRange(new MovingStatus[maxslot + 1]);
  136. for (int i = 0; i < movingStatus.Count; i++)
  137. movingStatus[i] = MovingStatus.Waiting;
  138. foreach (var wafer in cj.LotWafers)
  139. {
  140. WaferInfo currentWafer = WaferManager.Instance.GetWafer((ModuleName)wafer.OriginStation, wafer.OriginSlot);
  141. WaferManager.Instance.UpdateWaferProcessStatus((ModuleName)wafer.OriginStation, wafer.OriginSlot, EnumWaferProcessStatus.Idle);
  142. currentWafer.ProcessJob = _lstProcessJobs.Find(x => x.Sequence.Name == wafer.SequenceName);
  143. if (currentWafer.ProcessJob.Sequence.Steps[0].StepParameter.ContainsValue("VPA"))
  144. movingStatus[wafer.OriginSlot] = MovingStatus.WaitAlign;
  145. else
  146. movingStatus[wafer.OriginSlot] = MovingStatus.WaitProcess;
  147. }
  148. }
  149. if (!_cycleWatch.IsRunning)
  150. {
  151. _cycleWatch.Restart();
  152. }
  153. _starttime = DateTime.Now;
  154. _cycleState = RState.Running;
  155. return _cycleState;
  156. }
  157. //processJob(sequence num) ControlJob(1)
  158. public void CreateJob(Dictionary<string, object> param)
  159. {
  160. _isCycleMode = SC.GetValue<bool>("System.IsCycleMode");
  161. CycleNum = _isCycleMode ? SC.GetValue<int>("System.CycleCount") : 0;
  162. string[] slotSequence = (string[])param["SlotSequence"];
  163. string jobId = (string)param["JobId"];
  164. string module = (string)param["Module"];
  165. string lotId = jobId;
  166. if (param.ContainsKey("LotId"))
  167. lotId = (string)param["LotId"];
  168. if (!ModuleHelper.IsVCE(ModuleHelper.Converter(module)))
  169. {
  170. LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"{module} should be VCE");
  171. _faCallback.JobCreateFailed(module, lotId, jobId, "");
  172. return;
  173. }
  174. if (string.IsNullOrEmpty(jobId))
  175. {
  176. jobId = "CJ_Local_" + module;
  177. }
  178. if (_lstControlJobs.Exists(x => x.Name == jobId))
  179. {
  180. LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"{jobId} already created");
  181. _faCallback.JobCreateFailed(module, lotId, jobId, "");
  182. return;
  183. }
  184. ControlJobInfo cj = new ControlJobInfo();
  185. cj.Name = jobId;
  186. cj.Module = module;
  187. cj.LotName = lotId;
  188. cj.LotInnerId = Guid.NewGuid();
  189. cj.LotWafers = new List<WaferInfo>();
  190. cj.SetState(EnumControlJobState.WaitingForStart);
  191. cj.JetState = EnumJetCtrlJobState.Created;
  192. Dictionary<string, bool[]> seqSlot = new Dictionary<string, bool[]>();
  193. Dictionary<string, List<Tuple<ModuleName, int>>> seqSlotWafers = new Dictionary<string, List<Tuple<ModuleName, int>>>();
  194. Dictionary<string, string> indexSequence = new Dictionary<string, string>();
  195. bool enableGroupBySequence = SC.GetValue<bool>("Scheduler.GroupWaferBySequence");
  196. for (int i = 0; i < SC.GetValue<int>("VCE1.SlotNumber"); i++)
  197. {
  198. if (string.IsNullOrEmpty(slotSequence[i]) || string.IsNullOrEmpty(slotSequence[i].Trim()))
  199. continue;
  200. string groupName = enableGroupBySequence ? slotSequence[i].Trim() : i.ToString();
  201. indexSequence[groupName] = slotSequence[i];
  202. if (!seqSlot.ContainsKey(groupName))
  203. {
  204. seqSlot[groupName] = new bool[SC.GetValue<int>("VCE1.SlotNumber")];
  205. }
  206. if (!seqSlotWafers.ContainsKey(groupName))
  207. {
  208. seqSlotWafers[groupName] = new List<Tuple<ModuleName, int>>();
  209. }
  210. seqSlot[groupName][i] = true;
  211. if (!WaferManager.Instance.CheckHasWafer(module, i))
  212. {
  213. LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"job wafer: {module} slot {i + 1} not in the carrier");
  214. return;
  215. }
  216. if (!WaferManager.Instance.CheckWafer(ModuleHelper.Converter(module), i, WaferStatus.Normal))
  217. {
  218. LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"job wafer: {module} slot {i + 1} status is {WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).Status}");
  219. _faCallback.JobCreateFailed(module, lotId, jobId, "");
  220. return;
  221. }
  222. if (WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).ProcessState != EnumWaferProcessStatus.Idle)
  223. {
  224. LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"job wafer: {module} slot {i + 1} process status is {WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).ProcessState}");
  225. _faCallback.JobCreateFailed(module, lotId, jobId, "");
  226. return;
  227. }
  228. cj.LotWafers.Add(WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i));
  229. WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).SequenceName = slotSequence[i];
  230. seqSlotWafers[groupName].Add(Tuple.Create(ModuleHelper.Converter(module), i));
  231. cj.JobWaferSize = WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).Size;
  232. LOG.Write(eEvent.EV_ROUTER, ModuleName.System, $"Assigned wafer job, wafer {module}.{i + 1}, sequence: {slotSequence[i]}");
  233. }
  234. if (seqSlotWafers.Count == 0)
  235. {
  236. LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"job has not assign wafer");
  237. _faCallback.JobCreateFailed(module, lotId, jobId, "");
  238. return;
  239. }
  240. List<ProcessJobInfo> pjs = new List<ProcessJobInfo>();
  241. string[] seqs = seqSlot.Keys.ToArray();
  242. for (int i = 0; i < seqs.Length; i++)
  243. {
  244. ProcessJobInfo pj = new ProcessJobInfo();
  245. pj.Name = jobId + "_" + (i + 1);
  246. pj.Sequence = SequenceInfoHelper.GetInfo(indexSequence[seqs[i]]);
  247. pj.ControlJobName = cj.Name;
  248. pj.LotName = lotId;
  249. pj.SlotWafers = seqSlotWafers[seqs[i]];
  250. pj.SetState(EnumProcessJobState.Queued);
  251. if (!CheckSequencePmReady(pj.Sequence, null, out _, out string reason))
  252. {
  253. LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"no valid chamber for the {reason}");
  254. _faCallback.JobCreateFailed(module, lotId, jobId, "");
  255. return;
  256. }
  257. if (!RouteManager.IsATMMode && !CheckSequenceRecipeFileValid(pj.Sequence, out reason))
  258. {
  259. LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"recipe file not valid in the sequence, {reason}");
  260. _faCallback.JobCreateFailed(module, lotId, jobId, "");
  261. return;
  262. }
  263. pjs.Add(pj);
  264. }
  265. _dbCallback.LotUpdate(cj);
  266. foreach (var pj in pjs)
  267. {
  268. cj.ProcessJobNameList.Add(pj.Name);
  269. _lstProcessJobs.Add(pj);
  270. }
  271. _lstControlJobs.Add(cj);
  272. }
  273. public void AbortJob(string jobName)
  274. {
  275. }
  276. public RState Start(params object[] objs)
  277. {
  278. if (_TMRobot.IsVCESlitDoorClosed)
  279. {
  280. LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"VCE SlitDoor is Close cannot run!");
  281. return RState.Failed;
  282. }
  283. //普通Cycle
  284. if (objs.Length == 2)
  285. {
  286. var modules = ((string[])objs[0]).ToList();
  287. if (modules.Count >= 2)
  288. tmCycleRoutine.Clear();
  289. foreach (var mod in modules)
  290. {
  291. try
  292. {
  293. ModuleName module = ModuleHelper.Converter(mod);
  294. tmCycleRoutine.Add(module);
  295. }
  296. catch (Exception ex)
  297. {
  298. LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"Invalid module string: {mod}, Exception:{ex}");
  299. return RState.Failed;
  300. }
  301. }
  302. CycleNum = (int)objs[1];
  303. CycleNum = SC.GetValue<int>("System.CycleCount");
  304. _CycleWafers.Clear();
  305. CycleIndex = 0;
  306. for (int i = 0; i < _sourceSlotNumber; i++)
  307. {
  308. if (WaferManager.Instance.CheckHasWafer(_sourceModule, i))
  309. _CycleWafers.Enqueue(new MoveItem(_sourceModule, i, _destinationModule, i, Hand.None));
  310. }
  311. IsSequenceCycle = false;
  312. _cycleState = RState.Running;
  313. }
  314. else
  315. {
  316. LOG.Write(eEvent.ERR_TM,Module,"Invalid parameter for cycle!");
  317. _cycleState = RState.Failed;
  318. }
  319. return _cycleState;
  320. }
  321. public RState Monitor()
  322. {
  323. if (_cycleState == RState.Running)
  324. {
  325. //run sequence
  326. if (IsSequenceCycle)
  327. {
  328. CheckCycleDone();
  329. SETMRobotTask();
  330. SERunTMRobotTask();
  331. SEPMTask();
  332. }
  333. //only TMCycle
  334. else
  335. {
  336. Cycling();
  337. //PMProcess();
  338. }
  339. }
  340. return _cycleState;
  341. }
  342. #endregion
  343. #region 推进|检查|计数
  344. private void CheckCycleDone()
  345. {
  346. //how to calculate current wafer? => _currentWafer
  347. //1. wafer in vce
  348. //2. wafer is Idle
  349. _currentWafer = 0;
  350. for (int index = 0; index < movingStatus.Count;++index)
  351. {
  352. if(WaferManager.Instance.CheckHasWafer(ModuleName.VCE1,index) && movingStatus[index] == MovingStatus.Idle)
  353. _currentWafer ++;
  354. }
  355. //throughput = CycledWafer/time, time(h)
  356. _throughput = Math.Round(CycledWafer / (DateTime.Now - _starttime).TotalSeconds * 3600,2);
  357. //how to confirm a sequence is over?
  358. //3、wafer is all in vce
  359. //2、all status is over => idle
  360. //1、TMRobot and PM is available
  361. if (_TMRobot.IsAvailable && AllPMIsAvailable() && ALLStatusIsOver() && WaferAllInVCE())
  362. {
  363. ++CycleIndex;
  364. if (CycleIndex >= CycleNum)
  365. {
  366. _lstControlJobs.Find(lcj => lcj.State == EnumControlJobState.Executing).SetState(EnumControlJobState.WaitingForStart);
  367. _cycleState = RState.End;
  368. }
  369. else
  370. {
  371. _pastWafer += _currentWafer;
  372. ControlJobInfo cj = _lstControlJobs.Find(lcj => lcj.State == EnumControlJobState.Executing);
  373. _currentWafer = 0;
  374. int maxslot = 0;
  375. cj.LotWafers.ForEach(x => maxslot = x.OriginSlot > maxslot ? x.OriginSlot : maxslot);
  376. //waiting status means no use
  377. movingStatus = new List<MovingStatus>();
  378. movingStatus.AddRange(new MovingStatus[maxslot + 1]);
  379. for (int i = 0; i < movingStatus.Count; i++)
  380. movingStatus[i] = MovingStatus.Waiting;
  381. foreach (var wafer in cj.LotWafers)
  382. {
  383. WaferInfo currentWafer = WaferManager.Instance.GetWafer((ModuleName)wafer.OriginStation, wafer.OriginSlot);
  384. WaferManager.Instance.UpdateWaferProcessStatus((ModuleName)wafer.OriginStation, wafer.OriginSlot, EnumWaferProcessStatus.Idle);
  385. currentWafer.ProcessJob = _lstProcessJobs.Find(x => x.Sequence.Name == wafer.SequenceName);
  386. if (currentWafer.ProcessJob.Sequence.Steps[0].StepParameter.ContainsValue("VPA"))
  387. movingStatus[wafer.OriginSlot] = MovingStatus.WaitAlign;
  388. else
  389. movingStatus[wafer.OriginSlot] = MovingStatus.WaitProcess;
  390. }
  391. }
  392. }
  393. }
  394. //movingStatus is all Idle or no use
  395. private bool ALLStatusIsOver()
  396. {
  397. bool flag = true;
  398. movingStatus.ForEach(x => flag = (x == MovingStatus.Idle || x == MovingStatus.Waiting) && flag) ;
  399. return flag;
  400. }
  401. private bool WaferAllInVCE()
  402. {
  403. ControlJobInfo cj = _lstControlJobs.Find(lcj => lcj.State == EnumControlJobState.Executing);
  404. bool flag = true;
  405. cj.LotWafers.ForEach(wafer => flag = WaferManager.Instance.CheckHasWafer((ModuleName)wafer.OriginStation, wafer.OriginSlot) && flag);
  406. return flag;
  407. }
  408. private bool AllPMIsAvailable()
  409. {
  410. foreach (KeyValuePair<ModuleName,SchedulerModule> pair in dictSchedulers)
  411. if (ModuleHelper.IsPm(pair.Key) && !pair.Value.IsAvailable)
  412. return false;
  413. return true;
  414. }
  415. private void SERunTMRobotTask()
  416. {
  417. if (_TMRobot.IsAvailable)
  418. {
  419. if (_moveQueue.Count > 0 &&_TMRobot.PostMoveItems(_moveQueue.ToArray()))
  420. {
  421. foreach (var item in _moveQueue)
  422. {
  423. var wafer = WaferManager.Instance.GetWafer(item.SourceModule, item.SourceSlot);
  424. if (wafer.IsEmpty)
  425. {
  426. // post alarm
  427. _cycleState = RState.Failed;
  428. LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"Cannot run TM moving task as Get {item.SourceModule}{item.SourceModule} Wafer Info failed");
  429. return;
  430. }
  431. }
  432. _moveQueue.Clear();
  433. }
  434. }
  435. }
  436. private void SERunPMTask()
  437. {
  438. }
  439. private void SETMRobotTask()
  440. {
  441. //tm is free
  442. if (_TMRobot.IsAvailable)
  443. {
  444. CheckWaferFromVceNeedAlign();
  445. PrepareWaferOnRBToPM();
  446. GetBackWafer();
  447. GetNextWaferPickFromVCE();
  448. }
  449. }
  450. private void SEPMTask()
  451. {
  452. //foreach all pms and the wafer's receipe
  453. foreach (KeyValuePair<ModuleName,SchedulerModule> dict in dictSchedulers)
  454. {
  455. //pm has wafer && pm is available => find the wafer's receipe
  456. if (ModuleHelper.IsPm(dict.Key))
  457. {
  458. if (dict.Value.IsAvailable)
  459. {
  460. foreach (var ctrljob in _lstControlJobs)
  461. {
  462. //Port is run
  463. if (ctrljob.State == EnumControlJobState.Executing)
  464. {
  465. if (WaferManager.Instance.CheckHasWafer(dict.Key, 0))
  466. {
  467. WaferInfo currentWafer = WaferManager.Instance.GetWafer(dict.Key, 0);
  468. switch (movingStatus[currentWafer.OriginSlot])
  469. {
  470. case MovingStatus.WaitProcess:
  471. dict.Value.EventWaferArrived?.Invoke(this, new WaferMoveArgs(ModuleName.TMRobot, 0, dict.Key, 0));
  472. movingStatus[currentWafer.OriginSlot] = MovingStatus.StartProcess;
  473. break;
  474. case MovingStatus.Processing:
  475. movingStatus[currentWafer.OriginSlot] = MovingStatus.Idle;
  476. break;
  477. case MovingStatus.StartProcess:
  478. movingStatus[currentWafer.OriginSlot] = MovingStatus.Processing;
  479. break;
  480. default:
  481. break;
  482. }
  483. }
  484. }
  485. }
  486. }
  487. }
  488. }
  489. }
  490. //get the wafer need back
  491. private void GetBackWafer()
  492. {
  493. //find
  494. foreach (KeyValuePair<ModuleName, SchedulerModule> dict in dictSchedulers)
  495. {
  496. //pm has wafer && pm is available => find the wafer's receipe
  497. if (ModuleHelper.IsPm(dict.Key) && WaferManager.Instance.CheckHasWafer(dict.Key, 0) && WaferManager.Instance.CheckNoWafer(ModuleName.VPA, 0))
  498. {
  499. WaferInfo currentWafer = WaferManager.Instance.GetWafer(dict.Key, 0);
  500. //if the wafer has been processed, we need to pickfrom it.
  501. if (dict.Value.IsAvailable && movingStatus[currentWafer.OriginSlot] == MovingStatus.Idle && _TMRobot.IsAvailable)
  502. {
  503. _moveQueue.Add(new MoveItem(dict.Key,0, (ModuleName)currentWafer.OriginStation, currentWafer.OriginSlot,0));
  504. //one hand has wafer
  505. if (WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, 0)
  506. && movingStatus[WaferManager.Instance.GetWafer(ModuleName.TMRobot, 0).OriginSlot] == MovingStatus.WaitProcess
  507. && WaferManager.Instance.CheckNoWafer(ModuleName.TMRobot, 1))
  508. {
  509. WaferInfo wafer = WaferManager.Instance.GetWafer(ModuleName.TMRobot, 0);
  510. if (wafer.ProcessJob.Sequence.PMs.Contains(dict.Key))
  511. {
  512. _moveQueue.Add(new MoveItem(ModuleName.TMRobot,0,dict.Key,0,0));
  513. }
  514. }
  515. else if (WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, 1)
  516. && movingStatus[WaferManager.Instance.GetWafer(ModuleName.TMRobot, 1).OriginSlot] == MovingStatus.WaitProcess
  517. && WaferManager.Instance.CheckNoWafer(ModuleName.TMRobot, 0))
  518. {
  519. WaferInfo wafer = WaferManager.Instance.GetWafer(ModuleName.TMRobot, 1);
  520. if (wafer.ProcessJob.Sequence.PMs.Contains(dict.Key))
  521. {
  522. _moveQueue.Add(new MoveItem(ModuleName.TMRobot, 1, dict.Key, 0, (Hand)1));
  523. }
  524. }
  525. return;
  526. }
  527. }
  528. }
  529. }
  530. //get the wafer need Pick From vce
  531. private void GetNextWaferPickFromVCE()
  532. {
  533. if (_TMRobot.IsAvailable)
  534. {
  535. Dictionary<int, ModuleName> nextslot = new Dictionary<int, ModuleName>();
  536. foreach (var ctrljob in _lstControlJobs)
  537. {
  538. //Port is run
  539. if (ctrljob.State == EnumControlJobState.Executing)
  540. {
  541. bool nohastowaitpm = false;
  542. foreach (WaferInfo wafer in ctrljob.LotWafers)
  543. {
  544. //if need pm available =>pick 2
  545. if (!wafer.IsEmpty
  546. && SequenceNeedPMsAvailable(_lstProcessJobs.Find(x => x.Sequence.Name == wafer.SequenceName).Sequence.PMs)
  547. && ModuleHelper.IsVCE((ModuleName)wafer.OriginStation)
  548. && (movingStatus[wafer.OriginSlot] == MovingStatus.WaitProcess))
  549. {
  550. nohastowaitpm = true;
  551. ModuleName _destination = SearchPM(_lstProcessJobs.Find(x => x.Sequence.Name == wafer.SequenceName).Sequence.PMs, nextslot);
  552. nextslot.Add(wafer.OriginSlot, _destination);
  553. _moveQueue.Add(new MoveItem(ModuleName.VCE1, wafer.OriginSlot, _destination, 0, 0));
  554. if (nextslot.Count >= 2 || _moveQueue.Count >= 4)
  555. {
  556. return;
  557. }
  558. }
  559. //if need align available => pick 2
  560. if (!wafer.IsEmpty
  561. && WaferManager.Instance.CheckNoWafer(ModuleName.VPA, 0)
  562. && ModuleHelper.IsVCE((ModuleName)wafer.OriginStation)
  563. && SequenceNeedPMsAvailable(_lstProcessJobs.Find(x => x.Sequence.Name == wafer.SequenceName).Sequence.PMs)
  564. && (movingStatus[wafer.OriginSlot] == MovingStatus.WaitAlign))
  565. {
  566. nohastowaitpm = true;
  567. ModuleName _destination = ModuleName.TMRobot;
  568. //double pick
  569. if (WaferManager.Instance.CheckNoWafer(ModuleName.TMRobot, 0) && WaferManager.Instance.CheckNoWafer(ModuleName.TMRobot, 1))
  570. {
  571. nextslot.Add(wafer.OriginSlot, _destination);
  572. _moveQueue.Add(new MoveItem(ModuleName.VCE1, wafer.OriginSlot, _destination, 0, 0));
  573. }
  574. if (nextslot.Count >= 2 || _moveQueue.Count >= 4)
  575. {
  576. return;
  577. }
  578. }
  579. }
  580. //if all need PM unavailable =>pick 1 wait for swap
  581. //or 1 can input pm => pick 2, A in pm ,B wait for swap
  582. if (nextslot.Count == 0 || nohastowaitpm && nextslot.Count == 1
  583. && WaferManager.Instance.CheckNoWafer(ModuleName.TMRobot,0)
  584. && WaferManager.Instance.CheckNoWafer(ModuleName.TMRobot,1)
  585. && WaferManager.Instance.CheckNoWafer(ModuleName.VPA, 0))
  586. {
  587. foreach (WaferInfo wafer in ctrljob.LotWafers)
  588. {
  589. if (wafer.NextSequenceStep == 0 && !wafer.IsEmpty
  590. && ModuleHelper.IsVCE((ModuleName)wafer.SourceStation))
  591. {
  592. ModuleName _destination = _lstProcessJobs.Find(x => x.Sequence.Name == wafer.SequenceName).Sequence.PMs[0];
  593. nextslot.Add(wafer.SourceSlot, _destination);
  594. foreach (KeyValuePair<int, ModuleName> slot in nextslot)
  595. {
  596. _moveQueue.Add(new MoveItem(ModuleName.VCE1, slot.Key, slot.Value, 0, 0));
  597. }
  598. return;
  599. }
  600. if (movingStatus[wafer.OriginSlot] == MovingStatus.WaitAlign && !wafer.IsEmpty
  601. && WaferManager.Instance.CheckHasWafer((ModuleName)wafer.OriginStation, wafer.OriginSlot)
  602. && WaferManager.Instance.CheckNoWafer(ModuleName.TMRobot, 0)
  603. && WaferManager.Instance.CheckNoWafer(ModuleName.TMRobot, 1)
  604. && _moveQueue.Count == 0
  605. )
  606. {
  607. //ModuleName _destination = ModuleName.TMRobot;
  608. _moveQueue.Add(new MoveItem((ModuleName)wafer.OriginStation, wafer.OriginSlot, ModuleName.TMRobot, 0, 0));
  609. return;
  610. }
  611. }
  612. }
  613. }
  614. }
  615. return;
  616. }
  617. }
  618. private void PrepareWaferOnRBToPM()
  619. {
  620. //tm robot has wafer and wait for process which means have to go to PM
  621. if (WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, 0)
  622. && movingStatus[WaferManager.Instance.GetWafer(ModuleName.TMRobot, 0).OriginSlot] == MovingStatus.WaitProcess
  623. )
  624. {
  625. //find PM has no wafer
  626. WaferInfo wafer = WaferManager.Instance.GetWafer(ModuleName.TMRobot, 0);
  627. if (SequenceNeedPMsAvailable(wafer.ProcessJob.Sequence.PMs))
  628. {
  629. ModuleName _destination = SearchPM(wafer.ProcessJob.Sequence.PMs, new Dictionary<int, ModuleName>());
  630. if (ModuleHelper.IsPm(_destination) && _TMRobot.IsAvailable)
  631. {
  632. _moveQueue.Add(new MoveItem(ModuleName.TMRobot, 0, _destination, 0, 0));
  633. SERunTMRobotTask();
  634. return;
  635. }
  636. }
  637. }
  638. if (WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, 1)
  639. && movingStatus[WaferManager.Instance.GetWafer(ModuleName.TMRobot, 1).OriginSlot] == MovingStatus.WaitProcess)
  640. {
  641. WaferInfo wafer = WaferManager.Instance.GetWafer(ModuleName.TMRobot, 1);
  642. if (SequenceNeedPMsAvailable(wafer.ProcessJob.Sequence.PMs))
  643. {
  644. ModuleName _destination = SearchPM(wafer.ProcessJob.Sequence.PMs, new Dictionary<int, ModuleName>());
  645. if (ModuleHelper.IsPm(_destination) && _TMRobot.IsAvailable)
  646. {
  647. _moveQueue.Add(new MoveItem(ModuleName.TMRobot, 1, _destination, 0, (Hand)1));
  648. SERunTMRobotTask();
  649. return;
  650. }
  651. }
  652. }
  653. }
  654. private void CheckWaferFromVceNeedAlign()
  655. {
  656. //if has wafer and need align and align is empty => goto PA
  657. if (WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot,0)
  658. && movingStatus[WaferManager.Instance.GetWafer(ModuleName.TMRobot, 0).OriginSlot] == MovingStatus.WaitAlign
  659. && WaferManager.Instance.CheckNoWafer(ModuleName.VPA, 0)
  660. &&_TMRobot.IsAvailable)
  661. {
  662. _moveQueue.Add(new MoveItem(ModuleName.TMRobot, 0,ModuleName.VPA, 0, 0));
  663. movingStatus[WaferManager.Instance.GetWafer(ModuleName.TMRobot, 0).OriginSlot] = MovingStatus.StartAlign;
  664. SERunTMRobotTask();
  665. return;
  666. }
  667. if (WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, 1)
  668. && movingStatus[WaferManager.Instance.GetWafer(ModuleName.TMRobot, 1).OriginSlot] == MovingStatus.WaitAlign
  669. && WaferManager.Instance.CheckNoWafer(ModuleName.VPA, 0)
  670. && _TMRobot.IsAvailable)
  671. {
  672. _moveQueue.Add(new MoveItem(ModuleName.TMRobot, 1, ModuleName.VPA, 0, (Hand)1));
  673. movingStatus[WaferManager.Instance.GetWafer(ModuleName.TMRobot, 1).OriginSlot] = MovingStatus.StartAlign;
  674. SERunTMRobotTask();
  675. return;
  676. }
  677. //PA has wafer need align =>
  678. if (WaferManager.Instance.CheckHasWafer(ModuleName.VPA, 0) && _TMRobot.IsAvailable)
  679. {
  680. WaferInfo currentwafer = WaferManager.Instance.GetWafer(ModuleName.VPA, 0);
  681. switch (movingStatus[currentwafer.OriginSlot])
  682. {
  683. case MovingStatus.StartAlign:
  684. if(_TMRobot.IsAvailable)
  685. {
  686. float angle = float.Parse(currentwafer.ProcessJob.Sequence.Steps[0].StepParameter["AlignAngle"].ToString());
  687. _TMRobot.Align(angle);
  688. movingStatus[currentwafer.OriginSlot] = MovingStatus.Aligning;
  689. SERunTMRobotTask();
  690. return;
  691. }
  692. break;
  693. case MovingStatus.Aligning:
  694. movingStatus[currentwafer.OriginSlot] = MovingStatus.WaitProcess;
  695. if (WaferManager.Instance.CheckNoWafer(ModuleName.TMRobot, 0) && _TMRobot.IsAvailable)
  696. {
  697. _moveQueue.Add(new MoveItem(ModuleName.VPA, 0, ModuleName.TMRobot, 0, 0));
  698. SERunTMRobotTask();
  699. return;
  700. }
  701. if (WaferManager.Instance.CheckNoWafer(ModuleName.TMRobot, 1) && _TMRobot.IsAvailable)
  702. {
  703. _moveQueue.Add(new MoveItem(ModuleName.VPA, 0, ModuleName.TMRobot, 1, (Hand)1));
  704. SERunTMRobotTask();
  705. return;
  706. }
  707. break;
  708. }
  709. }
  710. }
  711. private void PrepareTMMoveitems()
  712. {
  713. }
  714. private void Cycling()
  715. {
  716. //所有的PM都Idle TM都空闲的情况
  717. if (IsAllNeedPMsAvailabe() && IsModuleAvailable(_destinationModule) && IsModuleAvailable(_sourceModule) && _TMRobot.IsAvailable)
  718. {
  719. //如果PM没有wafer tm也闲置 但Cycle数量未达到目标
  720. if (!PMsHasWafers() && _TMRobot.IsAvailable && _CycleWafers.Count == 0)
  721. {
  722. ++CycleIndex;
  723. if (CycleIndex < CycleNum)
  724. {
  725. for (int i = 0; i < _sourceSlotNumber; i++)
  726. {
  727. if (WaferManager.Instance.CheckHasWafer(_sourceModule, i))
  728. _CycleWafers.Enqueue(new MoveItem(_sourceModule, i, _destinationModule, i, Hand.None));
  729. }
  730. }
  731. else
  732. {
  733. _cycleState = RState.End;
  734. return;
  735. }
  736. }
  737. //wafer返回
  738. if (PMsHasWafers())
  739. {
  740. var pmSlots = GetReadyOutPMs();
  741. //var inSlots = GetReadyInSlot(_destinationModule, _destinationSlotNumber);
  742. for (int i = 0; i < pmSlots.Count ; i++)
  743. {
  744. WaferInfo _endwafer = WaferManager.Instance.GetWafer(pmSlots[i], 0);
  745. int _endslot = _endwafer.OriginSlot;
  746. _runningItems.Enqueue(new MoveItem(pmSlots[i], 0, _destinationModule, _endslot, Hand.Both));
  747. }
  748. }
  749. else
  750. {
  751. //进腔环节 可在此处根据PM的角度 增加进入角度
  752. //三个腔体的进入方式
  753. var InPMs = GetReadyInPMs();
  754. if (_CycleWafers.Count > 0 && InPMs.Count >= 1)
  755. {
  756. var item = _CycleWafers.Dequeue();
  757. _runningItems.Enqueue(new MoveItem(item.SourceModule, item.SourceSlot, InPMs[0], 0, Hand.Both));
  758. }
  759. if (_CycleWafers.Count > 0 && InPMs.Count >= 2)
  760. {
  761. var item = _CycleWafers.Dequeue();
  762. _runningItems.Enqueue(new MoveItem(item.SourceModule, item.SourceSlot, InPMs[1], 0, Hand.Both));
  763. }
  764. if (_CycleWafers.Count > 0 && InPMs.Count >= 3)
  765. {
  766. var item = _CycleWafers.Dequeue();
  767. _runningItems.Enqueue(new MoveItem(item.SourceModule, item.SourceSlot, InPMs[2], 0, Hand.Both));
  768. }
  769. }
  770. if (_runningItems.Count > 0)
  771. {
  772. if (_TMRobot.PostMoveItems(_runningItems.ToArray()))
  773. _runningItems.Clear();
  774. }
  775. }
  776. //存在PM可用
  777. //else if (IsExistPMsAvailabe() && IsModuleAvailable(_destinationModule) && IsModuleAvailable(_sourceModule) && _TMRobot.IsAvailable)
  778. //{
  779. // //获得可以用的chamber 向里面传片
  780. //}
  781. }
  782. private void PMProcess()
  783. {
  784. //foreach (ModuleName chamber in tmCycleRoutine)
  785. //{
  786. // if (ModuleHelper.IsPm(chamber) && WaferManager.Instance.CheckHasWafer(chamber, 0) && ModuleHelper.IsInstalled(chamber))
  787. // {
  788. // WaferInfo info = WaferManager.Instance.GetWafer(chamber, 0);
  789. //
  790. // dictSchedulers[chamber].EventWaferArrived?.Invoke(this, new WaferMoveArgs(ModuleName.TMRobot, 0, chamber, 0));
  791. // }
  792. //}
  793. }
  794. public void Abort()
  795. {
  796. CycleIndex = null;
  797. _runningItems.Clear();
  798. _CycleWafers.Clear();
  799. _moveQueue.Clear();
  800. movingStatus.Clear();
  801. foreach(var cj in _lstControlJobs.FindAll(x => x.State == EnumControlJobState.Executing))
  802. cj.SetState(EnumControlJobState.WaitingForStart);
  803. _TMRobot._entityTaskToken = (int)FSM_MSG.NONE;
  804. _cycleState = RState.End;
  805. }
  806. #endregion
  807. #region 搜索函数
  808. //回到的槽位确认 采用补进方式即自底向上填
  809. private List<int> GetReadyInSlot(ModuleName module, int slotCount)
  810. {
  811. List<int> slots = new List<int>();
  812. for (int i = 0; i < slotCount; i++)
  813. {
  814. if (WaferManager.Instance.CheckNoWafer(module, i))
  815. slots.Add(i);
  816. if (slots.Count >= 2)
  817. return slots;
  818. }
  819. return slots;
  820. }
  821. //确认
  822. private List<int> GetReadyOutSlot(ModuleName module, int slotCount)
  823. {
  824. List<int> slots = new List<int>();
  825. for (int i = 0; i < slotCount; i++)
  826. {
  827. if (WaferManager.Instance.CheckHasWafer(module, i))
  828. slots.Add(i);
  829. if (slots.Count >= 2)
  830. return slots;
  831. }
  832. return slots;
  833. }
  834. //确认PM是否可用 且没有wafer
  835. private List<ModuleName> GetReadyInPMs()
  836. {
  837. List<ModuleName> inpm = new List<ModuleName>();
  838. foreach (var module in tmCycleRoutine)
  839. {
  840. if (ModuleHelper.IsPm(module))
  841. {
  842. if (IsModuleAvailable(module) && WaferManager.Instance.CheckNoWafer(module, 0))
  843. {
  844. inpm.Add(module);
  845. if (inpm.Count >= 3)
  846. break;
  847. }
  848. }
  849. }
  850. return inpm;
  851. }
  852. private List<ModuleName> GetReadyOutPMs()
  853. {
  854. List<ModuleName> outpm = new List<ModuleName>();
  855. foreach (var module in tmCycleRoutine)
  856. {
  857. if (ModuleHelper.IsPm(module))
  858. {
  859. if (IsModuleAvailable(module) && WaferManager.Instance.CheckHasWafer(module, 0))
  860. {
  861. outpm.Add(module);
  862. if (outpm.Count >= 3)
  863. break;
  864. }
  865. }
  866. }
  867. return outpm;
  868. }
  869. //PM有wafer
  870. private bool PMsHasWafers()
  871. {
  872. foreach (var module in tmCycleRoutine)
  873. {
  874. if (ModuleHelper.IsPm(module) && ModuleHelper.IsInstalled(module))
  875. {
  876. if (WaferManager.Instance.CheckHasWafer(module, 0))
  877. return true;
  878. }
  879. }
  880. return false;
  881. }
  882. //PM全可用
  883. private bool IsAllNeedPMsAvailabe()
  884. {
  885. foreach (var module in tmCycleRoutine)
  886. {
  887. if (ModuleHelper.IsPm(module) && ModuleHelper.IsInstalled(module))
  888. {
  889. if (!IsModuleAvailable(module))
  890. return false;
  891. }
  892. }
  893. return true;
  894. }
  895. //PM存在可用
  896. private bool SequenceNeedPMsAvailable(List<ModuleName> needPMs)
  897. {
  898. foreach (var module in needPMs)
  899. {
  900. if (ModuleHelper.IsPm(module) && ModuleHelper.IsInstalled(module))
  901. {
  902. if (IsModuleAvailable(module) && WaferManager.Instance.CheckNoWafer(module,0))
  903. return true;
  904. }
  905. }
  906. return false;
  907. }
  908. private ModuleName SearchPM(List<ModuleName> needPMs,Dictionary<int,ModuleName> PMsChoice)
  909. {
  910. foreach (var module in needPMs)
  911. {
  912. if (ModuleHelper.IsPm(module) && ModuleHelper.IsInstalled(module))
  913. {
  914. if (IsModuleAvailable(module) && !PMsChoice.ContainsValue(module) && WaferManager.Instance.CheckNoWafer(module,0))
  915. return module;
  916. }
  917. }
  918. return ModuleName.TMRobot;
  919. }
  920. private bool IsModuleAvailable(ModuleName module)
  921. {
  922. return dictSchedulers.Keys.Contains(module) && dictSchedulers[module].IsAvailable;
  923. }
  924. //检查需要使用的pm
  925. private bool CheckSequencePmReady(SequenceInfo seq, List<ModuleName> pmOccupied, out List<ModuleName> pmUsed, out string reason)
  926. {
  927. pmUsed = new List<ModuleName>();
  928. reason = "";
  929. bool pmIsReady = true;
  930. for (int i = 0; i < seq.Steps.Count; i++)
  931. {
  932. SequenceStepInfo stepInfo = seq.Steps[i];
  933. bool hasPm = false;
  934. foreach (var module in stepInfo.StepModules)
  935. {
  936. if (!ModuleHelper.IsPm(module))
  937. {
  938. hasPm = true;
  939. break;
  940. }
  941. if (ModuleHelper.IsInstalled(module) && (pmOccupied == null || !pmOccupied.Contains(module)))
  942. {
  943. hasPm = true;
  944. }
  945. if (!pmUsed.Contains(module))
  946. pmUsed.Add(module);
  947. }
  948. if (pmIsReady && !hasPm)
  949. {
  950. reason = $"Step {i + 1} no valid PM, " + string.Join("|", stepInfo.StepModules);
  951. pmIsReady = false;
  952. }
  953. }
  954. return pmIsReady;
  955. }
  956. //检查是否又对应PM的recipe
  957. private bool CheckSequenceRecipeFileValid(SequenceInfo seq, out string reason)
  958. {
  959. for (int i = 0; i < seq.Steps.Count; i++)
  960. {
  961. SequenceStepInfo stepInfo = seq.Steps[i];
  962. foreach (var module in stepInfo.StepModules)
  963. {
  964. if (ModuleHelper.IsPm(module))
  965. {
  966. string attr = $"{module}Recipe";
  967. if (stepInfo.StepParameter.ContainsKey(attr)
  968. && !string.IsNullOrEmpty((string)stepInfo.StepParameter[attr]))
  969. {
  970. var recipeName = (string)stepInfo.StepParameter[attr];
  971. if (!string.IsNullOrEmpty(recipeName))
  972. {
  973. var recipeContent =
  974. RecipeFileManager.Instance.LoadRecipe($"{module}", recipeName, false, "Process");
  975. if (string.IsNullOrEmpty(recipeContent))
  976. {
  977. reason = $"Can not find recipe file{recipeName}";
  978. return false;
  979. }
  980. }
  981. }
  982. }
  983. }
  984. }
  985. reason = "";
  986. return true;
  987. }
  988. //找到对应sequence
  989. public ProcessJobInfo GetFirstProcessJob(ControlJobInfo cj)
  990. {
  991. foreach (var pj in _lstProcessJobs)
  992. {
  993. if (pj.ControlJobName == cj.Name)
  994. return pj;
  995. }
  996. return null;
  997. }
  998. #endregion
  999. }
  1000. }