Browse Source

Add WaferOffset related pages, drivers, acquisition logic, and simulator.

zhouhr 1 year ago
parent
commit
ca6cc2acfb

+ 2 - 0
Venus/Framework/Common/Common.csproj

@@ -245,6 +245,7 @@
     <Compile Include="Communications\Message.cs" />
     <Compile Include="Communications\NotifiableConnectionItem.cs" />
     <Compile Include="ControlDataContext\ChartDataLine.cs" />
+    <Compile Include="ControlDataContext\OffsetItem.cs" />
     <Compile Include="DataCenter\StatsDataManager.cs" />
     <Compile Include="DBCore\CarrierDataRecorder.cs" />
     <Compile Include="DBCore\DatabaseCleaner.cs" />
@@ -254,6 +255,7 @@
     <Compile Include="DBCore\LinearProcessDataRecorder.cs" />
     <Compile Include="DBCore\LeakCheckDataRecorder.cs" />
     <Compile Include="DBCore\MFCVerificationDataRecorder.cs" />
+    <Compile Include="DBCore\OffsetDataRecorder.cs" />
     <Compile Include="DBCore\ProcessDataRecorder.cs" />
     <Compile Include="DBCore\JobDataRecorder.cs" />
     <Compile Include="DBCore\WaferDataRecorder.cs" />

+ 1 - 0
Venus/Framework/Common/CommonData/SorterDefines/TransferInfo.cs

@@ -39,6 +39,7 @@ namespace Aitex.Sorter.Common
     }
 
 
+
     public enum MoveType
     {
         Move,     //single move

+ 4 - 0
Venus/Framework/Common/DataCenter/IQueryDataService.cs

@@ -12,6 +12,7 @@ using Aitex.Core.Util;
 using Aitex.Sorter.Common;
 using MECF.Framework.Common.CommonData;
 using MECF.Framework.Common.CommonData.DeviceData;
+using MECF.Framework.Common.ControlDataContext;
 using MECF.Framework.Common.DBCore;
 using MECF.Framework.Common.Equipment;
 using MECF.Framework.Common.IOCore;
@@ -191,5 +192,8 @@ namespace MECF.Framework.Common.DataCenter
         [OperationContract]
 
         List<HistoryStepItem> GetHistorySteps(DateTime begin, DateTime end);
+        
+		[OperationContract]
+        List<OffsetItem> QueryOffsetDataByTime(string moduleName, DateTime from_time, DateTime to_time);
     }
 }

+ 5 - 0
Venus/Framework/Common/DataCenter/QueryDataService.cs

@@ -11,6 +11,7 @@ using Aitex.Core.UI.ControlDataContext;
 using Aitex.Sorter.Common;
 using Aitex.Sorter.RT.Module.DBRecorder;
 using MECF.Framework.Common.CommonData;
+using MECF.Framework.Common.ControlDataContext;
 using MECF.Framework.Common.DBCore;
 using MECF.Framework.Common.IOCore;
 using Venus_Core;
@@ -203,5 +204,9 @@ namespace MECF.Framework.Common.DataCenter
         {
             return ProcessDataRecorder.GetHistoryStepList(begin,end);
         }
+        public List<OffsetItem> QueryOffsetDataByTime(string moduleName, DateTime from_time, DateTime to_time)
+        {
+            return OffsetDataRecorder.QueryOffsetDataByTime(moduleName, from_time, to_time);
+        }
     }
 }

+ 7 - 0
Venus/Framework/Common/DataCenter/QueryDataServiceClient.cs

@@ -8,6 +8,7 @@ using Aitex.Core.Util;
 using Aitex.Core.WCF;
 using Aitex.Sorter.Common;
 using MECF.Framework.Common.CommonData;
+using MECF.Framework.Common.ControlDataContext;
 using MECF.Framework.Common.DBCore;
 using MECF.Framework.Common.IOCore;
 using Venus_Core;
@@ -238,5 +239,11 @@ namespace MECF.Framework.Common.DataCenter
             });
             return result;
         }
+        public List<OffsetItem> QueryOffsetDataByTime(string moduleName, DateTime from_time, DateTime to_time)
+        {
+            List<OffsetItem> result = null;
+            Invoke(svc => { result = svc.QueryOffsetDataByTime(moduleName, from_time, to_time); });
+            return result;
+        }
     }
 }

