From 34130e9c86c9a7e40ac6f2df862917f01201c794 Mon Sep 17 00:00:00 2001 From: chi Date: Thu, 22 May 2025 16:21:56 +0900 Subject: [PATCH] add mapcontrol add remote path(gitlab) --- Cs_HMI/.vscode/launch.json | 26 + Cs_HMI/.vscode/tasks.json | 41 + Cs_HMI/AGVCSharp.sln | 15 + Cs_HMI/Project/AGV4.csproj | 17 +- Cs_HMI/Project/Class/EEMStatus.cs | 200 +- Cs_HMI/Project/PUB.cs | 10 +- Cs_HMI/Project/ViewForm/fAuto.Designer.cs | 89 +- Cs_HMI/Project/ViewForm/fAuto.cs | 6 + Cs_HMI/Project/ViewForm/fAuto.resx | 50 +- Cs_HMI/Project/sample.route | 144 ++ .../AGVMapControl/AGVMapControl.csproj | 14 + .../AGVMapControl/MapControl.Designer.cs | 37 + Cs_HMI/SubProject/AGVMapControl/MapControl.cs | 1688 +++++++++++++++++ .../AGVMapControl/MapControl.resx} | 0 Cs_HMI/SubProject/AGVMapControl/Models/AGV.cs | 70 + .../AGVMapControl/Models/CustomLine.cs | 14 + .../AGVMapControl/Models/MagnetLine.cs | 20 + .../AGVMapControl/Models/MapData.cs | 14 + .../AGVMapControl/Models/MapElements.cs | 13 + .../AGVMapControl/Models/MapText.cs | 14 + .../AGVMapControl/Models/RFIDLine.cs | 20 + .../AGVMapControl/Models/RFIDPoint.cs | 13 + .../AGVMapControl/Models/ToolBarItem.cs | 34 + 23 files changed, 2368 insertions(+), 181 deletions(-) create mode 100644 Cs_HMI/.vscode/launch.json create mode 100644 Cs_HMI/.vscode/tasks.json create mode 100644 Cs_HMI/Project/sample.route create mode 100644 Cs_HMI/SubProject/AGVMapControl/AGVMapControl.csproj create mode 100644 Cs_HMI/SubProject/AGVMapControl/MapControl.Designer.cs create mode 100644 Cs_HMI/SubProject/AGVMapControl/MapControl.cs rename Cs_HMI/{Project/StateMachine/_AGV.resx => SubProject/AGVMapControl/MapControl.resx} (100%) create mode 100644 Cs_HMI/SubProject/AGVMapControl/Models/AGV.cs create mode 100644 Cs_HMI/SubProject/AGVMapControl/Models/CustomLine.cs create mode 100644 Cs_HMI/SubProject/AGVMapControl/Models/MagnetLine.cs create mode 100644 Cs_HMI/SubProject/AGVMapControl/Models/MapData.cs create mode 100644 Cs_HMI/SubProject/AGVMapControl/Models/MapElements.cs create mode 100644 Cs_HMI/SubProject/AGVMapControl/Models/MapText.cs create mode 100644 Cs_HMI/SubProject/AGVMapControl/Models/RFIDLine.cs create mode 100644 Cs_HMI/SubProject/AGVMapControl/Models/RFIDPoint.cs create mode 100644 Cs_HMI/SubProject/AGVMapControl/Models/ToolBarItem.cs diff --git a/Cs_HMI/.vscode/launch.json b/Cs_HMI/.vscode/launch.json new file mode 100644 index 0000000..bddd567 --- /dev/null +++ b/Cs_HMI/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "../../../Amkor/AGV4/Debug/net8.0-windows/AGV4.dll", + "args": [], + "cwd": "${workspaceFolder}/Project", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/Cs_HMI/.vscode/tasks.json b/Cs_HMI/.vscode/tasks.json new file mode 100644 index 0000000..47f8d63 --- /dev/null +++ b/Cs_HMI/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/AGVCSharp.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/AGVCSharp.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/AGVCSharp.sln" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Cs_HMI/AGVCSharp.sln b/Cs_HMI/AGVCSharp.sln index 60edcdc..eb6340b 100644 --- a/Cs_HMI/AGVCSharp.sln +++ b/Cs_HMI/AGVCSharp.sln @@ -42,6 +42,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "arCommUtil", "SubProject\Co EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ENIGProtocol", "..\..\Protocol\enigprotocol\ENIGProtocol.csproj", "{16BD025D-CB0F-4A4B-A452-8FE629F02F0C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGVMapControl", "SubProject\AGVMapControl\AGVMapControl.csproj", "{D37909BE-DCD9-4408-AF06-28F92C69F83D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -231,6 +233,18 @@ Global {16BD025D-CB0F-4A4B-A452-8FE629F02F0C}.Release|x64.Build.0 = Release|Any CPU {16BD025D-CB0F-4A4B-A452-8FE629F02F0C}.Release|x86.ActiveCfg = Release|Any CPU {16BD025D-CB0F-4A4B-A452-8FE629F02F0C}.Release|x86.Build.0 = Release|Any CPU + {D37909BE-DCD9-4408-AF06-28F92C69F83D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D37909BE-DCD9-4408-AF06-28F92C69F83D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D37909BE-DCD9-4408-AF06-28F92C69F83D}.Debug|x64.ActiveCfg = Debug|Any CPU + {D37909BE-DCD9-4408-AF06-28F92C69F83D}.Debug|x64.Build.0 = Debug|Any CPU + {D37909BE-DCD9-4408-AF06-28F92C69F83D}.Debug|x86.ActiveCfg = Debug|Any CPU + {D37909BE-DCD9-4408-AF06-28F92C69F83D}.Debug|x86.Build.0 = Debug|Any CPU + {D37909BE-DCD9-4408-AF06-28F92C69F83D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D37909BE-DCD9-4408-AF06-28F92C69F83D}.Release|Any CPU.Build.0 = Release|Any CPU + {D37909BE-DCD9-4408-AF06-28F92C69F83D}.Release|x64.ActiveCfg = Release|Any CPU + {D37909BE-DCD9-4408-AF06-28F92C69F83D}.Release|x64.Build.0 = Release|Any CPU + {D37909BE-DCD9-4408-AF06-28F92C69F83D}.Release|x86.ActiveCfg = Release|Any CPU + {D37909BE-DCD9-4408-AF06-28F92C69F83D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -249,6 +263,7 @@ Global {AAF68D20-4590-4AB0-BB91-E0DD04C91DEC} = {C423C39A-44E7-4F09-B2F7-7943975FF948} {14E8C9A5-013E-49BA-B435-FFFFFF7DD623} = {C423C39A-44E7-4F09-B2F7-7943975FF948} {16BD025D-CB0F-4A4B-A452-8FE629F02F0C} = {C423C39A-44E7-4F09-B2F7-7943975FF948} + {D37909BE-DCD9-4408-AF06-28F92C69F83D} = {C423C39A-44E7-4F09-B2F7-7943975FF948} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B5B1FD72-356F-4840-83E8-B070AC21C8D9} diff --git a/Cs_HMI/Project/AGV4.csproj b/Cs_HMI/Project/AGV4.csproj index 0f2b449..9c0f260 100644 --- a/Cs_HMI/Project/AGV4.csproj +++ b/Cs_HMI/Project/AGV4.csproj @@ -8,12 +8,15 @@ enable x64 D:\Amkor\AGV4 + true + true + @@ -41,21 +44,19 @@ SettingsSingleFileGenerator Settings.Designer.cs + + PreserveNewest + - - lib\Winsock_Orcas.dll - - - lib\EEProtocol.dll - - - lib\arUtil.dll + + ..\DLL\Winsock Orcas.dll + diff --git a/Cs_HMI/Project/Class/EEMStatus.cs b/Cs_HMI/Project/Class/EEMStatus.cs index 7d14c78..61667e0 100644 --- a/Cs_HMI/Project/Class/EEMStatus.cs +++ b/Cs_HMI/Project/Class/EEMStatus.cs @@ -108,24 +108,24 @@ public static partial class EEMStatus StatusChecktime = DateTime.Now; } - //내부실행모드일때에만 파일을 처리한다 - if (_extrun == false) - { - var tsfile = DateTime.Now - FileCheckTime; - if (tsfile.TotalSeconds >= UpdateFileInterval) - { - if (UpdateRun == false) - { - UpdateRun = true; - Task.Run(() => - { - UpdateFileToDB(); - UpdateRun = false; - }); - } - FileCheckTime = DateTime.Now; - } - } + ////내부실행모드일때에만 파일을 처리한다 + //if (_extrun == false) + //{ + // var tsfile = DateTime.Now - FileCheckTime; + // if (tsfile.TotalSeconds >= UpdateFileInterval) + // { + // if (UpdateRun == false) + // { + // UpdateRun = true; + // Task.Run(() => + // { + // UpdateFileToDB(); + // UpdateRun = false; + // }); + // } + // FileCheckTime = DateTime.Now; + // } + //} } /// @@ -258,7 +258,7 @@ public static partial class EEMStatus //var di = new System.IO.DirectoryInfo(path); //var fi = di.GetFiles("*.sql", System.IO.SearchOption.TopDirectoryOnly).Where(t => t.LastWriteTime < DateTime.Now.AddMinutes(-3)).FirstOrDefault(); //if (fi != null) fi.Delete(); - if (state == 4 && extrun == false) UpdateFileToDB(); + //if (state == 4 && extrun == false) UpdateFileToDB(); return string.Empty; } catch (Exception ex) @@ -269,97 +269,97 @@ public static partial class EEMStatus - static void UpdateFileToDB() - { - if (mre.WaitOne(1000) == false) return; - mre.Reset(); - var cs = "Data Source=10.131.15.18;Initial Catalog=EE;Persist Security Info=True;User ID=eeuser;Password=Amkor123!"; - var path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Status"); - var di = new System.IO.DirectoryInfo(path); - if (di.Exists == false) return; - var file = di.GetFiles("*.sql", System.IO.SearchOption.TopDirectoryOnly) - .Where(t => t.LastWriteTime < DateTime.Now.AddSeconds(-3)) - .OrderByDescending(t => t.LastWriteTime).FirstOrDefault(); + //static void UpdateFileToDB() + //{ + // if (mre.WaitOne(1000) == false) return; + // mre.Reset(); + // var cs = "Data Source=10.131.15.18;Initial Catalog=EE;Persist Security Info=True;User ID=eeuser;Password=Amkor123!"; + // var path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Status"); + // var di = new System.IO.DirectoryInfo(path); + // if (di.Exists == false) return; + // var file = di.GetFiles("*.sql", System.IO.SearchOption.TopDirectoryOnly) + // .Where(t => t.LastWriteTime < DateTime.Now.AddSeconds(-3)) + // .OrderByDescending(t => t.LastWriteTime).FirstOrDefault(); - if (file == null) - { - mre.Set(); - return; - } + // if (file == null) + // { + // mre.Set(); + // return; + // } - //파일을 찾아야한다 - // PUB.log.Add($">> {file.FullName}"); + // //파일을 찾아야한다 + // // PUB.log.Add($">> {file.FullName}"); - try - { - var sql = System.IO.File.ReadAllText(file.FullName, System.Text.Encoding.Default); - if (string.IsNullOrEmpty(sql)) - { - //비어잇다면 - var errpath = System.IO.Path.Combine(di.FullName, "Error"); - var errfile = System.IO.Path.Combine(errpath, file.Name); - if (System.IO.Directory.Exists(errpath) == false) System.IO.Directory.CreateDirectory(errpath); - System.IO.File.Move(file.FullName, errfile);// file.MoveTo(errfile); - // ecnt += 1; - } - else - { - // var csstr = PUB.setting.ConnectionString; - // if (string.IsNullOrEmpty(csstr)) csstr = "Data Source=10.131.15.18;Initial Catalog=EE;Persist Security Info=True;User ID=eeuser;Password=Amkor123!"; - var cn = new System.Data.SqlClient.SqlConnection(cs); - var cmd = new System.Data.SqlClient.SqlCommand(sql, cn); - cn.Open(); - var cnt = cmd.ExecuteNonQuery(); - //if (cnt == 0) PUB.log.Add($"Result Empty : {sql}"); - cn.Close(); - cnt += 1; + // try + // { + // var sql = System.IO.File.ReadAllText(file.FullName, System.Text.Encoding.Default); + // if (string.IsNullOrEmpty(sql)) + // { + // //비어잇다면 + // var errpath = System.IO.Path.Combine(di.FullName, "Error"); + // var errfile = System.IO.Path.Combine(errpath, file.Name); + // if (System.IO.Directory.Exists(errpath) == false) System.IO.Directory.CreateDirectory(errpath); + // System.IO.File.Move(file.FullName, errfile);// file.MoveTo(errfile); + // // ecnt += 1; + // } + // else + // { + // // var csstr = PUB.setting.ConnectionString; + // // if (string.IsNullOrEmpty(csstr)) csstr = "Data Source=10.131.15.18;Initial Catalog=EE;Persist Security Info=True;User ID=eeuser;Password=Amkor123!"; + // var cn = new System.Data.SqlClient.SqlConnection(cs); + // var cmd = new System.Data.SqlClient.SqlCommand(sql, cn); + // cn.Open(); + // var cnt = cmd.ExecuteNonQuery(); + // //if (cnt == 0) PUB.log.Add($"Result Empty : {sql}"); + // cn.Close(); + // cnt += 1; - var errpath = System.IO.Path.Combine(di.FullName, "Complete"); - var errfile = System.IO.Path.Combine(errpath, file.Name); - if (System.IO.Directory.Exists(errpath) == false) System.IO.Directory.CreateDirectory(errpath); - //file.MoveTo(errfile); - System.IO.File.Move(file.FullName, errfile); - } + // var errpath = System.IO.Path.Combine(di.FullName, "Complete"); + // var errfile = System.IO.Path.Combine(errpath, file.Name); + // if (System.IO.Directory.Exists(errpath) == false) System.IO.Directory.CreateDirectory(errpath); + // //file.MoveTo(errfile); + // System.IO.File.Move(file.FullName, errfile); + // } - } - catch (Exception ex) - { - var errpath = System.IO.Path.Combine(di.FullName, "Error"); - var errfile = System.IO.Path.Combine(errpath, file.Name); - if (System.IO.Directory.Exists(errpath) == false) System.IO.Directory.CreateDirectory(errpath); - try - { - //file.MoveTo(errfile); - System.IO.File.Move(file.FullName, errfile); - } - catch (Exception ex2) - { - Console.WriteLine($"move error : {ex2.Message} : {errfile}"); - } + // } + // catch (Exception ex) + // { + // var errpath = System.IO.Path.Combine(di.FullName, "Error"); + // var errfile = System.IO.Path.Combine(errpath, file.Name); + // if (System.IO.Directory.Exists(errpath) == false) System.IO.Directory.CreateDirectory(errpath); + // try + // { + // //file.MoveTo(errfile); + // System.IO.File.Move(file.FullName, errfile); + // } + // catch (Exception ex2) + // { + // Console.WriteLine($"move error : {ex2.Message} : {errfile}"); + // } - //ecnt += 1; - } + // //ecnt += 1; + // } - try - { - //생성된지 10일이 넘은 자료는 삭제한다. - //시간소비를 피해서 1개의 파일만 작업한다 - var sqlfiles = di.GetFiles("*.sql", System.IO.SearchOption.AllDirectories); - //총3번의 데이터를 처리한다 - var files = sqlfiles.Where(t => t.LastWriteTime < DateTime.Now.AddDays(-10)).Select(t => t.FullName); - int i = 0; - var dellist = files.TakeWhile(t => i++ < 3); - foreach (var delfile in dellist) - System.IO.File.Delete(delfile); - } - catch - { + // try + // { + // //생성된지 10일이 넘은 자료는 삭제한다. + // //시간소비를 피해서 1개의 파일만 작업한다 + // var sqlfiles = di.GetFiles("*.sql", System.IO.SearchOption.AllDirectories); + // //총3번의 데이터를 처리한다 + // var files = sqlfiles.Where(t => t.LastWriteTime < DateTime.Now.AddDays(-10)).Select(t => t.FullName); + // int i = 0; + // var dellist = files.TakeWhile(t => i++ < 3); + // foreach (var delfile in dellist) + // System.IO.File.Delete(delfile); + // } + // catch + // { - } - mre.Set(); - } + // } + // mre.Set(); + //} } diff --git a/Cs_HMI/Project/PUB.cs b/Cs_HMI/Project/PUB.cs index fdc44fa..0842f4b 100644 --- a/Cs_HMI/Project/PUB.cs +++ b/Cs_HMI/Project/PUB.cs @@ -4,16 +4,10 @@ using System.Net; using System.Management; using System.Data; using AR; -using System.Security.Policy; -using Project.StateMachine; -using System.Data.SqlClient; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Speech.Synthesis; using System.Media; using System.Runtime.InteropServices; +using Microsoft.Data.SqlClient; namespace Project { @@ -406,7 +400,7 @@ namespace Project var conn = new SqlConnection(AGV4.Properties.Settings.Default.CS);// "Data Source=10.131.15.18;Initial Catalog=EE;Persist Security Info=True;User ID=eeuser;Password=Amkor123!"); conn.Open(); string ProcName = "AddPrgmUser3"; - SqlCommand cmd = new SqlCommand(ProcName, conn) + var cmd = new SqlCommand(ProcName, conn) { CommandType = CommandType.StoredProcedure }; diff --git a/Cs_HMI/Project/ViewForm/fAuto.Designer.cs b/Cs_HMI/Project/ViewForm/fAuto.Designer.cs index 38e8041..c72eb09 100644 --- a/Cs_HMI/Project/ViewForm/fAuto.Designer.cs +++ b/Cs_HMI/Project/ViewForm/fAuto.Designer.cs @@ -28,59 +28,68 @@ /// private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); - this.timer1 = new System.Windows.Forms.Timer(this.components); - this.ctlAuto1 = new Project.CtlAuto(); - this.SuspendLayout(); + components = new System.ComponentModel.Container(); + timer1 = new System.Windows.Forms.Timer(components); + ctlAuto1 = new CtlAuto(); + panel1 = new Panel(); + SuspendLayout(); // // timer1 // - this.timer1.Interval = 200; - this.timer1.Tick += new System.EventHandler(this.timer1_Tick); + timer1.Interval = 200; + timer1.Tick += timer1_Tick; // // ctlAuto1 // - this.ctlAuto1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(32)))), ((int)(((byte)(32))))); - this.ctlAuto1.BorderColor = System.Drawing.Color.Transparent; - this.ctlAuto1.Dock = System.Windows.Forms.DockStyle.Fill; - this.ctlAuto1.Font = new System.Drawing.Font("맑은 고딕", 20F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129))); - this.ctlAuto1.Font_BMS_Level = new System.Drawing.Font("Bahnschrift Condensed", 250F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ctlAuto1.Font_BMS_Volt = new System.Drawing.Font("Bahnschrift Condensed", 50.25F, System.Drawing.FontStyle.Bold); - this.ctlAuto1.Font_MAIN_BMSLevel = new System.Drawing.Font("Arial Narrow", 150F, System.Drawing.FontStyle.Bold); - this.ctlAuto1.ForeColor = System.Drawing.Color.White; - this.ctlAuto1.ItemGap = 0; - this.ctlAuto1.Items = null; - this.ctlAuto1.Location = new System.Drawing.Point(0, 0); - this.ctlAuto1.MinimumSize = new System.Drawing.Size(100, 30); - this.ctlAuto1.Name = "ctlAuto1"; - this.ctlAuto1.ProgressMax = 100F; - this.ctlAuto1.ProgressVal = 50F; - this.ctlAuto1.Scean = Project.CtlAuto.eScean.Normal; - this.ctlAuto1.Size = new System.Drawing.Size(1014, 579); - this.ctlAuto1.StatusMessage = "상태메세지 입니다"; - this.ctlAuto1.StopMessage = ""; - this.ctlAuto1.StopTime = new System.DateTime(((long)(0))); - this.ctlAuto1.TabIndex = 20; - this.ctlAuto1.Text = "ctlAuto1"; + ctlAuto1.BackColor = Color.FromArgb(32, 32, 32); + ctlAuto1.BorderColor = Color.Transparent; + ctlAuto1.Font = new Font("맑은 고딕", 20F, FontStyle.Regular, GraphicsUnit.Point, 129); + ctlAuto1.Font_BMS_Level = new Font("Bahnschrift Condensed", 250F, FontStyle.Bold, GraphicsUnit.Point, 0); + ctlAuto1.Font_BMS_Volt = new Font("Bahnschrift Condensed", 50.25F, FontStyle.Bold); + ctlAuto1.Font_MAIN_BMSLevel = new Font("Arial Narrow", 150F, FontStyle.Bold); + ctlAuto1.ForeColor = Color.White; + ctlAuto1.ItemGap = 0; + ctlAuto1.Items = null; + ctlAuto1.Location = new Point(835, 338); + ctlAuto1.MinimumSize = new Size(100, 30); + ctlAuto1.Name = "ctlAuto1"; + ctlAuto1.ProgressMax = 100F; + ctlAuto1.ProgressVal = 50F; + ctlAuto1.Scean = CtlAuto.eScean.Normal; + ctlAuto1.Size = new Size(179, 241); + ctlAuto1.StatusMessage = "상태메세지 입니다"; + ctlAuto1.StopMessage = ""; + ctlAuto1.StopTime = new DateTime(0L); + ctlAuto1.TabIndex = 20; + ctlAuto1.Text = "ctlAuto1"; + // + // panel1 + // + panel1.Dock = DockStyle.Fill; + panel1.Location = new Point(0, 0); + panel1.Name = "panel1"; + panel1.Size = new Size(1014, 579); + panel1.TabIndex = 21; // // fAuto // - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(15)))), ((int)(((byte)(15)))), ((int)(((byte)(15))))); - this.ClientSize = new System.Drawing.Size(1014, 579); - this.Controls.Add(this.ctlAuto1); - this.DoubleBuffered = true; - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; - this.Name = "fAuto"; - this.Text = "fAuto"; - this.Load += new System.EventHandler(this.fAuto_Load); - this.VisibleChanged += new System.EventHandler(this.fAuto_VisibleChanged); - this.ResumeLayout(false); - + AutoScaleMode = AutoScaleMode.None; + BackColor = Color.FromArgb(15, 15, 15); + ClientSize = new Size(1014, 579); + Controls.Add(panel1); + Controls.Add(ctlAuto1); + DoubleBuffered = true; + FormBorderStyle = FormBorderStyle.None; + Name = "fAuto"; + Text = "fAuto"; + Load += fAuto_Load; + VisibleChanged += fAuto_VisibleChanged; + ResumeLayout(false); } #endregion private System.Windows.Forms.Timer timer1; private CtlAuto ctlAuto1; + private Panel panel1; } } \ No newline at end of file diff --git a/Cs_HMI/Project/ViewForm/fAuto.cs b/Cs_HMI/Project/ViewForm/fAuto.cs index 9e29e0f..ec8d635 100644 --- a/Cs_HMI/Project/ViewForm/fAuto.cs +++ b/Cs_HMI/Project/ViewForm/fAuto.cs @@ -15,6 +15,7 @@ namespace Project.ViewForm { public partial class fAuto : Form { + AGVMapControl.MapControl mapctl; public fAuto() { InitializeComponent(); @@ -25,6 +26,11 @@ namespace Project.ViewForm this.ctlAuto1.Scean = CtlAuto.eScean.Progress; else this.ctlAuto1.Scean = CtlAuto.eScean.Normal; + this.mapctl = new AGVMapControl.MapControl(); + this.mapctl.Dock = DockStyle.Fill; + this.mapctl.Visible = true; + this.mapctl.BackColor = Color.FromArgb(32, 32, 32); + this.panel1.Controls.Add(mapctl); } private void fAuto_Load(object sender, EventArgs e) { diff --git a/Cs_HMI/Project/ViewForm/fAuto.resx b/Cs_HMI/Project/ViewForm/fAuto.resx index aac33d5..5655bc4 100644 --- a/Cs_HMI/Project/ViewForm/fAuto.resx +++ b/Cs_HMI/Project/ViewForm/fAuto.resx @@ -1,17 +1,17 @@  - diff --git a/Cs_HMI/Project/sample.route b/Cs_HMI/Project/sample.route new file mode 100644 index 0000000..113664f --- /dev/null +++ b/Cs_HMI/Project/sample.route @@ -0,0 +1,144 @@ +[RFID_POINTS] +100,486,T0002 +164,494,T0003 +229,493,T0004 +309,489,T0005 +370,490,T0006 +437,486,T0007 +483,459,T0008 +511,421,T0009 +543,371,T0010 +569,329,T0011 +608,289,T0012 +661,279,T0013 +700,297,T0014 +698,349,T0015 +698,391,T0016 +699,449,T0017 +691,491,T0018 +569,275,T0019 +518,264,T0020 +454,264,T0021 +388,261,T0022 +314,258,T0023 +639,234,T0024 +621,182,T0025 +629,143,T0026 +657,101,T0027 +627,82,T0028 +560,73,T0029 +499,65,T0030 +432,65,T0031 +264,232,T0032 +363,60,T0033 +654,508,T0034 +96,542,T0050 +159,542,T0051 +226,542,T0061 +309,541,T0070 +369,542,T0071 +483,165,T0010 +735,163,T0011 +523,170,T0001 +565,175,T0002 +597,182,T0003 +665,181,T0004 +700,176,T0005 +721,170,T0006 +[MAGNET_LINES] +99,485,220,492 +220,492,375,488 +375,488,457,479 +457,479,512,432 +512,432,552,353 +552,353,602,291 +602,291,651,278 +651,278,700,289 +700,289,704,380 +704,380,700,469 +700,469,688,488 +666,277,637,229 +637,229,625,170 +625,170,654,94 +654,94,624,78 +624,78,484,63 +484,63,365,64 +608,287,523,267 +523,267,409,260 +409,260,275,258 +99,486,95,542 +161,495,161,543 +230,496,228,537 +228,537,228,544 +306,488,306,543 +373,489,371,544 +655,508,691,486 +481,164,624,183 +624,183,706,181 +706,181,743,161 +[MAP_TEXTS] +179,251,-1,-16777216,Arial,12,TOPS-2 +239,52,-256,-65408,Arial,12,SSOTRON-3 +617,527,-16711936,-16777216,Arial,12,SSOTRON-1 +87,551,-16777216,16777215,Arial,12,B1 +150,551,-16777216,16777215,Arial,12,B2 +227,553,-16777216,16777215,Arial,12,B3 +299,555,-16777216,16777215,Arial,12,B4 +367,554,-16777216,16777215,Arial,12,B5 +453,128,-16777216,-8323073,Arial,12,CHG1 +725,124,-16777216,-8323073,Arial,12,CHG2 +[CUSTOM_LINES] +[RFID_LINES] +96,542,100,486,T0005,T0002,True,56.14267 +100,486,164,494,T0002,T0003,True,64.49806 +164,494,229,493,T0003,T0004,True,65.00769 +229,493,309,489,T0004,T0005,True,80.09994 +309,489,370,490,T0005,T0006,True,61.0082 +370,490,437,486,T0006,T0007,True,67.11929 +437,486,483,459,T0007,T0008,True,53.33854 +483,459,511,421,T0008,T0009,True,47.20169 +511,421,543,371,T0009,T0010,True,59.36329 +543,371,569,329,T0010,T0011,True,49.39635 +569,329,608,289,T0011,T0012,True,55.86591 +608,289,661,279,T0012,T0013,True,53.93515 +661,279,700,297,T0013,T0014,True,42.95346 +700,297,698,349,T0014,T0015,True,52.03845 +698,349,698,391,T0015,T0016,True,42 +698,391,699,449,T0016,T0017,True,58.00862 +699,449,691,491,T0017,T0018,True,42.75512 +691,491,654,508,T0018,T0034,True,40.71855 +159,542,164,494,T0006,T0003,True,48.25971 +226,542,229,493,T0007,T0004,True,49.09175 +309,541,309,489,T0008,T0005,True,52 +369,542,370,490,T0009,T0006,True,52.00961 +370,490,370,490,T0006,T0006,True,0 +608,289,569,275,T0012,T0019,True,41.4367 +569,275,518,264,T0019,T0020,True,52.17279 +518,264,454,264,T0020,T0021,True,64 +454,264,388,261,T0021,T0022,True,66.06815 +388,261,314,258,T0022,T0023,True,74.06078 +661,279,639,234,T0013,T0024,True,50.08992 +639,234,621,182,T0024,T0025,True,55.02727 +621,182,629,143,T0025,T0026,True,39.81206 +629,143,657,101,T0026,T0027,True,50.47772 +657,101,627,82,T0027,T0028,True,35.51056 +627,82,560,73,T0028,T0029,True,67.60178 +560,73,499,65,T0029,T0030,True,61.52235 +483,165,523,170,T0010,T0001,True,40.31129 +523,170,565,175,T0001,T0002,True,42.29657 +565,175,597,182,T0002,T0003,True,32.75668 +597,182,621,182,T0003,T0025,True,24 +621,182,665,181,T0025,T0004,True,44.01136 +665,181,700,176,T0004,T0005,True,35.35534 +700,176,721,170,T0005,T0006,True,21.84033 +721,170,735,163,T0006,T0011,True,15.65248 +735,163,721,170,T0011,T0006,True,15.65248 +721,170,735,163,T0006,T0011,True,15.65248 +314,258,388,261,T0023,T0022,True,74.06078 +388,261,454,264,T0022,T0021,True,66.06815 +454,264,518,264,T0021,T0020,True,64 +518,264,569,275,T0020,T0019,True,52.17279 +569,275,608,289,T0019,T0012,True,41.4367 +264,232,314,258,T0032,T0023,True,56.35601 +363,60,432,65,T0033,T0031,True,69.18092 +432,65,499,65,T0031,T0030,True,67 diff --git a/Cs_HMI/SubProject/AGVMapControl/AGVMapControl.csproj b/Cs_HMI/SubProject/AGVMapControl/AGVMapControl.csproj new file mode 100644 index 0000000..effcb7c --- /dev/null +++ b/Cs_HMI/SubProject/AGVMapControl/AGVMapControl.csproj @@ -0,0 +1,14 @@ + + + + net8.0-windows + enable + true + enable + + + + + + + diff --git a/Cs_HMI/SubProject/AGVMapControl/MapControl.Designer.cs b/Cs_HMI/SubProject/AGVMapControl/MapControl.Designer.cs new file mode 100644 index 0000000..9625998 --- /dev/null +++ b/Cs_HMI/SubProject/AGVMapControl/MapControl.Designer.cs @@ -0,0 +1,37 @@ +namespace AGVMapControl +{ + partial class MapControl + { + /// + /// 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 Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + this.ClientSize = new System.Drawing.Size(800, 450); + } + + #endregion + } +} diff --git a/Cs_HMI/SubProject/AGVMapControl/MapControl.cs b/Cs_HMI/SubProject/AGVMapControl/MapControl.cs new file mode 100644 index 0000000..031a293 --- /dev/null +++ b/Cs_HMI/SubProject/AGVMapControl/MapControl.cs @@ -0,0 +1,1688 @@ +using System.Collections.Immutable; +using System.Drawing.Design; +using System.Drawing.Drawing2D; +using System.Text.RegularExpressions; +using System.Windows.Forms; +using AGVMapControl.Models; + +namespace AGVMapControl +{ + public partial class MapControl : Control + { + private List rfidPoints; + private List magnetLines; + private List mapTexts; + private List customLines; + private List rfidLines; + private AGV agv; + private float zoom = 1.0f; + private PointF offset = PointF.Empty; + private Point lastMousePosition; + private bool isDragging = false; + private Point? previewStartPoint = null; + private Point currentMousePosition; + private const int SNAP_DISTANCE = 10; // 점 근접 거리 + private const int LINE_WIDTH = 20; // 선 굵기 + private const int TOOLBAR_WIDTH = 58; + private const int TOOLBAR_WIDTHR = 78; + private const int TOOLBAR_BUTTON_HEIGHT = 40; + private const int TOOLBAR_MARGIN = 5; + + private List toolbarRects; + + + private bool isAddingText = false; + private bool isAddingPoint = false; + private bool isDrawingCustomLine = false; + private bool isDrawingMagnetLine = false; + private bool isDrawingRFIDLine = false; + private bool isDeletingRFIDLine = false; + private bool isDrawingLine = false; + private MapText selectedText = null; + private CustomLine selectedLine = null; + private RFIDPoint selectedRFID = null; + private MagnetLine selectedMagnetLine = null; + private RFIDLine selectedRFIDLine = null; + private Point? draggingPoint = null; + private bool isDraggingPoint = false; + private const int SELECTION_DISTANCE = 15; // 선택 가능 거리를 늘림 + private bool isDraggingText = false; + private Point dragOffset; + private List currentPath; + + + public MapText SelectedText => selectedText; + public CustomLine SelectedLine => selectedLine; + public RFIDPoint SelectedRFID => selectedRFID; + public MagnetLine SelectedMagnetLine => selectedMagnetLine; + public RFIDLine SelectedRFIDLine => selectedRFIDLine; + + + + public float GetDistance(Point p1, Point p2) + { + float dx = p1.X - p2.X; + float dy = p1.Y - p2.Y; + return (float)Math.Sqrt(dx * dx + dy * dy); // double을 float로 명시적 캐스팅 + } + + public MapControl() + { + this.DoubleBuffered = true; + rfidPoints = new List(); + magnetLines = new List(); + mapTexts = new List(); + customLines = new List(); + rfidLines = new List(); + agv = new AGV(); + + // 툴바 버튼 영역 초기화 + UpdateToolbarRects(); + + // 마우스 이벤트 핸들러 추가 + this.MouseWheel += MapControl_MouseWheel; + this.MouseDown += MapControl_MouseDown; + this.MouseMove += MapControl_MouseMove; + this.MouseUp += MapControl_MouseUp; + this.MouseClick += MapControl_MouseClick; + this.MouseDoubleClick += MapControl_MouseDoubleClick; + this.Resize += MapControl_Resize; + } + + private void MapControl_Resize(object sender, EventArgs e) + { + UpdateToolbarRects(); + this.Invalidate(); + } + + private void UpdateToolbarRects() + { + int x, y, c, row; + var idx = 0; + + if (this.toolbarRects == null) this.toolbarRects = new List(); + else this.toolbarRects.Clear(); + + //left toolbar + x = TOOLBAR_MARGIN; + y = 10; + toolbarRects.Add(new ToolBarItem { Idx = idx++, Title = "+", Bounds = new Rectangle(x, y, TOOLBAR_WIDTH - 2 * TOOLBAR_MARGIN, TOOLBAR_BUTTON_HEIGHT) }); + + y += TOOLBAR_BUTTON_HEIGHT + TOOLBAR_MARGIN; + toolbarRects.Add(new ToolBarItem { Idx = idx++, Title = "-", Bounds = new Rectangle(x, y, TOOLBAR_WIDTH - 2 * TOOLBAR_MARGIN, TOOLBAR_BUTTON_HEIGHT) }); + + y += TOOLBAR_BUTTON_HEIGHT + TOOLBAR_MARGIN; + toolbarRects.Add(new ToolBarItem { Idx = idx++, Title = "1:1", Bounds = new Rectangle(x, y, TOOLBAR_WIDTH - 2 * TOOLBAR_MARGIN, TOOLBAR_BUTTON_HEIGHT) }); + + y += TOOLBAR_BUTTON_HEIGHT + TOOLBAR_MARGIN; + toolbarRects.Add(new ToolBarItem { Idx = idx++, Title = "Cut", Bounds = new Rectangle(x, y, TOOLBAR_WIDTH - 2 * TOOLBAR_MARGIN, TOOLBAR_BUTTON_HEIGHT) }); + + + //right toolbar + y = 10; + row = 0; + x = DisplayRectangle.Right - TOOLBAR_WIDTHR - TOOLBAR_MARGIN; + toolbarRects.Add(new ToolBarItem { Idx = idx++, Title = "Text", Bounds = new Rectangle(x, y, TOOLBAR_WIDTHR - 2 * TOOLBAR_MARGIN, TOOLBAR_BUTTON_HEIGHT) }); + + y += TOOLBAR_BUTTON_HEIGHT + TOOLBAR_MARGIN; + toolbarRects.Add(new ToolBarItem { Idx = idx++, Title = "Line", Bounds = new Rectangle(x, y, TOOLBAR_WIDTHR - 2 * TOOLBAR_MARGIN, TOOLBAR_BUTTON_HEIGHT) }); + + y += TOOLBAR_BUTTON_HEIGHT + TOOLBAR_MARGIN; + toolbarRects.Add(new ToolBarItem { Idx = idx++, Title = "Point", Bounds = new Rectangle(x, y, TOOLBAR_WIDTHR - 2 * TOOLBAR_MARGIN, TOOLBAR_BUTTON_HEIGHT) }); + + y += TOOLBAR_BUTTON_HEIGHT + TOOLBAR_MARGIN; + toolbarRects.Add(new ToolBarItem { Idx = idx++, Title = "Magnet", Bounds = new Rectangle(x, y, TOOLBAR_WIDTHR - 2 * TOOLBAR_MARGIN, TOOLBAR_BUTTON_HEIGHT) }); + + y += TOOLBAR_BUTTON_HEIGHT + TOOLBAR_MARGIN; + toolbarRects.Add(new ToolBarItem { Idx = idx++, Title = "Load", Bounds = new Rectangle(x, y, TOOLBAR_WIDTHR - 2 * TOOLBAR_MARGIN, TOOLBAR_BUTTON_HEIGHT) }); + + y += TOOLBAR_BUTTON_HEIGHT + TOOLBAR_MARGIN; + toolbarRects.Add(new ToolBarItem { Idx = idx++, Title = "Save", Bounds = new Rectangle(x, y, TOOLBAR_WIDTHR - 2 * TOOLBAR_MARGIN, TOOLBAR_BUTTON_HEIGHT) }); + + y += TOOLBAR_BUTTON_HEIGHT + TOOLBAR_MARGIN; + toolbarRects.Add(new ToolBarItem { Idx = idx++, Title = "Pos", Bounds = new Rectangle(x, y, TOOLBAR_WIDTHR - 2 * TOOLBAR_MARGIN, TOOLBAR_BUTTON_HEIGHT) }); + + y += TOOLBAR_BUTTON_HEIGHT + TOOLBAR_MARGIN; + toolbarRects.Add(new ToolBarItem { Idx = idx++, Title = "Path", Bounds = new Rectangle(x, y, TOOLBAR_WIDTHR - 2 * TOOLBAR_MARGIN, TOOLBAR_BUTTON_HEIGHT) }); + + } + + public void SetPreviewStartPoint(Point? point) + { + previewStartPoint = point; + this.Invalidate(); + } + + public void UpdatePreviewLine(Point currentPosition) + { + currentMousePosition = currentPosition; + this.Invalidate(); + } + + private Point SnapToPoint(Point point) + { + // RFID 포인트와 근접한지 확인 + foreach (var rfid in rfidPoints) + { + if (GetDistance(point, rfid.Location) <= SNAP_DISTANCE) + { + return rfid.Location; + } + } + + // 마그넷 라인의 끝점과 근접한지 확인 + foreach (var line in magnetLines) + { + if (GetDistance(point, line.StartPoint) <= SNAP_DISTANCE) + { + return line.StartPoint; + } + if (GetDistance(point, line.EndPoint) <= SNAP_DISTANCE) + { + return line.EndPoint; + } + } + + return point; + } + + // 화면 좌표를 실제 맵 좌표로 변환 + public Point ScreenToMap(Point screenPoint) + { + int adjustedX = screenPoint.X; + return new Point( + (int)((adjustedX - offset.X) / zoom), + (int)((screenPoint.Y - offset.Y) / zoom) + ); + } + + public RFIDPoint FindRFIDPoint(string rfidValue) + { + return rfidPoints.FirstOrDefault(r => r.RFIDValue.ToUpper() == rfidValue.ToUpper()); + } + + public bool AGVMoveToRFID(string rfidValue) + { + var rfidPoint = FindRFIDPoint(rfidValue); + if (rfidPoint != null) + { + agv.CurrentPosition = rfidPoint.Location; + agv.CurrentRFID = rfidValue; + this.Invalidate(); + return true; + } + return false; + } + private void MapControl_MouseWheel(object sender, MouseEventArgs e) + { + if (e.Delta > 0) + { + zoom *= 1.1f; + } + else + { + zoom /= 1.1f; + } + + zoom = Math.Max(0.1f, Math.Min(10.0f, zoom)); + this.Invalidate(); + } + + private void MapControl_MouseDown(object sender, MouseEventArgs e) + { + lastMousePosition = e.Location; + var mapPoint = ScreenToMap(e.Location); + + if (e.Button == MouseButtons.Middle) + { + isDragging = true; + this.Cursor = Cursors.SizeAll; + } + else if (e.Button == MouseButtons.Left && !isAddingText && !isDrawingCustomLine && !isDrawingRFIDLine && !isDrawingMagnetLine) + { + isDragging = true; + + // 텍스트 선택 및 드래그 시작 + foreach (var text in mapTexts) + { + var textSize = CreateGraphics().MeasureString(text.Text, text.Font); + var rect = new RectangleF( + text.Location.X - SELECTION_DISTANCE, + text.Location.Y - SELECTION_DISTANCE, + textSize.Width + SELECTION_DISTANCE * 2, + textSize.Height + SELECTION_DISTANCE * 2 + ); + + if (rect.Contains(mapPoint)) + { + selectedText = text; + isDraggingText = true; + // 드래그 시작점과 텍스트 위치의 차이를 저장 + dragOffset = new Point( + text.Location.X - mapPoint.X, + text.Location.Y - mapPoint.Y + ); + return; + } + } + + // 커스텀 라인 선택 및 드래그 포인트 설정 + foreach (var line in customLines) + { + float startDistance = GetDistance(mapPoint, line.StartPoint); + float endDistance = GetDistance(mapPoint, line.EndPoint); + + if (startDistance < SELECTION_DISTANCE) + { + selectedLine = line; + draggingPoint = line.StartPoint; + isDraggingPoint = true; + return; + } + else if (endDistance < SELECTION_DISTANCE) + { + selectedLine = line; + draggingPoint = line.EndPoint; + isDraggingPoint = true; + return; + } + } + + + // RFID 포인트 선택 + var clickedRFID = rfidPoints.FirstOrDefault(r => + GetDistance(mapPoint, r.Location) <= SNAP_DISTANCE); + if (clickedRFID != null) + { + selectedRFID = clickedRFID; + draggingPoint = clickedRFID.Location; + isDraggingPoint = true; + return; + } + + + } + } + + Point? lineStartPoint = null; + Point? branchPoint = null; + + private void MapControl_MouseMove(object sender, MouseEventArgs e) + { + currentMousePosition = e.Location; + var mapPoint = ScreenToMap(e.Location); + + if ((mousemode == eMouseMode.addcustomline || mousemode == eMouseMode.addrfidline) && branchPoint.HasValue) + { + UpdatePreviewLine(e.Location); + } + + // 툴바 버튼 호버 상태 업데이트 + var oldHovering = toolbarRects.OrderBy(t => t.Idx).Select(t => t.isHovering).ToArray(); + + //toolbar check + toolbarRects.ForEach(t => t.isHovering = t.Bounds.Contains(e.Location)); + + //hovering check + if (toolbarRects.Where(t => t.isHovering).Any()) + { + this.Cursor = Cursors.Hand; + } + else if (isDeletingRFIDLine) + { + this.Cursor = GetScissorsCursor(); + } + else + { + this.Cursor = Cursors.Default; + } + + //hovering display update + if (toolbarRects.Where(t => t.Dirty).Any()) + { + this.Invalidate(); + } + + if (isDragging) + { + if (e.Button == MouseButtons.Middle) + { + offset = new PointF( + offset.X + (e.Location.X - lastMousePosition.X), + offset.Y + (e.Location.Y - lastMousePosition.Y) + ); + } + else if (isDraggingText && selectedText != null) + { + // 텍스트 이동 - 드래그 오프셋 적용 + selectedText.Location = new Point( + mapPoint.X + dragOffset.X, + mapPoint.Y + dragOffset.Y + ); + } + else if (isDraggingPoint && draggingPoint.HasValue) + { + // 포인트 이동 + var delta = new Point( + mapPoint.X - ScreenToMap(lastMousePosition).X, + mapPoint.Y - ScreenToMap(lastMousePosition).Y + ); + + if (selectedLine != null) + { + // 커스텀 라인 포인트 이동 + if (draggingPoint.Value == selectedLine.StartPoint) + { + selectedLine.StartPoint = new Point( + selectedLine.StartPoint.X + delta.X, + selectedLine.StartPoint.Y + delta.Y + ); + } + else if (draggingPoint.Value == selectedLine.EndPoint) + { + selectedLine.EndPoint = new Point( + selectedLine.EndPoint.X + delta.X, + selectedLine.EndPoint.Y + delta.Y + ); + } + } + else if (selectedRFID != null) // RFID 포인트 이동 + { + // RFID 포인트 위치 업데이트 + selectedRFID.Location = new Point( + selectedRFID.Location.X + delta.X, + selectedRFID.Location.Y + delta.Y + ); + + // 연결된 RFID 라인 업데이트 + foreach (var line in rfidLines) + { + if (line.StartPoint == draggingPoint.Value) + { + line.StartPoint = selectedRFID.Location; + line.Distance = GetDistance(line.StartPoint, line.EndPoint); + } + if (line.EndPoint == draggingPoint.Value) + { + line.EndPoint = selectedRFID.Location; + line.Distance = GetDistance(line.StartPoint, line.EndPoint); + } + } + } + } + + lastMousePosition = e.Location; + this.Invalidate(); + } + + // 미리보기 라인 업데이트를 위한 마우스 위치 저장 + if (isDrawingRFIDLine || isDrawingCustomLine || previewStartPoint.HasValue) + { + currentMousePosition = mapPoint; + } + + this.Invalidate(); + } + private List CalculatePath(Point start, Point end) + { + var openList = new List { start }; + var closedList = new List(); + var cameFrom = new Dictionary(); + var gScore = new Dictionary { { start, 0 } }; + var fScore = new Dictionary { { start, Heuristic(start, end) } }; + + while (openList.Count > 0) + { + var current = openList.OrderBy(p => fScore.ContainsKey(p) ? fScore[p] : float.MaxValue).First(); + + if (current == end) + { + return ReconstructPath(cameFrom, current); + } + + openList.Remove(current); + closedList.Add(current); + + foreach (var neighbor in GetNeighbors(current)) + { + if (closedList.Contains(neighbor)) + continue; + + float tentativeGScore = gScore[current] + Distance(current, neighbor); + + if (!openList.Contains(neighbor)) + openList.Add(neighbor); + else if (tentativeGScore >= gScore[neighbor]) + continue; + + cameFrom[neighbor] = current; + gScore[neighbor] = tentativeGScore; + fScore[neighbor] = gScore[neighbor] + Heuristic(neighbor, end); + } + } + + return null; + } + private List ReconstructPath(Dictionary cameFrom, Point current) + { + var path = new List { current }; + while (cameFrom.ContainsKey(current)) + { + current = cameFrom[current]; + path.Insert(0, current); + } + return path; + } + private float Heuristic(Point a, Point b) + { + return (float)Math.Sqrt(Math.Pow(a.X - b.X, 2) + Math.Pow(a.Y - b.Y, 2)); + } + + + private void MapControl_MouseUp(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Middle) + { + isDragging = false; + this.Cursor = Cursors.Default; + } + else if (e.Button == MouseButtons.Left) + { + isDragging = false; + isDraggingPoint = false; + isDraggingText = false; + } + } + private float Distance(Point a, Point b) + { + // RFID 라인을 통한 연결 확인 + var rfidLines = GetRFIDLines(); + var directConnection = rfidLines.FirstOrDefault(line => + (line.StartPoint == a && line.EndPoint == b) || + (line.IsBidirectional && line.StartPoint == b && line.EndPoint == a)); + + if (directConnection != null) + { + return directConnection.Distance; + } + + // 직접 연결되지 않은 경우 매우 큰 값 반환 + return float.MaxValue; + } + + private List GetNeighbors(Point point) + { + var neighbors = new List(); + var rfidLines = GetRFIDLines(); + + // RFID 라인에서 이웃 노드 찾기 + foreach (var line in rfidLines) + { + if (line.StartPoint == point) + { + neighbors.Add(line.EndPoint); + } + else if (line.EndPoint == point) + { + // 양방향 연결인 경우에만 시작점을 이웃으로 추가 + if (line.IsBidirectional) + { + neighbors.Add(line.StartPoint); + } + } + } + + return neighbors.Distinct().ToList(); + } + public void SetPath(List rfids) + { + if (rfids == null || rfids.Count == 0) + return; + + var path = new List(); + foreach (var rfid in rfids) + { + var point = GetRFIDPoints().FirstOrDefault(p => p.RFIDValue == rfid); + if (point != null) + { + path.Add(point.Location); + } + } + + if (path.Count > 0) + { + DrawPath(path); + } + } + + + public string RFIDStartNo { get; set; } = string.Empty; + public int RFIDLastNumber = 0; + string filename = string.Empty; + private void MapControl_MouseClick(object sender, MouseEventArgs e) + { + var mapPoint = ScreenToMap(e.Location); + if (e.Button == MouseButtons.Right) + { + OnRightClick?.Invoke(this, e); + this.MouseMode = eMouseMode.Default; + } + else if (e.Button == MouseButtons.Left) + { + // 툴바 버튼 클릭 처리 + var toolbar = toolbarRects.FirstOrDefault(t => t.Bounds.Contains(e.Location)); + if (toolbar != null) + { + switch (toolbar.Title.ToLower()) + { + case "+": ZoomIn(); break; + case "-": ZoomOut(); break; + case "1:1": ResetZoom(); break; + case "cut": MouseMode = (eMouseMode.rfidcut); break; + case "text": MouseMode = (eMouseMode.addtext); break; + case "line": MouseMode = (eMouseMode.addrfidline); break; + case "cline": MouseMode = (eMouseMode.addcustomline); break; + case "point": MouseMode = (eMouseMode.addrfidpoint); break; + case "path": + + var input1 = AR.UTIL.InputBox("input start"); + if (input1.Item1 == false) return; + var input2 = AR.UTIL.InputBox("input end"); + if (input2.Item1 == false) return; + + var startRFID = input1.Item2; + var endRFID = input2.Item2; + var startPoint = FindRFIDPoint(startRFID); + var endPoint = FindRFIDPoint(endRFID); + + if (startPoint == null || endPoint == null) + { + MessageBox.Show("유효한 RFID 값을 입력해주세요.", "경로 계산", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + var path = CalculatePath(startPoint.Location, endPoint.Location); + if (path != null && path.Count > 0) + { + DrawPath(path); + + // 경로 상의 모든 RFID 값을 가져옴 + var rfidPath = new List(); + foreach (var point in path) + { + var rfid = GetRFIDPoints() + .FirstOrDefault(r => r.Location == point); + if (rfid != null) + { + rfidPath.Add(rfid.RFIDValue); + } + } + + MessageBox.Show($"경로가 계산되었습니다.\nRFID 순서: {string.Join(" -> ", rfidPath)}", + "경로 계산", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + else + { + MessageBox.Show("경로를 찾을 수 없습니다.", "경로 계산", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + break; + case "pos": + var tag = AR.UTIL.InputBox("input rfid tag value"); + if (tag.Item1 && tag.Item2 != "") + { + var targetRFID = AGVMoveToRFID(tag.Item2); + } + break; + case "save": + using (var od = new SaveFileDialog()) + { + od.Filter = "path data|*.route"; + od.FilterIndex = 0; + od.RestoreDirectory = true; + od.FileName = System.IO.Path.GetFileName(this.filename); + od.InitialDirectory = System.IO.Path.GetDirectoryName(this.filename); + if (od.ShowDialog() == DialogResult.OK) + { + filename = od.FileName; + this.SaveToFile(filename); + this.Invalidate(); + } + } + break; + case "load": + using (var od = new OpenFileDialog()) + { + od.Filter = "path data|*.route"; + od.FilterIndex = 0; + + + if (string.IsNullOrEmpty(this.filename) == false) + { + od.FileName = System.IO.Path.GetFileName(this.filename); + od.InitialDirectory = System.IO.Path.GetDirectoryName(this.filename); + } + else + { + od.RestoreDirectory = true; + } + + + if (od.ShowDialog() == DialogResult.OK) + { + filename = od.FileName; + this.LoadFromFile(filename); + this.Invalidate(); + } + } + break; + } + return; + } + + // RFID 포인트 선택 + var clickedRFID = rfidPoints.FirstOrDefault(r => GetDistance(mapPoint, r.Location) <= SNAP_DISTANCE); + switch (mousemode) + { + case eMouseMode.rfidcut: + DeleteNearbyRFIDLine(mapPoint); + break; + case eMouseMode.addrfidpoint: + if (string.IsNullOrEmpty(this.RFIDStartNo) == false) + { + + AddRFIDPoint(mapPoint, RFIDStartNo); + + // 숫자로 끝나는 RFID 값인 경우 자동 증가 + if (Regex.IsMatch(RFIDStartNo, @"^[A-Za-z]+\d+$")) + { + // 마지막 숫자 부분 추출 + Match match = Regex.Match(RFIDStartNo, @"\d+$"); + if (match.Success) + { + int currentNumber = int.Parse(match.Value); + if (currentNumber > this.RFIDLastNumber) + { + RFIDLastNumber = currentNumber; + } + RFIDLastNumber++; + + // 숫자 부분을 새로운 번호로 교체 + RFIDStartNo = RFIDStartNo.Substring(0, match.Index) + RFIDLastNumber.ToString("D4"); + } + } + } + break; + case eMouseMode.addtext: + var text = new MapText + { + Location = mapPoint, + Text = "새 텍스트", + TextColor = Color.Black, + BackgroundColor = Color.Transparent, + Font = new Font("Arial", 12) + }; + mapTexts.Add(text); + selectedText = text; + this.Invalidate(); + break; + case eMouseMode.addcustomline: + if (previewStartPoint == null) + { + previewStartPoint = mapPoint; + } + else + { + var line = new CustomLine + { + StartPoint = previewStartPoint.Value, + EndPoint = mapPoint, + LineColor = Color.Red, + LineWidth = 2 + }; + customLines.Add(line); + selectedLine = line; + previewStartPoint = null; + this.Invalidate(); + } + break; + case eMouseMode.addrfidline: + if (clickedRFID != null) + { + if (previewStartPoint == null) + { + previewStartPoint = clickedRFID.Location; + } + else + { + var startRFID = rfidPoints.FirstOrDefault(r => r.Location == previewStartPoint); + if (startRFID != null) + { + var line = new RFIDLine + { + StartPoint = previewStartPoint.Value, + EndPoint = clickedRFID.Location, + StartRFID = startRFID.RFIDValue, + EndRFID = clickedRFID.RFIDValue, + Distance = GetDistance(previewStartPoint.Value, clickedRFID.Location) + }; + rfidLines.Add(line); + selectedRFIDLine = line; + + // 선 근처의 RFID 포인트들을 찾아서 추가 + AddNearbyRFIDPoints(line); + } + // 다음 라인을 위해 현재 클릭한 RFID를 시작점으로 설정 + previewStartPoint = clickedRFID.Location; + } + this.Invalidate(); + } + break; + } + } + } + + private void AddNearbyRFIDPoints(RFIDLine line) + { + const float NEARBY_DISTANCE = 20.0f; // 근처로 간주하는 거리를 50에서 20으로 줄임 + + // 선 근처의 RFID 포인트들을 찾아서 거리에 따라 정렬 + var nearbyPoints = new List<(RFIDPoint Point, float Distance, float ProjectionRatio)>(); + + foreach (var rfid in rfidPoints) + { + if (rfid.Location == line.StartPoint || rfid.Location == line.EndPoint) + continue; + + // 선분과 RFID 포인트 사이의 최단 거리 계산 + float distance = GetDistanceToLine(rfid.Location, line.StartPoint, line.EndPoint); + + if (distance <= NEARBY_DISTANCE) + { + // 시작점으로부터의 투영 비율 계산 (0~1 사이 값) + float projectionRatio = GetProjectionRatio(rfid.Location, line.StartPoint, line.EndPoint); + if (projectionRatio >= 0 && projectionRatio <= 1) // 선분 위에 있는 점만 포함 + { + nearbyPoints.Add((rfid, distance, projectionRatio)); + } + } + } + + // 시작점에서 끝점 방향으로 정렬 + nearbyPoints.Sort((a, b) => a.ProjectionRatio.CompareTo(b.ProjectionRatio)); + + // 이전 RFID 값과 위치를 저장 + string prevRFID = line.StartRFID; + Point prevPoint = line.StartPoint; + + // 정렬된 포인트들을 순차적으로 연결 + foreach (var item in nearbyPoints) + { + var rfidLine = new RFIDLine + { + StartPoint = prevPoint, + EndPoint = item.Point.Location, + StartRFID = prevRFID, + EndRFID = item.Point.RFIDValue, + Distance = GetDistance(prevPoint, item.Point.Location) + }; + rfidLines.Add(rfidLine); + + // 현재 포인트를 다음 선분의 시작점으로 설정 + prevPoint = item.Point.Location; + prevRFID = item.Point.RFIDValue; + } + + // 마지막 포인트와 원래 선의 끝점을 연결 + var finalLine = new RFIDLine + { + StartPoint = prevPoint, + EndPoint = line.EndPoint, + StartRFID = prevRFID, + EndRFID = line.EndRFID, + Distance = GetDistance(prevPoint, line.EndPoint) + }; + rfidLines.Add(finalLine); + + // 원래 선은 제거 + rfidLines.Remove(line); + } + + private float GetProjectionRatio(Point point, Point lineStart, Point lineEnd) + { + float lineLength = GetDistance(lineStart, lineEnd); + if (lineLength == 0) return 0; + + return ((point.X - lineStart.X) * (lineEnd.X - lineStart.X) + + (point.Y - lineStart.Y) * (lineEnd.Y - lineStart.Y)) / (lineLength * lineLength); + } + + private float GetDistanceToLine(Point point, Point lineStart, Point lineEnd) + { + float lineLength = GetDistance(lineStart, lineEnd); + if (lineLength == 0) return GetDistance(point, lineStart); + + float t = ((point.X - lineStart.X) * (lineEnd.X - lineStart.X) + + (point.Y - lineStart.Y) * (lineEnd.Y - lineStart.Y)) / (lineLength * lineLength); + + t = Math.Max(0, Math.Min(1, t)); + + float projectionX = lineStart.X + t * (lineEnd.X - lineStart.X); + float projectionY = lineStart.Y + t * (lineEnd.Y - lineStart.Y); + + return GetDistance(point, new Point((int)projectionX, (int)projectionY)); + } + + private void MapControl_MouseDoubleClick(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + // 화면 좌표를 맵 좌표로 변환 + var mapPoint = ScreenToMap(e.Location); + + // 텍스트 객체 찾기 + foreach (var text in mapTexts) + { + var textSize = CreateGraphics().MeasureString(text.Text, text.Font); + var rect = new RectangleF( + text.Location.X - SELECTION_DISTANCE, + text.Location.Y - SELECTION_DISTANCE, + textSize.Width + SELECTION_DISTANCE * 2, + textSize.Height + SELECTION_DISTANCE * 2 + ); + + if (rect.Contains(mapPoint)) + { + var rlt = AR.UTIL.InputBox("input", text.Text); + if (rlt.Item1 == true) + { + text.Text = rlt.Item2;// dialog.InputText; + this.Invalidate(); + } + break; + } + } + } + } + + public event EventHandler OnRightClick; + + public void SetRFIDPoints(List points) + { + rfidPoints = points; + this.Invalidate(); + } + + public void SetMagnetLines(List lines) + { + magnetLines = lines; + this.Invalidate(); + } + + public void SetAGV(AGV vehicle) + { + agv = vehicle; + this.Invalidate(); + } + + public void SetMapTexts(List texts) + { + mapTexts = texts; + if (mapTexts == null) mapTexts = new List(); + this.Invalidate(); + } + + public void SetCustomLines(List lines) + { + customLines = lines; + if (customLines == null) customLines = new List(); + this.Invalidate(); + } + + public void SetIsAddingText(bool value) + { + isAddingText = value; + isDrawingCustomLine = false; + this.Cursor = value ? Cursors.IBeam : Cursors.Default; + } + + public void SetIsDrawingCustomLine(bool value) + { + isDrawingCustomLine = value; + isAddingText = false; + this.Cursor = value ? Cursors.Cross : Cursors.Default; + } + + public void SetIsDrawingRFIDLine(bool value) + { + isDrawingRFIDLine = value; + isDrawingCustomLine = false; + isAddingText = false; + this.Cursor = value ? Cursors.Cross : Cursors.Default; + } + + public void SetIsDeletingRFIDLine(bool value) + { + isDeletingRFIDLine = value; + isDrawingCustomLine = false; + isAddingText = false; + isDrawingRFIDLine = false; + this.Cursor = value ? GetScissorsCursor() : Cursors.Default; + } + + public void SetIsDrawingLine(bool value) + { + isDrawingLine = value; + isDrawingCustomLine = false; + isAddingText = false; + isDrawingRFIDLine = false; + this.Cursor = value ? Cursors.Cross : Cursors.Default; + } + + public enum eMouseMode : byte + { + Default = 0, + pan, + rfidcut, + addtext, + addcustomline, + addrfidpoint, + addrfidline, + } + + private eMouseMode mousemode = eMouseMode.Default; + public eMouseMode MouseMode + { + get { return mousemode; } + set + { + if (this.mousemode == value) mousemode = eMouseMode.Default; + else this.mousemode = value; + switch (this.mousemode) + { + case eMouseMode.pan: this.Cursor = Cursors.Hand; break; + case eMouseMode.addrfidline: this.Cursor = Cursors.Default; break; + case eMouseMode.addrfidpoint: this.Cursor = Cursors.Default; break; + case eMouseMode.addtext: this.Cursor = Cursors.Default; break; + case eMouseMode.addcustomline: this.Cursor = Cursors.Default; break; + default: this.Cursor = Cursors.Default; break; + } + previewStartPoint = null; + Invalidate(); + } + } + + + public void SetIsAddingMagnet(bool value) + { + + } + + public void SetIsAddingPoint(bool value) + { + isDrawingCustomLine = false; + isDrawingLine = false; + isDrawingMagnetLine = false; + isDrawingRFIDLine = false; + isAddingPoint = value; + this.Cursor = value ? Cursors.Cross : Cursors.Default; + } + + private Cursor GetScissorsCursor() + { + // 가위 커서 아이콘 생성 + using (var bitmap = new Bitmap(32, 32)) + using (var g = Graphics.FromImage(bitmap)) + { + g.Clear(Color.Transparent); + + // 가위 모양 그리기 + using (var pen = new Pen(Color.Black, 2)) + { + // 가위 손잡이 + g.DrawEllipse(pen, 12, 20, 8, 8); + g.DrawLine(pen, 16, 20, 16, 16); + + // 가위 날 + g.DrawLine(pen, 16, 16, 8, 8); + g.DrawLine(pen, 16, 16, 24, 8); + } + + return new Cursor(bitmap.GetHicon()); + } + } + + public void DrawPath(List path) + { + currentPath = path; + this.Invalidate(); + } + + protected override void OnPaint(PaintEventArgs e) + { + //base.OnPaint(e); + + + + e.Graphics.TranslateTransform(offset.X, offset.Y); + e.Graphics.ScaleTransform(zoom, zoom); + + DrawMap(e.Graphics); + DrawCustomLines(e.Graphics); + DrawMapTexts(e.Graphics); + DrawRFIDLines(e.Graphics); + DrawPath(e.Graphics); + DrawAGV(e.Graphics); + + // 선택된 개체 강조 표시 + if (selectedRFID != null) + { + using (Pen pen = new Pen(Color.Blue, 2)) + { + pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash; + e.Graphics.DrawEllipse(pen, + selectedRFID.Location.X - 10, + selectedRFID.Location.Y - 10, + 20, 20); + } + } + + if (selectedMagnetLine != null) + { + using (Pen pen = new Pen(Color.Blue, 2)) + { + pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash; + e.Graphics.DrawLine(pen, selectedMagnetLine.StartPoint, selectedMagnetLine.EndPoint); + foreach (var branch in selectedMagnetLine.BranchPoints) + { + e.Graphics.DrawEllipse(pen, branch.X - 5, branch.Y - 5, 10, 10); + } + } + } + + if (selectedLine != null) + { + using (Pen pen = new Pen(Color.Blue, 2)) + { + pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash; + e.Graphics.DrawLine(pen, selectedLine.StartPoint, selectedLine.EndPoint); + } + } + + // 미리보기 라인 그리기 + if (previewStartPoint.HasValue) + { + using (Pen previewPen = new Pen(Color.FromArgb(180, Color.Yellow), LINE_WIDTH)) + { + previewPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash; + e.Graphics.DrawLine(previewPen, previewStartPoint.Value, currentMousePosition); + } + } + + // 그래픽스 변환 초기화 + e.Graphics.ResetTransform(); + + // 툴바 버튼 그리기 + foreach (var item in this.toolbarRects) + DrawToolbarButton(e.Graphics, item.Bounds, item.Title, item.isHovering); + + + } + + + private void DrawMap(Graphics g) + { + //// 마그넷 라인 그리기 + //foreach (var line in magnetLines) + //{ + // using (Pen linePen = new Pen(Color.FromArgb(180, Color.Yellow), LINE_WIDTH)) + // { + // g.DrawLine(linePen, line.StartPoint, line.EndPoint); + // } + + // // 분기점 그리기 + // foreach (var branch in line.BranchPoints) + // { + // g.FillEllipse(Brushes.Red, branch.X - 3, branch.Y - 3, 6, 6); + // var direction = line.BranchDirections[branch]; + // g.DrawString(direction.ToString(), Font, Brushes.Black, branch.X + 5, branch.Y - 5); + // } + //} + + + + // RFID 포인트 그리기 + foreach (var rfid in rfidPoints) + { + g.FillEllipse(Brushes.Green, rfid.Location.X - 3, rfid.Location.Y - 3, 6, 6); + g.DrawString(rfid.RFIDValue, Font, Brushes.Black, rfid.Location.X + 5, rfid.Location.Y - 5); + } + + //var rfidpts = rfidPoints.Select(t => t.Location).ToArray(); + //g.DrawLines(new Pen(Color.FromArgb(100, Color.Magenta), 10), rfidpts); + + } + + private void DrawAGV(Graphics g) + { + var agvsize = 30; + var halfsize = (int)(agvsize / 2); + var rect = new Rectangle(agv.CurrentPosition.X - halfsize, + agv.CurrentPosition.Y - halfsize, + agvsize, agvsize); + + var recti = new Rectangle(rect.X + 5, rect.Y + 5, rect.Width - 10, rect.Height - 10); + + using (var sb = new SolidBrush(Color.FromArgb(150, Color.Gold))) + g.FillEllipse(sb, rect); + + using (var pen = new Pen(Color.Black)) + g.DrawEllipse(pen, rect); + + using (var sb = new SolidBrush(Color.FromArgb(150, Color.White))) + g.FillEllipse(sb, recti); + + using (var pen = new Pen(Color.Black)) + g.DrawEllipse(pen, recti); + } + + private void DrawCustomLines(Graphics g) + { + if (customLines == null) return; + foreach (var line in customLines) + { + using (Pen linePen = new Pen(line.LineColor, line.LineWidth)) + { + g.DrawLine(linePen, line.StartPoint, line.EndPoint); + } + } + } + + private void DrawMapTexts(Graphics g) + { + if (mapTexts == null) return; + foreach (var text in mapTexts) + { + var textSize = g.MeasureString(text.Text, text.Font); + var rect = new RectangleF( + text.Location.X, + text.Location.Y, + textSize.Width, + textSize.Height + ); + + if (text.BackgroundColor != Color.Transparent) + { + using (var brush = new SolidBrush(text.BackgroundColor)) + { + g.FillRectangle(brush, rect); + } + } + + using (var brush = new SolidBrush(text.TextColor)) + { + g.DrawString(text.Text, text.Font, brush, text.Location); + } + + if (text == selectedText) + { + using (Pen pen = new Pen(Color.Blue, 1)) + { + pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash; + g.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height); + } + } + } + } + + private void DrawRFIDLines(Graphics g) + { + foreach (var line in rfidLines) + { + using (Pen linePen = new Pen(Color.FromArgb(100, Color.Black), 2)) + { + if (!line.IsBidirectional) + { + // 단방향 화살표 그리기 + var arrowSize = 10; + var angle = Math.Atan2(line.EndPoint.Y - line.StartPoint.Y, line.EndPoint.X - line.StartPoint.X); + var arrowPoint = new PointF( + line.EndPoint.X - (float)(arrowSize * Math.Cos(angle)), + line.EndPoint.Y - (float)(arrowSize * Math.Sin(angle)) + ); + g.DrawLine(linePen, line.StartPoint, arrowPoint); + + // 화살표 머리 그리기 + var arrowAngle = Math.PI / 6; + var arrowLength = 15; + var arrow1 = new PointF( + arrowPoint.X - (float)(arrowLength * Math.Cos(angle - arrowAngle)), + arrowPoint.Y - (float)(arrowLength * Math.Sin(angle - arrowAngle)) + ); + var arrow2 = new PointF( + arrowPoint.X - (float)(arrowLength * Math.Cos(angle + arrowAngle)), + arrowPoint.Y - (float)(arrowLength * Math.Sin(angle + arrowAngle)) + ); + g.DrawLine(linePen, arrowPoint, arrow1); + g.DrawLine(linePen, arrowPoint, arrow2); + } + else + { + g.DrawLine(linePen, line.StartPoint, line.EndPoint); + } + } + } + + // 미리보기 라인 그리기 + if (previewStartPoint.HasValue && isDrawingRFIDLine) + { + using (Pen previewPen = new Pen(Color.FromArgb(180, Color.Green), 2)) + { + previewPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash; + var currentMapPosition = ScreenToMap(currentMousePosition); + g.DrawLine(previewPen, previewStartPoint.Value, currentMapPosition); + } + } + } + + private void DrawPath(Graphics g) + { + if (currentPath == null || currentPath.Count < 2) + return; + + Color pathColor = Color.FromArgb(180, Color.HotPink); + int pathWidth = 10; + + using (Pen pathPen = new Pen(pathColor, pathWidth)) + { + pathPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash; + for (int i = 0; i < currentPath.Count - 1; i++) + { + g.DrawLine(pathPen, currentPath[i], currentPath[i + 1]); + } + } + } + + public List GetRFIDPoints() + { + return rfidPoints; + } + + public List GetMagnetLines() + { + return magnetLines; + } + + public List GetMapTexts() + { + return mapTexts; + } + + public List GetCustomLines() + { + return customLines; + } + + public List GetRFIDLines() + { + return rfidLines; + } + + public void SetRFIDLines(List lines) + { + rfidLines = lines; + this.Invalidate(); + } + + public void AddRFIDLine(Point startPoint, Point endPoint, string startRFID, string endRFID, bool isBidirectional = true) + { + // 시작점과 끝점 사이의 모든 RFID 포인트 찾기 + var allPoints = new List<(RFIDPoint Point, float Distance)>(); + var lineVector = new Point(endPoint.X - startPoint.X, endPoint.Y - startPoint.Y); + var lineLength = (float)Math.Sqrt(lineVector.X * lineVector.X + lineVector.Y * lineVector.Y); + + foreach (var rfid in rfidPoints) + { + if (rfid.Location == startPoint || rfid.Location == endPoint) + continue; + + // RFID 포인트가 선 위에 있는지 확인 + var pointVector = new Point(rfid.Location.X - startPoint.X, rfid.Location.Y - startPoint.Y); + var dotProduct = pointVector.X * lineVector.X + pointVector.Y * lineVector.Y; + var projectionLength = dotProduct / (lineLength * lineLength); + + if (projectionLength >= 0 && projectionLength <= 1) + { + // 선과 RFID 포인트 사이의 거리 계산 + var distance = GetDistanceToLine(rfid.Location, startPoint, endPoint); + if (distance <= SNAP_DISTANCE) + { + allPoints.Add((rfid, projectionLength)); + } + } + } + + // 시작점에서 끝점 방향으로 정렬 + allPoints.Sort((a, b) => a.Distance.CompareTo(b.Distance)); + + // 모든 연결 정보를 포함하는 RFID 라인 생성 + var line = new RFIDLine + { + StartPoint = startPoint, + EndPoint = endPoint, + StartRFID = startRFID, + EndRFID = endRFID, + IsBidirectional = isBidirectional, + Distance = GetDistance(startPoint, endPoint) + }; + + // 연결된 RFID 목록 생성 + line.ConnectedRFIDs.Add(startRFID); + foreach (var point in allPoints) + { + line.ConnectedRFIDs.Add(point.Point.RFIDValue); + } + line.ConnectedRFIDs.Add(endRFID); + + // 다음/이전 RFID 정보 설정 + for (int i = 0; i < line.ConnectedRFIDs.Count - 1; i++) + { + var current = line.ConnectedRFIDs[i]; + var next = line.ConnectedRFIDs[i + 1]; + line.NextRFID[current] = next; + line.PrevRFID[next] = current; + + if (isBidirectional) + { + line.NextRFID[next] = current; + line.PrevRFID[current] = next; + } + } + + rfidLines.Add(line); + this.Invalidate(); + } + + public void LoadMapData(MapData mapData) + { + rfidPoints = mapData.RFIDPoints ?? new List(); + magnetLines = mapData.MagnetLines ?? new List(); + mapTexts = mapData.MapTexts ?? new List(); + customLines = mapData.CustomLines ?? new List(); + rfidLines = mapData.RFIDLines ?? new List(); + this.Invalidate(); + } + + public void ClearMap() + { + rfidPoints.Clear(); + magnetLines.Clear(); + mapTexts.Clear(); + customLines.Clear(); + rfidLines.Clear(); + + // 선택 상태도 초기화 + selectedText = null; + selectedLine = null; + selectedRFID = null; + selectedMagnetLine = null; + selectedRFIDLine = null; + draggingPoint = null; + + // 미리보기 상태도 초기화 + previewStartPoint = null; + + // 화면 갱신 + this.Invalidate(); + } + + public void AddRFIDPoint(Point mapLocation, string rfidValue) + { + // 이미 맵 좌표로 변환된 위치를 사용 + var rfidPoint = new RFIDPoint + { + Location = mapLocation, + RFIDValue = rfidValue + }; + + rfidPoints.Add(rfidPoint); + this.Invalidate(); + } + + public void AddMagnetLine(Point startPoint, Point endPoint, Point? branchPoint = null, BranchDirection? branchDirection = null) + { + // 이미 맵 좌표로 변환된 위치를 받아서 사용 + var line = new MagnetLine + { + StartPoint = startPoint, + EndPoint = endPoint + }; + + if (branchPoint.HasValue && branchDirection.HasValue) + { + line.BranchPoints.Add(branchPoint.Value); + line.BranchDirections[branchPoint.Value] = branchDirection.Value; + } + + magnetLines.Add(line); + this.Invalidate(); + } + + public void SaveToFile(string filename) + { + var lines = new List(); + + // RFID 포인트 저장 + lines.Add("[RFID_POINTS]"); + foreach (var point in rfidPoints) + { + lines.Add($"{point.Location.X},{point.Location.Y},{point.RFIDValue}"); + } + + // 자석선 저장 + lines.Add("[MAGNET_LINES]"); + foreach (var line in magnetLines) + { + var branchInfo = ""; + foreach (var branch in line.BranchPoints) + { + if (line.BranchDirections.ContainsKey(branch)) + { + branchInfo += $"|{branch.X},{branch.Y},{(int)line.BranchDirections[branch]}"; + } + } + lines.Add($"{line.StartPoint.X},{line.StartPoint.Y},{line.EndPoint.X},{line.EndPoint.Y}{branchInfo}"); + } + + // 텍스트 저장 + lines.Add("[MAP_TEXTS]"); + foreach (var text in mapTexts) + { + lines.Add($"{text.Location.X},{text.Location.Y},{text.TextColor.ToArgb()},{text.BackgroundColor.ToArgb()},{text.Font.Name},{text.Font.Size},{text.Text}"); + } + + // 커스텀 라인 저장 + lines.Add("[CUSTOM_LINES]"); + foreach (var line in customLines) + { + lines.Add($"{line.StartPoint.X},{line.StartPoint.Y},{line.EndPoint.X},{line.EndPoint.Y},{line.LineColor.ToArgb()},{line.LineWidth}"); + } + + // RFID 라인 저장 + lines.Add("[RFID_LINES]"); + foreach (var line in rfidLines) + { + lines.Add($"{line.StartPoint.X},{line.StartPoint.Y},{line.EndPoint.X},{line.EndPoint.Y}," + + $"{line.StartRFID},{line.EndRFID},{line.IsBidirectional},{line.Distance}"); + } + + File.WriteAllLines(filename, lines); + } + + public void LoadFromFile(string filename) + { + ClearMap(); + + var lines = File.ReadAllLines(filename); + var section = ""; + + foreach (var line in lines) + { + if (line.StartsWith("[") && line.EndsWith("]")) + { + section = line; + continue; + } + + switch (section) + { + case "[RFID_POINTS]": + var rfidParts = line.Split(','); + if (rfidParts.Length >= 3) + { + AddRFIDPoint( + new Point(int.Parse(rfidParts[0]), int.Parse(rfidParts[1])), + rfidParts[2] + ); + } + break; + + case "[MAGNET_LINES]": + var lineParts = line.Split('|'); + var mainParts = lineParts[0].Split(','); + if (mainParts.Length >= 4) + { + var magnetLine = new MagnetLine + { + StartPoint = new Point(int.Parse(mainParts[0]), int.Parse(mainParts[1])), + EndPoint = new Point(int.Parse(mainParts[2]), int.Parse(mainParts[3])) + }; + + // 분기점 정보 처리 + for (int i = 1; i < lineParts.Length; i++) + { + var branchParts = lineParts[i].Split(','); + if (branchParts.Length >= 3) + { + var branchPoint = new Point( + int.Parse(branchParts[0]), + int.Parse(branchParts[1]) + ); + magnetLine.BranchPoints.Add(branchPoint); + magnetLine.BranchDirections[branchPoint] = (BranchDirection)int.Parse(branchParts[2]); + } + } + magnetLines.Add(magnetLine); + } + break; + + case "[MAP_TEXTS]": + var textParts = line.Split(','); + if (textParts.Length >= 7) + { + var text = new MapText + { + Location = new Point(int.Parse(textParts[0]), int.Parse(textParts[1])), + TextColor = Color.FromArgb(int.Parse(textParts[2])), + BackgroundColor = Color.FromArgb(int.Parse(textParts[3])), + Font = new Font(textParts[4], float.Parse(textParts[5])), + Text = string.Join(",", textParts.Skip(6)) // 텍스트에 쉼표가 포함될 수 있으므로 + }; + mapTexts.Add(text); + } + break; + + case "[CUSTOM_LINES]": + var customLineParts = line.Split(','); + if (customLineParts.Length >= 6) + { + var customLine = new CustomLine + { + StartPoint = new Point(int.Parse(customLineParts[0]), int.Parse(customLineParts[1])), + EndPoint = new Point(int.Parse(customLineParts[2]), int.Parse(customLineParts[3])), + LineColor = Color.FromArgb(int.Parse(customLineParts[4])), + LineWidth = int.Parse(customLineParts[5]) + }; + customLines.Add(customLine); + } + break; + + case "[RFID_LINES]": + var rfidLineParts = line.Split(','); + if (rfidLineParts.Length >= 8) + { + AddRFIDLine( + new Point(int.Parse(rfidLineParts[0]), int.Parse(rfidLineParts[1])), + new Point(int.Parse(rfidLineParts[2]), int.Parse(rfidLineParts[3])), + rfidLineParts[4], + rfidLineParts[5], + bool.Parse(rfidLineParts[6]) + ); + } + break; + } + } + + this.Invalidate(); + } + + private void DeleteNearbyRFIDLine(Point clickPoint) + { + const float DELETE_DISTANCE = 10.0f; // 클릭 지점으로부터의 허용 거리 + RFIDLine lineToDelete = null; + float minDistance = float.MaxValue; + + foreach (var line in rfidLines) + { + float distance = GetDistanceToLine(clickPoint, line.StartPoint, line.EndPoint); + if (distance < DELETE_DISTANCE && distance < minDistance) + { + minDistance = distance; + lineToDelete = line; + } + } + + if (lineToDelete != null) + { + rfidLines.Remove(lineToDelete); + this.Invalidate(); + } + } + + private void ZoomIn() + { + zoom *= 1.2f; + zoom = Math.Min(10.0f, zoom); + this.Invalidate(); + } + + private void ZoomOut() + { + zoom /= 1.2f; + zoom = Math.Max(0.1f, zoom); + this.Invalidate(); + } + + private void ResetZoom() + { + zoom = 1.0f; + offset = PointF.Empty; + this.Invalidate(); + } + + private void DrawToolbarButton(Graphics g, Rectangle rect, string text, bool isHovering) + { + var color1 = isHovering ? Color.LightSkyBlue : Color.White; + var color2 = isHovering ? Color.DeepSkyBlue : Color.WhiteSmoke; + using (var brush = new LinearGradientBrush(rect, color1, color2, LinearGradientMode.Vertical)) + using (var pen = new Pen(Color.Gray)) + using (var font = new Font("Tahoma", 9, FontStyle.Bold)) + using (var format = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }) + { + g.FillRectangle(Brushes.LightGray, rect.X + 2, rect.Y + 2, rect.Width, rect.Height); + g.FillRectangle(brush, rect); + g.DrawRectangle(pen, rect); + g.DrawString(text, font, Brushes.Black, rect, format); + } + } + } +} diff --git a/Cs_HMI/Project/StateMachine/_AGV.resx b/Cs_HMI/SubProject/AGVMapControl/MapControl.resx similarity index 100% rename from Cs_HMI/Project/StateMachine/_AGV.resx rename to Cs_HMI/SubProject/AGVMapControl/MapControl.resx diff --git a/Cs_HMI/SubProject/AGVMapControl/Models/AGV.cs b/Cs_HMI/SubProject/AGVMapControl/Models/AGV.cs new file mode 100644 index 0000000..8fba0a4 --- /dev/null +++ b/Cs_HMI/SubProject/AGVMapControl/Models/AGV.cs @@ -0,0 +1,70 @@ +using System.Drawing; +using System.Collections.Generic; +using System.Linq; + +namespace AGVMapControl.Models +{ + public class AGV + { + public Point CurrentPosition { get; set; } + public string CurrentRFID { get; set; } + public Direction CurrentDirection { get; set; } + public bool IsMoving { get; set; } + public List CurrentPath { get; set; } + public List PlannedPath { get; set; } + public List PathRFIDs { get; set; } + + public AGV() + { + CurrentPath = new List(); + PlannedPath = new List(); + PathRFIDs = new List(); + CurrentDirection = Direction.Forward; + } + + public void Move() + { + if (CurrentPath.Count > 0) + { + CurrentPosition = CurrentPath[0]; + CurrentPath.RemoveAt(0); + } + } + + public void SetNewPath(List path) + { + CurrentPath = new List(path); + } + + public void ClearPlannedPath() + { + PlannedPath.Clear(); + PathRFIDs.Clear(); + } + } + + public enum Direction + { + Forward, + Backward + } + + public class PathNode + { + public Point Location { get; set; } + public string RFID { get; set; } + public double G { get; set; } // 시작점에서 현재 노드까지의 비용 + public double H { get; set; } // 현재 노드에서 목표점까지의 예상 비용 + public double F => G + H; // 총 비용 + public PathNode Parent { get; set; } + + public PathNode(Point location, string rfid) + { + Location = location; + RFID = rfid; + G = 0; + H = 0; + Parent = null; + } + } +} \ No newline at end of file diff --git a/Cs_HMI/SubProject/AGVMapControl/Models/CustomLine.cs b/Cs_HMI/SubProject/AGVMapControl/Models/CustomLine.cs new file mode 100644 index 0000000..73b348d --- /dev/null +++ b/Cs_HMI/SubProject/AGVMapControl/Models/CustomLine.cs @@ -0,0 +1,14 @@ +using System.Drawing; +using System; + + +namespace AGVMapControl.Models +{ + public class CustomLine + { + public Point StartPoint { get; set; } + public Point EndPoint { get; set; } + public Color LineColor { get; set; } + public int LineWidth { get; set; } + } +} \ No newline at end of file diff --git a/Cs_HMI/SubProject/AGVMapControl/Models/MagnetLine.cs b/Cs_HMI/SubProject/AGVMapControl/Models/MagnetLine.cs new file mode 100644 index 0000000..f731a93 --- /dev/null +++ b/Cs_HMI/SubProject/AGVMapControl/Models/MagnetLine.cs @@ -0,0 +1,20 @@ +using System.Drawing; +using System; +using System.Collections.Generic; + +namespace AGVMapControl.Models +{ + public class MagnetLine + { + public Point StartPoint { get; set; } + public Point EndPoint { get; set; } + public List BranchPoints { get; set; } + public Dictionary BranchDirections { get; set; } + + public MagnetLine() + { + BranchPoints = new List(); + BranchDirections = new Dictionary(); + } + } +} \ No newline at end of file diff --git a/Cs_HMI/SubProject/AGVMapControl/Models/MapData.cs b/Cs_HMI/SubProject/AGVMapControl/Models/MapData.cs new file mode 100644 index 0000000..e32bf12 --- /dev/null +++ b/Cs_HMI/SubProject/AGVMapControl/Models/MapData.cs @@ -0,0 +1,14 @@ +using System.Drawing; +using System; +using System.Collections.Generic; +namespace AGVMapControl.Models +{ + public class MapData + { + public List RFIDPoints { get; set; } = new List(); + public List MagnetLines { get; set; } = new List(); + public List MapTexts { get; set; } = new List(); + public List CustomLines { get; set; } = new List(); + public List RFIDLines { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/Cs_HMI/SubProject/AGVMapControl/Models/MapElements.cs b/Cs_HMI/SubProject/AGVMapControl/Models/MapElements.cs new file mode 100644 index 0000000..2157d90 --- /dev/null +++ b/Cs_HMI/SubProject/AGVMapControl/Models/MapElements.cs @@ -0,0 +1,13 @@ +using System.Drawing; +using System; +using System.Collections.Generic; + +namespace AGVMapControl.Models +{ + public enum BranchDirection + { + Left, + Straight, + Right + } +} \ No newline at end of file diff --git a/Cs_HMI/SubProject/AGVMapControl/Models/MapText.cs b/Cs_HMI/SubProject/AGVMapControl/Models/MapText.cs new file mode 100644 index 0000000..1404242 --- /dev/null +++ b/Cs_HMI/SubProject/AGVMapControl/Models/MapText.cs @@ -0,0 +1,14 @@ +using System.Drawing; +using System; +using System.Collections.Generic; +namespace AGVMapControl.Models +{ + public class MapText + { + public Point Location { get; set; } + public string Text { get; set; } + public Color TextColor { get; set; } + public Color BackgroundColor { get; set; } + public Font Font { get; set; } + } +} \ No newline at end of file diff --git a/Cs_HMI/SubProject/AGVMapControl/Models/RFIDLine.cs b/Cs_HMI/SubProject/AGVMapControl/Models/RFIDLine.cs new file mode 100644 index 0000000..3337cde --- /dev/null +++ b/Cs_HMI/SubProject/AGVMapControl/Models/RFIDLine.cs @@ -0,0 +1,20 @@ +using System.Drawing; +using System; +using System.Collections.Generic; + +namespace AGVMapControl.Models +{ + public class RFIDLine + { + public Point StartPoint { get; set; } + public Point EndPoint { get; set; } + public string StartRFID { get; set; } + public string EndRFID { get; set; } + public bool IsBidirectional { get; set; } = true; // 양방향 이동 가능 여부 + public float Distance { get; set; } // 두 RFID 포인트 사이의 거리 + + public List ConnectedRFIDs { get; set; } = new List(); // 연결된 모든 RFID 값들 + public Dictionary NextRFID { get; set; } = new Dictionary(); // 각 RFID의 다음 RFID + public Dictionary PrevRFID { get; set; } = new Dictionary(); // 각 RFID의 이전 RFID + } +} \ No newline at end of file diff --git a/Cs_HMI/SubProject/AGVMapControl/Models/RFIDPoint.cs b/Cs_HMI/SubProject/AGVMapControl/Models/RFIDPoint.cs new file mode 100644 index 0000000..5939805 --- /dev/null +++ b/Cs_HMI/SubProject/AGVMapControl/Models/RFIDPoint.cs @@ -0,0 +1,13 @@ +using System.Drawing; +using System; +using System.Collections.Generic; +namespace AGVMapControl.Models +{ + public class RFIDPoint + { + public Point Location { get; set; } + public string RFIDValue { get; set; } + public string NextRFID { get; set; } // 다음 RFID 포인트의 값 + public bool IsBidirectional { get; set; } // 양방향 연결 여부 + } +} \ No newline at end of file diff --git a/Cs_HMI/SubProject/AGVMapControl/Models/ToolBarItem.cs b/Cs_HMI/SubProject/AGVMapControl/Models/ToolBarItem.cs new file mode 100644 index 0000000..235b67c --- /dev/null +++ b/Cs_HMI/SubProject/AGVMapControl/Models/ToolBarItem.cs @@ -0,0 +1,34 @@ +using System.Drawing; +using System.Collections.Generic; +using System.Linq; + +namespace AGVMapControl.Models +{ + public class ToolBarItem + { + private bool _ishovering = false; + public int Idx { get; set; } + public Rectangle Bounds { get; set; } + public bool isHovering + { + get { return _ishovering; } + set + { + Dirty = _ishovering != value; + _ishovering = value; + } + } + + public string Title { get; set; } + public bool Dirty { get; private set; } + + public ToolBarItem() + { + Bounds = Rectangle.Empty; + } + + + } + + +} \ No newline at end of file