From a04a0505d000c482900f8254ae1c2c576b837e97 Mon Sep 17 00:00:00 2001 From: backuppc Date: Fri, 23 Jan 2026 17:33:10 +0900 Subject: [PATCH] .. --- AGVEmulator/DevAGV.cs | 64 +- AGVEmulator/RunCode/_AGV.cs | 17 +- AGVEmulator/fMain.Designer.cs | 458 +++++------ AGVEmulator/fMain.cs | 28 +- .../Controls/UnifiedAGVCanvas.cs | 4 +- .../PathFinding/Core/AGVPathResult.cs | 2 + .../PathFinding/Planning/AGVPathfinder.cs | 555 ++++++++++++- .../AGVSimulator/Forms/SimulatorForm.cs | 741 +----------------- Cs_HMI/Project/StateMachine/_AGV.cs | 3 - Cs_HMI/Project/StateMachine/_Loop.cs | 2 +- Cs_HMI/Project/ViewForm/fAuto.cs | 14 +- Cs_HMI/Project/fMain.cs | 1 + 12 files changed, 889 insertions(+), 1000 deletions(-) diff --git a/AGVEmulator/DevAGV.cs b/AGVEmulator/DevAGV.cs index eaa59b8..7dcf64d 100644 --- a/AGVEmulator/DevAGV.cs +++ b/AGVEmulator/DevAGV.cs @@ -15,7 +15,8 @@ namespace AGVEmulator public UInt16 system0 = 0; public UInt16 system1 = 0; public UInt16 error = 0; - public byte signal = 0; + public byte signal1 = 0; + public byte signal2 = 0; public char sts_bunki = 'S'; public char sts_speed = 'L'; public char sts_dir = 'F'; @@ -57,16 +58,22 @@ namespace AGVEmulator /// cross_ctrl_comm_error, } - public enum esignal + public enum esignal2 + { + cart_detect1 = 0, + cart_detect2, + } + + public enum esignal1 { front_gate_out = 0, rear_sensor_out, mark_sensor_1, mark_sensor_2, - front_left_sensor, - front_right_sensor, - front_center_sensor, charger_align_sensor, + lift_up, + lift_down, + magnet_on } public enum esystemflag0 { @@ -119,7 +126,8 @@ namespace AGVEmulator system0, system1, error, - signal, + signal1, + signal2, } public enum estsvaluetype { @@ -266,11 +274,17 @@ namespace AGVEmulator if (SetBit(ref error, idx, value)) ValueChanged?.Invoke(this, new ValueChangedArgs(idx, value, evaluetype.error)); } - public void SetAGV(DevAGV.esignal flag, bool value) + public void SetAGV(DevAGV.esignal1 flag, bool value) { var idx = (int)flag; - if (SetBit(ref signal, idx, value)) - ValueChanged?.Invoke(this, new ValueChangedArgs(idx, value, evaluetype.signal)); + if (SetBit(ref signal1, idx, value)) + ValueChanged?.Invoke(this, new ValueChangedArgs(idx, value, evaluetype.signal1)); + } + public void SetAGV(DevAGV.esignal2 flag, bool value) + { + var idx = (int)flag; + if (SetBit(ref signal2, idx, value)) + ValueChanged?.Invoke(this, new ValueChangedArgs(idx, value, evaluetype.signal2)); } public void SetSTS(estsvaluetype target, char value) @@ -311,6 +325,29 @@ namespace AGVEmulator switch (frame.cmd) { + case "CLF"://마그넷 * 리프트 + var cmd2 = frame.data.Substring(0, 2); + switch(cmd2) + { + case "ON": + SetAGV(esignal1.magnet_on, true); + break; + case "UP": + SetAGV(esignal1.lift_down, false); + SetAGV(esignal1.lift_up, true); + break; + case "DN": + SetAGV(esignal1.lift_up, false); + SetAGV(esignal1.lift_down, true); + break; + case "ST": + break; + case "OF": + SetAGV(esignal1.magnet_on, false); + break; + + } + break; case "CRN": //기동명령 //sts_dir = frame.data[0]; SetSTS(estsvaluetype.direction, frame.data[0]); @@ -472,11 +509,12 @@ namespace AGVEmulator barr[20] = (byte)this.sts_bunki; barr[21] = (byte)this.sts_dir; barr[22] = (byte)this.sts_sensor; - //bufarr = System.Text.Encoding.Default.GetBytes(p.sensor.ToString().PadLeft(2, '0')); - //Array.Copy(bufarr, 0, barr, 22, bufarr.Length); - bufarr = System.Text.Encoding.Default.GetBytes(signal.ToString("X2").PadLeft(2, '0')); + + bufarr = System.Text.Encoding.Default.GetBytes(signal1.ToString("X2").PadLeft(2, '0')); Array.Copy(bufarr, 0, barr, 23, bufarr.Length); - //barr[22] = (byte)'5'; + + bufarr = System.Text.Encoding.Default.GetBytes(signal2.ToString("X2").PadLeft(2, '0')); + Array.Copy(bufarr, 0, barr, 25, bufarr.Length); barr[barr.Length - 3] = (byte)'*'; barr[barr.Length - 2] = (byte)'*'; diff --git a/AGVEmulator/RunCode/_AGV.cs b/AGVEmulator/RunCode/_AGV.cs index 8305ab4..f26ff10 100644 --- a/AGVEmulator/RunCode/_AGV.cs +++ b/AGVEmulator/RunCode/_AGV.cs @@ -142,7 +142,7 @@ namespace AGVEmulator } } break; - case DevAGV.evaluetype.signal: + case DevAGV.evaluetype.signal1: foreach (CheckBox c in panel8.Controls) { var idx = int.Parse(c.Tag.ToString()); @@ -153,7 +153,17 @@ namespace AGVEmulator } } break; - + case DevAGV.evaluetype.signal2: + foreach (CheckBox c in panel2.Controls) + { + var idx = int.Parse(c.Tag.ToString()); + if (idx == e.Idx) + { + c.Checked = e.Value; + break; + } + } + break; } } @@ -168,7 +178,8 @@ namespace AGVEmulator aaplycheckboxbit(ref AGV.system0, panel6); aaplycheckboxbit(ref AGV.system1, panel7); aaplycheckboxbit(ref AGV.error, panel9); - aaplycheckboxbit(ref AGV.signal, panel8); + aaplycheckboxbit(ref AGV.signal1, panel8); + aaplycheckboxbit(ref AGV.signal2, panel2); if (this.agvViewer1.StopbyMark) AGV.sts_speed = 'S'; else AGV.sts_speed = GetGroupItemCheckbox(groupBox4); diff --git a/AGVEmulator/fMain.Designer.cs b/AGVEmulator/fMain.Designer.cs index c06e5f9..9ac93be 100644 --- a/AGVEmulator/fMain.Designer.cs +++ b/AGVEmulator/fMain.Designer.cs @@ -32,35 +32,35 @@ namespace AGVEmulator private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - AGVEmulator.UC.AgvViewer.ptdata ptdata57 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata58 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata59 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata60 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata61 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata62 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata63 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata64 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata65 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata66 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata67 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata68 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata69 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata70 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata71 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata72 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata73 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata74 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata75 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata76 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata77 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata78 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata79 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata80 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata81 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata82 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata83 = new AGVEmulator.UC.AgvViewer.ptdata(); - AGVEmulator.UC.AgvViewer.ptdata ptdata84 = new AGVEmulator.UC.AgvViewer.ptdata(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(fMain)); + AGVEmulator.UC.AgvViewer.ptdata ptdata1 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata2 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata3 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata4 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata5 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata6 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata7 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata8 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata9 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata10 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata11 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata12 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata13 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata14 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata15 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata16 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata17 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata18 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata19 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata20 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata21 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata22 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata23 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata24 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata25 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata26 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata27 = new AGVEmulator.UC.AgvViewer.ptdata(); + AGVEmulator.UC.AgvViewer.ptdata ptdata28 = new AGVEmulator.UC.AgvViewer.ptdata(); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.rtBMS = new arCtl.LogTextBox(); this.panel1 = new System.Windows.Forms.Panel(); @@ -86,11 +86,11 @@ namespace AGVEmulator this.label2 = new System.Windows.Forms.Label(); this.label1 = new System.Windows.Forms.Label(); this.trackBar1 = new System.Windows.Forms.TrackBar(); - this.serBMS = new AGVEmulator.SerialConn(); this.rtAGV = new arCtl.LogTextBox(); this.panel4 = new System.Windows.Forms.Panel(); this.groupBox9 = new System.Windows.Forms.GroupBox(); this.groupBox10 = new System.Windows.Forms.GroupBox(); + this.panel2 = new System.Windows.Forms.Panel(); this.panel8 = new System.Windows.Forms.Panel(); this.groupBox11 = new System.Windows.Forms.GroupBox(); this.panel9 = new System.Windows.Forms.Panel(); @@ -128,7 +128,6 @@ namespace AGVEmulator this.button4 = new System.Windows.Forms.Button(); this.groupBox3 = new System.Windows.Forms.GroupBox(); this.rtCAL = new arCtl.LogTextBox(); - this.serCAL = new AGVEmulator.SerialConn(); this.timer1 = new System.Windows.Forms.Timer(this.components); this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabPage4 = new System.Windows.Forms.TabPage(); @@ -136,11 +135,11 @@ namespace AGVEmulator this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); this.rtAGVPro = new arCtl.LogTextBox(); this.panel12 = new System.Windows.Forms.Panel(); - this.agvViewer1 = new AGVEmulator.UC.AgvViewer(); - this.serAGV = new AGVEmulator.SerialConn(); this.tabPage2 = new System.Windows.Forms.TabPage(); this.tabPage3 = new System.Windows.Forms.TabPage(); this.panel3 = new System.Windows.Forms.Panel(); + this.button3 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); this.nudIDAgv = new System.Windows.Forms.NumericUpDown(); this.label7 = new System.Windows.Forms.Label(); this.numericUpDown2 = new System.Windows.Forms.NumericUpDown(); @@ -167,8 +166,10 @@ namespace AGVEmulator this.sbBMS = new System.Windows.Forms.ToolStripStatusLabel(); this.toolStripStatusLabel2 = new System.Windows.Forms.ToolStripStatusLabel(); this.sbCAL = new System.Windows.Forms.ToolStripStatusLabel(); - this.button2 = new System.Windows.Forms.Button(); - this.button3 = new System.Windows.Forms.Button(); + this.agvViewer1 = new AGVEmulator.UC.AgvViewer(); + this.serAGV = new AGVEmulator.SerialConn(); + this.serBMS = new AGVEmulator.SerialConn(); + this.serCAL = new AGVEmulator.SerialConn(); this.groupBox1.SuspendLayout(); this.panel1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.trbT2)).BeginInit(); @@ -503,18 +504,6 @@ namespace AGVEmulator this.trackBar1.Value = 7000; this.trackBar1.Scroll += new System.EventHandler(this.trackBar1_Scroll); // - // serBMS - // - this.serBMS.BaudRate = 9600; - this.serBMS.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.serBMS.dev = null; - this.serBMS.Dock = System.Windows.Forms.DockStyle.Top; - this.serBMS.Location = new System.Drawing.Point(3, 17); - this.serBMS.Name = "serBMS"; - this.serBMS.PortName = "COM31"; - this.serBMS.Size = new System.Drawing.Size(1134, 84); - this.serBMS.TabIndex = 1; - // // rtAGV // this.rtAGV.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(24)))), ((int)(((byte)(24))))); @@ -563,6 +552,7 @@ namespace AGVEmulator // // groupBox10 // + this.groupBox10.Controls.Add(this.panel2); this.groupBox10.Controls.Add(this.panel8); this.groupBox10.Dock = System.Windows.Forms.DockStyle.Left; this.groupBox10.Location = new System.Drawing.Point(652, 44); @@ -572,16 +562,25 @@ namespace AGVEmulator this.groupBox10.TabStop = false; this.groupBox10.Text = "signal"; // + // panel2 + // + this.panel2.AutoScroll = true; + this.panel2.AutoSize = true; + this.panel2.Location = new System.Drawing.Point(3, 162); + this.panel2.Name = "panel2"; + this.panel2.Size = new System.Drawing.Size(183, 68); + this.panel2.TabIndex = 1; + this.panel2.Tag = "sg2"; + // // panel8 // this.panel8.AutoScroll = true; this.panel8.AutoSize = true; - this.panel8.Dock = System.Windows.Forms.DockStyle.Fill; - this.panel8.Location = new System.Drawing.Point(3, 17); + this.panel8.Location = new System.Drawing.Point(5, 11); this.panel8.Name = "panel8"; - this.panel8.Size = new System.Drawing.Size(183, 213); + this.panel8.Size = new System.Drawing.Size(180, 127); this.panel8.TabIndex = 1; - this.panel8.Tag = "sg"; + this.panel8.Tag = "sg1"; // // groupBox11 // @@ -998,18 +997,6 @@ namespace AGVEmulator this.rtCAL.TabIndex = 2; this.rtCAL.Text = ""; // - // serCAL - // - this.serCAL.BaudRate = 9600; - this.serCAL.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.serCAL.dev = null; - this.serCAL.Dock = System.Windows.Forms.DockStyle.Top; - this.serCAL.Location = new System.Drawing.Point(3, 17); - this.serCAL.Name = "serCAL"; - this.serCAL.PortName = "COM41"; - this.serCAL.Size = new System.Drawing.Size(776, 84); - this.serCAL.TabIndex = 1; - // // timer1 // this.timer1.Interval = 200; @@ -1095,149 +1082,6 @@ namespace AGVEmulator this.panel12.Size = new System.Drawing.Size(1140, 120); this.panel12.TabIndex = 5; // - // agvViewer1 - // - this.agvViewer1.Dock = System.Windows.Forms.DockStyle.Fill; - this.agvViewer1.FontMrk = new System.Drawing.Font("Microsoft Sans Serif", 7F); - this.agvViewer1.FontTag = new System.Drawing.Font("Microsoft Sans Serif", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.agvViewer1.lastmark = ""; - this.agvViewer1.lastmarkdir = ""; - this.agvViewer1.lasttag = ""; - this.agvViewer1.lasttagdir = ""; - ptdata57.active = false; - ptdata57.data = "NOT"; - ptdata57.pos = 30F; - ptdata58.active = false; - ptdata58.data = "QA"; - ptdata58.pos = 200F; - ptdata59.active = false; - ptdata59.data = "CHG"; - ptdata59.pos = 300F; - ptdata60.active = false; - ptdata60.data = "QC"; - ptdata60.pos = 400F; - ptdata61.active = false; - ptdata61.data = "#FVI-1"; - ptdata61.pos = 500F; - ptdata62.active = false; - ptdata62.data = "#FVI-2"; - ptdata62.pos = 600F; - ptdata63.active = false; - ptdata63.data = "#FVI-3"; - ptdata63.pos = 700F; - ptdata64.active = false; - ptdata64.data = "#FVI-4"; - ptdata64.pos = 800F; - ptdata65.active = false; - ptdata65.data = "#FVI-5"; - ptdata65.pos = 900F; - ptdata66.active = false; - ptdata66.data = "POT"; - ptdata66.pos = 970F; - this.agvViewer1.listMRK = new AGVEmulator.UC.AgvViewer.ptdata[] { - ptdata57, - ptdata58, - ptdata59, - ptdata60, - ptdata61, - ptdata62, - ptdata63, - ptdata64, - ptdata65, - ptdata66}; - ptdata67.active = false; - ptdata67.data = "9000"; - ptdata67.pos = 80F; - ptdata68.active = false; - ptdata68.data = "9001"; - ptdata68.pos = 120F; - ptdata69.active = false; - ptdata69.data = "9010"; - ptdata69.pos = 180F; - ptdata70.active = false; - ptdata70.data = "9011"; - ptdata70.pos = 220F; - ptdata71.active = false; - ptdata71.data = "9020"; - ptdata71.pos = 280F; - ptdata72.active = false; - ptdata72.data = "9021"; - ptdata72.pos = 320F; - ptdata73.active = false; - ptdata73.data = "9030"; - ptdata73.pos = 380F; - ptdata74.active = false; - ptdata74.data = "9031"; - ptdata74.pos = 420F; - ptdata75.active = false; - ptdata75.data = "9040"; - ptdata75.pos = 480F; - ptdata76.active = false; - ptdata76.data = "9041"; - ptdata76.pos = 520F; - ptdata77.active = false; - ptdata77.data = "9050"; - ptdata77.pos = 580F; - ptdata78.active = false; - ptdata78.data = "9051"; - ptdata78.pos = 620F; - ptdata79.active = false; - ptdata79.data = "9060"; - ptdata79.pos = 680F; - ptdata80.active = false; - ptdata80.data = "9061"; - ptdata80.pos = 720F; - ptdata81.active = false; - ptdata81.data = "9070"; - ptdata81.pos = 780F; - ptdata82.active = false; - ptdata82.data = "9071"; - ptdata82.pos = 820F; - ptdata83.active = false; - ptdata83.data = "9000"; - ptdata83.pos = 10F; - ptdata84.active = false; - ptdata84.data = "9001"; - ptdata84.pos = 50F; - this.agvViewer1.listTAG = new AGVEmulator.UC.AgvViewer.ptdata[] { - ptdata67, - ptdata68, - ptdata69, - ptdata70, - ptdata71, - ptdata72, - ptdata73, - ptdata74, - ptdata75, - ptdata76, - ptdata77, - ptdata78, - ptdata79, - ptdata80, - ptdata81, - ptdata82, - ptdata83, - ptdata84}; - this.agvViewer1.Location = new System.Drawing.Point(241, 0); - this.agvViewer1.Name = "agvViewer1"; - this.agvViewer1.Size = new System.Drawing.Size(899, 120); - this.agvViewer1.StopbyMark = false; - this.agvViewer1.TabIndex = 0; - this.agvViewer1.Text = "agvViewer1"; - this.agvViewer1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.agvViewer1_MouseDown); - // - // serAGV - // - this.serAGV.BaudRate = 9600; - this.serAGV.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.serAGV.dev = null; - this.serAGV.Dock = System.Windows.Forms.DockStyle.Left; - this.serAGV.Location = new System.Drawing.Point(0, 0); - this.serAGV.Name = "serAGV"; - this.serAGV.PortName = "COM21"; - this.serAGV.Size = new System.Drawing.Size(241, 120); - this.serAGV.TabIndex = 0; - // // tabPage2 // this.tabPage2.Controls.Add(this.groupBox1); @@ -1282,6 +1126,28 @@ namespace AGVEmulator this.panel3.Size = new System.Drawing.Size(364, 622); this.panel3.TabIndex = 15; // + // button3 + // + this.button3.Location = new System.Drawing.Point(246, 339); + this.button3.Name = "button3"; + this.button3.Size = new System.Drawing.Size(86, 38); + this.button3.TabIndex = 15; + this.button3.Tag = "--"; + this.button3.Text = "Pick Off"; + this.button3.UseVisualStyleBackColor = true; + this.button3.Click += new System.EventHandler(this.button3_Click); + // + // button2 + // + this.button2.Location = new System.Drawing.Point(246, 295); + this.button2.Name = "button2"; + this.button2.Size = new System.Drawing.Size(86, 38); + this.button2.TabIndex = 14; + this.button2.Tag = "--"; + this.button2.Text = "Pick On"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // // nudIDAgv // this.nudIDAgv.Font = new System.Drawing.Font("굴림", 20F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129))); @@ -1542,27 +1408,172 @@ namespace AGVEmulator this.sbCAL.Size = new System.Drawing.Size(19, 17); this.sbCAL.Text = "●"; // - // button2 + // agvViewer1 // - this.button2.Location = new System.Drawing.Point(246, 295); - this.button2.Name = "button2"; - this.button2.Size = new System.Drawing.Size(86, 38); - this.button2.TabIndex = 14; - this.button2.Tag = "--"; - this.button2.Text = "Pick On"; - this.button2.UseVisualStyleBackColor = true; - this.button2.Click += new System.EventHandler(this.button2_Click); + this.agvViewer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.agvViewer1.FontMrk = new System.Drawing.Font("Microsoft Sans Serif", 7F); + this.agvViewer1.FontTag = new System.Drawing.Font("Microsoft Sans Serif", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.agvViewer1.lastmark = ""; + this.agvViewer1.lastmarkdir = ""; + this.agvViewer1.lasttag = ""; + this.agvViewer1.lasttagdir = ""; + ptdata1.active = false; + ptdata1.data = "NOT"; + ptdata1.pos = 30F; + ptdata2.active = false; + ptdata2.data = "QA"; + ptdata2.pos = 200F; + ptdata3.active = false; + ptdata3.data = "CHG"; + ptdata3.pos = 300F; + ptdata4.active = false; + ptdata4.data = "QC"; + ptdata4.pos = 400F; + ptdata5.active = false; + ptdata5.data = "#FVI-1"; + ptdata5.pos = 500F; + ptdata6.active = false; + ptdata6.data = "#FVI-2"; + ptdata6.pos = 600F; + ptdata7.active = false; + ptdata7.data = "#FVI-3"; + ptdata7.pos = 700F; + ptdata8.active = false; + ptdata8.data = "#FVI-4"; + ptdata8.pos = 800F; + ptdata9.active = false; + ptdata9.data = "#FVI-5"; + ptdata9.pos = 900F; + ptdata10.active = false; + ptdata10.data = "POT"; + ptdata10.pos = 970F; + this.agvViewer1.listMRK = new AGVEmulator.UC.AgvViewer.ptdata[] { + ptdata1, + ptdata2, + ptdata3, + ptdata4, + ptdata5, + ptdata6, + ptdata7, + ptdata8, + ptdata9, + ptdata10}; + ptdata11.active = false; + ptdata11.data = "9000"; + ptdata11.pos = 80F; + ptdata12.active = false; + ptdata12.data = "9001"; + ptdata12.pos = 120F; + ptdata13.active = false; + ptdata13.data = "9010"; + ptdata13.pos = 180F; + ptdata14.active = false; + ptdata14.data = "9011"; + ptdata14.pos = 220F; + ptdata15.active = false; + ptdata15.data = "9020"; + ptdata15.pos = 280F; + ptdata16.active = false; + ptdata16.data = "9021"; + ptdata16.pos = 320F; + ptdata17.active = false; + ptdata17.data = "9030"; + ptdata17.pos = 380F; + ptdata18.active = false; + ptdata18.data = "9031"; + ptdata18.pos = 420F; + ptdata19.active = false; + ptdata19.data = "9040"; + ptdata19.pos = 480F; + ptdata20.active = false; + ptdata20.data = "9041"; + ptdata20.pos = 520F; + ptdata21.active = false; + ptdata21.data = "9050"; + ptdata21.pos = 580F; + ptdata22.active = false; + ptdata22.data = "9051"; + ptdata22.pos = 620F; + ptdata23.active = false; + ptdata23.data = "9060"; + ptdata23.pos = 680F; + ptdata24.active = false; + ptdata24.data = "9061"; + ptdata24.pos = 720F; + ptdata25.active = false; + ptdata25.data = "9070"; + ptdata25.pos = 780F; + ptdata26.active = false; + ptdata26.data = "9071"; + ptdata26.pos = 820F; + ptdata27.active = false; + ptdata27.data = "9000"; + ptdata27.pos = 10F; + ptdata28.active = false; + ptdata28.data = "9001"; + ptdata28.pos = 50F; + this.agvViewer1.listTAG = new AGVEmulator.UC.AgvViewer.ptdata[] { + ptdata11, + ptdata12, + ptdata13, + ptdata14, + ptdata15, + ptdata16, + ptdata17, + ptdata18, + ptdata19, + ptdata20, + ptdata21, + ptdata22, + ptdata23, + ptdata24, + ptdata25, + ptdata26, + ptdata27, + ptdata28}; + this.agvViewer1.Location = new System.Drawing.Point(241, 0); + this.agvViewer1.Name = "agvViewer1"; + this.agvViewer1.Size = new System.Drawing.Size(899, 120); + this.agvViewer1.StopbyMark = false; + this.agvViewer1.TabIndex = 0; + this.agvViewer1.Text = "agvViewer1"; + this.agvViewer1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.agvViewer1_MouseDown); // - // button3 + // serAGV // - this.button3.Location = new System.Drawing.Point(246, 339); - this.button3.Name = "button3"; - this.button3.Size = new System.Drawing.Size(86, 38); - this.button3.TabIndex = 15; - this.button3.Tag = "--"; - this.button3.Text = "Pick Off"; - this.button3.UseVisualStyleBackColor = true; - this.button3.Click += new System.EventHandler(this.button3_Click); + this.serAGV.BaudRate = 9600; + this.serAGV.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.serAGV.dev = null; + this.serAGV.Dock = System.Windows.Forms.DockStyle.Left; + this.serAGV.Location = new System.Drawing.Point(0, 0); + this.serAGV.Name = "serAGV"; + this.serAGV.PortName = "COM21"; + this.serAGV.Size = new System.Drawing.Size(241, 120); + this.serAGV.TabIndex = 0; + // + // serBMS + // + this.serBMS.BaudRate = 9600; + this.serBMS.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.serBMS.dev = null; + this.serBMS.Dock = System.Windows.Forms.DockStyle.Top; + this.serBMS.Location = new System.Drawing.Point(3, 17); + this.serBMS.Name = "serBMS"; + this.serBMS.PortName = "COM31"; + this.serBMS.Size = new System.Drawing.Size(1134, 84); + this.serBMS.TabIndex = 1; + // + // serCAL + // + this.serCAL.BaudRate = 9600; + this.serCAL.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.serCAL.dev = null; + this.serCAL.Dock = System.Windows.Forms.DockStyle.Top; + this.serCAL.Location = new System.Drawing.Point(3, 17); + this.serCAL.Name = "serCAL"; + this.serCAL.PortName = "COM41"; + this.serCAL.Size = new System.Drawing.Size(776, 84); + this.serCAL.TabIndex = 1; // // fMain // @@ -1734,6 +1745,7 @@ namespace AGVEmulator private ToolStripButton toolStripButton5; private Button button3; private Button button2; + private Panel panel2; } } diff --git a/AGVEmulator/fMain.cs b/AGVEmulator/fMain.cs index 352e443..e6f6e93 100644 --- a/AGVEmulator/fMain.cs +++ b/AGVEmulator/fMain.cs @@ -153,10 +153,10 @@ namespace AGVEmulator chk.CheckedChanged += Chk_CheckedChanged; this.panel7.Controls.Add(chk); } - arrs = Enum.GetNames(typeof(DevAGV.esignal)); + arrs = Enum.GetNames(typeof(DevAGV.esignal1)); foreach (var item in arrs) { - var data = (DevAGV.esignal)Enum.Parse(typeof(DevAGV.esignal), item); + var data = (DevAGV.esignal1)Enum.Parse(typeof(DevAGV.esignal1), item); var chk = new CheckBox(); chk.Text = $"[{(int)data:00}] {item}"; @@ -167,6 +167,20 @@ namespace AGVEmulator chk.CheckedChanged += Chk_CheckedChanged; this.panel8.Controls.Add(chk); } + arrs = Enum.GetNames(typeof(DevAGV.esignal2)); + foreach (var item in arrs) + { + var data = (DevAGV.esignal2)Enum.Parse(typeof(DevAGV.esignal2), item); + + var chk = new CheckBox(); + chk.Text = $"[{(int)data:00}] {item}"; + chk.AutoSize = true; + chk.Visible = true; + chk.Tag = (int)data; + chk.Dock = DockStyle.Top; + chk.CheckedChanged += Chk_CheckedChanged; + this.panel2.Controls.Add(chk); + } arrs = Enum.GetNames(typeof(DevAGV.eerror)); foreach (var item in arrs) { @@ -344,7 +358,7 @@ namespace AGVEmulator private void AgvViewer1_MarkTouched(object sender, UC.AgvViewer.TagArgs e) { // throw new NotImplementedException(); - AGV.SetAGV(esignal.mark_sensor_1, e.Active); + AGV.SetAGV(esignal1.mark_sensor_1, e.Active); logAGV.Add($"mark {e.Data} touch:{e.Active}"); } @@ -730,10 +744,14 @@ namespace AGVEmulator var v2 = (DevAGV.eerror)idx; AGV.SetAGV(v2, chk.Checked); break; - case "sg": - var v3 = (DevAGV.esignal)idx; + case "sg1": + var v3 = (DevAGV.esignal1)idx; AGV.SetAGV(v3, chk.Checked); break; + case "sg2": + var v4 = (DevAGV.esignal2)idx; + AGV.SetAGV(v4, chk.Checked); + break; } chk.BackColor = chk.Checked ? Color.Lime : SystemColors.Window; diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs index 259e50b..48f78d5 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs @@ -810,11 +810,11 @@ namespace AGVNavigationCore.Controls /// /// 동기화 모드 종료 /// - public void ExitSyncMode() + public void ExitSyncMode(CanvasMode newmode) { if (_canvasMode == CanvasMode.Sync) { - _canvasMode = CanvasMode.Edit; // 기본 모드로 복귀 (또는 이전 모드) + _canvasMode = newmode; // 기본 모드로 복귀 (또는 이전 모드) UpdateModeUI(); Invalidate(); } diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs index b5fbc7e..ef63a38 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs @@ -102,6 +102,8 @@ namespace AGVNavigationCore.PathFinding.Core /// public AgvDirection PrevDirection { get; set; } + public MapNode Gateway { get; set; } + /// /// 기본 생성자 /// diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs index 5c35028..dfe41d0 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs @@ -169,8 +169,8 @@ namespace AGVNavigationCore.PathFinding.Planning else if (magdir == "R") magnetDirection = MagnetDirection.Right; } - - + + } var nodeInfo = new NodeMotorInfo(i + 1, node.Id, node.RfidId, prevDirection, nextNode, magnetDirection); @@ -194,5 +194,556 @@ namespace AGVNavigationCore.PathFinding.Planning + /// + /// 길목(Gateway) 기반 고급 경로 계산 (기존 SimulatorForm.CalcPath 이관) + /// + public AGVPathResult CalculatePath(MapNode startNode, MapNode targetNode, MapNode prevNode, AgvDirection prevDir) + { + AGVPathResult Retval; + // var o_StartNode = startNode; + // startNode, targetNode는 이미 인자로 받음 + + if (startNode == null || targetNode == null) return AGVPathResult.CreateFailure("시작/종료노드가 지정되지 않음"); + + try + { + // 종료노드라면 이전위치로 이동시켜야한다. (Simulator Logic) + // 만약 시작노드가 끝단(ConnectedMapNodes.Count == 1)이라면, + // AGV가 해당 노드에 '도착'한 상태가 아니라 '작업' 중일 수 있으므로 + // 이전 노드(진입점)로 위치를 보정하여 경로를 계산한다. + AGVPathResult LimitPath = null; + if (startNode.ConnectedMapNodes.Count == 1) + { + // 시작점 -> 이전점 경로 (보통 후진이나 전진 1칸) + LimitPath = this.FindPathAStar(startNode, prevNode); + if (LimitPath.Success) + { + for (int i = 0; i < LimitPath.Path.Count; i++) + { + var nodeinfo = LimitPath.Path[i]; + var dir = (prevDir == AgvDirection.Forward ? AgvDirection.Backward : AgvDirection.Forward); + LimitPath.DetailedPath.Add(new NodeMotorInfo(i + 1, nodeinfo.Id, nodeinfo.RfidId, dir)); + } + + // 시작 위치 및 방향 변경 + // var org_start = startNode; // Unused + startNode = prevNode; + prevNode = LimitPath.Path.First(); // startNode (original) + prevDir = (prevDir == AgvDirection.Forward ? AgvDirection.Backward : AgvDirection.Forward); + } + else + { + // 경로 생성 실패 시 보정 없이 진행하거나 에러 처리 + // 여기서는 일단 기존 로직대로 진행 + } + } + + // 2. Buffer-to-Buffer 예외 처리 + // 05~31 구간 체크 + var node05 = _mapNodes.FirstOrDefault(n => n.RfidId == 5); + var node31 = _mapNodes.FirstOrDefault(n => n.RfidId == 31); + + bool fixpath = false; + Retval = null; + MapNode gatewayNode = null; + + if (node05 != null && node31 != null) + { + // 버퍼 구간 경로 테스트 + var rlt = this.FindPathAStar(node05, node31); + if (rlt.Success) + { + // 버퍼구간내에 시작과 종료가 모두 포함되어있다 + if (rlt.Path.Find(n => n.Id == startNode.Id) != null && + rlt.Path.Find(n => n.Id == targetNode.Id) != null) + { + Retval = CalcPathBufferToBuffer(startNode, targetNode, prevNode, prevDir); + fixpath = true; + } + } + } + + if (!fixpath) + { + // 3. 목적지별 Gateway 및 진입 조건 확인 + gatewayNode = GetGatewayNode(targetNode); + + if (gatewayNode == null) + { + // 게이트웨이가 없는 경우라면(일반 노드 등), Gateway 로직 없이 기본 경로 탐색 + Retval = this.FindBasicPath(startNode, targetNode, prevNode, prevDir); + } + else + { + // Gateway Node 찾음 + // 4. Start -> Gateway 경로 계산 (A*) + var pathToGateway = this.FindBasicPath(startNode, gatewayNode, prevNode, prevDir); + if (pathToGateway.Success == false) + return AGVPathResult.CreateFailure($"Gateway({gatewayNode.ID2})까지 경로 실패: {pathToGateway.Message}"); + + // 방향을 확인하여, 왔던 방향으로 되돌아가야 한다면 방향 반전 + if (pathToGateway.Path.Count > 1) + { + var predictNext = pathToGateway.Path[1]; + if (predictNext.Id == prevNode.Id) + { + var reverseDir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward; + foreach (var item in pathToGateway.DetailedPath) + item.MotorDirection = reverseDir; + } + } + + // 마지막 경로는 게이트웨이이므로 제거 (Gateway 진입 후 처리는 GetPathFromGateway에서 담당) + if (pathToGateway.Path.Count > 0 && pathToGateway.Path.Last().Id == gatewayNode.Id) + { + var idx = pathToGateway.Path.Count - 1; + pathToGateway.Path.RemoveAt(idx); + pathToGateway.DetailedPath.RemoveAt(idx); + } + + // 5. Gateway -> Target 경로 계산 (회차 패턴 및 최종 진입 포함) + MapNode GateprevNode = pathToGateway.Path.LastOrDefault() ?? prevNode; + NodeMotorInfo GatePrevDetail = pathToGateway.DetailedPath.LastOrDefault(); + + var arrivalOrientation = GatePrevDetail?.MotorDirection ?? prevDir; + var gatewayPathResult = GetPathFromGateway(gatewayNode, targetNode, GateprevNode, arrivalOrientation); + + if (!gatewayPathResult.Success) + return AGVPathResult.CreateFailure($"{gatewayPathResult.Message}"); + + Retval = CombinePaths(pathToGateway, gatewayPathResult); + } + } + + //게이트웨이 + Retval.Gateway = gatewayNode; + + // 경로 오류 검사 + if (Retval == null || Retval.Success == false) return Retval ?? AGVPathResult.CreateFailure("경로 계산 결과 없음"); + + if (LimitPath != null) + { + Retval = CombinePaths(LimitPath, Retval); + } + + // 해당 경로와 대상의 도킹포인트 방향 검사 + if (targetNode.DockDirection != DockingDirection.DontCare) + { + var lastPath = Retval.DetailedPath.LastOrDefault(); + if (lastPath != null) + { + if (targetNode.DockDirection == DockingDirection.Forward && lastPath.MotorDirection != AgvDirection.Forward) + { + return AGVPathResult.CreateFailure($"생성된 경로와 목적지의 도킹방향이 일치하지 않습니다(FWD) Target:{targetNode.DockDirection}"); + } + if (targetNode.DockDirection == DockingDirection.Backward && lastPath.MotorDirection != AgvDirection.Backward) + { + return AGVPathResult.CreateFailure($"생성된 경로와 목적지의 도킹방향이 일치하지 않습니다(BWD) Target:{targetNode.DockDirection}"); + } + } + } + + // 경로 최적화: A -> B -> A 패턴 제거 + // 6[F][R] → 13[B][L] → 6[F][L] 같은 경우 제거 + while (fixpath == false) + { + var updatecount = 0; + for (int i = 0; i < Retval.DetailedPath.Count - 2; i++) + { + var n1 = Retval.DetailedPath[i]; + var n2 = Retval.DetailedPath[i + 1]; + var n3 = Retval.DetailedPath[i + 2]; + + if (n1.NodeId == n3.NodeId) + { + bool isInverse = false; + // 1. 모터 방향이 반대인가? (F <-> B) + bool isMotorInverse = (n1.MotorDirection != n2.MotorDirection) && + (n1.MotorDirection == AgvDirection.Forward || n1.MotorDirection == AgvDirection.Backward) && + (n2.MotorDirection == AgvDirection.Forward || n2.MotorDirection == AgvDirection.Backward); + + if (isMotorInverse) + { + // 2. 마그넷 방향이 반대인가? (L <-> R, S <-> S) + bool isMagnetInverse = false; + if (n1.MagnetDirection == MagnetDirection.Straight && n2.MagnetDirection == MagnetDirection.Straight) isMagnetInverse = true; + else if (n1.MagnetDirection == MagnetDirection.Left && n2.MagnetDirection == MagnetDirection.Right) isMagnetInverse = true; + else if (n1.MagnetDirection == MagnetDirection.Right && n2.MagnetDirection == MagnetDirection.Left) isMagnetInverse = true; + + if (isMagnetInverse) isInverse = true; + } + + if (isInverse) + { + // 제자리 회귀 경로 발견 -> 앞의 두 노드(n1, n2)를 제거하여 n3만 남김 + Retval.DetailedPath.RemoveAt(i); + Retval.DetailedPath.RemoveAt(i); + + if (Retval.Path.Count > i + 1) + { + Retval.Path.RemoveAt(i); + Retval.Path.RemoveAt(i); + } + i--; // 인덱스 재조정 + updatecount += 1; + } + } + } + if (updatecount == 0) break; + } + + // 불가능한 회전 경로 검사 (사용자 요청 로직 반영) + for (int i = 0; i < Retval.DetailedPath.Count - 2; i++) + { + var n1 = Retval.DetailedPath[i]; + var n2 = Retval.DetailedPath[i + 1]; + var n3 = Retval.DetailedPath[i + 2]; + + if (n1.NodeId == n3.NodeId && + n1.MotorDirection == n3.MotorDirection && + n1.MotorDirection == n2.MotorDirection) // Fix: 중간 노드 방향도 같을 때만 에러 + { + return AGVPathResult.CreateFailure($"불가능한 회전 경로가 포함되어있습니다. {n1.RfidId}->{n2.RfidId}->{n3.RfidId}"); + } + } + + // 기타 검증 로직 (마지막 노드 도킹, 시작노드 일치 등) + var lastnode = Retval.Path.Last(); + if (lastnode.StationType != StationType.Normal) + { + var lastnodePath = Retval.DetailedPath.Last(); + if (lastnode.DockDirection == DockingDirection.Forward && lastnodePath.MotorDirection != AgvDirection.Forward) + return AGVPathResult.CreateFailure($"목적지의 모터방향({lastnode.DockDirection}) 불일치 경로방향({lastnodePath.MotorDirection})"); + if (lastnode.DockDirection == DockingDirection.Backward && lastnodePath.MotorDirection != AgvDirection.Backward) + return AGVPathResult.CreateFailure($"목적지의 모터방향({lastnode.DockDirection}) 불일치 경로방향({lastnodePath.MotorDirection})"); + } + + // 첫번째 노드 일치 검사 - 필요시 수행 (startNode가 변경될 수 있어서 o_StartNode 등 필요할 수도 있음) + // 여기서는 생략 혹은 간단히 체크 + + // 되돌아가는 길 방향 일치 검사 + if (Retval.DetailedPath.Count > 1) + { + var FirstDetailPath = Retval.DetailedPath[0]; + var NextDetailPath = Retval.DetailedPath[1]; + AgvDirection? PredictNextDir = null; + + if (NextDetailPath.NodeId == prevNode.Id) + { + if (NextDetailPath.MagnetDirection == MagnetDirection.Straight) + PredictNextDir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward; + } + + if (PredictNextDir != null && (FirstDetailPath.MotorDirection != (AgvDirection)PredictNextDir)) + { + // return AGVPathResult.CreateFailure($"되돌아가는 길인데 방향이 일치하지않음"); + // 경고 수준이나 무시 가능한 경우도 있음 + } + } + + // 연결성 검사 + for (int i = 0; i < Retval.DetailedPath.Count - 1; i++) + { + var cnode = Retval.Path[i]; + var nnode = Retval.Path[i + 1]; + + if (cnode.ConnectedNodes.Contains(nnode.Id) == false && cnode.Id != nnode.Id) + { + return AGVPathResult.CreateFailure($"[{cnode.RfidId}] 노드에 연결되지 않은 [{nnode.RfidId}]노드가 지정됨"); + } + } + + //각 도킹포인트별로 절대 움직이면 안되는 조건확인 + var firstnode = Retval.Path.FirstOrDefault(); + var firstDet = Retval.DetailedPath.First(); + var failmessage = $"[{firstnode.ID2}] 노드의 시작모터 방향({firstDet.MotorDirection})이 올바르지 않습니다"; + if (firstnode.StationType == StationType.Charger1 && firstDet.MotorDirection != AgvDirection.Forward) + return AGVPathResult.CreateFailure(failmessage); + else if (firstnode.StationType == StationType.Loader && firstDet.MotorDirection != AgvDirection.Backward) + return AGVPathResult.CreateFailure(failmessage); + else if (firstnode.StationType == StationType.UnLoader && firstDet.MotorDirection != AgvDirection.Backward) + return AGVPathResult.CreateFailure(failmessage); + else if (firstnode.StationType == StationType.Clearner && firstDet.MotorDirection != AgvDirection.Backward) + return AGVPathResult.CreateFailure(failmessage); + else if (firstnode.StationType == StationType.Buffer) + { + //버퍼는 도킹이되어잇느닞 확인하고. 그때 방향을 체크해야한다. + } + + + return Retval; + } + catch (Exception ex) + { + return AGVPathResult.CreateFailure($"[계산오류] {ex.Message}"); + } + } + + private AGVPathResult CalcPathBufferToBuffer(MapNode start, MapNode target, MapNode prev, AgvDirection prevDir) + { + // Monitor Side 판단 및 Buffer 간 이동 로직 + int deltaX = 0; + int deltaY = 0; + if (prev == null) return AGVPathResult.CreateFailure("이전 노드 정보가 없습니다"); + else + { + deltaX = start.Position.X - prev.Position.X; + deltaY = -(start.Position.Y - prev.Position.Y); + } + + if (Math.Abs(deltaY) > Math.Abs(deltaX)) + deltaX = deltaY; + + bool isMonitorLeft = false; + + if (deltaX > 0) // 오른쪽(Forward)으로 이동해 옴 + isMonitorLeft = (prevDir == AgvDirection.Backward); + else if (deltaX < 0) // 왼쪽(Reverse)으로 이동해 옴 + isMonitorLeft = (prevDir == AgvDirection.Forward); + else + return AGVPathResult.CreateFailure("이전 노드와의 방향을 알 수 없습니다"); + + if (isMonitorLeft) + { + // Monitor Left -> Gateway 탈출 + var GateWayNode = _mapNodes.FirstOrDefault(n => n.RfidId == 6); + var reverseDir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward; + + AGVPathResult escPath = null; + if (start.Position.X > prev.Position.X) + escPath = this.FindBasicPath(start, GateWayNode, prev, prevDir); + else + escPath = this.FindBasicPath(start, GateWayNode, prev, reverseDir); + + if (!escPath.Success) return AGVPathResult.CreateFailure("버퍼 탈출 경로 실패"); + + var lastNode = escPath.Path.Last(); + var lastPrev = escPath.Path[escPath.Path.Count - 2]; + var lastDir = escPath.DetailedPath.Last().MotorDirection; + + var gateToTarget = GetPathFromGateway(GateWayNode, target, lastPrev, lastDir); + + escPath.Path.RemoveAt(escPath.Path.Count - 1); + escPath.DetailedPath.RemoveAt(escPath.DetailedPath.Count - 1); + + return CombinePaths(escPath, gateToTarget); + } + else + { + // Monitor Right -> 직접 진입 또는 Overshoot + bool isTargetLeft = target.Position.X < start.Position.X; + + if (target == start) + { + // 제자리 재정렬 (Same as Simulator logic) + var list = new List(); + var retval = AGVPathResult.CreateSuccess(list, new List(), 0, 0); + var resversedir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward; + + retval.Path.Add(target); + + if (deltaX < 0) + { + var nextNode = start.ConnectedMapNodes.Where(t => t.Id != prev.Id && t.StationType == StationType.Buffer).FirstOrDefault(); + if (nextNode != null) + { + retval.DetailedPath.Add(new NodeMotorInfo(1, target.Id, target.RfidId, prevDir)); + retval.Path.Add(nextNode); + var lastDefailt = retval.DetailedPath.Last(); + retval.DetailedPath.Add(new NodeMotorInfo(lastDefailt.seq + 1, nextNode.Id, nextNode.RfidId, AgvDirection.Forward) + { + Speed = SpeedLevel.M, + }); + retval.Path.Add(target); + retval.DetailedPath.Add(new NodeMotorInfo((retval.DetailedPath.Max(t => t.seq) + 1), target.Id, target.RfidId, AgvDirection.Forward)); + retval.Path.Add(target); + retval.DetailedPath.Add(new NodeMotorInfo(retval.DetailedPath.Max(t => t.seq) + 1, target.Id, target.RfidId, AgvDirection.Backward)); + } + else + { + retval.DetailedPath.Add(new NodeMotorInfo(1, target.Id, target.RfidId, resversedir)); + retval.Path.Add(prev); + retval.DetailedPath.Add(new NodeMotorInfo(retval.DetailedPath.Last().seq + 1, prev.Id, prev.RfidId, prevDir) + { + Speed = SpeedLevel.M, + }); + retval.Path.Add(target); + retval.DetailedPath.Add(new NodeMotorInfo(retval.DetailedPath.Max(t => t.seq) + 1, target.Id, target.RfidId, prevDir)); + } + } + else + { + retval.DetailedPath.Add(new NodeMotorInfo(1, target.Id, target.RfidId, prevDir)); + var nextNode = start.ConnectedMapNodes.Where(t => t.Id != prev.Id && t.StationType == StationType.Buffer).FirstOrDefault(); + retval.Path.Add(nextNode); + var lastDefailt = retval.DetailedPath.Last(); + retval.DetailedPath.Add(new NodeMotorInfo(lastDefailt.seq + 1, nextNode.Id, nextNode.RfidId, AgvDirection.Backward) + { + Speed = SpeedLevel.L, + }); + retval.Path.Add(target); + retval.DetailedPath.Add(new NodeMotorInfo(retval.DetailedPath.Max(t => t.seq) + 1, target.Id, target.RfidId, AgvDirection.Backward)); + } + return retval; + } + else if (isTargetLeft) + { + return this.FindBasicPath(start, target, prev, AgvDirection.Backward); + } + else + { + // Overshoot + var path1 = this.FindBasicPath(start, target, prev, AgvDirection.Forward); + if (path1.Path.Count < 2) return AGVPathResult.CreateFailure("Overshoot 경로 생성 실패"); + + var last = path1.Path.Last(); + var lastD = path1.DetailedPath.Last(); + path1.Path.RemoveAt(path1.Path.Count - 1); + path1.DetailedPath.RemoveAt(path1.DetailedPath.Count - 1); + + path1.Path.Add(last); + path1.DetailedPath.Add(new NodeMotorInfo(lastD.seq + 1, lastD.NodeId, lastD.RfidId, AgvDirection.Backward) + { + Speed = SpeedLevel.L, + }); + + return path1; + } + } + } + + private AGVPathResult GetPathFromGateway(MapNode GTNode, MapNode targetNode, MapNode PrevNode, AgvDirection PrevDirection) + { + AGVPathResult resultPath = null; + var deltaX = GTNode.Position.X - PrevNode.Position.X; + var isMonitorLeft = false; + + if (deltaX > 0) isMonitorLeft = PrevDirection == AgvDirection.Backward; + else isMonitorLeft = PrevDirection == AgvDirection.Forward; + + if (targetNode.StationType == StationType.Loader || targetNode.StationType == StationType.Charger2) + { + deltaX = GTNode.Position.Y - PrevNode.Position.Y; + if (deltaX < 0) isMonitorLeft = PrevDirection == AgvDirection.Backward; + else isMonitorLeft = PrevDirection == AgvDirection.Forward; + } + + switch (targetNode.StationType) + { + case StationType.Loader: + case StationType.Charger2: + case StationType.Charger1: + case StationType.UnLoader: + case StationType.Clearner: + case StationType.Buffer: + var rlt1 = new AGVPathResult(); + rlt1.Success = true; + + var motdir = targetNode.DockDirection == DockingDirection.Backward ? AgvDirection.Backward : AgvDirection.Forward; + var pathtarget = this.FindBasicPath(GTNode, targetNode, PrevNode, motdir); + + if ((targetNode.DockDirection == DockingDirection.Backward && isMonitorLeft) || + (targetNode.DockDirection == DockingDirection.Forward && !isMonitorLeft)) + { + var turnPatterns = GetTurnaroundPattern(GTNode, targetNode); + if (turnPatterns == null || !turnPatterns.Any()) return new AGVPathResult { Success = false, Message = $"회차 패턴 없음: Dir {PrevDirection}" }; + + foreach (var item in turnPatterns) + { + var rfidvalue = ushort.Parse(item.Substring(0, 4)); + var node = _mapNodes.FirstOrDefault(t => t.RfidId == rfidvalue); + rlt1.Path.Add(node); + + AgvDirection nodedir = item.Substring(4, 1) == "F" ? AgvDirection.Forward : AgvDirection.Backward; + MagnetDirection magnet = MagnetDirection.Straight; + var magchar = item.Substring(5, 1); + if (magchar == "L") magnet = MagnetDirection.Left; + else if (magchar == "R") magnet = MagnetDirection.Right; + + rlt1.DetailedPath.Add(new NodeMotorInfo(rlt1.DetailedPath.Count, node.Id, node.RfidId, nodedir, null, magnet) + { + Speed = SpeedLevel.L, + }); + } + + if (pathtarget.DetailedPath.First().NodeId != rlt1.DetailedPath.Last().NodeId || + pathtarget.DetailedPath.First().MotorDirection != rlt1.DetailedPath.Last().MotorDirection) + { + // Gateway 턴 마지막 주소 불일치 경고 (로깅 등) + } + + pathtarget.Path.RemoveAt(0); + pathtarget.DetailedPath.RemoveAt(0); + } + return CombinePaths(rlt1, pathtarget); + + default: + return AGVPathResult.CreateFailure($"지원되지 않는 StationType: {targetNode.StationType}"); + } + } + + private MapNode GetGatewayNode(MapNode node) + { + var rfid = 0; + if (node.StationType == StationType.UnLoader) rfid = 10; + else if (node.StationType == StationType.Charger1) rfid = 9; + else if (node.StationType == StationType.Clearner) rfid = 6; + else if (node.StationType == StationType.Charger2) rfid = 13; + else if (node.StationType == StationType.Loader) rfid = 13; + else if (node.StationType == StationType.Buffer) rfid = 6; + + if (rfid == 0) return null; + return _mapNodes.FirstOrDefault(t => t.RfidId == rfid); + } + + private List GetTurnaroundPattern(MapNode gatewayNode, MapNode targetNode) + { + switch (gatewayNode.RfidId) + { + case 6: + if (targetNode.StationType == StationType.Buffer) + return new List { "0006BL", "0007FS", "0013BL", "0006BL" }; + else + return new List { "0006BL", "0007FS", "0013BL", "0006BS" }; + case 9: return new List { "0009FL", "0010BS", "0007FL", "0009FS" }; + case 10: return new List { "0010BR", "0009FR", "0007BS", "0010BS" }; + case 13: return new List { "0013BL", "0006FL", "0007BS", "0013BS" }; + default: return null; + } + } + + private AGVPathResult CombinePaths(AGVPathResult p1, AGVPathResult p2) + { + var res = new AGVPathResult(); + res.Success = true; + + var p1last = p1.DetailedPath.LastOrDefault(); + var p2fist = p2.DetailedPath.FirstOrDefault(); + + if (p1last != null && p2fist != null && + (p1last.NodeId == p2fist.NodeId && p1last.MotorDirection == p2fist.MotorDirection && p1last.MagnetDirection == p2fist.MagnetDirection)) + { + p1.Path.RemoveAt(p1.Path.Count - 1); + p1.DetailedPath.RemoveAt(p1.DetailedPath.Count - 1); + } + + foreach (var item in p1.Path) res.Path.Add(item); + foreach (var item in p2.Path) res.Path.Add(item); + + foreach (var item in p1.DetailedPath) + { + var maxseq = res.DetailedPath.Count == 0 ? 0 : res.DetailedPath.Max(t => t.seq); + item.seq = maxseq + 1; + res.DetailedPath.Add(item); + } + foreach (var item in p2.DetailedPath) + { + var maxseq = res.DetailedPath.Count == 0 ? 0 : res.DetailedPath.Max(t => t.seq); + item.seq = maxseq + 1; + res.DetailedPath.Add(item); + } + + return res; + } } } \ No newline at end of file diff --git a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs index 5b7c532..b58c104 100644 --- a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs +++ b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs @@ -2580,742 +2580,13 @@ namespace AGVSimulator.Forms public AGVPathResult CalcPath(MapNode startNode, MapNode targetNode, List nodes, MapNode prevNode, AgvDirection prevDir) { - AGVPathResult Retval; - var o_StartNode = startNode; - if (startNode == null || targetNode == null) return AGVPathResult.CreateFailure("시작/종료노드가 지정되지 않음"); + // Core Logic으로 이관됨 + var pathFinder = new AGVPathfinder(nodes); + var result = pathFinder.CalculatePath(startNode, targetNode, prevNode, prevDir); - - try - { - var pathFinder = new AGVPathfinder(nodes); - var selectedAGV = _agvListCombo.SelectedItem as VirtualAGV; - if (selectedAGV == null) return AGVPathResult.CreateFailure("Virtual AGV 없음"); - - //종료노드라면 이전위치로 이동시켜야한다. - AGVPathResult LimitPath = null; - if (startNode.ConnectedMapNodes.Count == 1) - { - LimitPath = pathFinder.FindPathAStar(startNode, prevNode); - for (int i = 0; i < LimitPath.Path.Count; i++) - { - var nodeinfo = LimitPath.Path[i]; - var dir = (prevDir == AgvDirection.Forward ? AgvDirection.Backward : AgvDirection.Forward); - LimitPath.DetailedPath.Add(new NodeMotorInfo(i + 1, nodeinfo.Id, nodeinfo.RfidId, dir)); - } - - //시작위치 및 방향을변경해준다 - var org_start = startNode; - startNode = prevNode; - prevNode = org_start; - prevDir = (prevDir == AgvDirection.Forward ? AgvDirection.Backward : AgvDirection.Forward); - } - - - //var prevNode = selectedAGV.PrevNode; - //var prevDir = selectedAGV.PrevDirection; - - // 2. Buffer-to-Buffer 예외 처리 - var node05 = FindNode(5); //05~31사이의 노드는 모두 버퍼이다. - var node31 = FindNode(31); - if (node05 == null || node31 == null) return AGVPathResult.CreateFailure("버퍼구간 노드가 없습니다(05~31)"); - - var rlt = pathFinder.FindPathAStar(node05, node31); - if (rlt.Success == false) return AGVPathResult.CreateFailure("버퍼구간 노드경로 확인 실패(05~31)"); - - //하이라이트노드 해제 - _simulatorCanvas.HighlightNodeId = null; - - //버퍼구간내에 시작과 종료가 모두 포함되어있다 - bool fixpath = false; - if (rlt.Path.Contains(startNode) && rlt.Path.Contains(targetNode)) - { - Retval = CalcPathBufferToBuffer(pathFinder, startNode, targetNode, prevNode, prevDir, selectedAGV); - fixpath = true; - } - else - { - // 3. 목적지별 Gateway 및 진입 조건 확인 - var gatewayNode = GetGatewayNode(targetNode); - if (gatewayNode == null) - { - //게이트웨이가 없는 경우라면 목적지가 도킹포인트가 아니므로, a*알골리즘으로 진행 방향만 맟춰서 이동한다 - Retval = pathFinder.FindBasicPath(startNode, targetNode, prevNode, prevDir); - } - else - { - // Gateway Node 찾음 - _simulatorCanvas.HighlightNodeId = gatewayNode.Id; // Gateway 강조 설정 - - - // 4. Start -> Gateway 경로 계산 (A*) - var pathToGateway = pathFinder.FindBasicPath(startNode, gatewayNode, prevNode, prevDir); - if (pathToGateway.Success == false) - AGVPathResult.CreateFailure($"Gateway({gatewayNode.ID2})까지 경로 실패: {pathToGateway.Message}"); - - //방향을 확인하여, 왓던방향으로 가야하는디. 반대로 가야하느닞 체크한다. - if (pathToGateway.Path.Count > 1) - { - var predictNext = pathToGateway.Path[1]; //다음으로 찍어야할 노드값 - - //이전노드id와 다음노드id가 같다면 왓던 방향으로 되돌아가야 하므로 방향을 반전시켜야한다. - var GateyatoDIR = prevDir; - if (predictNext.Id == prevNode.Id) - { - var reverseDir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward; - foreach (var item in pathToGateway.DetailedPath) - item.MotorDirection = reverseDir; - } - } - - - - - //마지막경로는 게이트웨이이므로 제거하낟.(260113) - if (pathToGateway.Path.Count > 0 && pathToGateway.Path.Last().Id == gatewayNode.Id) - { - var idx = pathToGateway.Path.Count - 1; - pathToGateway.Path.RemoveAt(idx); - pathToGateway.DetailedPath.RemoveAt(idx); - } - - // 5. Gateway -> Target 경로 계산 (회차 패턴 및 최종 진입 포함) - MapNode GateprevNode = pathToGateway.Path.LastOrDefault() ?? prevNode; - NodeMotorInfo GatePrevDetail = pathToGateway.DetailedPath.LastOrDefault(); - - var arrivalOrientation = GatePrevDetail?.MotorDirection ?? prevDir; - var gatewayPathResult = GetPathFromGateway(pathFinder, gatewayNode, targetNode, GateprevNode, arrivalOrientation); - if (!gatewayPathResult.Success) return AGVPathResult.CreateFailure($"{gatewayPathResult.Message}"); - Retval = CombinePaths(pathToGateway, gatewayPathResult); - } - - } - - - - //경로오류 검사 - if (Retval.Success == false) return Retval; - - if (LimitPath != null) - { - Retval = CombinePaths(LimitPath, Retval); - } - - - - //해당경로와 대상의 도킹포인트의 방향을 검사합니다 - if (targetNode.DockDirection != DockingDirection.DontCare) - { - var lastPath = Retval.DetailedPath.Last(); - if (targetNode.DockDirection == DockingDirection.Forward && lastPath.MotorDirection != AgvDirection.Forward) - { - return AGVPathResult.CreateFailure($"생성된 경로와 목적지의 도킹방향이 일치하지 않습니다(FWD) Target:{targetNode.DockDirection}, Path:{Retval.GetDetailedPathInfo()}"); - } - if (targetNode.DockDirection == DockingDirection.Backward && lastPath.MotorDirection != AgvDirection.Backward) - { - return AGVPathResult.CreateFailure($"생성된 경로와 목적지의 도킹방향이 일치하지 않습니다(BWD) Target:{targetNode.DockDirection}, Path:{Retval.GetDetailedPathInfo()}"); - } - } - - //6[F][R] → 13[B][L] → 6[F][L] 이런경우를 찾아서 경로를 최적화한다. - //위 예제에서는 6FR 13BL 이 제자리로 오는 경로이므로 6FL만 남기면된다. - //단순히 ID만 같은게 아니라, 실제로 갔다가 되돌아오는 패턴(역방향)인지 확인해야 함. - while (fixpath == false && true) - { - var updatecount = 0; - for (int i = 0; i < Retval.DetailedPath.Count - 2; i++) - { - var n1 = Retval.DetailedPath[i]; - var n2 = Retval.DetailedPath[i + 1]; - var n3 = Retval.DetailedPath[i + 2]; - - if (n1.NodeId == n3.NodeId) - { - bool isInverse = false; - - // 1. 모터 방향이 반대인가? (F <-> B) - bool isMotorInverse = (n1.MotorDirection != n2.MotorDirection) && - (n1.MotorDirection == AgvDirection.Forward || n1.MotorDirection == AgvDirection.Backward) && - (n2.MotorDirection == AgvDirection.Forward || n2.MotorDirection == AgvDirection.Backward); - - if (isMotorInverse) - { - // 2. 마그넷 방향이 반대인가? (L <-> R, S <-> S) - bool isMagnetInverse = false; - if (n1.MagnetDirection == MagnetDirection.Straight && n2.MagnetDirection == MagnetDirection.Straight) isMagnetInverse = true; - else if (n1.MagnetDirection == MagnetDirection.Left && n2.MagnetDirection == MagnetDirection.Right) isMagnetInverse = true; - else if (n1.MagnetDirection == MagnetDirection.Right && n2.MagnetDirection == MagnetDirection.Left) isMagnetInverse = true; - - if (isMagnetInverse) isInverse = true; - } - - if (isInverse) - { - // 제자리 회귀 경로 발견 -> 앞의 두 노드(n1, n2)를 제거하여 n3만 남김 - Retval.DetailedPath.RemoveAt(i); - Retval.DetailedPath.RemoveAt(i); - - if (Retval.Path.Count > i + 1) - { - Retval.Path.RemoveAt(i); - Retval.Path.RemoveAt(i); - } - i--; // 인덱스 재조정 - updatecount += 1; - } - } - } - if (updatecount == 0) break; - } - - - //detail 경로를 확인해서 왓던길에서 바로 방향이 전환되는 경우를 찾는다 - for (int i = 0; i < Retval.DetailedPath.Count - 2; i++) - { - - var n1 = Retval.DetailedPath[i]; - var n2 = Retval.DetailedPath[i + 1]; - var n3 = Retval.DetailedPath[i + 2]; - - if (n1.NodeId == n3.NodeId && n1.MotorDirection == n3.MotorDirection && n1.MotorDirection == n2.MotorDirection) - { - return AGVPathResult.CreateFailure($"불가능한 회전 경로가 포함되어있습니다. {n1.RfidId}->{n2.RfidId}->{n3.RfidId}\n{Retval.GetDetailedPathInfo()}"); - } - - } - - //최종 목적지를 확인하여 도킹 노드라면 그 도킹노드방향과 모터방향을 체크한다. - var lastnode = Retval.Path.Last(); - if (lastnode.StationType != StationType.Normal) - { - var lastnodePath = Retval.DetailedPath.Last(); - if (lastnode.DockDirection == DockingDirection.Forward && lastnodePath.MotorDirection != AgvDirection.Forward) - return AGVPathResult.CreateFailure($"목적지의 모터방향({lastnode.DockDirection}) 불일치 경로방향({lastnodePath.MotorDirection}) {Retval.GetDetailedPathInfo(true)}"); - if (lastnode.DockDirection == DockingDirection.Backward && lastnodePath.MotorDirection != AgvDirection.Backward) - return AGVPathResult.CreateFailure($"목적지의 모터방향({lastnode.DockDirection}) 불일치 경로방향({lastnodePath.MotorDirection}) {Retval.GetDetailedPathInfo(true)}"); - } - - //계산된노드와 시작노드 ID를 체크한다. - if (o_StartNode.ID2 != Retval.Path.First().ID2) - { - return AGVPathResult.CreateFailure($"첫번째노드({Retval.Path.First().ID2})가 시작노드({o_StartNode.ID2})와 일치하지 않습니다. {Retval.GetDetailedPathInfo(true)}"); - } - - //이전진행방향을 체크하여.. 지정된 다음노드가 올바른지 확인한다. - if (Retval.DetailedPath.Count > 1) - { - var FirstDetailPath = Retval.DetailedPath[0];//.First(); - var NextDetailPath = Retval.DetailedPath[1]; - - AgvDirection? PredictNextDir = null;// = prevDir;//== AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward; - if (NextDetailPath.NodeId == prevNode.Id) - { - //다음노드와 이전노드 ID가 일치하다면. 왓던 방향으로 되돌아 가는 경우이다 - if (NextDetailPath.MagnetDirection == MagnetDirection.Straight) - PredictNextDir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward; - else - PredictNextDir = null; //이경우는 모두 정의하지 못해서 skip 한다 - } - else - { - //다음노드와 이전노드가 일치하지 않으므로 이전 진행방향으로 이동하는 경우이다 - //if (FirstDetailPath.MagnetDirection == MagnetDirection.Straight) - // PredictNextDir = prevDir; - //else - PredictNextDir = null; //이경우는 모두 정의하지 못해서 skip 한다 - } - if (PredictNextDir != null && (FirstDetailPath.MotorDirection != (AgvDirection)PredictNextDir)) - { - return AGVPathResult.CreateFailure($"되돌아가는 길인데 방향이 일치하지않음(예상:{PredictNextDir}, 계산값:{FirstDetailPath.MotorDirection}) {Retval.GetDetailedPathInfo(true)}"); - } - } - - //모든 디테일데이터의 실제 위치를 기반으로 전체 노드를 추정한다 - for (int i = 0; i < Retval.DetailedPath.Count - 1; i++) - { - var pnode = (i == 0 ? prevNode : Retval.Path[i - 1]); - var pdir = (i == 0 ? prevDir : Retval.DetailedPath[i - 1].MotorDirection); - var cnode = Retval.Path[i]; - var nnode = Retval.Path[i + 1]; - - //연결된 노드에 다음노드 id가 포함되는지 확인한다 - if (cnode.ConnectedNodes.Contains(nnode.Id) == false && cnode.Id != nnode.Id) - { - return AGVPathResult.CreateFailure($"[{cnode.RfidId}] 노드에 연결되지 않은 [{nnode.RfidId}]노드가 지정됨 {Retval.GetDetailedPathInfo(true)}"); - } - } - - - //최종결과 반환 - return Retval; - } - catch (Exception ex) - { - return AGVPathResult.CreateFailure($"[계산오류] {ex.Message}"); - } - - } - - private AGVPathResult CalcPathBufferToBuffer(AGVPathfinder pathfinder, MapNode start, MapNode target, MapNode prev, AgvDirection prevDir, VirtualAGV agv) - { - // Monitor Side 판단 로직 - // 현재 AGV의 물리적 방향(Monitor Side)이 "Right" 상태여야 버퍼 진입이 용이하다고 가정. - // Monitor Left 상태(부적절한 방향)라면 Gateway로 탈출해야 함. - - // 이동 벡터 X 변화량 - // prev가 없거나 start와 같으면 이동 방향을 알 수 없음 -> 이 경우 보수적으로 기존 로직(Backward면 탈출) 따름 - int deltaX = 0; - int deltaY = 0; - if (prev == null) return AGVPathResult.CreateFailure("이전 노드 정보가 없습니다"); - else - { - deltaX = start.Position.X - prev.Position.X; - deltaY = -(start.Position.Y - prev.Position.Y); - } - - if (Math.Abs(deltaY) > Math.Abs(deltaX)) - deltaX = deltaY; - - bool isMonitorLeft = false; - - if (deltaX > 0) // 오른쪽(Forward)으로 이동해 옴 (예: 2 -> 4), 위로 이동함 - { - // 이동방향(Right) + 전진(F) => Monitor Right (Good) - // 이동방향(Right) + 후진(B) => Monitor Left (Bad) - isMonitorLeft = (prevDir == AgvDirection.Backward); - } - else if (deltaX < 0) // 왼쪽(Reverse)으로 이동해 옴 (예: 4 -> 2), 아래로 이동함 - { - // 이동방향(Left) + 전진(F) => Monitor Left (Bad) - // 이동방향(Left) + 후진(B) => Monitor Right (Good) - isMonitorLeft = (prevDir == AgvDirection.Forward); - } - else // 제자리 또는 수직 이동 - { - // 판단 불가 시 기존 로직(Backward면 Gateway) 유지 - return AGVPathResult.CreateFailure("이전 노드와의 방향을 알 수 없습니다"); - } - - if (isMonitorLeft) - { - // Monitor Left 상태 (방향 불일치) -> Gateway로 탈출 - var GateWayNode = FindNode(6); - var reverseDir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward; - - //왼족에서 오른조긍로 이동하면 prevdir 을 그대로 쓰는데.. 오른쪽에서 왼쪽으로 이동했다면 방향을 변경해야한다 - AGVPathResult escPath = null; - if (start.Position.X > prev.Position.X) - { - escPath = pathfinder.FindBasicPath(start, GateWayNode, prev, prevDir); - if (!escPath.Success) return AGVPathResult.CreateFailure("버퍼 탈출 경로 실패"); - } - else - { - escPath = pathfinder.FindBasicPath(start, GateWayNode, prev, reverseDir); - if (!escPath.Success) return AGVPathResult.CreateFailure("버퍼 탈출 경로 실패"); - } - - - - var lastNode = escPath.Path.Last(); // Should be GW6 - var lastPrev = escPath.Path[escPath.Path.Count - 2]; - var lastDir = escPath.DetailedPath.Last().MotorDirection; - - // Gateway 도착 후, Target(Buffer)으로 이동 - // 여기서부터는 "Monitor Right" 로직(즉, 적절한 방향 진입)을 적용합니다. - // 6번에서 Target이 왼쪽이면 Direct(Backward), 오른쪽이면 Overshoot(Forward->Backward) - - - bool isTargetLeftOfGW = target.Position.X < GateWayNode.Position.X; - AGVPathResult entryPath = null; - - //게이트웨이까지 후진으로 이동했다면 모니터방향이 오른쪽이다 => 방향전환필요 - var gateToTarget = GetPathFromGateway(pathfinder, GateWayNode, target, lastPrev, lastDir); - - escPath.Path.RemoveAt(escPath.Path.Count - 1); - escPath.DetailedPath.RemoveAt(escPath.DetailedPath.Count - 1); - - - var final = CombinePaths(escPath, gateToTarget); - ApplyResultToSimulator(final, agv); - UpdateAdvancedPathDebugInfo(final); - return final; - } - else - { - // Monitor Right 상태 (방향 일치) -> 직접 진입 또는 Overshoot - - bool isTargetLeft = target.Position.X < start.Position.X; - - if (target == start) - { - //시작위치랑 종료위치랑 같고, 방향도 같다면 이동할 필요는 없는 조건이다 - //오른쪽으로 한번더 이동해서그곳까지 이동한 후 역방향으로 MS 처리한다 - //PREV 가 아닌 다른 노드가 이동할 대상이다 - //현재위치에서 지정방향으로 이동한다. - var list = new List(); - var retval = AGVPathResult.CreateSuccess(list, new List(), 0, 0); - var resversedir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward; - - //현재위치에서 반드시 시작해야하낟. - retval.Path.Add(target); - - - - if (deltaX < 0) - { - //장비가 오른쪽에서 왼쪽으로 오는 경우와 - var nextNode = start.ConnectedMapNodes.Where(t => t.Id != prev.Id && t.StationType == StationType.Buffer).FirstOrDefault(); - if (nextNode != null) - { - //방향결정 - retval.DetailedPath.Add(new NodeMotorInfo(1, target.Id, target.RfidId, prevDir)); - - //다음노드로 이동을한다. - retval.Path.Add(nextNode); - var lastDefailt = retval.DetailedPath.Last(); - retval.DetailedPath.Add(new NodeMotorInfo(lastDefailt.seq + 1, nextNode.Id, nextNode.RfidId, AgvDirection.Forward) - { - Speed = SpeedLevel.M, - IsPass = false, - }); - - ////다시원래노드로 이동을해야한다.(반대방향으로) - retval.Path.Add(target); - retval.DetailedPath.Add(new NodeMotorInfo((retval.DetailedPath.Max(t => t.seq) + 1), target.Id, target.RfidId, AgvDirection.Forward)); - - //최종목적지로 간다 - retval.Path.Add(target); - retval.DetailedPath.Add(new NodeMotorInfo(retval.DetailedPath.Max(t => t.seq) + 1, target.Id, target.RfidId, AgvDirection.Backward)); - } - else - { - //방향결정(다음노드가 없으니 반대방향으로 들어가야한다) - retval.DetailedPath.Add(new NodeMotorInfo(1, target.Id, target.RfidId, resversedir)); - - //다음노드가 없으므로 이전노드로 처리를 해야한다. - retval.Path.Add(prev); - retval.DetailedPath.Add(new NodeMotorInfo(retval.DetailedPath.Last().seq + 1, prev.Id, prev.RfidId, prevDir) - { - Speed = SpeedLevel.M, - IsPass = false, - }); - - //최종목적지로 간다 - retval.Path.Add(target); - retval.DetailedPath.Add(new NodeMotorInfo(retval.DetailedPath.Max(t => t.seq) + 1, target.Id, target.RfidId, prevDir)); - } - - - } - else - { - //왼쪽에서 오른쪽으로 오는 경우가 서로 다르다 - retval.DetailedPath.Add(new NodeMotorInfo(1, target.Id, target.RfidId, prevDir)); - - var nextNode = start.ConnectedMapNodes.Where(t => t.Id != prev.Id && t.StationType == StationType.Buffer).FirstOrDefault(); - //한번더 이동을한다.(그리고는 반대로 저속 이동을 하게한다) - retval.Path.Add(nextNode); - var lastDefailt = retval.DetailedPath.Last(); - retval.DetailedPath.Add(new NodeMotorInfo(lastDefailt.seq + 1, nextNode.Id, nextNode.RfidId, AgvDirection.Backward) - { - Speed = SpeedLevel.L, - IsPass = false, - }); - - //최종목적지로 간다 - retval.Path.Add(target); - retval.DetailedPath.Add(new NodeMotorInfo(retval.DetailedPath.Max(t => t.seq) + 1, target.Id, target.RfidId, AgvDirection.Backward)); - } - - - - - return retval; - } - else if (isTargetLeft) - { - var directPath = pathfinder.FindBasicPath(start, target, prev, AgvDirection.Backward); - ApplyResultToSimulator(directPath, agv); - UpdateAdvancedPathDebugInfo(directPath); - return directPath; - } - else - { - //대상이 나보다 우측에 있으니 RFID값이 읽어지는 위치까지 이동후에 다시 반대방향으로 마크스탑 해야 함 - //그냥 대상 노드까지 이동을 한다.. RFID값이 실제 멈추는 위치 이전에 있으니 그곳까지 이동하고 역방향 마크스탑하면 동일한 위치이다. 위 식은 그 이전노드까지 확실하게 이동하는 코드이다 - //var overshootNode = target.ConnectedMapNodes.OrderByDescending(n => n.Position.X).FirstOrDefault(); - //if (overshootNode == null || overshootNode.Position.X <= target.Position.X) - // return (false, "Overshoot 공간 부족"); - - var path1 = pathfinder.FindBasicPath(start, target, prev, AgvDirection.Forward); - if (path1.Path.Count < 2) - { - return AGVPathResult.CreateFailure("Overshoot 경로 생성 실패"); - } - - //마지막위치는 제거한다. 바로 방향을 전환하기 위함이다. - var last = path1.Path.Last(); - var lastD = path1.DetailedPath.Last(); - path1.Path.RemoveAt(path1.Path.Count - 1); - path1.DetailedPath.RemoveAt(path1.DetailedPath.Count - 1); - - //목표에서 방향바꿔 마크스탑을 해야한다 - path1.Path.Add(last); - - //디테일은 방향바꿔서 추가 필요(속도는 저속처리해준다) - path1.DetailedPath.Add(new NodeMotorInfo(lastD.seq + 1, lastD.NodeId, lastD.RfidId, AgvDirection.Backward) - { - Speed = SpeedLevel.L, - IsPass = false, - }); - - //var p1Last = path1.Path.Last(); - //var p1Prev = path1.Path[path1.Path.Count > 1 ? path1.Path.Count - 2 : 0]; // Safety check - //var p1Dir = path1.DetailedPath.Last().MotorDirection; - - //var path2 = _advancedPathfinder.FindPath(start, target, p1Last, p1Dir, AgvDirection.Backward); - - //if (path2.Success && path2.DetailedPath.Last().NodeId == target.Id) - // path2.DetailedPath = path2.DetailedPath.Take(path2.DetailedPath.Count - 1).ToList(); - - //var final = CombinePaths(path1, path2); - ApplyResultToSimulator(path1, agv); - UpdateAdvancedPathDebugInfo(path1); - return path1;// (true, path1, "버퍼 우측(Overshoot)"); - } - } - } - - - - - /// - /// 노드를 찾기위한 함수 - /// - /// - /// - private MapNode FindNode(ushort rfid) - { - return _simulatorCanvas.Nodes.FirstOrDefault(n => n.RfidId == rfid); - } - /// - /// 노드를 찾기위한 함수 - /// - /// - /// - private MapNode FindNode(string nodeid) - { - return _simulatorCanvas.Nodes.FirstOrDefault(n => n.Id == nodeid); - } - - - - /// - /// Gateway 도착 후, Target까지의 경로(회차 및 최종진입 포함)를 계산합니다. - /// - /// 게이트웨이 노드값(현재노드값) - /// 최종 목표값 - /// 게이트웨이 진입 전 노드 - /// 게이트웨이 진입 전 모터방향 - /// - private AGVPathResult GetPathFromGateway(AGVPathfinder pathFinder, MapNode GTNode, MapNode targetNode, MapNode PrevNode, AgvDirection PrevDirection) - { - AGVPathResult resultPath = null; - - //게이트웨이 진입 한 방향을 보고. 목적지와 도킹방향이 일치하는지 결정한다. - var deltaX = GTNode.Position.X - PrevNode.Position.X; - var isMonitorLeft = false; - - if (deltaX > 0) //게이트웨이가 우측에 있다 - { - //이떄 모터방향이 후진이라면 모니터는 왼쪽이고, 반대는 오른쪽이다 - isMonitorLeft = PrevDirection == AgvDirection.Backward; - } - else - { - isMonitorLeft = PrevDirection == AgvDirection.Forward; - } - - //로더는 상/하 개념으로 처리해야한다.X축이아닌 Y축을 봐야한다. - if (targetNode.StationType == StationType.Loader || targetNode.StationType == StationType.Charger2) - { - deltaX = GTNode.Position.Y - PrevNode.Position.Y; - if (deltaX < 0) isMonitorLeft = PrevDirection == AgvDirection.Backward; - else isMonitorLeft = PrevDirection == AgvDirection.Forward; - } - - switch (targetNode.StationType) - { - case StationType.Loader: - case StationType.Charger2: - case StationType.Charger1: - case StationType.UnLoader: - case StationType.Clearner: - case StationType.Buffer: - - //버퍼는 모니터가 왼쪽에 있으면 안된다. - //충전기1만 전진 도킹을 한다. - List turnPatterns = new List(); - AGVPathResult rlt1 = new AGVPathResult(); - rlt1.Success = true; - - //목적지까지 바로 계산한다 - var motdir = targetNode.DockDirection == DockingDirection.Backward ? AgvDirection.Backward : AgvDirection.Forward; - var pathtarget = pathFinder.FindBasicPath(GTNode, targetNode, PrevNode, motdir); - - if ((targetNode.DockDirection == DockingDirection.Backward && isMonitorLeft) || - (targetNode.DockDirection == DockingDirection.Forward && !isMonitorLeft)) - { - //턴을 하는 - turnPatterns = GetTurnaroundPattern(GTNode, targetNode); - if (turnPatterns == null || turnPatterns.Any() == false) return new AGVPathResult { Success = false, Message = $"회차 패턴 없음: Dir {PrevDirection}" }; - foreach (var item in turnPatterns) - { - var rfidvalue = ushort.Parse(item.Substring(0, 4)); - var node = _simulatorCanvas.Nodes.FirstOrDefault(t => t.RfidId == rfidvalue); - - //경로노드추가 - rlt1.Path.Add(node); - - //Detail 정보도 추가한다. - AgvDirection nodedir = item.Substring(4, 1) == "F" ? AgvDirection.Forward : AgvDirection.Backward; - MagnetDirection magnet = MagnetDirection.Straight; - var magchar = item.Substring(5, 1); - if (magchar == "L") magnet = MagnetDirection.Left; - else if (magchar == "R") magnet = MagnetDirection.Right; - rlt1.DetailedPath.Add(new NodeMotorInfo(rlt1.DetailedPath.Count, node.Id, node.RfidId, nodedir, null, magnet) - { - Speed = SpeedLevel.L, - }); - } - - //시작위치가 겹치므로 제거해줘야하낟. - if (pathtarget.DetailedPath.First().NodeId != rlt1.DetailedPath.Last().NodeId || - pathtarget.DetailedPath.First().MotorDirection != rlt1.DetailedPath.Last().MotorDirection) - { - new AGVPathResult { Success = false, Message = $"게이트웨이 턴 마지막 주소와, 이 후 주소의 시작 노드ID가 일치하지 않습니다" }; - } - - pathtarget.Path.RemoveAt(0); - pathtarget.DetailedPath.RemoveAt(0); - } - - var lastpath = CombinePaths(rlt1, pathtarget); - return lastpath; - - default: - throw new Exception("ASdf"); - } - - return null; - } - - /// - /// 지정한 노드의 게이트웨이를 반환합니다. - /// 도킹노드가 아닐경우 NULL이 반환됩니다. - /// - /// - /// - private MapNode GetGatewayNode(MapNode node) - { - var rfid = 0; - if (node.StationType == StationType.UnLoader) rfid = 10; //unloader - else if (node.StationType == StationType.Charger1) rfid = 9; //charger #1 - else if (node.StationType == StationType.Clearner) rfid = 6; //cleaner - else if (node.StationType == StationType.Charger2) rfid = 13; //charger #2 - else if (node.StationType == StationType.Loader) rfid = 13; //loader - else if (node.StationType == StationType.Buffer) rfid = 6; - - if (rfid == 0) return null; - return this._simulatorCanvas.Nodes.FirstOrDefault(t => t.RfidId == rfid); - } - - private AgvDirection GetRequiredGatewayDirection(string gatewayLogId) - { - switch (gatewayLogId) - { - case "0010": return AgvDirection.Backward; - case "0009": return AgvDirection.Forward; - case "0006": return AgvDirection.Backward; - case "0013": return AgvDirection.Backward; - default: return AgvDirection.Forward; - } - } - - /// - /// 대상노드와 게이트웨이노드를 가지고 턴 노드값을 반환합니다 - /// (이 값은 하드코딩 되어있음) - /// - /// - /// - /// - private List GetTurnaroundPattern(MapNode gatewayNode, MapNode targetNode) - { - switch (gatewayNode.RfidId) - { - case 6: - //버퍼와 11을 다르게하낟. - if (targetNode.StationType == StationType.Buffer) - { - return new List { "0006BL", "0007FS", "0013BL", "0006BL" }; - } - else - { - return new List { "0006BL", "0007FS", "0013BL", "0006BS" }; - } - case 9: return new List { "0009FL", "0010BS", "0007FL", "0009FS" }; - case 10: return new List { "0010BR", "0009FR", "0007BS", "0010BS" }; - case 13: return new List { "0013BL", "0006FL", "0007BS", "0013BS" }; - default: return null; - } - } - - /// - /// p1+p2 - /// - /// - /// - /// - private AGVPathResult CombinePaths(AGVPathResult p1, AGVPathResult p2) - { - var res = new AGVPathResult(); - res.Success = true; - - var p1last = p1.DetailedPath.LastOrDefault(); - var p2fist = p2.DetailedPath.FirstOrDefault(); - - //p1의 마지막과 p2의 시작이 같다면 p1의 마지막을 제거한다. - if (p1last != null && p2fist != null && - (p1last.NodeId == p2fist.NodeId && p1last.MotorDirection == p2fist.MotorDirection && p1last.MagnetDirection == p2fist.MagnetDirection)) - { - p1.Path.RemoveAt(p1.Path.Count - 1); - p1.DetailedPath.RemoveAt(p1.DetailedPath.Count - 1); - } - - - foreach (var item in p1.Path) - { - res.Path.Add(item); - } - foreach (var item in p2.Path) - { - res.Path.Add(item); - } - - foreach (var item in p1.DetailedPath) - { - var maxseq = res.DetailedPath.Count == 0 ? 0 : res.DetailedPath.Max(t => t.seq); - item.seq = maxseq + 1; - res.DetailedPath.Add(item); - } - foreach (var item in p2.DetailedPath) - { - var maxseq = res.DetailedPath.Count == 0 ? 0 : res.DetailedPath.Max(t => t.seq); - item.seq = maxseq + 1; - res.DetailedPath.Add(item); - } - - return res; + //게이트웨이노드를 하이라이트강조 한단 + this._simulatorCanvas.HighlightNodeId = (result.Gateway?.Id ?? string.Empty); + return result; } private void ApplyResultToSimulator(AGVPathResult result, VirtualAGV agv) diff --git a/Cs_HMI/Project/StateMachine/_AGV.cs b/Cs_HMI/Project/StateMachine/_AGV.cs index c53d070..cb40c3a 100644 --- a/Cs_HMI/Project/StateMachine/_AGV.cs +++ b/Cs_HMI/Project/StateMachine/_AGV.cs @@ -187,9 +187,6 @@ namespace Project // 맵 노드 리스트에 추가 PUB._mapCanvas.Nodes.Add(newNode); - // 캔버스에 노드 반영 (재설정) - PUB._mapCanvas.Nodes = PUB._mapCanvas.Nodes; - // 로그 기록 PUB.log.AddI($"RFID:{PUB.Result.LastTAG} 노드를 자동 추가했습니다 (NodeId: {newNodeId})"); diff --git a/Cs_HMI/Project/StateMachine/_Loop.cs b/Cs_HMI/Project/StateMachine/_Loop.cs index 2038fde..8487d9c 100644 --- a/Cs_HMI/Project/StateMachine/_Loop.cs +++ b/Cs_HMI/Project/StateMachine/_Loop.cs @@ -172,7 +172,7 @@ namespace Project if (PUB._mapCanvas != null) { this.Invoke(new Action(() => { - PUB._mapCanvas.ExitSyncMode(); + PUB._mapCanvas.ExitSyncMode( AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run); })); } diff --git a/Cs_HMI/Project/ViewForm/fAuto.cs b/Cs_HMI/Project/ViewForm/fAuto.cs index 2472335..e6a7ddc 100644 --- a/Cs_HMI/Project/ViewForm/fAuto.cs +++ b/Cs_HMI/Project/ViewForm/fAuto.cs @@ -34,8 +34,7 @@ namespace Project.ViewForm private void InitializeMapCanvas() { - PUB._mapCanvas.NodeSelect += OnNodeSelected;; - + PUB._mapCanvas.NodeSelect += OnNodeSelected;; // 스플리터 패널에 맵 캔버스 추가 panel1.Controls.Add(PUB._mapCanvas); } @@ -143,9 +142,6 @@ namespace Project.ViewForm PUB.AGV.DataReceive += AGV_DataReceive; this.timer1.Start(); - - // Set Run Mode - PUB._mapCanvas.Mode = AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run; } private void AGV_DataReceive(object sender, arDev.Narumi.DataEventArgs e) { @@ -182,9 +178,6 @@ namespace Project.ViewForm PUB.sm.StepChanged -= Sm_StepChanged; this.ctlAuto1.ButtonClick -= CtlAuto1_ButtonClick; PUB.AGV.DataReceive -= AGV_DataReceive; - - // Reset Mode to Edit - PUB._mapCanvas.Mode = AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Edit; } bool tmrun = false; @@ -195,11 +188,6 @@ namespace Project.ViewForm if (timer1.Enabled) { timer1.Start(); - // 화면이 보일 때 Run 모드로 강제 설정 - if (PUB._mapCanvas.Mode != AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run) - { - PUB._mapCanvas.Mode = AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run; - } } else timer1.Stop(); } diff --git a/Cs_HMI/Project/fMain.cs b/Cs_HMI/Project/fMain.cs index 398144a..dd8b32c 100644 --- a/Cs_HMI/Project/fMain.cs +++ b/Cs_HMI/Project/fMain.cs @@ -62,6 +62,7 @@ namespace Project PUB._mapCanvas.ShowGrid = false; PUB._mapCanvas.BackColor = Color.FromArgb(32, 32, 32); PUB._mapCanvas.ForeColor = Color.White; + PUB._mapCanvas.Mode = AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run; this.panTopMenu.MouseMove += LbTitle_MouseMove; this.panTopMenu.MouseUp += LbTitle_MouseUp;