+ 1 - 0
Venus/Venus_Core/Venus_Core.csproj

@@ -63,6 +63,7 @@
     <Compile Include="PMLeakCheckResult.cs" />
     <Compile Include="PMVATPerformance.cs" />
     <Compile Include="RecipeResult.cs" />
+    <Compile Include="RobotArmPan.cs" />
     <Compile Include="StepItem.cs" />
     <Compile Include="SubItem.cs" />
     <Compile Include="ModuleName.cs" />

+ 8 - 0
Venus/Venus_MainPages/Venus_MainPages.csproj

@@ -194,6 +194,7 @@
     <Compile Include="ViewModels\VATPerformanceViewModel.cs" />
     <Compile Include="ViewModels\WaferDialogViewModel.cs" />
     <Compile Include="ViewModels\WaferHistoryDBViewModel.cs" />
+    <Compile Include="ViewModels\WaferOffsetViewModel.cs" />
     <Compile Include="Views\ButterflyValveView.xaml.cs">
       <DependentUpon>ButterflyValveView.xaml</DependentUpon>
     </Compile>
@@ -290,6 +291,9 @@
     <Compile Include="Views\WaferHistoryDBView.xaml.cs">
       <DependentUpon>WaferHistoryDBView.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Views\WaferOffsetView.xaml.cs">
+      <DependentUpon>WaferOffsetView.xaml</DependentUpon>
+    </Compile>
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Framework\Common\Common.csproj">
@@ -442,6 +446,10 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
+    <Page Include="Views\WaferOffsetView.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>

+ 32 - 0
Venus/Venus_RT/Config/DBModel.sql

@@ -375,6 +375,38 @@ begin
 			OWNER TO postgres;
 			GRANT SELECT ON TABLE "pj_data" TO postgres; 
     end if; 
+------------------------------------------------------------------------------------------------
+ --	
+	 if not exists(select * from information_schema.tables  
+	        where  
+	            table_catalog = CURRENT_CATALOG and table_schema = CURRENT_SCHEMA 
+	            and table_name = 'offset_data') then 
+	 
+				CREATE TABLE "offset_data"
+				(
+					"guid" text NOT NULL,
+					"source_module" text NOT NULL,
+					"source_slot" integer NOT NULL,
+					"destination_module" text NOT NULL,
+					"destination_slot" integer NOT NULL,
+					"origin_module" text NOT NULL,
+					"origin_slot" integer NOT NULL,
+					"arm_position" text NOT NULL,
+					"arm_pan" text NOT NULL,
+					"offset_x" real NOT NULL,
+					"offset_y" real NOT NULL,
+					"offset_d" real NOT NULL,
+					"start_time" timestamp without time zone,
+					"end_time" timestamp without time zone,
+					CONSTRAINT "offset_data_pkey" PRIMARY KEY ("guid","start_time","end_time")
+				)
+				WITH (
+				OIDS=FALSE
+				);
+				ALTER TABLE "offset_data"
+				OWNER TO postgres;
+				GRANT SELECT ON TABLE "offset_data" TO postgres; 
+	    end if; 
  ------------------------------------------------------------------------------------------------
  --	
  if not exists(select * from information_schema.tables  

+ 5 - 0
Venus/Venus_RT/Devices/TM/ITransferRobot.cs

@@ -11,9 +11,14 @@ namespace Venus_RT.Devices
         RState Status { get;}
         RobotMoveInfo TMRobotMoveInfo { get; }
         bool IsHomed { get; }
+        double Offset_X { get; }
+        double Offset_Y { get; }
+
+        double Offset_D { get; }
         bool Home();
         bool Halt();
         bool CheckLoad(Hand hand = Hand.Blade1);
+        bool QueryAwc();
         bool Goto(ModuleName station, int slot, Hand hand);
         bool MoveTo(ModuleName stnFrom, ModuleName stnTo, Hand hand);
         bool PickExtend(ModuleName chamber, int slot, Hand hand);

+ 112 - 2
Venus/Venus_RT/Devices/TM/SIASUNRobot.cs

@@ -31,12 +31,18 @@ namespace Venus_RT.Devices
             PickRetract,
             PlaceExtent,
             PlaceRetract,
+            QueryAwc
         }
 
         private RState _status;
         private bool _IsHomed;
         public RState Status { get { return _status; } }
         public bool IsHomed { get { return _IsHomed; } }
