From bcbb95dfc27bc6a29db47d7aaf0e78a1f8d7c6ea Mon Sep 17 00:00:00 2001 From: lhiven <236881222@qq.com> Date: Fri, 8 Dec 2023 14:32:05 +0900 Subject: [PATCH] =?UTF-8?q?1=20=E5=A2=9E=E5=8A=A0=E8=BD=B4=E6=98=AF?= =?UTF-8?q?=E5=90=A6=E8=BF=90=E5=8A=A8=E5=88=B0=E4=BD=8D=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=202=20=E5=9B=BA=E9=AB=98=E5=8D=A1=E5=A2=9E=E5=8A=A0=E5=9C=A8HS?= =?UTF-8?q?IO=E5=8F=A3=E8=BE=93=E5=87=BA=E7=94=B5=E5=B9=B3=E6=88=96?= =?UTF-8?q?=E8=80=85=E8=84=89=E5=86=B2=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Rs.Motion/Base/IAxis.cs | 8 +- Rs.Motion/GugaoPulse/GugaoPulseAxis.cs | 54 ++++++-- Rs.Motion/GugaoPulse/GugaoPulseCardManager.cs | 18 ++- Rs.MotionPlat/Flow/LoadAndUnloadTask.cs | 6 +- Rs.MotionPlat/Flow/TestCenter.cs | 8 +- Rs.MotionPlat/Form2.Designer.cs | 90 +++++++++++++ Rs.MotionPlat/Form2.cs | 27 ++++ Rs.MotionPlat/Form2.resx | 120 ++++++++++++++++++ Rs.MotionPlat/FormMain.Designer.cs | 15 +-- .../Recipe/DownCameraLocation.Designer.cs | 1 - .../StockTrayLocationRecipe.Designer.cs | 3 +- Rs.MotionPlat/Recipe/TrayRecipe.Designer.cs | 1 - .../Recipe/TurnoverLocationRecipe.Designer.cs | 1 - Rs.MotionPlat/Rs.MotionPlat.csproj | 9 ++ 14 files changed, 328 insertions(+), 33 deletions(-) create mode 100644 Rs.MotionPlat/Form2.Designer.cs create mode 100644 Rs.MotionPlat/Form2.cs create mode 100644 Rs.MotionPlat/Form2.resx diff --git a/Rs.Motion/Base/IAxis.cs b/Rs.Motion/Base/IAxis.cs index ac568bf..3027f11 100644 --- a/Rs.Motion/Base/IAxis.cs +++ b/Rs.Motion/Base/IAxis.cs @@ -124,6 +124,12 @@ namespace Rs.Motion.Base return ErrorCode.Ok; } + public virtual ErrorCode IsArrived(out bool bIsArrived) + { + bIsArrived = false; + return ErrorCode.Ok; + } + public virtual ErrorCode IsInPosition(out bool bIsInPosition) { bIsInPosition = false; @@ -172,7 +178,7 @@ namespace Rs.Motion.Base return ErrorCode.Ok; } - public virtual ErrorCode ComparePulse(ushort channel) + public virtual ErrorCode ComparePulse(ushort channel,bool dp=true) { return ErrorCode.Ok; } diff --git a/Rs.Motion/GugaoPulse/GugaoPulseAxis.cs b/Rs.Motion/GugaoPulse/GugaoPulseAxis.cs index 066feb4..a1c32c8 100644 --- a/Rs.Motion/GugaoPulse/GugaoPulseAxis.cs +++ b/Rs.Motion/GugaoPulse/GugaoPulseAxis.cs @@ -385,7 +385,8 @@ namespace Rs.Motion.GugaoPulse } else { - LogHelper.MoveLog($"acc={trapPrm.acc},dec={trapPrm.dec},targetpos={dfPos},maxvel={fMaxVel}"); + m_dfCommandPosition = dfPosVal; + LogHelper.MoveLog($"acc={trapPrm.acc},dec={trapPrm.dec},targetpos={m_dfCommandPosition},maxvel={fMaxVel}"); return ErrorCode.Ok; } } @@ -752,13 +753,11 @@ namespace Rs.Motion.GugaoPulse /// public void DefaultHome(object obj) { - MessageQueue.Instance.Insert($"{Config.AxisName} go home"); if (!SafeCheck()) { MessageQueue.Instance.Insert($"{Config.AxisName} go home unsafe"); return; - } - + } MessageQueue.Instance.Insert($"{Config.AxisName} go home"); //判断是否在原点 GetOrgStatus(out bool bOrg); @@ -794,10 +793,10 @@ namespace Rs.Motion.GugaoPulse homePrm.acc = ((homeSpeedPulse / 1000.0) / (Config.AccTime * 1000.0)); homePrm.dec = ((homeSpeedPulse / 1000.0) / (Config.AccTime * 1000.0)); //homePrm.pad1_1 = 0; - homePrm.homeOffset = (int)Config.HomeOffset; + homePrm.homeOffset = (int)dfHome_offset; homePrm.searchHomeDistance = 0; homePrm.searchIndexDistance = 0; - homePrm.edge = 0; + homePrm.edge = (short)(Config.HomeOrgLogic == 0 ? 1 : 0); apiResult = mc_pulse.GT_GoHome((short)Config.CardId, (short)Config.AxisId, ref homePrm); // LogHelper.Debug($"axis {Config.AxisName} start home ,homedir={Config.HomeDir},homemode={Config.HomeMode}"); @@ -810,8 +809,10 @@ namespace Rs.Motion.GugaoPulse timer.Restart(); while (HomeStatus != EHomeStatus.Finished && HomeStatus != EHomeStatus.Abort && timer.ElapsedMilliseconds < 2 * 60 * 1000) { + mc_pulse.GT_GetHomeStatus((short)Config.CardId, (short)Config.AxisId, out mc_pulse.THomeStatus pHomeStatus); //MessageQueue.Instance.Insert($"run={pHomeStatus.run},stage={pHomeStatus.stage},err={pHomeStatus.error}"); + //MessageQueue.Instance.Insert($"run={pHomeStatus.run},stage={pHomeStatus.stage},err={pHomeStatus.error}"); Thread.Sleep(10); if(pHomeStatus.run==0 && pHomeStatus.stage==100 && pHomeStatus.error==0) { @@ -872,6 +873,36 @@ namespace Rs.Motion.GugaoPulse return ErrorCode.Ok; } + public override ErrorCode IsArrived(out bool bIsArrived) + { + bIsArrived = false; + if (!cardManager.IsInitialized) + { + return ErrorCode.CardNotInit; + } + bool bIsStop = false; + ErrorCode err = IsStop(out bIsStop); + if (err > ErrorCode.Ok) + { + return err; + } + if (!bIsStop) + { + return ErrorCode.Ok; + } + double currentPos = 0.0; + if(Config.EnableEncoder==1) + { + GetEncoderPosition(out currentPos); + } + else + { + GetPrfPosition(out currentPos); + } + bIsArrived = (currentPos - m_dfCommandPosition) < 0.01; + return ErrorCode.Ok; + } + /// /// 判断运动是否到位 /// @@ -1619,9 +1650,16 @@ namespace Rs.Motion.GugaoPulse return ErrorCode.Ok; } - public override ErrorCode ComparePulse(ushort channel) + public override ErrorCode ComparePulse(ushort channel,bool dp=true) { - apiResult = mc_pulse.GT_ComparePulse((short)Config.CardId, (short)channel, 0, (short)Config.HcmpPulseWidth); + short value = (short)channel; + if(value!=0) + { + value = (short)(1 << (channel - 1)); + + } + MessageQueue.Instance.Insert(value.ToString()); + apiResult = mc_pulse.GT_ComparePulse((short)Config.CardId, (short)(1<<(channel-1)), (short)(dp?1:0), (short)Config.HcmpPulseWidth); if(apiResult != 0) { return ErrorCode.Fail; diff --git a/Rs.Motion/GugaoPulse/GugaoPulseCardManager.cs b/Rs.Motion/GugaoPulse/GugaoPulseCardManager.cs index f200c12..d9dbd00 100644 --- a/Rs.Motion/GugaoPulse/GugaoPulseCardManager.cs +++ b/Rs.Motion/GugaoPulse/GugaoPulseCardManager.cs @@ -98,7 +98,9 @@ namespace Rs.Motion.GugaoPulse { return ErrorCode.Fail; } - + short value = 32767 * 5 / 10; + apiResult = mc_pulse.GT_SetDac(0, 11, ref value, 1); + MessageQueue.Instance.Insert($"GT_SetDac ret={apiResult}"); } } else @@ -128,5 +130,19 @@ namespace Rs.Motion.GugaoPulse //return null; } + public ErrorCode ComparePulse(short CardId,ushort channel, bool dp = true,short HcmpPulseWidth=100) + { + short value = (short)channel; + if (value != 0) + { + value = (short)(1 << (channel - 1)); + } + apiResult = mc_pulse.GT_ComparePulse(CardId, (short)(1 << (channel - 1)), (short)(dp ? 1 : 0), HcmpPulseWidth); + if (apiResult != 0) + { + return ErrorCode.Fail; + } + return ErrorCode.Ok; + } } } diff --git a/Rs.MotionPlat/Flow/LoadAndUnloadTask.cs b/Rs.MotionPlat/Flow/LoadAndUnloadTask.cs index 436f0fa..74c42bd 100644 --- a/Rs.MotionPlat/Flow/LoadAndUnloadTask.cs +++ b/Rs.MotionPlat/Flow/LoadAndUnloadTask.cs @@ -130,7 +130,7 @@ namespace Rs.MotionPlat.Flow infos.TurnoverID = m_schedulingMaterial.TurnoverID; infos.Infos = turnoverResult; string content = JsonConvert.SerializeObject(infos, new StringEnumConverter()); - LogHelper.TestCenterLog("发送:" + content); + LogHelper.Debug("发送:" + content); return content; } public string GetTestLoadString() @@ -141,7 +141,7 @@ namespace Rs.MotionPlat.Flow infos.TurnoverID = m_schedulingMaterial.TurnoverID; infos.Infos = testLoadTaskList; string content = JsonConvert.SerializeObject(infos, new StringEnumConverter()); - LogHelper.TestCenterLog("发送:" + content); + LogHelper.Debug("发送:" + content); return content; } public string GetTestUnLoadString() @@ -152,7 +152,7 @@ namespace Rs.MotionPlat.Flow infos.TurnoverID = m_schedulingMaterial.TurnoverID; infos.Infos = testUnloadTaskList; string content = JsonConvert.SerializeObject(infos, new StringEnumConverter()); - LogHelper.TestCenterLog("发送:" + content); + LogHelper.Debug("发送:" + content); return content; } diff --git a/Rs.MotionPlat/Flow/TestCenter.cs b/Rs.MotionPlat/Flow/TestCenter.cs index 0fcacdc..55fe345 100644 --- a/Rs.MotionPlat/Flow/TestCenter.cs +++ b/Rs.MotionPlat/Flow/TestCenter.cs @@ -80,13 +80,13 @@ namespace Rs.MotionPlat.Flow ReplayTaskEvent?.Invoke(info.ToString()); break; case EInstruction.LoadAndUnload: - LogHelper.TestCenterLog("接收:" + msg); + LogHelper.Debug("接收:" + msg); MachineManage.Instance.RunState = ERunState.Busying; sm = JsonConvert.DeserializeObject(msg); schedulResult = new SchedulingResult() { Instruction = sm.Instruction, State = ERunState.Busying, GroupID = sm.GroupID, TurnoverID = sm.TurnoverID }; string buffer = schedulResult.ToString(); client.Send(buffer); - LogHelper.TestCenterLog(buffer); + LogHelper.Debug(buffer); LoadAndUnloadTask.Instance.Add(sm); MessageQueue.Instance.Insert(sm.Instruction.ToString()); ReplayTaskEvent?.Invoke(schedulResult.ToString()); @@ -209,7 +209,7 @@ namespace Rs.MotionPlat.Flow private void Client_OnDisconnected(System.Net.Sockets.Socket obj) { MessageQueue.Instance.Insert($"中控断开连接"); - LogHelper.TestCenterLog("中控断开连接"); + LogHelper.Debug("中控断开连接"); } /// @@ -218,7 +218,7 @@ namespace Rs.MotionPlat.Flow /// private void Client_OnConnected(System.Net.Sockets.Socket obj) { - LogHelper.TestCenterLog("中控连接成功"); + LogHelper.Debug("中控连接成功"); MessageQueue.Instance.Insert($"中控连接成功"); } diff --git a/Rs.MotionPlat/Form2.Designer.cs b/Rs.MotionPlat/Form2.Designer.cs new file mode 100644 index 0000000..0cda0b4 --- /dev/null +++ b/Rs.MotionPlat/Form2.Designer.cs @@ -0,0 +1,90 @@ +namespace Rs.MotionPlat +{ + partial class Form2 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.button1 = new System.Windows.Forms.Button(); + this.rsTray1 = new Rs.Controls.RsTray(); + this.SuspendLayout(); + // + // button1 + // + this.button1.Location = new System.Drawing.Point(606, 215); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(75, 23); + this.button1.TabIndex = 1; + this.button1.Text = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // rsTray1 + // + this.rsTray1.CanDraw = true; + this.rsTray1.ColSpace = 5; + this.rsTray1.ColumnNum = 10; + this.rsTray1.HeadText = "0000"; + this.rsTray1.InitSlotStatus = Rs.Controls.ESlotStatus.Have; + this.rsTray1.ItemName = "1010"; + this.rsTray1.LeftSpaceWidth = 20; + this.rsTray1.Location = new System.Drawing.Point(132, 89); + this.rsTray1.Name = "rsTray1"; + this.rsTray1.OffsetX = 0F; + this.rsTray1.OffsetY = 0F; + this.rsTray1.OffsetYEven = 0F; + this.rsTray1.RowNum = 9; + this.rsTray1.RowSpace = 3; + this.rsTray1.SelectSlot = null; + this.rsTray1.ShowStatus = true; + this.rsTray1.ShowText = true; + this.rsTray1.SinglePoint = false; + this.rsTray1.Size = new System.Drawing.Size(315, 457); + this.rsTray1.SortDir = Rs.Controls.ESortDir.LeftToRight; + this.rsTray1.Status = null; + this.rsTray1.TabIndex = 0; + this.rsTray1.TopSpaceHeight = 20; + // + // Form2 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(16)))), ((int)(((byte)(16)))), ((int)(((byte)(16))))); + this.ClientSize = new System.Drawing.Size(883, 694); + this.Controls.Add(this.button1); + this.Controls.Add(this.rsTray1); + this.Name = "Form2"; + this.Text = "Form2"; + this.ResumeLayout(false); + + } + + #endregion + + private Controls.RsTray rsTray1; + private System.Windows.Forms.Button button1; + } +} \ No newline at end of file diff --git a/Rs.MotionPlat/Form2.cs b/Rs.MotionPlat/Form2.cs new file mode 100644 index 0000000..8172503 --- /dev/null +++ b/Rs.MotionPlat/Form2.cs @@ -0,0 +1,27 @@ +using Rs.Controls; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Rs.MotionPlat +{ + public partial class Form2 : Form + { + public Form2() + { + InitializeComponent(); + } + + private void button1_Click(object sender, EventArgs e) + { + TraySlot slot = rsTray1.GetSlot(Rs.Controls.ESlotStatus.Have); + MessageBox.Show(slot.Index.ToString()+$" r={slot.Row},col={slot.Column}"); + } + } +} diff --git a/Rs.MotionPlat/Form2.resx b/Rs.MotionPlat/Form2.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Rs.MotionPlat/Form2.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Rs.MotionPlat/FormMain.Designer.cs b/Rs.MotionPlat/FormMain.Designer.cs index 025ce6f..b37d5c6 100644 --- a/Rs.MotionPlat/FormMain.Designer.cs +++ b/Rs.MotionPlat/FormMain.Designer.cs @@ -563,7 +563,6 @@ this.trayEmpty1.ShowText = false; this.trayEmpty1.SinglePoint = false; this.trayEmpty1.Size = new System.Drawing.Size(121, 399); - this.trayEmpty1.SortDir = Rs.Controls.ESortDir.Horizontal; this.trayEmpty1.Status = null; this.trayEmpty1.TabIndex = 0; this.trayEmpty1.TopSpaceHeight = 20; @@ -591,7 +590,6 @@ this.trayInput.ShowText = false; this.trayInput.SinglePoint = false; this.trayInput.Size = new System.Drawing.Size(121, 399); - this.trayInput.SortDir = Rs.Controls.ESortDir.Horizontal; this.trayInput.Status = null; this.trayInput.TabIndex = 0; this.trayInput.TopSpaceHeight = 20; @@ -618,8 +616,7 @@ this.trayOk.ShowStatus = true; this.trayOk.ShowText = false; this.trayOk.SinglePoint = false; - this.trayOk.Size = new System.Drawing.Size(121, 399); - this.trayOk.SortDir = Rs.Controls.ESortDir.Horizontal; + this.trayOk.Size = new System.Drawing.Size(121, 399); this.trayOk.Status = null; this.trayOk.TabIndex = 0; this.trayOk.TopSpaceHeight = 20; @@ -647,8 +644,7 @@ this.trayNg.ShowStatus = true; this.trayNg.ShowText = false; this.trayNg.SinglePoint = false; - this.trayNg.Size = new System.Drawing.Size(121, 399); - this.trayNg.SortDir = Rs.Controls.ESortDir.Horizontal; + this.trayNg.Size = new System.Drawing.Size(121, 399); this.trayNg.Status = null; this.trayNg.TabIndex = 0; this.trayNg.TopSpaceHeight = 20; @@ -676,8 +672,7 @@ this.trayMulti.ShowStatus = true; this.trayMulti.ShowText = false; this.trayMulti.SinglePoint = false; - this.trayMulti.Size = new System.Drawing.Size(121, 399); - this.trayMulti.SortDir = Rs.Controls.ESortDir.Horizontal; + this.trayMulti.Size = new System.Drawing.Size(121, 399); this.trayMulti.Status = null; this.trayMulti.TabIndex = 0; this.trayMulti.TopSpaceHeight = 20; @@ -705,8 +700,7 @@ this.trayEmpty2.ShowStatus = true; this.trayEmpty2.ShowText = false; this.trayEmpty2.SinglePoint = false; - this.trayEmpty2.Size = new System.Drawing.Size(124, 399); - this.trayEmpty2.SortDir = Rs.Controls.ESortDir.Horizontal; + this.trayEmpty2.Size = new System.Drawing.Size(124, 399); this.trayEmpty2.Status = null; this.trayEmpty2.TabIndex = 0; this.trayEmpty2.TopSpaceHeight = 20; @@ -759,7 +753,6 @@ this.trayTurnover.ShowText = false; this.trayTurnover.SinglePoint = false; this.trayTurnover.Size = new System.Drawing.Size(244, 193); - this.trayTurnover.SortDir = Rs.Controls.ESortDir.Horizontal; this.trayTurnover.Status = null; this.trayTurnover.TabIndex = 1; this.trayTurnover.TopSpaceHeight = 20; diff --git a/Rs.MotionPlat/Recipe/DownCameraLocation.Designer.cs b/Rs.MotionPlat/Recipe/DownCameraLocation.Designer.cs index 61546f1..cfccd1a 100644 --- a/Rs.MotionPlat/Recipe/DownCameraLocation.Designer.cs +++ b/Rs.MotionPlat/Recipe/DownCameraLocation.Designer.cs @@ -130,7 +130,6 @@ this.rsTray1.SelectSlot = null; this.rsTray1.ShowText = true; this.rsTray1.Size = new System.Drawing.Size(215, 577); - this.rsTray1.SortDir = Rs.Controls.ESortDir.Horizontal; this.rsTray1.TabIndex = 0; this.rsTray1.TopSpaceHeight = 20; // diff --git a/Rs.MotionPlat/Recipe/StockTrayLocationRecipe.Designer.cs b/Rs.MotionPlat/Recipe/StockTrayLocationRecipe.Designer.cs index b000b09..4597176 100644 --- a/Rs.MotionPlat/Recipe/StockTrayLocationRecipe.Designer.cs +++ b/Rs.MotionPlat/Recipe/StockTrayLocationRecipe.Designer.cs @@ -453,8 +453,7 @@ this.rsTray1.SelectSlot = null; this.rsTray1.ShowText = false; this.rsTray1.SinglePoint = false; - this.rsTray1.Size = new System.Drawing.Size(361, 242); - this.rsTray1.SortDir = Rs.Controls.ESortDir.Horizontal; + this.rsTray1.Size = new System.Drawing.Size(361, 242); this.rsTray1.TabIndex = 0; this.rsTray1.TopSpaceHeight = 20; this.rsTray1.SlotClickEvent += new System.Action(this.rsTray1_SlotClickEvent); diff --git a/Rs.MotionPlat/Recipe/TrayRecipe.Designer.cs b/Rs.MotionPlat/Recipe/TrayRecipe.Designer.cs index 2a9663a..1d5fba6 100644 --- a/Rs.MotionPlat/Recipe/TrayRecipe.Designer.cs +++ b/Rs.MotionPlat/Recipe/TrayRecipe.Designer.cs @@ -139,7 +139,6 @@ this.rsTray1.ShowText = true; this.rsTray1.SinglePoint = false; this.rsTray1.Size = new System.Drawing.Size(297, 585); - this.rsTray1.SortDir = Rs.Controls.ESortDir.Horizontal; this.rsTray1.TabIndex = 0; this.rsTray1.TopSpaceHeight = 20; this.rsTray1.SlotClickEvent += new System.Action(this.rsTray1_SlotClickEvent); diff --git a/Rs.MotionPlat/Recipe/TurnoverLocationRecipe.Designer.cs b/Rs.MotionPlat/Recipe/TurnoverLocationRecipe.Designer.cs index fc4f481..09ca04e 100644 --- a/Rs.MotionPlat/Recipe/TurnoverLocationRecipe.Designer.cs +++ b/Rs.MotionPlat/Recipe/TurnoverLocationRecipe.Designer.cs @@ -851,7 +851,6 @@ this.rsTray1.ShowText = false; this.rsTray1.SinglePoint = false; this.rsTray1.Size = new System.Drawing.Size(237, 265); - this.rsTray1.SortDir = Rs.Controls.ESortDir.Horizontal; this.rsTray1.TabIndex = 0; this.rsTray1.TopSpaceHeight = 20; this.rsTray1.SlotClickEvent += new System.Action(this.rsTray1_SlotClickEvent); diff --git a/Rs.MotionPlat/Rs.MotionPlat.csproj b/Rs.MotionPlat/Rs.MotionPlat.csproj index 3495836..a00fff6 100644 --- a/Rs.MotionPlat/Rs.MotionPlat.csproj +++ b/Rs.MotionPlat/Rs.MotionPlat.csproj @@ -173,6 +173,12 @@ Form1.cs + + Form + + + Form2.cs + Form @@ -403,6 +409,9 @@ Form1.cs + + Form2.cs + FormIO.cs