From d6aed58516cccb4ee9b44f4a1fc6a7d1ead978d3 Mon Sep 17 00:00:00 2001 From: backuppc Date: Thu, 12 Feb 2026 09:58:01 +0900 Subject: [PATCH] .. --- .../AGVMapEditor/Forms/MainForm.Designer.cs | 132 +++++++-- AGVLogic/AGVMapEditor/Forms/MainForm.cs | 238 ++++++++++++++-- AGVLogic/AGVMapEditor/Forms/MainForm.resx | 221 +++++++++------ .../Controls/UnifiedAGVCanvas.Events.cs | 74 +++-- .../Controls/UnifiedAGVCanvas.Mouse.cs | 93 ++++++- .../Controls/UnifiedAGVCanvas.cs | 40 ++- AGVLogic/AGVNavigationCore/Models/Enums.cs | 8 +- .../AGVNavigationCore/Models/MapMagnet.cs | 12 + AGVLogic/AGVNavigationCore/Models/MapNode.cs | 36 ++- .../PathFinding/Planning/AGVPathfinder.cs | 143 ++++++---- AGVLogic/AGVSimulator/Forms/SimulatorForm.cs | 2 +- HMI/Project/StateMachine/Step/_SM_RUN.cs | 7 +- .../StateMachine/Step/_SM_RUN_ENTER.cs | 4 +- HMI/Project/StateMachine/Step/_SM_RUN_EXIT.cs | 4 +- HMI/Project/StateMachine/_Xbee.cs | 8 +- HMI/Project/ViewForm/fAuto.cs | 34 +-- NewMap.json | 260 ++++++++++-------- 17 files changed, 914 insertions(+), 402 deletions(-) diff --git a/AGVLogic/AGVMapEditor/Forms/MainForm.Designer.cs b/AGVLogic/AGVMapEditor/Forms/MainForm.Designer.cs index 323576b..b01b59e 100644 --- a/AGVLogic/AGVMapEditor/Forms/MainForm.Designer.cs +++ b/AGVLogic/AGVMapEditor/Forms/MainForm.Designer.cs @@ -31,6 +31,7 @@ namespace AGVMapEditor.Forms System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); this.statusStrip1 = new System.Windows.Forms.StatusStrip(); this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); + this.sbFile = new System.Windows.Forms.ToolStripStatusLabel(); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabPageNodes = new System.Windows.Forms.TabPage(); @@ -50,6 +51,10 @@ namespace AGVMapEditor.Forms this.btDirDelete = new System.Windows.Forms.ToolStripButton(); this.btMakeDirdata = new System.Windows.Forms.ToolStripButton(); this.toolStripButton2 = new System.Windows.Forms.ToolStripButton(); + this.tabPage3 = new System.Windows.Forms.TabPage(); + this.lstMagnet = new System.Windows.Forms.ListBox(); + this.toolStrip5 = new System.Windows.Forms.ToolStrip(); + this.btDelMagnet = new System.Windows.Forms.ToolStripButton(); this._propertyGrid = new System.Windows.Forms.PropertyGrid(); this.panel1 = new System.Windows.Forms.Panel(); this.toolStrip3 = new System.Windows.Forms.ToolStrip(); @@ -61,11 +66,12 @@ namespace AGVMapEditor.Forms this.btnDelete = new System.Windows.Forms.ToolStripButton(); this.btnEditImage = new System.Windows.Forms.ToolStripButton(); this.separator1 = new System.Windows.Forms.ToolStripSeparator(); - this.btnConnect = new System.Windows.Forms.ToolStripButton(); - this.btnDeleteConnection = new System.Windows.Forms.ToolStripButton(); + this.btnConnNode = new System.Windows.Forms.ToolStripButton(); + this.btnConnDir = new System.Windows.Forms.ToolStripButton(); this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); this.btnToggleGrid = new System.Windows.Forms.ToolStripButton(); this.btnFitMap = new System.Windows.Forms.ToolStripButton(); + this.btAddMagnet = new System.Windows.Forms.ToolStripButton(); this.toolStrip2 = new System.Windows.Forms.ToolStrip(); this.btnNew = new System.Windows.Forms.ToolStripButton(); this.btnOpen = new System.Windows.Forms.ToolStripButton(); @@ -77,7 +83,7 @@ namespace AGVMapEditor.Forms this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); this.toolStripButton1 = new System.Windows.Forms.ToolStripDropDownButton(); this.allTurnLeftRightCrossOnToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.btAddMagnet = new System.Windows.Forms.ToolStripButton(); + this.toolStripButton3 = new System.Windows.Forms.ToolStripButton(); this.statusStrip1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); this.splitContainer1.Panel1.SuspendLayout(); @@ -90,6 +96,8 @@ namespace AGVMapEditor.Forms this.tabPage2.SuspendLayout(); this.tableLayoutPanel1.SuspendLayout(); this.toolStrip4.SuspendLayout(); + this.tabPage3.SuspendLayout(); + this.toolStrip5.SuspendLayout(); this.toolStrip3.SuspendLayout(); this.toolStrip2.SuspendLayout(); this.SuspendLayout(); @@ -97,7 +105,8 @@ namespace AGVMapEditor.Forms // statusStrip1 // this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.toolStripStatusLabel1}); + this.toolStripStatusLabel1, + this.sbFile}); this.statusStrip1.Location = new System.Drawing.Point(0, 751); this.statusStrip1.Name = "statusStrip1"; this.statusStrip1.Size = new System.Drawing.Size(1200, 22); @@ -110,6 +119,12 @@ namespace AGVMapEditor.Forms this.toolStripStatusLabel1.Size = new System.Drawing.Size(39, 17); this.toolStripStatusLabel1.Text = "Ready"; // + // sbFile + // + this.sbFile.Name = "sbFile"; + this.sbFile.Size = new System.Drawing.Size(17, 17); + this.sbFile.Text = "--"; + // // splitContainer1 // this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; @@ -135,6 +150,7 @@ namespace AGVMapEditor.Forms this.tabControl1.Controls.Add(this.tabPageNodes); this.tabControl1.Controls.Add(this.tabPage1); this.tabControl1.Controls.Add(this.tabPage2); + this.tabControl1.Controls.Add(this.tabPage3); this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; this.tabControl1.Location = new System.Drawing.Point(0, 0); this.tabControl1.Name = "tabControl1"; @@ -330,6 +346,47 @@ namespace AGVMapEditor.Forms this.toolStripButton2.Text = "Clear"; this.toolStripButton2.Click += new System.EventHandler(this.toolStripButton2_Click); // + // tabPage3 + // + this.tabPage3.Controls.Add(this.lstMagnet); + this.tabPage3.Controls.Add(this.toolStrip5); + this.tabPage3.Location = new System.Drawing.Point(4, 22); + this.tabPage3.Name = "tabPage3"; + this.tabPage3.Size = new System.Drawing.Size(292, 309); + this.tabPage3.TabIndex = 3; + this.tabPage3.Text = "마그넷라인"; + this.tabPage3.UseVisualStyleBackColor = true; + // + // lstMagnet + // + this.lstMagnet.Dock = System.Windows.Forms.DockStyle.Fill; + this.lstMagnet.FormattingEnabled = true; + this.lstMagnet.ItemHeight = 12; + this.lstMagnet.Location = new System.Drawing.Point(0, 25); + this.lstMagnet.Name = "lstMagnet"; + this.lstMagnet.Size = new System.Drawing.Size(292, 284); + this.lstMagnet.TabIndex = 2; + // + // toolStrip5 + // + this.toolStrip5.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.btDelMagnet, + this.toolStripButton3}); + this.toolStrip5.Location = new System.Drawing.Point(0, 0); + this.toolStrip5.Name = "toolStrip5"; + this.toolStrip5.Size = new System.Drawing.Size(292, 25); + this.toolStrip5.TabIndex = 6; + this.toolStrip5.Text = "toolStrip5"; + // + // btDelMagnet + // + this.btDelMagnet.Image = ((System.Drawing.Image)(resources.GetObject("btDelMagnet.Image"))); + this.btDelMagnet.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btDelMagnet.Name = "btDelMagnet"; + this.btDelMagnet.Size = new System.Drawing.Size(61, 22); + this.btDelMagnet.Text = "Delete"; + this.btDelMagnet.Click += new System.EventHandler(this.btDelMagnet_Click); + // // _propertyGrid // this._propertyGrid.Dock = System.Windows.Forms.DockStyle.Bottom; @@ -355,8 +412,8 @@ namespace AGVMapEditor.Forms this.btnDelete, this.btnEditImage, this.separator1, - this.btnConnect, - this.btnDeleteConnection, + this.btnConnNode, + this.btnConnDir, this.toolStripSeparator1, this.btnToggleGrid, this.btnFitMap, @@ -394,6 +451,8 @@ namespace AGVMapEditor.Forms this.btnAddNode.Size = new System.Drawing.Size(111, 22); this.btnAddNode.Text = "노드 추가 (A)"; this.btnAddNode.ToolTipText = "노드 추가 (A)"; + this.btnAddNode.ButtonClick += new System.EventHandler(this.btnAddNode_ButtonClick); + this.btnAddNode.BackColorChanged += new System.EventHandler(this.btnAddNode_BackColorChanged); // // btnAddLabel // @@ -438,21 +497,20 @@ namespace AGVMapEditor.Forms this.separator1.Name = "separator1"; this.separator1.Size = new System.Drawing.Size(6, 25); // - // btnConnect + // btnConnNode // - this.btnConnect.Image = ((System.Drawing.Image)(resources.GetObject("btnConnect.Image"))); - this.btnConnect.Name = "btnConnect"; - this.btnConnect.Size = new System.Drawing.Size(95, 22); - this.btnConnect.Text = "노드연결 (C)"; + this.btnConnNode.Image = ((System.Drawing.Image)(resources.GetObject("btnConnNode.Image"))); + this.btnConnNode.Name = "btnConnNode"; + this.btnConnNode.Size = new System.Drawing.Size(95, 22); + this.btnConnNode.Text = "노드연결 (C)"; // - // btnDeleteConnection + // btnConnDir // - this.btnDeleteConnection.Image = ((System.Drawing.Image)(resources.GetObject("btnDeleteConnection.Image"))); - this.btnDeleteConnection.ImageTransparentColor = System.Drawing.Color.Magenta; - this.btnDeleteConnection.Name = "btnDeleteConnection"; - this.btnDeleteConnection.Size = new System.Drawing.Size(94, 22); - this.btnDeleteConnection.Text = "연결삭제 (X)"; - this.btnDeleteConnection.ToolTipText = "연결 삭제 (X)"; + this.btnConnDir.Image = ((System.Drawing.Image)(resources.GetObject("btnConnDir.Image"))); + this.btnConnDir.Name = "btnConnDir"; + this.btnConnDir.Size = new System.Drawing.Size(95, 22); + this.btnConnDir.Text = "방향연결 (C)"; + this.btnConnDir.Click += new System.EventHandler(this.btnConnDir_Click); // // toolStripSeparator1 // @@ -475,6 +533,15 @@ namespace AGVMapEditor.Forms this.btnFitMap.Text = "맵 맞춤"; this.btnFitMap.ToolTipText = "맵 전체 보기"; // + // btAddMagnet + // + this.btAddMagnet.Image = ((System.Drawing.Image)(resources.GetObject("btAddMagnet.Image"))); + this.btAddMagnet.ImageTransparentColor = System.Drawing.Color.Magenta; + this.btAddMagnet.Name = "btAddMagnet"; + this.btAddMagnet.Size = new System.Drawing.Size(87, 22); + this.btAddMagnet.Text = "마그넷추가"; + this.btAddMagnet.Click += new System.EventHandler(this.btAddMagnet_Click); + // // toolStrip2 // this.toolStrip2.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -574,14 +641,15 @@ namespace AGVMapEditor.Forms this.allTurnLeftRightCrossOnToolStripMenuItem.Text = "All TurnLeft/Right/Cross On"; this.allTurnLeftRightCrossOnToolStripMenuItem.Click += new System.EventHandler(this.allTurnLeftRightCrossOnToolStripMenuItem_Click); // - // btAddMagnet + // toolStripButton3 // - this.btAddMagnet.Image = ((System.Drawing.Image)(resources.GetObject("btAddMagnet.Image"))); - this.btAddMagnet.ImageTransparentColor = System.Drawing.Color.Magenta; - this.btAddMagnet.Name = "btAddMagnet"; - this.btAddMagnet.Size = new System.Drawing.Size(87, 22); - this.btAddMagnet.Text = "마그넷추가"; - this.btAddMagnet.Click += new System.EventHandler(this.btAddMagnet_Click); + this.toolStripButton3.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; + this.toolStripButton3.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton3.Image"))); + this.toolStripButton3.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripButton3.Name = "toolStripButton3"; + this.toolStripButton3.Size = new System.Drawing.Size(66, 22); + this.toolStripButton3.Text = "Refresh"; + this.toolStripButton3.Click += new System.EventHandler(this.toolStripButton3_Click_1); // // MainForm // @@ -615,6 +683,10 @@ namespace AGVMapEditor.Forms this.tableLayoutPanel1.ResumeLayout(false); this.toolStrip4.ResumeLayout(false); this.toolStrip4.PerformLayout(); + this.tabPage3.ResumeLayout(false); + this.tabPage3.PerformLayout(); + this.toolStrip5.ResumeLayout(false); + this.toolStrip5.PerformLayout(); this.toolStrip3.ResumeLayout(false); this.toolStrip3.PerformLayout(); this.toolStrip2.ResumeLayout(false); @@ -650,9 +722,8 @@ namespace AGVMapEditor.Forms private System.Windows.Forms.ToolStripButton btnSelect; private System.Windows.Forms.ToolStripButton btnMove; private System.Windows.Forms.ToolStripButton btnEditImage; - private System.Windows.Forms.ToolStripButton btnConnect; + private System.Windows.Forms.ToolStripButton btnConnNode; private System.Windows.Forms.ToolStripButton btnDelete; - private System.Windows.Forms.ToolStripButton btnDeleteConnection; private System.Windows.Forms.ToolStripSeparator separator1; private System.Windows.Forms.ToolStripButton btnToggleGrid; private System.Windows.Forms.ToolStripButton btnFitMap; @@ -675,5 +746,12 @@ namespace AGVMapEditor.Forms private System.Windows.Forms.Button button3; private System.Windows.Forms.ToolStripButton toolStripButton2; private System.Windows.Forms.ToolStripButton btAddMagnet; + private System.Windows.Forms.TabPage tabPage3; + private System.Windows.Forms.ListBox lstMagnet; + private System.Windows.Forms.ToolStrip toolStrip5; + private System.Windows.Forms.ToolStripButton btDelMagnet; + private System.Windows.Forms.ToolStripStatusLabel sbFile; + private System.Windows.Forms.ToolStripButton btnConnDir; + private System.Windows.Forms.ToolStripButton toolStripButton3; } } \ No newline at end of file diff --git a/AGVLogic/AGVMapEditor/Forms/MainForm.cs b/AGVLogic/AGVMapEditor/Forms/MainForm.cs index 810bb56..8625c82 100644 --- a/AGVLogic/AGVMapEditor/Forms/MainForm.cs +++ b/AGVLogic/AGVMapEditor/Forms/MainForm.cs @@ -115,6 +115,7 @@ namespace AGVMapEditor.Forms _mapCanvas.NodesSelected += OnNodesSelected; // 다중 선택 이벤트 _mapCanvas.NodeMoved += OnNodeMoved; _mapCanvas.NodeDeleted += OnNodeDeleted; + _mapCanvas.ConnectionCreated += OnConnectionCreated; _mapCanvas.ConnectionDeleted += OnConnectionDeleted; _mapCanvas.ImageDoubleClicked += OnImageDoubleClicked; _mapCanvas.MapChanged += OnMapChanged; @@ -144,9 +145,8 @@ namespace AGVMapEditor.Forms btnAddLabel.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.AddLabel; btnAddImage.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.AddImage; - btnConnect.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.Connect; + btnConnNode.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.Connect; btnDelete.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.Delete; - btnDeleteConnection.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.DeleteConnection; // 그리드 토글 버튼 btnToggleGrid.Click += (s, e) => _mapCanvas.ShowGrid = !_mapCanvas.ShowGrid; @@ -182,6 +182,7 @@ namespace AGVMapEditor.Forms _hasChanges = true; UpdateTitle(); RefreshNodeList(); + RefreshMagnetList(); // 추가 // RFID 자동 할당 } @@ -215,6 +216,20 @@ namespace AGVMapEditor.Forms { // 단일 선택은 기존 방식 사용 _selectedNode = nodes[0]; + + // Sync with lstMagnet + if (_selectedNode is MapMagnet magnet) + { + lstMagnet.SelectedItem = magnet; + } + else + { + lstMagnet.SelectedItem = null; + } + + //this._mapCanvas.SelectedNode = nodes; + //this._mapCanvas.Invalidate(); + UpdateNodeProperties(); UpdateImageEditButton(); } @@ -242,17 +257,20 @@ namespace AGVMapEditor.Forms _hasChanges = true; UpdateTitle(); RefreshNodeList(); + RefreshMagnetList(); // 추가 ClearNodeProperties(); // RFID 자동 할당 } - private void OnConnectionCreated(object sender, (MapNode From, MapNode To) connection) { _hasChanges = true; UpdateTitle(); + RefreshNodeConnectionList(); UpdateNodeProperties(); // 연결 정보 업데이트 } + + private void OnConnectionDeleted(object sender, (MapNode From, MapNode To) connection) { _hasChanges = true; @@ -261,6 +279,8 @@ namespace AGVMapEditor.Forms UpdateNodeProperties(); // 연결 정보 업데이트 } + + private void OnImageDoubleClicked(object sender, MapImage image) { // 이미지 노드 더블클릭 시 이미지 편집창 표시 @@ -280,6 +300,7 @@ namespace AGVMapEditor.Forms { _hasChanges = true; UpdateTitle(); + RefreshMagnetDirectionList(); // 방향 정보 업데이트 } private void OnBackgroundClicked(object sender, Point location) @@ -621,7 +642,7 @@ namespace AGVMapEditor.Forms private void LoadMapFromFile(string filePath) { var result = MapLoader.LoadMapFromFile(filePath); - + sbFile.Text = filePath; if (result.Success) { // 맵 캔버스에 데이터 설정 @@ -637,6 +658,7 @@ namespace AGVMapEditor.Forms UpdateTitle(); UpdateNodeList(); RefreshNodeConnectionList(); + RefreshMagnetList(); // 추가 @@ -767,6 +789,7 @@ namespace AGVMapEditor.Forms { RefreshNodeList(); RefreshNodeConnectionList(); + RefreshMagnetList(); // 추가 RefreshMapCanvas(); ClearNodeProperties(); } @@ -829,7 +852,7 @@ namespace AGVMapEditor.Forms var item = node as MapNode; if (item.StationType == StationType.Normal) foreColor = Color.DimGray; - else if (item.StationType == StationType.Charger1 || item.StationType == StationType.Charger2) + else if (item.StationType == StationType.Charger) foreColor = Color.Red; else foreColor = Color.DarkGreen; @@ -945,13 +968,21 @@ namespace AGVMapEditor.Forms _mapCanvas?.HighlightConnection(connectionInfo.FromNodeId, connectionInfo.ToNodeId); // 연결된 노드들을 맵에서 하이라이트 표시 (선택적) - var fromNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectionInfo.FromNodeId); - if (fromNode != null) - { - _selectedNode = fromNode; - UpdateNodeProperties(); - _mapCanvas?.Invalidate(); - } + //var fromNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectionInfo.FromNodeId); + //if (fromNode != null) + //{ + // if (_selectedNode != fromNode) + // { + // _selectedNode = fromNode; + // _mapCanvas.SelectedNode = fromNode; // 캔버스 선택 상태 동기화 + + // // 속성창 업데이트 (리스트 리프레시 포함) + // // 주의: RefreshMagnetDirectionList()가 호출되어도 lstNodeConnection에는 영향이 없으므로 안전함 + // UpdateNodeProperties(); + + // _mapCanvas?.Invalidate(); + // } + //} } else { @@ -1303,9 +1334,9 @@ namespace AGVMapEditor.Forms { foreach (var node in this._mapCanvas.Nodes) { - node.CanTurnLeft = true; - node.CanTurnRight = true; - node.DisableCross = false; + node.CanTurnLeft = false; + node.CanTurnRight = false; + node.DisableCross = true; node.ModifiedDate = DateTime.Now; } @@ -1367,6 +1398,9 @@ namespace AGVMapEditor.Forms private void RefreshMagnetDirectionList() { + // 이벤트 임시 제거 (DataSource 변경 시 불필요한 이벤트 발생 방지) + lstMagnetDirection.SelectedIndexChanged -= LstMagnetDirection_SelectedIndexChanged; + // 현재 선택된 항목 기억 int selectedIndex = lstMagnetDirection.SelectedIndex; @@ -1374,7 +1408,12 @@ namespace AGVMapEditor.Forms lstMagnetDirection.DataSource = null; lstMagnetDirection.Items.Clear(); - if (this._mapCanvas.Nodes == null) return; + if (this._mapCanvas.Nodes == null) + { + // 이벤트 다시 연결 (빠른 리턴 시에도 연결 필요) + lstMagnetDirection.SelectedIndexChanged += LstMagnetDirection_SelectedIndexChanged; + return; + } var directions = new List(); @@ -1410,8 +1449,7 @@ namespace AGVMapEditor.Forms lstMagnetDirection.DataSource = directions; } - // 이벤트 연결 - lstMagnetDirection.SelectedIndexChanged -= LstMagnetDirection_SelectedIndexChanged; + // 이벤트 다시 연결 lstMagnetDirection.SelectedIndexChanged += LstMagnetDirection_SelectedIndexChanged; lstMagnetDirection.DoubleClick -= LstMagnetDirection_DoubleClick; @@ -1420,7 +1458,15 @@ namespace AGVMapEditor.Forms // 선택 항목 복원 (가능한 경우) -> 선택된 객체가 다르게 생성되므로 인덱스로 복원 시도 if (selectedIndex >= 0 && selectedIndex < lstMagnetDirection.Items.Count) { - lstMagnetDirection.SelectedIndex = selectedIndex; + try + { + lstMagnetDirection.SelectedIndex = selectedIndex; + } + catch (Exception ex) + { + + } + } } @@ -1430,6 +1476,44 @@ namespace AGVMapEditor.Forms { // 버튼 상태 업데이트 UpdateDirectionButtons(info); + + // 캔버스에서 해당 연결선 강조 표시 + if (info.FromNode != null && info.ToNode != null) + { + _mapCanvas.HighlightConnection(info.FromNode.Id, info.ToNode.Id); + + // FromNode 선택 (속성창 갱신 루프 방지 위해 _propertyGrid 직접 설정 고려) + // 하지만 _selectedNode 변경 시 RefreshMagnetDirectionList()가 호출되어 리스트가 재생성되면 선택이 풀릴 수 있음 + // 따라서 여기서는 캔버스 상의 선택 표시만 변경하고, 전체 속성 업데이트(리스트 리프레시 포함)는 건너뛰거나 + // 리스트 리프레시 로직에서 선택 상태 유지를 보완해야 함. + + // 일단 _selectedNode를 변경하되, RefreshMagnetDirectionList에서 선택 인덱스 복원을 하므로 괜찮을 것으로 예상됨. + // 만약 깜빡임이나 끊김이 심하면 UpdateNodeProperties 내의 RefreshMagnetDirectionList 호출을 조건부로 변경해야 함. + + if (_selectedNode != info.FromNode) + { + var prevSelected = _selectedNode; + _selectedNode = info.FromNode; + + // _mapCanvas.SelectedNode 설정 (이것만으로는 PropertyGrid 갱신 안됨) + _mapCanvas.SelectedNode = info.FromNode; + + // PropertyGrid 갱신 (리스트 리프레시 포함) + // 주의: 여기서 UpdateNodeProperties()를 부르면 리스트가 다시 그려지면서 선택 이벤트가 다시 발생할 수 있음. + // 하지만 인덱스 복원 로직이 있으므로 무한루프는 아닐 수 있으나, 비효율적임. + + // 해결책: 리스트 리프레시 없이 속성창만 갱신 + _propertyGrid.SelectedObject = _selectedNode; + UpdateImageEditButton(); + + // 캔버스 다시 그리기 + _mapCanvas.Invalidate(); + } + } + } + else + { + _mapCanvas.ClearHighlightedConnection(); } } @@ -1670,8 +1754,8 @@ namespace AGVMapEditor.Forms } } - // 현재 선택된 노드의 속성창 및 리스트 갱신 - UpdateNodeProperties(); + // 현재 선택된 노드의 속성창 및 리스트 갱신 + UpdateNodeProperties(); } private void btAddMagnet_Click(object sender, EventArgs e) @@ -1695,11 +1779,11 @@ namespace AGVMapEditor.Forms string id = _mapCanvas.GenerateUniqueNodeId(); var magnet = new MapMagnet { Id = id }; - + // 점 생성 시 정규화(Snap) 처리 int cx = (int)worldCX; int cy = (int)worldCY; - + magnet.StartPoint = new Point(cx - 50, cy); magnet.EndPoint = new Point(cx + 50, cy); @@ -1711,14 +1795,118 @@ namespace AGVMapEditor.Forms // 캔버스에 추가 _mapCanvas.Magnets.Add(magnet); _hasChanges = true; - + UpdateTitle(); RefreshMapCanvas(); RefreshNodeList(); - + RefreshMagnetList(); // 추가 + // 추가된 마그넷 선택 //_mapCanvas.SelectedNode = magnet; UpdateNodeProperties(); } + + private void btDelMagnet_Click(object sender, EventArgs e) + { + //선택한 마그넷라인을 삭제 (삭제 후 맵에 바로 반영되도록 업데이트필요) + if (lstMagnet.SelectedItem is MapMagnet magnet) + { + _mapCanvas.RemoveMagnet(magnet); + RefreshMagnetList(); + } + } + + private void RefreshMagnetList() + { + lstMagnet.DataSource = null; + lstMagnet.Items.Clear(); + + if (_mapCanvas.Magnets != null && _mapCanvas.Magnets.Count > 0) + { + lstMagnet.DataSource = _mapCanvas.Magnets; + } + + // 이벤트 연결 + lstMagnet.SelectedIndexChanged -= LstMagnet_SelectedIndexChanged; + lstMagnet.SelectedIndexChanged += LstMagnet_SelectedIndexChanged; + + lstMagnet.DoubleClick -= LstMagnet_DoubleClick; + lstMagnet.DoubleClick += LstMagnet_DoubleClick; + + lstMagnet.DrawMode = DrawMode.OwnerDrawFixed; + lstMagnet.DrawItem -= LstMagnet_DrawItem; + lstMagnet.DrawItem += LstMagnet_DrawItem; + } + + private void LstMagnet_DrawItem(object sender, DrawItemEventArgs e) + { + e.DrawBackground(); + + if (e.Index >= 0 && e.Index < lstMagnet.Items.Count) + { + var magnet = lstMagnet.Items[e.Index] as MapMagnet; + if (magnet != null) + { + Brush brush = Brushes.Black; + if (magnet.ControlPoint != null) // Curve + { + brush = Brushes.Blue; // Curve는 파란색 + } + + // 선택된 항목은 흰색 글씨 (배경이 파란색이므로) + if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) + { + brush = Brushes.White; + } + + e.Graphics.DrawString(magnet.ToString(), e.Font, brush, e.Bounds); + } + } + + e.DrawFocusRectangle(); + } + + private void LstMagnet_SelectedIndexChanged(object sender, EventArgs e) + { + if (lstMagnet.SelectedItem is MapMagnet magnet) + { + _mapCanvas.SelectedNode = magnet; + //UpdateNodeProperties(); // SelectedNode setter에서 Invalidate 호출됨 + } + } + + private void LstMagnet_DoubleClick(object sender, EventArgs e) + { + if (lstMagnet.SelectedItem is MapMagnet magnet) + { + _mapCanvas.PanTo(magnet.Position); + } + } + + private void btnAddNode_ButtonClick(object sender, EventArgs e) + { + + } + + private void btnAddNode_BackColorChanged(object sender, EventArgs e) + { + + } + + private void btnDeleteConnection_Click(object sender, EventArgs e) + { + _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.DeleteConnection; + } + + private void btnConnDir_Click(object sender, EventArgs e) + { + //방향연결(노드연결과 유사), 두 노드간의 방향을 생성한다 기본값 straight + _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.ConnectDirection; + } + + private void toolStripButton3_Click_1(object sender, EventArgs e) + { + RefreshMagnetList(); + } } } \ No newline at end of file diff --git a/AGVLogic/AGVMapEditor/Forms/MainForm.resx b/AGVLogic/AGVMapEditor/Forms/MainForm.resx index 1bc0a34..64ced17 100644 --- a/AGVLogic/AGVMapEditor/Forms/MainForm.resx +++ b/AGVLogic/AGVMapEditor/Forms/MainForm.resx @@ -123,7 +123,49 @@ 249, 17 + + 17, 56 + + + 123, 56 + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK + YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X + /aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t + I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM + cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh + 6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD + lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A + HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb + 1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC + nOccAdABIDXXE1nzAAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK + YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X + /aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t + I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM + cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh + 6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD + lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A + HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb + 1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC + nOccAdABIDXXE1nzAAAAAElFTkSuQmCC + + + + 462, 17 + + + 249, 17 + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 @@ -187,21 +229,18 @@ nOccAdABIDXXE1nzAAAAAElFTkSuQmCC - - 462, 17 - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHrSURBVDhPldBLaBNBHMfx/0kUVBBJ0lxWPIhihBJKyAqS - pHkQIS+9hXg3RhQviicrITnmJqFnQQ8RqiamRqkhj6VCQtuIQaVQc7dg3d3s61Dy0w002KnU9nP67+x8 - h2GIDmD0kT+mLk/fZNf3pQkznCrM3DFnZflSRG05euast7izcpM72GGqMP1ZFRw1tXm+qq9dg9LiHgwb - dnFYP51i9/6T0r4wp39Kwfh2F8bGI2irEYjvTmo/Gpbj7N4JpXNxShUcdbV1DvpaHMb3HNrP4uiVb2Cj - cQtadxbSh6OQ3tM82+6iNLk5rXcd7ecJGIaB0WiE1dcp6F9v41eNvmxV6QzbTMjtKYtct9Wi0Si63S50 - XUe/30fjaQTG+n1IVRpKb4lnuzFtyc4Nl06VE4kE0uk0CoUCSqUSqvOzMNYfYnORtqVFWhEr9JhtJ+Lx - +DjmeR5+vx+xWAzqSgRy3Q65dgJbFeLYZmIndrvd8Pl8sFqt5pWfbL6hbalCl6Uy9cSXlGG7sWQyiXw+ - P469Xi8sFgvMdblCV6RXVDNnvKAjPxfoKttSOBxGLpfbE+8QFyj09/cugUAA2WwWLpcLHo9nT7yvTCaD - wWAAp9OJUCh0uNhkHtDpdFAsFscPxv7/r2AweM+8ts1mO3z8x29G9wsX9Ki7DgAAAABJRU5ErkJggg== + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHsSURBVDhPldBLaBNBHMfx/0kUVBBJ0lxWPIhihBJKyAqS + pHkQIS+9hXg3RhQviicrITnmJqFnQQ8RqiamRqkhj6VCQtuIQaVQc64H6+5mX4eSn26gwU6ltp/Tf2fn + OwxDdACjj/wxdXn6Jru+L02Y4VRh5o45K8uXImrL0TNnvcWdlZvcwQ5ThenPquCoqc3zVX3tGpQW92DY + sIvD+ukUu/eflPaFOf1TCsa3uzA2HkFbjUB8d1L70bAcZ/dOKJ2LU6rgqKutc9DX4jC+59B+FkevfAMb + jVvQurOQPhyF9J7m2XYXpcnNab3raD9PwDAMjEYjrL5OQf96G79q9GWrSmfYZkJuT1nkuq0WjUbR7Xah + 6zr6/T4aTyMw1u9DqtJQeks8241pS3ZuuHSqnEgkkE6nUSgUUCqVUJ2fhbH+EJuLtC0t0opYocdsOxGP + x8cxz/Pw+/2IxWJQVyKQ63bItRPYqhDHNhM7sdvths/ng9VqNa/8ZPMNbUsVuiyVqSe+pAzbjSWTSeTz + +XHs9XphsVhgrssVuiK9opo54wUd+blAV9mWwuEwcrncnniHuEChv793CQQCyGazcLlc8Hg8e+J9ZTIZ + DAYDOJ1OhEKhw8Um84BOp4NisTh+MPb/fwWDwXvmtW022+HjP34DP4sLE797GZoAAAAASUVORK5CYII= @@ -217,83 +256,83 @@ YIGDKaeH7rEGFFd1IN1M4c5nAYIcIXLXvmW+uOKfXMvpRO9rFnzJi9lqBKPZYVCedzYsH6SQ2l+Eu2SD bfNyWeHqqhbxahSCGIM2MwSKrYzDWboBx5sxIsP6yvTPH0lk3YoGI9lhaB8NQZO+gl8Dj7SN1tpAvgAA AABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHZSURBVDhPnZJda9NwFMb3JbxV/BaD4ufZjcwhejsdiqig + 8+1u8w2vUprNrE3axDWmKW1aQ0rbtPaFUkra2paCotWU9YVHzh8SidMVfSCEnHOe33MgZ61araJSqaBc + LqNUKqFYLKJQKMCyLHqfW1sl27axXC5PPb1ej0F0XT8bQslkcByHJXc6HR9CNdM0z4bQyjRMafSdz+et + yWSC+XzO6gQ0DOPvEEr1BlOp1Ekmk3Gm0ylGX7pI2Yes1263kU6n/wyhZG/lxWIBMo+/fsKj6BbuHWxA + sw9Yr9VqQdM0SJIUhJim6Y7HYzY0m80w/NzFbvQK9t5t46V6C4+jWz6k2WwimUwGIblc7pJhGO5oNGJD + H50PuH+4gefHN/FGu4tX6u0ApNFoQJblICSbzYYURXGHwyEbqncLeChs4sXxjg/ZPdpEshRm/VqtBlEU + wXHcL4iu66FEIuEOBoNTkNfv7+CZdA1Pj65jvpixLeLxOCKRyAUfQFJVlUH6/b4PefD2Mp7ErmJf2cH3 + H998M8dxFwNmT7IsMwhdIlvXsbCv3MD0xF1t9iSKYigWi7l0id4vJrMkSavNngRBWBcEwaUDq9fr/2b2 + xPP8Os/z7n+ZPREkHA6f/73u6SfD/w8v3D5c0gAAAABJRU5ErkJggg== iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHHSURBVDhPnZJva9pQFMb7JfZ2Y9+iIPtke7OujL7sWopr - 2bt2bETMi4CJXlCROI0SFP/OJDqXiladgw7cks0ozzhXbopzq2wPhHDPOc/vOSR3r91uo9VqodFooF6v - o1aroVqtolKp0PvB3i41m02sVqutZzgccoiu6/dDKJkMg8GAJ7uuG0KoZprm/RBamYYpjc7lcrkyn88R - BAGvE9AwjL9DKFUM5vP5n4VCYeD7Pj7ffEeBTXmv3+9T788QShYrL5dLkPnL1MerQwcnT228T60hvV4P - uVwOmqZtQkzT9GazGR9aLBY8OfrcweVxH2/PXJwfdkOI4zjIZDKbkFKp9MQwDG86XQ9Z9VuefHXyCdL5 - 9RbEtm0wxjYhxWIxkk6nvclkwoe6ra+IHth4c3oHiR440LV1v9PpQFVVSJJ0B9F1PcIY88bj8RbkXdTF - 66MeLo4+IAiWfItkMol4PP4oBJCy2WwklUp5o9EohJw9s3Dxoour0y6+zf3QLEnS4w2zEGOMQ+gm8g/X - vMXly4/44Qe7zUKqqkYSiYRHN1H8YjJrmrbbLKQoyr6iKB5dMMuy/s0sJMvyvizL3n+ZhQgSi8Ue/l4X - +gV3kBaRV83skgAAAABJRU5ErkJggg== + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHGSURBVDhPnZJfa9pgFMb7JXa7sm9RkH2y3awro5dbS3Et + u2vHRsRcBEw0gqJxGiUo/q1JdDYVnVoLLbglm1GecV55U5xbZXsghPe85/k9h+TstFotNJtN1Ot11Go1 + VKtVVCoVlMtlej/Z2aZGo4HlcrnxDAYDBtE07XEIJZOh3++zZMdxAgjVDMN4HEIjUzOl0blUKpVnsxl8 + 32d1Auq6/ncIpfLGbDb7M5/P9z3Pw83X78irE3bX6/WQy+X+DKFkPvJisQCZbyce3h3YOHph4XNiBel2 + u8hkMlAUZR1iGIY7nU5Z03w+Z8nhVzbO3/Tw8cTB6UEngNi2jVQqtQ4pFovPdV13J5NVk1m7Y8kXR1cQ + Tq83IJZlQVXVdUihUAglk0l3PB6zpk7zHuF9Cx+OHyDhfRuasrpvt9uQZRmCIDxANE0LJRIJdzQabUA+ + hR28P+zi7PASvr9gU8TjcUSj0d0AQEqn0wwyHA4DyMlLE2evO7g47uDbzAvMgiA8WzNzqarKILSJ7MM1 + 7nD+9gt+eP52M5csy6FYLObSJvJfTGZFUbabuSRJ2pMkyaUFM03z38xcoijuiaLo/peZiyCRSOTp73Wu + X1R9FoLvbSO9AAAAAElFTkSuQmCC iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAG4SURBVDhPnZLditpQFIXnJXrb0rcYkD5YL4pMYS7bKaX0 - HSLmIiV/BzRjIxolKGoM/iAijlbFizLIJOjJYZV9hmSwtiPtghCy917f2pB9EYYh+v0+er0eut0uOp0O - 2u02Wq0WvV9cnFMQBBBCnDyLxUJCXNd9HkLJZJjP5zJ5NptlEKr5vv88hFamYUqj72az2drtduCcyzoB - Pc/7O4RS08Fqtbqv1WrzOI7Bf/4AD23Zm06n1PszhJLTlZMkAZnF/QaJdYXk21vw0JK9yWSCSqUC0zSP - Ib7vR9vtVg4dDgeZnJh5COcDxPfPENZVBhmPxyiXy8eQRqPxxvO8aLPZPELuOjJZ3N5AVL+eQEajERhj - x5B6vZ4rlUrRer1+hCwDcCMPcfvpCWLmwQNd9geDAQzDgKIoTxDXdXOMsWi1Wp1C3C8Q7BoPxjVEwuUW - lmWhWCy+ygAkx3Fytm1Hy+Uygxz0dxD2e8TlG+yjXWZWFOX1kTkVY0xC6BIlZNEDdz5C7OPz5lSGYeR0 - XY/oEtNfTGbTNM+bU2madqlpWkQHNhwO/82cSlXVS1VVo/8ypyJIoVB4+Xs91S9svB2DRfl8BwAAAABJ - RU5ErkJggg== - - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHaSURBVDhPnZJda9NwFMb3Jbx1+C0Gxc+zG5lD9FY3FFFB - 59udUze8Smk2szZpM9MsprRpDSlt09oXSilpa1sKilZT1hceOX9IJE5X9IEQcs55fs+BnJVKpYJyuYxS - qYRisYhCoYB8Pg/Lsuh9YWWZbNvGYrE483S7XQbRdf18CCWTwXEcltxut30I1UzTPB9CK9MwpdF3Lpez - xuMxZrMZqxPQMIy/QyjVG0ylUqfpdNqZTCYYfungvX3Ieq1Wi3p/hlCyt/J8PgeZR18/4XF0E/cP1qHZ - B6zXbDahaRokSQpCTNN0R6MRG5pOpxh87mAnehUv3t3Ea/U2nkQ3fUij0UAymQxCstnsZcMw3OFwyIY+ - Oh/w4HAdL5UtvNHuYU+9E4DU63XIshyEZDKZkKIo7mAwYEO1Th6PhA28UrZ9yM7RBpLFMOtXq1WIogiO - 435BdF0PybLs9vv9M5D9k7t4Ll3Hs6MbmM2nbIt4PI5IJLLqA0iqqoYSiYTb6/V8yMO3V/A0dg27x9v4 - /uObb+Y47lLA7EmWZQahS2TrOhZ2j29hcuouN3sSRTEUi8VcukTvF5NZkqTlZk+CIKwJguDSgdVqtX8z - e+J5fo3nefe/zJ4IEg6HL/5e9/QT5xIPPuD/DmQAAAAASUVORK5CYII= + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAG3SURBVDhPnZLditpQFIXnJXrb0rcYkD5YL4pMYS7bKaX0 + HSLmIiW/ghmNaJSgqDH4g4hYrYoXpUgT9OSwyj7DyWBtR9oFIWTvvb61IfsqiiIMBgP0+330ej10u110 + Oh202216P7u6pDAMwTk/e5bLpYB4nvc0hJLJsFgsRPJ8Ps8gVAuC4GkIrUzDlEbfrVarvd/vwRgTdQL6 + vv93CKXKwWq1eqjX64skScC+fwOLbNGbzWao1Wp/hlCyXDlNU5CZ/9gitW6QfnkNFlmiN51OUalUYJrm + KSQIgni324mh4/EoklMzD+6+A69+BLduMshkMkG5XD6FNJvNV77vx9vt9gHytSuS+f0deO3zGWQ8HsNx + nFNIo9HIlUqleLPZPEBWIZiRB7//8Agx82ChLvrD4RCGYUBRlEeI53k527bj9Xp9DvE+gTu3+GncgqdM + bGFZForF4osMQHJdV0BWq1UGOepvwO23SMp3OMT7zKwoyssTs5TjOAJClyggyz6Y+x78kFw2SxmGkdN1 + PaZLlL+YzKZpXjZLaZp2rWlaTAc2Go3+zSylquq1qqrxf5mlCFIoFJ7/Xpf6BUmpHXRPK0SnAAAAAElF + TkSuQmCC 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 - W91zf/4A4AuLF1bhN90AAAAASUVORK5CYII= + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAALySURBVDhPhdLfT1tlHMdx/gPvjYnJLrxZjF7otZk63XRx + OjdFl7UxZBonZjFulhVxyNzOoFLOKAi0wLHDbdhuw9EOmNhuK0PWsQMHimwqk0FBIND1tKftM4Lsbdpo + sxF/fJLv1fN9Xk/yfL8Fim+oWvGq87JX1e3u/y/Zo+qus2pNwd9p7ry2MB2N3jNSKVJp8Z+1ZAgW4kka + Tg3E80D25aSRot4fpdqvUX7+W9zXHXhGvuCkVkFdyMb7rU4+bunD0jLOYlIge4f1PFDjVvWs3nA+itTd + g3vQQfvoPlyD79FwtYjG8B5svYfY03QCq3KDBf0fgGRa0NQ9Q3lXG6e0ChrDuzn24y5sobeQLhdiD32E + ufZLyo7/zLyeQfYOPQjoaYGrZ5aDPjdfq2XI/SakS2/weXAbn/2wDSlQjEmuRGlv4+aJQn49dxit5QW0 + +vWmHBBPCVou/I7k68IROkpNfxHlwdexfr+Vg4FCPv2uhPrj+5kLWtBH/WDMEh89Tahq03wOuGMIlN45 + HF0jlHgUpM69VAV2I/W+Q6n3A+ytxSyOlbMc7WTuko1M5Cx/3L7C8FfvioLsbGNJgTswn0NqfRqfVJg4 + Yn8eybaB5tpNxMbKWM1cIXOrmNi1D7nRVoTmfHMp7Hj1sRywlBBYPa9gad/CvpMv4Sx9moiynbFvNnIn + coBVMUB6woyY3kly3IpWu+XecM3GJ3OfWO2+rscSgtDExXz1RzroO7eXuUHLX5dNiKm3SYyVcqvpZRTl + jJGfgj0HZB4AghcrGb+wn9VMCDFZxN3pXeiRA/zS+CIzU5PInvvGWOdVp2LxBAlDkEjdZTjYxk9dVka6 + ZWbCO0hP7iSmlTDR/BpLs7+xuHaVXR1qlfP0UF92ObKyp/I5SN7mpnM7QevD9DduJnDkmdVWxWdkz+Uz + Q7qrQ5XzwNocMq9bWR6oYzl8jPDhDbjNjxg9lvVPrO3712x96iEhmR9d8R/dvNJb/exlf8nj69b23J8/ + AbuKiwBr5ZOIAAAAAElFTkSuQmCC - + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHrSURBVDhPY/j//z8DJRhDgFSMIUAqxhAgFcMZdnZ2 @@ -308,19 +347,19 @@ gg== - + - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHvSURBVDhP7Y7Ni1JhGMVvXadgWrWzRQyzTEFyla0cx6sL - P6K7USGoRYr1SoJ9zGh3YbgYQZjAC+YqIURoo61slDLQcRORgWCt7gxShBRp0JQ2mSeegS4mzn/QgRce - zvmdw8tx/6XKZrOdYIzdicfj/VwuB0mSuoyxc4wxrSRJCnmUEUPsP2WdTnfM6XSm0uk0er0eFEVBvV6n - wo4kSY12uz0ir9lsghhiqaMOGI3GC+Fw+Gun0+n7fL6ngiCMA4EAZFlGLBYD3eRRVigUusRSZ3oglUgk - hrIsB10u16LJZPpWq9XQarXQaDRQLBZBHmXEEEsddcBgMFwPBoODTCZzWxTFqsVi2bfb7RBF8eDRTR5l - xBBLHXVAr9efcbvd75LJ5CCfz38plUrIZrOIRCKIRqMHd2WD4cXFpd/PVxfwxL64/2jl+H11gOM43mw2 - n3U4HLt+v38vFAoNGWM/PR7PZa/Xa31wyTx6c+s8huVNTN5W8P3xTbwK6cbPrJob0yOHakvglR/lTUB2 - A+sngY1lfE6toGzhd2bZuSqvaiaT1yVMaxDXgvxZdq6qAv9+7+FVIK7FaI1Df41D9xqPio3/MMvO1bb3 - 1L2XV5Z+fVxfxqfYAhT/EdTsmvGWcPTuLHuotr2no1Urv0vfph/9Lf8B0zIZ8W7n4ScAAAAASUVORK5C - YII= + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m + dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHrSURBVDhPY/j//z8DJRhDgFSMIUAqxhAgFcMZdnZ2 + 3FlZWaWtra3v5s2b97+mpuZhTk6OeU5OjkRNTc1dkBhIDqQGpBbFACMjIzYfH5+uiRMn/n/x4sX/u3fv + /j948OD/xsbGe9XV1Ydu3rz5AyR25MiR/yA1ILUgPXADDA0N/YqLiz9cvXr1XURExDZvb+8/qamp/ydN + mvS/srLyP4gNEgPJrVix4iFILUgPsgu6mpqavk+ePDnd39+fy97e/vPevXv/nzt37v+hQ4f+r1279j9I + DCQHUgNSC9IDN0BXVzczPT39/bRp00qCgoJ2Ojo6/nJzc/sfGBgIxiA2SAwkB1IDUgvSAzdAU1NT09fX + 93pvb+/7VatWvV23bt3/6dOn/y8sLPxfUVEBZvcvbP1fODXmX1qf//+IJse/Lrm6E+EGqKmpMdvb2xt4 + eXndT0lJ+ZKdnf09Ozv7Z3h4eFx4eLhzZm3cz8bVif+3XJn6/8KzXf/7d2f9D+/X/W+eLdmDEa/YcGy7 + y/eNlyf833ht0n8Q6NmT+r9/TzrIgO8YirFhn2rD/9uuzAFrhoFNl6aCDMBUjA1b5kh/796V9L99VwJY + c/vOBNJcYJ4t2RLco/G/d1cK2GYQDeITHQZQQzrMsyU/gZwNpTtA4gBRO5Y8lpxI5AAAAABJRU5ErkJg + gg== @@ -335,18 +374,18 @@ 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 diff --git a/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs b/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs index af87165..3e89fda 100644 --- a/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs +++ b/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs @@ -374,11 +374,26 @@ namespace AGVNavigationCore.Controls foreach (var targetNode in node.ConnectedMapNodes) { if (targetNode == null) continue; + + // 강조된 연결은 나중에 그리기 위해 건너뜀 + if (IsConnectionHighlighted(node.Id, targetNode.Id)) continue; + DrawConnection(g, node, targetNode); } } } + // 1.1 강조된 연결 그리기 (항상 위에 표시되도록) + if (_highlightedConnection.HasValue) + { + var n1 = _nodes.FirstOrDefault(n => n.Id == _highlightedConnection.Value.FromNodeId); + var n2 = _nodes.FirstOrDefault(n => n.Id == _highlightedConnection.Value.ToNodeId); + if (n1 != null && n2 != null) + { + DrawConnection(g, n1, n2); + } + } + // 2. 마그넷 그리기 (별도 리스트 사용) if (_magnets != null) { @@ -442,10 +457,17 @@ namespace AGVNavigationCore.Controls g.DrawLine(_magnetPen, startPoint, endPoint); } - // 호버된 마그넷 강조 - if (magnet == _hoveredNode) + // 호버되거나 선택된 마그넷 강조 (선택: Red, 호버: Orange) + bool isHovered = (magnet == _hoveredNode); + bool isSelected = (magnet == _selectedNode); + + if (isHovered || isSelected) { - using (var highlightPen = new Pen(Color.Orange, 19)) + Color highlightColor = isSelected ? Color.Red : Color.Orange; + // 선택된 상태에서 호버되면? -> 선택 색상 우선 (Red) 또는 명확한 구분 필요 + // 여기서는 선택이 더 중요하므로 Red 유지 + + using (var highlightPen = new Pen(highlightColor, 19) { StartCap = LineCap.Round, EndCap = LineCap.Round }) { if (magnet.ControlPoint != null) { @@ -470,15 +492,6 @@ namespace AGVNavigationCore.Controls g.DrawLine(highlightPen, startPoint, endPoint); } } - // Redraw normal to keep it on top? No, highlight is usually outer. - // If I draw highlight AFTER, it covers. - // But DrawMagnet is void. If I draw highlight after, it's fine if I want it to glow. - // Actually _magnetPen is Width 15, very thick. - // If I draw highlight Width 19 *before* normal, it acts as border. - // But this method draws normal first. - // So I should refactor to calculate path first, then draw? - // Or just draw highlight on top with alpha? - // Let's draw highlight on top for now, maybe with alpha. } // 선택된 마그넷 핸들 그리기 @@ -859,7 +872,7 @@ namespace AGVNavigationCore.Controls { case NodeType.Normal: var item = _selectedNode as MapNode; - if ( item.StationType == StationType.Charger1 || item.StationType == StationType.Charger2) + if ( item.StationType == StationType.Charger) DrawTriangleGhost(g, ghostBrush); else DrawPentagonGhost(g, ghostBrush); @@ -1049,13 +1062,12 @@ namespace AGVNavigationCore.Controls switch (node.StationType) { case StationType.Loader: - case StationType.UnLoader: - case StationType.Clearner: + case StationType.Cleaner: + case StationType.Plating: case StationType.Buffer: DrawPentagonNodeShape(g, node, brush); break; - case StationType.Charger1: - case StationType.Charger2: + case StationType.Charger: DrawTriangleNodeShape(g, node, brush); break; case StationType.Limit: @@ -1470,7 +1482,7 @@ namespace AGVNavigationCore.Controls // 🔥 노드의 폰트 설정 사용 (0 이하일 경우 기본값 7.0f 사용) var topFont = new Font("Arial", 9, FontStyle.Bold); - var btmFont = new Font("Arial", 12, FontStyle.Bold); + var btmFont = new Font("Arial", node.NodeTextFontSize, FontStyle.Bold); // 메인 텍스트 크기 측정 var TopSize = g.MeasureString(TopIDText, topFont); @@ -1496,8 +1508,7 @@ namespace AGVNavigationCore.Controls Color bgColor = Color.White; switch (node.StationType) { - case StationType.Charger1: - case StationType.Charger2: + case StationType.Charger: fgColor = Color.White; bgColor = Color.Tomato; break; @@ -1505,12 +1516,12 @@ namespace AGVNavigationCore.Controls fgColor = Color.Black; bgColor = Color.White; break; - case StationType.Clearner: + case StationType.Plating: fgColor = Color.Black; bgColor = Color.DeepSkyBlue; break; case StationType.Loader: - case StationType.UnLoader: + case StationType.Cleaner: fgColor = Color.Black; bgColor = Color.Gold; break; @@ -1519,6 +1530,8 @@ namespace AGVNavigationCore.Controls break; } + + var rectpaddingx = 4; @@ -1541,7 +1554,8 @@ namespace AGVNavigationCore.Controls } - using (var descBrush = new SolidBrush(fgColor)) + + using (var descBrush = new SolidBrush(node.NodeTextForeColor)) { g.DrawString(BottomLabelText, btmFont, descBrush, roundRect, new StringFormat { @@ -1793,12 +1807,16 @@ namespace AGVNavigationCore.Controls switch (node.StationType) { - case StationType.Normal: bgColor = Color.DeepSkyBlue; break; - case StationType.Charger1: bgColor = Color.Tomato; break; - case StationType.Charger2: bgColor = Color.Tomato; break; + case StationType.Normal: + if(node.CanTurnLeft || node.CanTurnRight) + bgColor = Color.Violet; + else + bgColor = Color.DeepSkyBlue; + break; + case StationType.Charger: bgColor = Color.Tomato; break; case StationType.Loader: - case StationType.UnLoader: bgColor = Color.Gold; break; - case StationType.Clearner: bgColor = Color.DeepSkyBlue; break; + case StationType.Cleaner: bgColor = Color.Gold; break; + case StationType.Plating: bgColor = Color.DeepSkyBlue; break; case StationType.Buffer: bgColor = Color.WhiteSmoke; break; case StationType.Limit: bgColor = Color.Red; break; default: bgColor = Color.White; break; diff --git a/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs b/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs index 6c45c2a..0d29027 100644 --- a/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs +++ b/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs @@ -104,6 +104,10 @@ namespace AGVNavigationCore.Controls HandleConnectClick(hitNode as MapNode); break; + case EditMode.ConnectDirection: + HandleConnectDirectionClick(hitNode as MapNode); + break; + case EditMode.Delete: HandleDeleteClick(hitNode); break; @@ -227,6 +231,23 @@ namespace AGVNavigationCore.Controls if (handleIdx != -1) { _dragHandleIndex = handleIdx; + + // 핸들 드래그 시 초기 오프셋 설정 (점프 현상 방지) + if (_selectedNode is MapMagnet magnet) + { + Point handlePos = Point.Empty; + if (handleIdx == 0) handlePos = magnet.StartPoint; + else if (handleIdx == 1) handlePos = magnet.EndPoint; + else if (handleIdx == 2 && magnet.ControlPoint != null) + handlePos = new Point((int)magnet.ControlPoint.X, (int)magnet.ControlPoint.Y); + + _dragOffset = new Point(worldPoint.X - handlePos.X, worldPoint.Y - handlePos.Y); + } + else + { + _dragOffset = Point.Empty; // Mark 등은 오프셋 없이 마우스 포인터 기준 계산 + } + _isDragging = true; _isPanning = false; Capture = true; @@ -562,12 +583,11 @@ namespace AGVNavigationCore.Controls switch (node.StationType) { case StationType.Loader: - case StationType.UnLoader: - case StationType.Clearner: + case StationType.Cleaner: + case StationType.Plating: case StationType.Buffer: return IsPointInPentagon(point, node); - case StationType.Charger2: - case StationType.Charger1: + case StationType.Charger: return IsPointInTriangle(point, node); default: return IsPointInCircle(point, node); @@ -887,7 +907,9 @@ namespace AGVNavigationCore.Controls var newNode = new MapNode { Id = newNodeId, - Position = worldPoint + Position = worldPoint, + CanTurnLeft=false, + CanTurnRight= false, }; _nodes.Add(newNode); @@ -998,6 +1020,31 @@ namespace AGVNavigationCore.Controls Invalidate(); } + private void HandleConnectDirectionClick(MapNode hitNode) + { + if (hitNode == null) return; + + if (!_isConnectionMode) + { + // 연결 시작 (방향 설정) + _isConnectionMode = true; + _connectionStartNode = hitNode; + _selectedNode = hitNode; + } + else + { + // 연결 완료 + if (_connectionStartNode != null && _connectionStartNode != hitNode) + { + // 기본값 S (Straight)로 방향 설정 + SetMagnetDirection(_connectionStartNode, hitNode, MagnetPosition.S); + } + CancelConnection(); + } + + Invalidate(); + } + private void HandleDeleteClick(MapNode hitNode) { if (hitNode == null) return; @@ -1025,10 +1072,46 @@ namespace AGVNavigationCore.Controls toNode.ConnectedNodes.Contains(fromNode.Id)) return; + // 양방향 연결 생성 (AGV가 양쪽 방향으로 이동 가능하도록) // 양방향 연결 생성 (AGV가 양쪽 방향으로 이동 가능하도록) fromNode.AddConnection(toNode.Id); toNode.AddConnection(fromNode.Id); + // 🔥 화면 표시용 ConnectedMapNodes 리스트도 즉시 갱신해야 함 + if (!fromNode.ConnectedMapNodes.Contains(toNode)) + fromNode.ConnectedMapNodes.Add(toNode); + + if (!toNode.ConnectedMapNodes.Contains(fromNode)) + toNode.ConnectedMapNodes.Add(fromNode); + + ConnectionCreated?.Invoke(this, (fromNode, toNode)); + MapChanged?.Invoke(this, EventArgs.Empty); + } + + public void SetMagnetDirection(MapNode fromNode, MapNode toNode, MagnetPosition direction) + { + if (fromNode == null || toNode == null) return; + + // 이미 연결된 노드인지 확인 (연결되어 있어야 방향 설정 가능) + // 이미 연결된 노드인지 확인 (연결되어 있어야 방향 설정 가능) + if (!fromNode.ConnectedNodes.Contains(toNode.Id)) + { + // 연결되어 있지 않으면 자동 연결 + CreateConnection(fromNode, toNode); + } + + if (fromNode.MagnetDirections == null) + fromNode.MagnetDirections = new Dictionary(); + + if (fromNode.MagnetDirections.ContainsKey(toNode.Id)) + { + fromNode.MagnetDirections[toNode.Id] = direction; + } + else + { + fromNode.MagnetDirections.Add(toNode.Id, direction); + } + MapChanged?.Invoke(this, EventArgs.Empty); } diff --git a/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs b/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs index 4458ace..4ef8de4 100644 --- a/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs +++ b/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs @@ -20,7 +20,7 @@ namespace AGVNavigationCore.Controls { #region Constants - private const int NODE_SIZE = 24; + private const int NODE_SIZE = 18; private const int NODE_RADIUS = NODE_SIZE / 2; private const int GRID_SIZE = 20; private const float CONNECTION_WIDTH = 1.0f; @@ -56,6 +56,7 @@ namespace AGVNavigationCore.Controls DeleteConnection, // 연결 삭제 모드 AddLabel, // 라벨 추가 모드 AddImage, // 이미지 추가 모드 + ConnectDirection, // 방향 연결 모드 } #endregion @@ -139,7 +140,7 @@ namespace AGVNavigationCore.Controls string _alertmesage = ""; bool showalert = false; - + // 깜박임 효과를 위한 타이머 및 상태 private Timer _alertBlinkTimer; private bool _isAlertBlinkOn = true; @@ -176,7 +177,7 @@ namespace AGVNavigationCore.Controls //_isAlertBlinkOn = !_isAlertBlinkOn; //Invalidate(); } - + // 브러쉬 및 펜 private Brush _normalNodeBrush; @@ -220,6 +221,7 @@ namespace AGVNavigationCore.Controls public event EventHandler> NodesSelected; // 다중 선택 이벤트 public event EventHandler NodeDeleted; public event EventHandler NodeMoved; + public event EventHandler<(MapNode From, MapNode To)> ConnectionCreated; // 연결 생성 이벤트 추가 public event EventHandler<(MapNode From, MapNode To)> ConnectionDeleted; public event EventHandler ImageDoubleClicked; public event EventHandler LabelDoubleClicked; @@ -266,6 +268,18 @@ namespace AGVNavigationCore.Controls { if (_nodes != null && _nodes.Contains(node)) { + // 🔥 삭제되는 노드와 연결된 다른 노드들의 연결 정보도 삭제 + foreach (var otherNode in _nodes.ToList()) // ToList()로 복사본 순회 (안전장치) + { + if (otherNode == node) continue; + + // 다른 노드 -> 삭제되는 노드 연결 제거 + if (otherNode.ConnectedNodes.Contains(node.Id)) + { + otherNode.RemoveConnection(node.Id); + } + } + _nodes.Remove(node); Invalidate(); } @@ -336,10 +350,10 @@ namespace AGVNavigationCore.Controls { // Zoom 값 범위 제한 float newZoom = Math.Max(0.1f, Math.Min(5.0f, zoom)); - + _panOffset = new PointF(panX, panY); _zoomFactor = newZoom; - + Invalidate(); } @@ -408,12 +422,12 @@ namespace AGVNavigationCore.Controls /// /// 선택된 노드 (단일) /// - public MapNode SelectedNode + public NodeBase SelectedNode { - get { return this._selectedNode as MapNode; } - set - { - _selectedNode = value; + get { return this._selectedNode; } + set + { + _selectedNode = value; if (value != null) { _selectedNodes.Clear(); @@ -423,7 +437,7 @@ namespace AGVNavigationCore.Controls { _selectedNodes.Clear(); } - Invalidate(); + Invalidate(); } } @@ -746,8 +760,8 @@ namespace AGVNavigationCore.Controls _destinationNodePen = new Pen(Color.Orange, 4); _pathPen = new Pen(Color.Purple, 3); _agvPen = new Pen(Color.Red, 3); - _highlightedConnectionPen = new Pen(Color.Red, 4) { DashStyle = DashStyle.Solid }; - _magnetPen = new Pen(Color.FromArgb(100, Color.LightSkyBlue), 15) { DashStyle = DashStyle.Solid }; + _highlightedConnectionPen = new Pen(Color.Red, 6) { DashStyle = DashStyle.Solid }; + _magnetPen = new Pen(Color.FromArgb(100, Color.LightSkyBlue), 15) { DashStyle = DashStyle.Solid, StartCap = LineCap.Round, EndCap = LineCap.Round }; _markPen = new Pen(Color.White, 3); // 마크는 흰색 선으로 표시 } diff --git a/AGVLogic/AGVNavigationCore/Models/Enums.cs b/AGVLogic/AGVNavigationCore/Models/Enums.cs index ddad122..198f68e 100644 --- a/AGVLogic/AGVNavigationCore/Models/Enums.cs +++ b/AGVLogic/AGVNavigationCore/Models/Enums.cs @@ -67,15 +67,13 @@ namespace AGVNavigationCore.Models /// 로더 Loader, /// 클리너 - Clearner, + Plating, /// 오프로더 - UnLoader, + Cleaner, /// 버퍼 Buffer, /// 충전기1 - Charger1, - /// 충전기2 - Charger2, + Charger, /// /// 끝점(더이상 이동불가) diff --git a/AGVLogic/AGVNavigationCore/Models/MapMagnet.cs b/AGVLogic/AGVNavigationCore/Models/MapMagnet.cs index 5556b00..1e38900 100644 --- a/AGVLogic/AGVNavigationCore/Models/MapMagnet.cs +++ b/AGVLogic/AGVNavigationCore/Models/MapMagnet.cs @@ -76,5 +76,17 @@ namespace AGVNavigationCore.Models get => new Point((int)P2.X, (int)P2.Y); set { P2.X = value.X; P2.Y = value.Y; } } + + public override string ToString() + { + if (ControlPoint == null) + { + return $"[LINE] ({P1.X:F0},{P1.Y:F0}) -> ({P2.X:F0},{P2.Y:F0})"; + } + else + { + return $"[CURVE] ({P1.X:F0},{P1.Y:F0}) -> ({P2.X:F0},{P2.Y:F0})"; + } + } } } diff --git a/AGVLogic/AGVNavigationCore/Models/MapNode.cs b/AGVLogic/AGVNavigationCore/Models/MapNode.cs index 45b4d8a..b7d608d 100644 --- a/AGVLogic/AGVNavigationCore/Models/MapNode.cs +++ b/AGVLogic/AGVNavigationCore/Models/MapNode.cs @@ -15,9 +15,7 @@ namespace AGVNavigationCore.Models { - [Category("라벨 설정")] - [Description("표시할 텍스트입니다.")] - public string Text { get; set; } = ""; + public StationType StationType { get; set; } @@ -28,10 +26,9 @@ namespace AGVNavigationCore.Models { if (StationType == StationType.Buffer) return true; if (StationType == StationType.Loader) return true; - if (StationType == StationType.UnLoader) return true; - if (StationType == StationType.Clearner) return true; - if (StationType == StationType.Charger1) return true; - if (StationType == StationType.Charger2) return true; + if (StationType == StationType.Cleaner) return true; + if (StationType == StationType.Plating) return true; + if (StationType == StationType.Charger) return true; return false; } } @@ -56,11 +53,11 @@ namespace AGVNavigationCore.Models [Category("주행 설정")] [Description("제자리 회전(좌) 가능 여부입니다.")] - public bool CanTurnLeft { get; set; } = true; + public bool CanTurnLeft { get; set; } = false; [Category("주행 설정")] [Description("제자리 회전(우) 가능 여부입니다.")] - public bool CanTurnRight { get; set; } = true; + public bool CanTurnRight { get; set; } = false; [Category("주행 설정")] [Description("교차로 주행 가능 여부입니다.")] @@ -73,7 +70,7 @@ namespace AGVNavigationCore.Models } set { _disablecross = value; } } - private bool _disablecross = false; + private bool _disablecross = true; [Category("주행 설정")] [Description("노드 통과 시 제한 속도입니다.")] @@ -105,6 +102,10 @@ namespace AGVNavigationCore.Models set => _textFontSize = value > 0 ? value : 7.0f; } + [Category("노드 텍스트")] + [Description("표시할 텍스트입니다.")] + public string Text { get; set; } = ""; + public MapNode() : base() { Type = NodeType.Normal; @@ -121,9 +122,9 @@ namespace AGVNavigationCore.Models { get { - if (StationType == StationType.Charger1 || StationType == StationType.Charger2 || StationType == StationType.Buffer || - StationType == StationType.Clearner || StationType == StationType.Loader || - StationType == StationType.UnLoader) return true; + if (StationType == StationType.Charger || StationType == StationType.Buffer || + StationType == StationType.Plating || StationType == StationType.Loader || + StationType == StationType.Cleaner) return true; return false; } } @@ -141,6 +142,15 @@ namespace AGVNavigationCore.Models { if (ConnectedNodes.Remove(nodeId)) { + if (MagnetDirections != null && MagnetDirections.ContainsKey(nodeId)) + { + MagnetDirections.Remove(nodeId); + } + + // 🔥 ConnectedMapNodes에서도 제거 (화면 갱신용) + var target = ConnectedMapNodes.Find(n => n.Id == nodeId); + if (target != null) ConnectedMapNodes.Remove(target); + ModifiedDate = DateTime.Now; } } diff --git a/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs b/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs index dfe41d0..641a469 100644 --- a/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs +++ b/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs @@ -240,17 +240,17 @@ namespace AGVNavigationCore.PathFinding.Planning // 2. Buffer-to-Buffer 예외 처리 // 05~31 구간 체크 - var node05 = _mapNodes.FirstOrDefault(n => n.RfidId == 5); - var node31 = _mapNodes.FirstOrDefault(n => n.RfidId == 31); + var node_buff_sta = _mapNodes.Where(t => t.StationType == StationType.Buffer).OrderBy(s => s.RfidId).FirstOrDefault();// (n => n.RfidId == 5); + var node_buff_end = _mapNodes.Where(t => t.StationType == StationType.Buffer).OrderByDescending(s => s.RfidId).FirstOrDefault();// bool fixpath = false; Retval = null; MapNode gatewayNode = null; - if (node05 != null && node31 != null) + if (node_buff_sta != null && node_buff_end != null) { // 버퍼 구간 경로 테스트 - var rlt = this.FindPathAStar(node05, node31); + var rlt = this.FindPathAStar(node_buff_sta, node_buff_end); if (rlt.Success) { // 버퍼구간내에 시작과 종료가 모두 포함되어있다 @@ -265,53 +265,89 @@ namespace AGVNavigationCore.PathFinding.Planning if (!fixpath) { - // 3. 목적지별 Gateway 및 진입 조건 확인 - gatewayNode = GetGatewayNode(targetNode); + - if (gatewayNode == null) + if (targetNode.StationType == StationType.Limit || targetNode.StationType == StationType.Normal) { - // 게이트웨이가 없는 경우라면(일반 노드 등), 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}"); + + //목적지까지 그대로 방향을 계산한다. + var pathToTaget = this.FindBasicPath(startNode, targetNode, prevNode, prevDir); + if (pathToTaget.Success == false) + return AGVPathResult.CreateFailure($"Target({targetNode.ID2})까지 경로 실패: {pathToTaget.Message}"); // 방향을 확인하여, 왔던 방향으로 되돌아가야 한다면 방향 반전 - if (pathToGateway.Path.Count > 1) + //다음이동방향이 이전노드와 동일하다면? 되돌아가야한다는것이다 + var predictNext = pathToTaget.Path[1]; + if (predictNext.Id == prevNode.Id) { - var predictNext = pathToGateway.Path[1]; - if (predictNext.Id == prevNode.Id) + var reverseDir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward; + foreach (var item in pathToTaget.DetailedPath) + item.MotorDirection = reverseDir; + } + + if (targetNode.DockDirection != DockingDirection.DontCare) + { + Retval = pathToTaget; + } + //현재 진행방향과 목적지의 도킹방향이 일치하는지 확인한다. + else if ((pathToTaget.DetailedPath.Last().MotorDirection == AgvDirection.Backward && targetNode.DockDirection == DockingDirection.Backward) || + (pathToTaget.DetailedPath.Last().MotorDirection == AgvDirection.Forward && targetNode.DockDirection == DockingDirection.Forward)) + { + //일치하는 경우 그대로 사용하낟. + Retval = pathToTaget; + } + else + { + //불일치하는경우라면 Turn 가능노드를 찾아서 이동한 후 턴을 한다. + + // 3. 목적지별 Turn 및 진입 조건 확인 + gatewayNode = GetTurnNode(targetNode); + + + // 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 reverseDir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward; - foreach (var item in pathToGateway.DetailedPath) - item.MotorDirection = reverseDir; + //다음이동방향이 이전노드와 동일하다면? 되돌아가야한다는것이다 + 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); } - - // 마지막 경로는 게이트웨이이므로 제거 (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); } } @@ -457,13 +493,13 @@ namespace AGVNavigationCore.PathFinding.Planning 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) + if (firstnode.StationType == StationType.Charger && 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) + else if (firstnode.StationType == StationType.Cleaner && firstDet.MotorDirection != AgvDirection.Backward) return AGVPathResult.CreateFailure(failmessage); - else if (firstnode.StationType == StationType.Clearner && firstDet.MotorDirection != AgvDirection.Backward) + else if (firstnode.StationType == StationType.Plating && firstDet.MotorDirection != AgvDirection.Backward) return AGVPathResult.CreateFailure(failmessage); else if (firstnode.StationType == StationType.Buffer) { @@ -621,7 +657,7 @@ namespace AGVNavigationCore.PathFinding.Planning if (deltaX > 0) isMonitorLeft = PrevDirection == AgvDirection.Backward; else isMonitorLeft = PrevDirection == AgvDirection.Forward; - if (targetNode.StationType == StationType.Loader || targetNode.StationType == StationType.Charger2) + if (targetNode.StationType == StationType.Loader) { deltaX = GTNode.Position.Y - PrevNode.Position.Y; if (deltaX < 0) isMonitorLeft = PrevDirection == AgvDirection.Backward; @@ -631,14 +667,14 @@ namespace AGVNavigationCore.PathFinding.Planning switch (targetNode.StationType) { case StationType.Loader: - case StationType.Charger2: - case StationType.Charger1: - case StationType.UnLoader: - case StationType.Clearner: + case StationType.Charger: + case StationType.Cleaner: + case StationType.Plating: 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); @@ -682,15 +718,14 @@ namespace AGVNavigationCore.PathFinding.Planning } } - private MapNode GetGatewayNode(MapNode node) + private MapNode GetTurnNode(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 (node.StationType == StationType.Cleaner) rfid = 3; + else if (node.StationType == StationType.Charger) rfid = 3; + else if (node.StationType == StationType.Plating) rfid = 3; + else if (node.StationType == StationType.Loader) rfid = 3; + else if (node.StationType == StationType.Buffer) rfid = 3; if (rfid == 0) return null; return _mapNodes.FirstOrDefault(t => t.RfidId == rfid); diff --git a/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs b/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs index ea25bee..20aa68f 100644 --- a/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs +++ b/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs @@ -1596,7 +1596,7 @@ namespace AGVSimulator.Forms MotorDirection = directionName, CurrentPosition = GetNodeDisplayName(currentNode), TargetPosition = GetNodeDisplayName(targetNode), - DockingPosition = (targetNode.StationType == StationType.Charger1 || targetNode.StationType == StationType.Charger2) ? "충전기" : "장비" + DockingPosition = (targetNode.StationType == StationType.Charger ) ? "충전기" : "장비" }; if (calcResult.Success) diff --git a/HMI/Project/StateMachine/Step/_SM_RUN.cs b/HMI/Project/StateMachine/Step/_SM_RUN.cs index 3741a0d..ee20309 100644 --- a/HMI/Project/StateMachine/Step/_SM_RUN.cs +++ b/HMI/Project/StateMachine/Step/_SM_RUN.cs @@ -136,8 +136,7 @@ namespace Project PUB.sm.SetNewRunStep(ERunStep.ERROR); } break; - case AGVNavigationCore.Models.StationType.Charger1: - case AGVNavigationCore.Models.StationType.Charger2: + case AGVNavigationCore.Models.StationType.Charger: break; @@ -145,11 +144,11 @@ namespace Project PUB.XBE.StepMC = Device.eDocStep.ReadyForEnter; break; - case AGVNavigationCore.Models.StationType.Clearner: + case AGVNavigationCore.Models.StationType.Plating: PUB.XBE.StepMC = Device.eDocStep.ReadyForEnter; break; - case AGVNavigationCore.Models.StationType.UnLoader: + case AGVNavigationCore.Models.StationType.Cleaner: PUB.XBE.StepMC = Device.eDocStep.ReadyForEnter; break; diff --git a/HMI/Project/StateMachine/Step/_SM_RUN_ENTER.cs b/HMI/Project/StateMachine/Step/_SM_RUN_ENTER.cs index e585682..53f3771 100644 --- a/HMI/Project/StateMachine/Step/_SM_RUN_ENTER.cs +++ b/HMI/Project/StateMachine/Step/_SM_RUN_ENTER.cs @@ -39,8 +39,8 @@ namespace Project { //장비 노드 여부 확인 (버퍼가 아닌 도킹 가능 노드여야 함) if (PUB._virtualAGV.CurrentNode.StationType != StationType.Loader && - PUB._virtualAGV.CurrentNode.StationType != StationType.Clearner && - PUB._virtualAGV.CurrentNode.StationType != StationType.UnLoader) + PUB._virtualAGV.CurrentNode.StationType != StationType.Plating && + PUB._virtualAGV.CurrentNode.StationType != StationType.Cleaner) { SetRunStepError(ENIGProtocol.AGVErrorCode.NOT_EQUIPMENTPOINT, $"[{funcname}-{PUB.sm.RunStepSeq}] 현재 위치가 장비 노드가 아닙니다({PUB._virtualAGV.CurrentNode.StationType})"); return false; diff --git a/HMI/Project/StateMachine/Step/_SM_RUN_EXIT.cs b/HMI/Project/StateMachine/Step/_SM_RUN_EXIT.cs index 83a173b..26217d4 100644 --- a/HMI/Project/StateMachine/Step/_SM_RUN_EXIT.cs +++ b/HMI/Project/StateMachine/Step/_SM_RUN_EXIT.cs @@ -40,8 +40,8 @@ namespace Project { //장비 노드 여부 확인 (버퍼가 아닌 도킹 가능 노드여야 함) if (PUB._virtualAGV.CurrentNode.StationType != StationType.Loader && - PUB._virtualAGV.CurrentNode.StationType != StationType.Clearner && - PUB._virtualAGV.CurrentNode.StationType != StationType.UnLoader) + PUB._virtualAGV.CurrentNode.StationType != StationType.Plating && + PUB._virtualAGV.CurrentNode.StationType != StationType.Cleaner) { SetRunStepError(ENIGProtocol.AGVErrorCode.NOT_EQUIPMENTPOINT, $"[{funcname}-{PUB.sm.RunStepSeq}] 현재 위치가 장비 노드가 아닙니다({PUB._virtualAGV.CurrentNode.StationType})"); return false; diff --git a/HMI/Project/StateMachine/_Xbee.cs b/HMI/Project/StateMachine/_Xbee.cs index 2e5b050..8b2fbc1 100644 --- a/HMI/Project/StateMachine/_Xbee.cs +++ b/HMI/Project/StateMachine/_Xbee.cs @@ -114,9 +114,9 @@ namespace Project switch (StationType) { case StationType.Loader: nextStep = ERunStep.LOADER_IN; break; - case StationType.UnLoader: nextStep = ERunStep.UNLOADER_IN; break; + case StationType.Cleaner: nextStep = ERunStep.UNLOADER_IN; break; case StationType.Buffer: nextStep = ERunStep.BUFFER_IN; break; - case StationType.Clearner: nextStep = ERunStep.CLEANER_IN; break; + case StationType.Plating: nextStep = ERunStep.CLEANER_IN; break; default: PUB.log.AddE($"[{logPrefix}-{cmd}] 해당 노드타입({StationType})은 작업을 지원하지 않습니다."); return; @@ -160,9 +160,9 @@ namespace Project switch (StationType) { case StationType.Loader: nextStep = ERunStep.LOADER_OUT; break; - case StationType.UnLoader: nextStep = ERunStep.UNLOADER_OUT; break; + case StationType.Cleaner: nextStep = ERunStep.UNLOADER_OUT; break; case StationType.Buffer: nextStep = ERunStep.BUFFER_OUT; break; - case StationType.Clearner: nextStep = ERunStep.CLEANER_OUT; break; + case StationType.Plating: nextStep = ERunStep.CLEANER_OUT; break; default: PUB.log.AddE($"[{logPrefix}-{cmd}] 해당 노드타입({StationType})은 작업을 지원하지 않습니다."); return; diff --git a/HMI/Project/ViewForm/fAuto.cs b/HMI/Project/ViewForm/fAuto.cs index a4e6090..cd9e7c0 100644 --- a/HMI/Project/ViewForm/fAuto.cs +++ b/HMI/Project/ViewForm/fAuto.cs @@ -34,7 +34,7 @@ namespace Project.ViewForm private void InitializeMapCanvas() { - PUB._mapCanvas.NodeSelect += OnNodeSelected;; + PUB._mapCanvas.NodeSelect += OnNodeSelected; ; // 스플리터 패널에 맵 캔버스 추가 panel1.Controls.Add(PUB._mapCanvas); } @@ -49,8 +49,8 @@ namespace Project.ViewForm // [Run Mode] Left Click: AGV Operation if (PUB._mapCanvas.Mode == AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run && e.Button == MouseButtons.Left) { - HandleRunModeClick(mapnode); - return; + HandleRunModeClick(mapnode); + return; } if (e.Button != MouseButtons.Right) return; @@ -71,7 +71,7 @@ namespace Project.ViewForm menu.Items.Add(pickOff); // Charge - if (mapnode.StationType == StationType.Charger1 || mapnode.StationType == StationType.Charger2) + if (mapnode.StationType == StationType.Charger ) { var charge = new ToolStripMenuItem("Charge (Move & Charge)"); charge.Click += (s, args) => ExecuteManualCommand(mapnode, ENIGProtocol.AGVCommandHE.Charger); @@ -122,7 +122,7 @@ namespace Project.ViewForm PUB.log.AddI($"[Manual Command] {cmd} to ({targetNode.Id})"); // FindPathResult contains DetailedPath already. - // PUB._virtualAGV.SetPath(result); + // PUB._virtualAGV.SetPath(result); PUB._virtualAGV.TargetNode = targetNode as MapNode; // 3. 작업 설정 @@ -141,7 +141,7 @@ namespace Project.ViewForm PUB.AGV.DataReceive += AGV_DataReceive; - + } private void AGV_DataReceive(object sender, arDev.Narumi.DataEventArgs e) { @@ -185,7 +185,7 @@ namespace Project.ViewForm private void fAuto_VisibleChanged(object sender, EventArgs e) { this.timer1.Enabled = this.Visible; - if (timer1.Enabled) + if (timer1.Enabled) { timer1.Start(); } @@ -194,7 +194,7 @@ namespace Project.ViewForm private void timer1_Tick_1(object sender, EventArgs e) { - + } private void HandleRunModeClick(MapNode targetNode) @@ -204,7 +204,7 @@ namespace Project.ViewForm ENIGProtocol.AGVCommandHE targetCmd = ENIGProtocol.AGVCommandHE.Goto; string confirmMsg = ""; - if (targetNode.StationType == StationType.Charger1 || targetNode.StationType == StationType.Charger2) + if (targetNode.StationType == StationType.Charger) { if (MessageBox.Show($"[{targetNode.Id}] 충전기로 이동하여 충전을 진행하시겠습니까?", "작업 확인", MessageBoxButtons.YesNo) == DialogResult.Yes) { @@ -217,7 +217,7 @@ namespace Project.ViewForm { // Loader, Unloader, Buffer, Cleaner - Pick/Drop Selection ContextMenuStrip menu = new ContextMenuStrip(); - + var pickOn = new ToolStripMenuItem("Pick On (Move & Pick)"); pickOn.Click += (s, args) => ExecuteManualCommand(targetNode, ENIGProtocol.AGVCommandHE.PickOnEnter); menu.Items.Add(pickOn); @@ -249,14 +249,14 @@ namespace Project.ViewForm if (PUB.sm.Step < eSMStep.IDLE) return; if (PUB._mapCanvas == null) return; tmrun = true; - + var errmsg = string.Empty; - if(PUB.AGV.IsOpen==false) + if (PUB.AGV.IsOpen == false) { errmsg = "AGV컨트롤러 연결실패"; } - else if(PUB.XBE.IsOpen==false ) + else if (PUB.XBE.IsOpen == false) { errmsg = "XBEE 연결실패"; } @@ -264,11 +264,11 @@ namespace Project.ViewForm { errmsg = "XBEE 통신불가"; } - else if(PUB.AGV.error.Value > 0) + else if (PUB.AGV.error.Value > 0) { errmsg = PUB.AGV.error.ToString(); } - else if(PUB.AGV.system1.stop_by_front_detect) + else if (PUB.AGV.system1.stop_by_front_detect) { errmsg = "전방 물체감지로 인해 정지"; } @@ -276,11 +276,11 @@ namespace Project.ViewForm { errmsg = "BMS 연결실패"; } - else if(VAR.BOOL[eVarBool.FLAG_AUTORUN]==false) + else if (VAR.BOOL[eVarBool.FLAG_AUTORUN] == false) { errmsg = "자동모드가 아닙니다"; } - + PUB._mapCanvas.SetAlertMessage(errmsg); diff --git a/NewMap.json b/NewMap.json index 4c49a02..823fd22 100644 --- a/NewMap.json +++ b/NewMap.json @@ -21,13 +21,15 @@ "ID2": "0070(*N001)", "Id": "N001", "Type": 0, - "Position": "421, 271" + "Position": "448, 249" }, { "StationType": 2, "CanDocking": true, "DockDirection": 2, - "MagnetDirections": {}, + "MagnetDirections": { + "5": 0 + }, "ConnectedNodes": [ "5" ], @@ -44,7 +46,7 @@ "ID2": "0072(*N010)", "Id": "N010", "Type": 0, - "Position": "341, 487" + "Position": "292, 484" }, { "StationType": 4, @@ -68,7 +70,7 @@ "ID2": "0034(*N018)", "Id": "N018", "Type": 0, - "Position": "283, 623" + "Position": "319, 620" }, { "StationType": 4, @@ -92,7 +94,7 @@ "ID2": "0033(*N005)", "Id": "N005", "Type": 0, - "Position": "223, 625" + "Position": "278, 620" }, { "StationType": 4, @@ -116,7 +118,7 @@ "ID2": "0032(*N020)", "Id": "N020", "Type": 0, - "Position": "164, 626" + "Position": "235, 621" }, { "StationType": 4, @@ -140,7 +142,7 @@ "ID2": "0031(*N021)", "Id": "N021", "Type": 0, - "Position": "108, 626" + "Position": "194, 620" }, { "StationType": 0, @@ -164,7 +166,7 @@ "ID2": "0009(*1)", "Id": "1", "Type": 0, - "Position": "338, 625" + "Position": "363, 620" }, { "StationType": 0, @@ -188,7 +190,7 @@ "ID2": "0008(*2)", "Id": "2", "Type": 0, - "Position": "395, 622" + "Position": "404, 620" }, { "StationType": 0, @@ -196,12 +198,12 @@ "DockDirection": 0, "MagnetDirections": { "N033": 1, - "N034": 2 + "N035": 2 }, "ConnectedNodes": [ "2", "N033", - "N034" + "N035" ], "CanTurnLeft": false, "CanTurnRight": false, @@ -216,18 +218,19 @@ "ID2": "0007(*4)", "Id": "4", "Type": 0, - "Position": "444, 602" + "Position": "443, 620" }, { "StationType": 0, "CanDocking": false, "DockDirection": 0, "MagnetDirections": { - "N031": 1 + "N034": 1, + "N010": 0 }, "ConnectedNodes": [ "N010", - "N031" + "N034" ], "CanTurnLeft": false, "CanTurnRight": false, @@ -242,7 +245,7 @@ "ID2": "0005(*5)", "Id": "5", "Type": 0, - "Position": "449, 485" + "Position": "441, 485" }, { "StationType": 1, @@ -265,17 +268,18 @@ "ID2": "0071(*6)", "Id": "6", "Type": 0, - "Position": "523, 585" + "Position": "571, 507" }, { "StationType": 0, "CanDocking": false, "DockDirection": 0, - "MagnetDirections": {}, + "MagnetDirections": { + "N031": 0 + }, "ConnectedNodes": [ "6", - "N031", - "N034" + "N031" ], "CanTurnLeft": false, "CanTurnRight": false, @@ -290,7 +294,7 @@ "ID2": "0004(*7)", "Id": "7", "Type": 0, - "Position": "524, 530" + "Position": "571, 448" }, { "StationType": 5, @@ -313,7 +317,7 @@ "ID2": "0073(*8)", "Id": "8", "Type": 0, - "Position": "367, 389" + "Position": "327, 381" }, { "StationType": 0, @@ -337,7 +341,7 @@ "ID2": "0002(*9)", "Id": "9", "Type": 0, - "Position": "515, 312" + "Position": "565, 297" }, { "StationType": 0, @@ -361,10 +365,10 @@ "ID2": "0001(*10)", "Id": "10", "Type": 0, - "Position": "479, 270" + "Position": "514, 247" }, { - "StationType": 7, + "StationType": 6, "CanDocking": false, "DockDirection": 0, "MagnetDirections": {}, @@ -384,7 +388,7 @@ "ID2": "0091(*N027)", "Id": "N027", "Type": 0, - "Position": "-59, 627" + "Position": "70, 620" }, { "StationType": 4, @@ -408,7 +412,7 @@ "ID2": "0035(*N028)", "Id": "N028", "Type": 0, - "Position": "47, 627" + "Position": "153, 620" }, { "StationType": 4, @@ -432,20 +436,20 @@ "ID2": "0036(*N029)", "Id": "N029", "Type": 0, - "Position": "-7, 629" + "Position": "110, 619" }, { "StationType": 0, "CanDocking": false, "DockDirection": 0, "MagnetDirections": { - "5": 2, - "7": 0 + "7": 0, + "N034": 2 }, "ConnectedNodes": [ - "7", "9", - "5" + "7", + "N034" ], "CanTurnLeft": true, "CanTurnRight": true, @@ -460,13 +464,15 @@ "ID2": "0003(*N031)", "Id": "N031", "Type": 0, - "Position": "517, 358" + "Position": "568, 354" }, { "StationType": 0, "CanDocking": false, "DockDirection": 0, - "MagnetDirections": {}, + "MagnetDirections": { + "N033": 0 + }, "ConnectedNodes": [ "8", "N033" @@ -484,13 +490,16 @@ "ID2": "0006(*N032)", "Id": "N032", "Type": 0, - "Position": "405, 452" + "Position": "368, 445" }, { "StationType": 0, "CanDocking": false, "DockDirection": 0, - "MagnetDirections": {}, + "MagnetDirections": { + "4": 0, + "N032": 0 + }, "ConnectedNodes": [ "N032", "4" @@ -508,19 +517,21 @@ "ID2": "0010(*N033)", "Id": "N033", "Type": 0, - "Position": "426, 522" + "Position": "413, 525" }, { "StationType": 0, "CanDocking": false, "DockDirection": 0, "MagnetDirections": { - "4": 2, - "7": 0 + "N035": 0, + "5": 2, + "N031": 1 }, "ConnectedNodes": [ - "4", - "7" + "N031", + "N035", + "5" ], "CanTurnLeft": false, "CanTurnRight": false, @@ -535,7 +546,34 @@ "ID2": "0011(*N034)", "Id": "N034", "Type": 0, - "Position": "521, 461" + "Position": "524, 437" + }, + { + "StationType": 0, + "CanDocking": false, + "DockDirection": 0, + "MagnetDirections": { + "4": 0, + "N034": 0 + }, + "ConnectedNodes": [ + "N034", + "4" + ], + "CanTurnLeft": false, + "CanTurnRight": false, + "DisableCross": true, + "SpeedLimit": 1, + "AliasName": "", + "IsActive": true, + "RfidId": 12, + "NodeTextForeColor": "Black", + "NodeTextFontSize": 7.0, + "Text": "", + "ID2": "0012(*N035)", + "Id": "N035", + "Type": 0, + "Position": "467, 524" } ], "Labels": [], @@ -689,12 +727,12 @@ "Magnets": [ { "P1": { - "X": 440.0, + "X": 439.0, "Y": 623.0 }, "P2": { - "X": -56.0, - "Y": 627.0 + "X": 69.0, + "Y": 621.0 }, "ControlPoint": null, "Id": "5a0edec2-7ac3-4c99-bbb4-8debde0c1d07", @@ -702,67 +740,67 @@ }, { "P1": { - "X": 525.0, - "Y": 616.0 + "X": 569.0, + "Y": 509.0 }, "P2": { - "X": 516.16556848250036, - "Y": 309.44573330767145 + "X": 565.16556848250036, + "Y": 295.44573330767145 }, "ControlPoint": null, "Id": "a5424add-e8c9-483c-a5db-733dca1b8f57", "Type": 4 }, + { + "P1": { + "X": 528.0, + "Y": 487.0 + }, + "P2": { + "X": 275.0, + "Y": 484.0 + }, + "ControlPoint": null, + "Id": "0bbb27a4-2355-4294-9f2d-a40e4d3d2930", + "Type": 4 + }, + { + "P1": { + "X": 440.0, + "Y": 249.0 + }, + "P2": { + "X": 513.0, + "Y": 248.0 + }, + "ControlPoint": null, + "Id": "92527d4a-8e63-404f-86e8-37568bd4790e", + "Type": 4 + }, + { + "P1": { + "X": 512.0, + "Y": 248.0 + }, + "P2": { + "X": 566.0, + "Y": 302.0 + }, + "ControlPoint": { + "X": 566.0, + "Y": 241.0 + }, + "Id": "0db9553a-f203-478d-8c17-e07f00987828", + "Type": 4 + }, { "P1": { "X": 481.0, "Y": 486.0 }, "P2": { - "X": 315.0, - "Y": 487.0 - }, - "ControlPoint": null, - "Id": "0bbb27a4-2355-4294-9f2d-a40e4d3d2930", - "Type": 4 - }, - { - "P1": { - "X": 377.0, - "Y": 270.0 - }, - "P2": { - "X": 471.66556848250042, - "Y": 269.44573330767145 - }, - "ControlPoint": null, - "Id": "92527d4a-8e63-404f-86e8-37568bd4790e", - "Type": 4 - }, - { - "P1": { - "X": 463.0, - "Y": 270.0 - }, - "P2": { - "X": 514.66666666666674, - "Y": 309.99999999999994 - }, - "ControlPoint": { - "X": 517.44444444444434, - "Y": 266.11111111111109 - }, - "Id": "0db9553a-f203-478d-8c17-e07f00987828", - "Type": 4 - }, - { - "P1": { - "X": 469.0, - "Y": 489.0 - }, - "P2": { - "X": 524.0, - "Y": 404.0 + "X": 565.0, + "Y": 388.0 }, "ControlPoint": null, "Id": "N032", @@ -771,11 +809,11 @@ { "P1": { "X": 443.0, - "Y": 631.0 + "Y": 622.0 }, "P2": { - "X": 443.0, - "Y": 568.0 + "X": 442.0, + "Y": 579.0 }, "ControlPoint": null, "Id": "N031", @@ -784,11 +822,11 @@ { "P1": { "X": 442.0, - "Y": 574.0 + "Y": 579.0 }, "P2": { - "X": 397.0, - "Y": 432.0 + "X": 368.0, + "Y": 446.0 }, "ControlPoint": null, "Id": "N030", @@ -796,12 +834,12 @@ }, { "P1": { - "X": 360.0, - "Y": 408.0 + "X": 327.0, + "Y": 413.0 }, "P2": { - "X": 402.0, - "Y": 439.0 + "X": 369.0, + "Y": 444.0 }, "ControlPoint": null, "Id": "N032", @@ -809,12 +847,12 @@ }, { "P1": { - "X": 366.0, - "Y": 333.0 + "X": 327.0, + "Y": 329.0 }, "P2": { - "X": 366.0, - "Y": 415.0 + "X": 327.0, + "Y": 411.0 }, "ControlPoint": null, "Id": "N033", @@ -822,12 +860,12 @@ }, { "P1": { - "X": 528.0, - "Y": 483.0 + "X": 484.0, + "Y": 481.0 }, "P2": { - "X": 446.0, - "Y": 591.0 + "X": 443.0, + "Y": 578.0 }, "ControlPoint": null, "Id": "N035", @@ -838,6 +876,6 @@ "BackgroundColorArgb": -14671840, "ShowGrid": false }, - "CreatedDate": "2026-02-11T13:07:50.7796603+09:00", + "CreatedDate": "2026-02-12T09:53:53.0138602+09:00", "Version": "1.3" } \ No newline at end of file