+        private double offset_x = 0;
+        private double offset_y = 0;
+        public double Offset_X => offset_x;
+        public double Offset_Y => offset_y;
+        public double Offset_D => Math.Round(Math.Sqrt(Math.Pow(offset_x, 2) + Math.Pow(offset_y, 2)), 2);//欧式距离 保留后两位
 
         public RobotMoveInfo TMRobotMoveInfo { get { return _robotMoveInfo; } }
 
@@ -50,6 +56,10 @@ namespace Venus_RT.Devices
 
         private readonly Regex _rex_check_load = new Regex(@"LOAD\s+(A|B)\s+(\w+)\s*");
         private readonly Regex _rex_error_code = new Regex(@"_ERR\s+(\d+)\s*");
+        private readonly Regex _rex_event_offset = new Regex(@"_EVENT\sROBOT\s[0-9|\s]*.*");
+        private readonly Regex _rex_event_getoffset = new Regex(@"(?<=_EVENT\sROBOT\s)(.+?)(?=\sB)");
+        private readonly Regex _rex_query_awc = new Regex(@"WAF_CEN\sRT[-|0-9|\s]*LFT[-|0-9|\s]*OFFSET[-|0-9|\s]*");
+        private readonly Regex _rex_query_getoffset = new Regex(@"(?<=WAF_CEN\sRT[-|0-9|\s]*LFT[-|0-9|\s]*OFFSET\s)(.*)");
 
         private const string EOF = "\r\n";
 
@@ -95,6 +105,19 @@ namespace Venus_RT.Devices
             _status = RState.Running;
             return _SendCommand($"CHECK LOAD {_checkLoadStation} ARM {Hand2Arm(hand)}");
         }
+
+        public bool QueryAwc()
+        {
+            //检查防止状态交叉
+            if (CheckRobotStatus() == false)
+                return false;
+            offset_x = 0;
+            offset_y = 0;
+            _currentOP = OPStep.QueryAwc;
+            _status = RState.Running;
+            return _SendCommand("RQ WAF_CEN DATA");
+        }
+
         public bool Goto(ModuleName station, int slot, Hand hand)
         {
             if (CheckRobotStatus() == false)
@@ -227,15 +250,19 @@ namespace Venus_RT.Devices
                 case OPStep.PlaceExtent:
                 case OPStep.PlaceRetract:
                     {
-                        if (RevMsg.Trim() == "_RDY" || ( RevMsg.Contains("_RDY") && !RevMsg.Contains("_ERR")))
+                        if (RevMsg.Trim() == "_RDY" || (RevMsg.Contains("_RDY") && !RevMsg.Contains("_ERR")))
                         {
                             _currentOP = OPStep.Idle;
                             _status = RState.End;
+                            if (RevMsg.Contains("_EVENT"))
+                            {
+                                GetEventMsg(RevMsg);
+                            }
                         }
                         else
                         {
                             ReportWrongMsg(RevMsg);
-                        }   
+                        }
 
                         if (_currentOP != OPStep.PickExtend && _currentOP != OPStep.PlaceExtent)
                             SetRobotMovingInfo(RobotAction.None, Hand.Both, ModuleName.TMRobot);
@@ -279,9 +306,44 @@ namespace Venus_RT.Devices
                         }
                     }
                     break;
+                case OPStep.QueryAwc:
+                    {
+                        //不沾包
+                        if (RevMsg.Trim() == "_RDY")
+                        {
+                            _currentOP = OPStep.Idle;
+                            _status = RState.End;
+                        }
+                        //单条查询处理
+                        GetAwcMsg(RevMsg);
+                        //沾包
+                        if (RevMsg.Trim() != "_RDY" && RevMsg.Contains("_RDY"))
+                        {
+                            if (!RevMsg.Contains("_ERR"))
+                            {
+                                foreach (string msg in RevMsg.Split('\n'))
+                                    GetAwcMsg(msg);
+
+                                _currentOP = OPStep.Idle;
+                                _status = RState.End;
+                            }
+                            else
+                            {
+                                foreach (string msg in RevMsg.Split('\n'))
+                                    if (msg.Contains("_ERR"))
+                                        ErrorMessageHandler(_rex_error_code.Match(RevMsg.Trim()).Value);
+
+                                _currentOP = OPStep.Idle;
+                                _status = RState.Failed;
+                            }
+                        }
+                    }
+                    break;
                 default:
                     if (!RevMsg.Contains("_EVENT"))
                         ReportWrongMsg(RevMsg);
+                    else
+                        GetEventMsg(RevMsg);
                     break;
             }
         }
@@ -612,5 +674,53 @@ namespace Venus_RT.Devices
             _robotMoveInfo.ArmTarget = hand == Hand.Blade1 ? RobotArm.ArmA : (hand == Hand.Both ? RobotArm.Both : RobotArm.ArmB);
             _robotMoveInfo.BladeTarget = $"{_robotMoveInfo.ArmTarget}.{target}";
         }
+
+        private void GetEventMsg(string revMsg)
+        {
+            //在包含数据的前提下
+            switch (_robotMoveInfo.Action)
+            {
+                case RobotAction.Picking:
+                case RobotAction.Placing:
+                case RobotAction.Extending:
+                case RobotAction.Retracting:
+                    if (_rex_event_offset.IsMatch(revMsg))
+                    {
+                        //offset_x = _rex_event_getoffset.Match(revMsg).Value.Split(' ')[0];
+                        //offset_y = _rex_event_getoffset.Match(revMsg).Value.Split(' ')[1];
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        
+
+        private void GetAwcMsg(string revMsg)
+        {
+            revMsg = revMsg.Trim();
+            if (_rex_query_awc.IsMatch(revMsg))
+            {
+                string offset_r_t = _rex_query_getoffset.Match(revMsg).Value;
+                //最大仅6位 不超过int范围
+                int offset_r;
+                int offset_t;
+                if (int.TryParse(offset_r_t.Split(' ')[0], out offset_r) && int.TryParse(offset_r_t.Split(' ')[1], out offset_t))
+                {
+                    // 9/26 新松暂未提供转换公式 暂时使用相关数据
+                    offset_x = offset_r * Math.Cos(offset_t);
+                    offset_y = offset_t * Math.Sin(offset_t);
+                }
+                else
+                {
+                    LOG.Write(eEvent.ERR_TM_ROBOT, ModuleName.TMRobot, $"TM Robot returned illegal offset data! Raw Data:{revMsg}");
+                }
+            }
+            //else
+            //{
+            //    LOG.Write(eEvent.ERR_TM_ROBOT, ModuleName.TMRobot, $"The awc parameter format returned by TM Robot is incorrect! Raw Data:{revMsg}");
+            //}
+        }
     }
 }

+ 47 - 0
Venus/Venus_RT/Modules/TM/MFPMPlaceRoutine.cs

@@ -11,6 +11,8 @@ using Aitex.Core.Util;
 using Venus_RT.Modules.PMs;
 using MECF.Framework.Common.Schedulers;
 using System.Collections.Generic;
+using MECF.Framework.Common.DBCore;
+using System;
 
 namespace Venus_RT.Modules.TM
 {
@@ -21,9 +23,11 @@ namespace Venus_RT.Modules.TM
             WaitPMReady,
             PMPrepare,
             ArmExtend,
+            QueryAWC,
             LiftUpWafer,
             PlaceDelay,
             ArmRetract,
+            SavePlaceData,
             NotifyDone,
         }
 
@@ -37,6 +41,8 @@ namespace Venus_RT.Modules.TM
         private int _targetSlot;
         private Hand _hand;
 
+        private DateTime _starttime;
+
         public MFPMPlaceRoutine(JetTM tm, ITransferRobot robot) : base(ModuleName.TMRobot)
         {
             _JetTM = tm;
@@ -45,6 +51,7 @@ namespace Venus_RT.Modules.TM
         }
         public RState Start(params object[] objs)
         {
+            _starttime = DateTime.Now;
             if (!_robot.IsHomed)
             {
                 LOG.Write(eEvent.ERR_TM, Module, $"TM Robot is not homed, please home it first");
@@ -93,9 +100,11 @@ namespace Venus_RT.Modules.TM
             Runner.Wait((int)PlaceStep.WaitPMReady,     () => _pmModule.IsIdle,                         _delay_60s)
                 .Run((int)PlaceStep.PMPrepare,          ModulePrepare,          IsModulePrepareReady,   _delay_60s)
                 .Run((int)PlaceStep.ArmExtend,          ArmExtend,              WaitRobotExtendDone,    _placingTimeout)
+                .Run((int)PlaceStep.QueryAWC,           QueryAWC,               WaitRobotQueryDone,     _delay_1s)
                 .Run((int)PlaceStep.LiftUpWafer,        NotifyPMPlaceWafer,     WaitPMWaferLiftUp,      _delay_30s)
                 .Delay((int)PlaceStep.PlaceDelay,                                                       _placeDelayTime)
                 .Run((int)PlaceStep.ArmRetract,         ArmRetract,             WaitRobotRetractDone,   _delay_30s)
+                .Run((int)PlaceStep.SavePlaceData,      RecordAWCData,          NullFun)
                 .End((int)PlaceStep.NotifyDone,         NotifyPMDone,                                   _delay_50ms);
             return Runner.Status;
         }
@@ -119,6 +128,10 @@ namespace Venus_RT.Modules.TM
         {
             return _robot.PlaceRetract(_targetModule, _targetSlot, _hand);
         }
+        private bool QueryAWC()
+        {
+            return _robot.QueryAwc(); ;
+        }
         private bool WaitRobotExtendDone()
         {
             if (_robot.Status == RState.Running)
@@ -136,6 +149,40 @@ namespace Venus_RT.Modules.TM
                 return true;
             }
         }
+
+        private bool RecordAWCData()
+        {
+            //已经move后的数据
+            string _origin_module = $"LP{WaferManager.Instance.GetWafer(_targetModule, _targetSlot).OriginStation}";
+            int _origin_slot = WaferManager.Instance.GetWafer(_targetModule, _targetSlot).OriginSlot;
+            //查询完毕 插入数据
+            OffsetDataRecorder.RecordOffsetData(
+                Guid.NewGuid().ToString(),
+                ModuleName.TMRobot, 0,
+                _targetModule, _targetSlot,
+                _origin_module, _origin_slot,
+                _hand, RobotArmPan.None,
+                _robot.Offset_X, _robot.Offset_Y, _robot.Offset_D,
+                _starttime, DateTime.Now);
+            return true;
+        }
+
+        private bool WaitRobotQueryDone()
+        {
+            if (_robot.Status == RState.Running)
+            {
+                return false;
+            }
+            else if (_robot.Status == RState.End)
+            {
+                return true;
+            }
+            else
+            {
+                Runner.Stop($"TM Robot Query Awc failed, {_robot.Status}");
+                return true;
+            }
+        }
         private bool NotifyPMPlaceWafer()
         {
             _pmModule.PostMsg(PMEntity.MSG.LiftUpWafer);

+ 46 - 0
Venus/Venus_RT/Modules/TM/MFPlaceRoutine.cs

@@ -10,6 +10,8 @@ using Aitex.Core.RT.Log;
 using Aitex.Core.Util;
 using MECF.Framework.Common.Schedulers;
 using System.Collections.Generic;
+using System;
+using MECF.Framework.Common.DBCore;
 
 namespace Venus_RT.Modules.TM
 {
@@ -21,6 +23,7 @@ namespace Venus_RT.Modules.TM
             ModulePrepare,
             OpenSlitDoor,
             Placing,
+            QueryAwc,
             CloseSlitDoor,
             NotifyDone,
         }
@@ -34,6 +37,8 @@ namespace Venus_RT.Modules.TM
         int _targetSlot;
         Hand _hand;
 
+        private DateTime _starttime;
+
         public MFPlaceRoutine(JetTM tm, ITransferRobot robot) : base(ModuleName.TMRobot)
         {
             _JetTM = tm;
@@ -43,6 +48,8 @@ namespace Venus_RT.Modules.TM
         }
         public RState Start(params object[] objs)
         {
+            _starttime = DateTime.Now;
+
             if (!_robot.IsHomed)
             {
                 LOG.Write(eEvent.ERR_TM, Module, $"TM Robot is not homed, please home it first");
@@ -88,6 +95,7 @@ namespace Venus_RT.Modules.TM
                 .Run((int)PlaceStep.ModulePrepare,      ModulePrepare,          IsModulePrepareReady)
                 .Run((int)PlaceStep.OpenSlitDoor,       OpenSlitDoor,           IsSlitDoorOpen)
                 .Run((int)PlaceStep.Placing,            Placing,                WaitPlaceDone)
+                .Run((int)PlaceStep.QueryAwc,           QueryAwc,               WaitQueryDoneAndRecord)
                 .Run((int)PlaceStep.CloseSlitDoor,      CloseSlitDoor,          IsSlitDoorClosed)
                 .End((int)PlaceStep.NotifyDone,         NotifyLLDone,           _delay_50ms);          
             return Runner.Status;
@@ -153,6 +161,44 @@ namespace Venus_RT.Modules.TM
             }
         }
 
+
+        private bool QueryAwc()
+        {
+            if (_robot.QueryAwc())
+                return true;
+            else
+                return false;
+        }
+
+        private bool WaitQueryDoneAndRecord()
+        {
+            if (_robot.Status == RState.Running)
+            {
+                return false;
+            }
+            else if (_robot.Status == RState.End)
+            {
+                //已经move后的数据
+                string _origin_module = $"LP{WaferManager.Instance.GetWafer(_targetModule, _targetSlot).OriginStation}";
+                int _origin_slot = WaferManager.Instance.GetWafer(_targetModule, _targetSlot).OriginSlot;
+                //查询完毕 插入数据
+                OffsetDataRecorder.RecordOffsetData(
+                    Guid.NewGuid().ToString(),
+                    ModuleName.TMRobot, 0,
+                    _targetModule, _targetSlot,
+                    _origin_module, _origin_slot,
+                    _hand, RobotArmPan.None,
+                    _robot.Offset_X, _robot.Offset_Y, _robot.Offset_D,
+                    _starttime, DateTime.Now);
+                return true;
+            }
+            else
+            {
+                Runner.Stop($"TM Robot Query Awc failed, {_robot.Status}");
+                return true;
+            }
+        }
+
         private bool NotifyLLDone()
         {
             _llModule.PostMsg(LLEntity.MSG.TM_Exchange_Ready);

+ 9 - 1
Venus/Venus_Simulator/Devices/TMSimulatorServer.cs

@@ -32,7 +32,15 @@ namespace Venus_Simulator.Devices
                 OnWriteMessage("_RDY");
             }
 
-            if(_check_load.IsMatch(str))
+            if (str.Contains("RQ WAF_CEN DATA"))
+            {
+                string t = new Random().Next(-50000, 50000).ToString().PadLeft(6, '0');
+                string r = new Random().Next(-50000, 50000).ToString().PadLeft(6, '0');
+                OnWriteMessage($"WAF_CEN RT 000000 000000 000000 000000 LFT 000000 000000 000000 000000 OFFSET {r} {t}");
+                OnWriteMessage("_RDY");
+            }
+
+            if (_check_load.IsMatch(str))
             {
                 Match result = _check_load.Match(str);
                 string station = result.Groups[1].Value;

+ 7 - 0
Venus/Venus_Themes/Venus_Themes.csproj

@@ -108,6 +108,9 @@
     <Compile Include="Themes\Attach\ElementForeground.cs" />
     <Compile Include="Themes\Attach\IconElement.cs" />
     <Compile Include="unity\UIEvents.cs" />
+    <Compile Include="UserControls\Axes2D.xaml.cs">
+      <DependentUpon>Axes2D.xaml</DependentUpon>
+    </Compile>
     <Compile Include="UserControls\ButterflyValve.xaml.cs">
       <DependentUpon>ButterflyValve.xaml</DependentUpon>
     </Compile>
@@ -269,6 +272,10 @@
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
     </Page>
+    <Page Include="UserControls\Axes2D.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
     <Page Include="UserControls\ButterflyValve.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>

+ 6 - 0
Venus/Venus_UI/Config/UIMenu.json

@@ -273,6 +273,12 @@
         "Name": "IO",
         "View": "IOView",
         "IsShow": "true"
+      },
+      {
+        "Id": "WaferOffset",
+        "IsShow": "true",
+        "Name": "Wafer Offset",
+        "View": "WaferOffsetView"
       }
     ]