This commit is contained in:
backuppc
2026-02-12 09:58:01 +09:00
parent 2b3a9b3d1d
commit d6aed58516
17 changed files with 914 additions and 402 deletions

View File

@@ -31,6 +31,7 @@ namespace AGVMapEditor.Forms
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
this.statusStrip1 = new System.Windows.Forms.StatusStrip(); this.statusStrip1 = new System.Windows.Forms.StatusStrip();
this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel();
this.sbFile = new System.Windows.Forms.ToolStripStatusLabel();
this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.splitContainer1 = new System.Windows.Forms.SplitContainer();
this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabControl1 = new System.Windows.Forms.TabControl();
this.tabPageNodes = new System.Windows.Forms.TabPage(); this.tabPageNodes = new System.Windows.Forms.TabPage();
@@ -50,6 +51,10 @@ namespace AGVMapEditor.Forms
this.btDirDelete = new System.Windows.Forms.ToolStripButton(); this.btDirDelete = new System.Windows.Forms.ToolStripButton();
this.btMakeDirdata = new System.Windows.Forms.ToolStripButton(); this.btMakeDirdata = new System.Windows.Forms.ToolStripButton();
this.toolStripButton2 = 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._propertyGrid = new System.Windows.Forms.PropertyGrid();
this.panel1 = new System.Windows.Forms.Panel(); this.panel1 = new System.Windows.Forms.Panel();
this.toolStrip3 = new System.Windows.Forms.ToolStrip(); this.toolStrip3 = new System.Windows.Forms.ToolStrip();
@@ -61,11 +66,12 @@ namespace AGVMapEditor.Forms
this.btnDelete = new System.Windows.Forms.ToolStripButton(); this.btnDelete = new System.Windows.Forms.ToolStripButton();
this.btnEditImage = new System.Windows.Forms.ToolStripButton(); this.btnEditImage = new System.Windows.Forms.ToolStripButton();
this.separator1 = new System.Windows.Forms.ToolStripSeparator(); this.separator1 = new System.Windows.Forms.ToolStripSeparator();
this.btnConnect = new System.Windows.Forms.ToolStripButton(); this.btnConnNode = new System.Windows.Forms.ToolStripButton();
this.btnDeleteConnection = new System.Windows.Forms.ToolStripButton(); this.btnConnDir = new System.Windows.Forms.ToolStripButton();
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
this.btnToggleGrid = new System.Windows.Forms.ToolStripButton(); this.btnToggleGrid = new System.Windows.Forms.ToolStripButton();
this.btnFitMap = 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.toolStrip2 = new System.Windows.Forms.ToolStrip();
this.btnNew = new System.Windows.Forms.ToolStripButton(); this.btnNew = new System.Windows.Forms.ToolStripButton();
this.btnOpen = 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.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
this.toolStripButton1 = new System.Windows.Forms.ToolStripDropDownButton(); this.toolStripButton1 = new System.Windows.Forms.ToolStripDropDownButton();
this.allTurnLeftRightCrossOnToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.allTurnLeftRightCrossOnToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.btAddMagnet = new System.Windows.Forms.ToolStripButton(); this.toolStripButton3 = new System.Windows.Forms.ToolStripButton();
this.statusStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
this.splitContainer1.Panel1.SuspendLayout(); this.splitContainer1.Panel1.SuspendLayout();
@@ -90,6 +96,8 @@ namespace AGVMapEditor.Forms
this.tabPage2.SuspendLayout(); this.tabPage2.SuspendLayout();
this.tableLayoutPanel1.SuspendLayout(); this.tableLayoutPanel1.SuspendLayout();
this.toolStrip4.SuspendLayout(); this.toolStrip4.SuspendLayout();
this.tabPage3.SuspendLayout();
this.toolStrip5.SuspendLayout();
this.toolStrip3.SuspendLayout(); this.toolStrip3.SuspendLayout();
this.toolStrip2.SuspendLayout(); this.toolStrip2.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
@@ -97,7 +105,8 @@ namespace AGVMapEditor.Forms
// statusStrip1 // statusStrip1
// //
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 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.Location = new System.Drawing.Point(0, 751);
this.statusStrip1.Name = "statusStrip1"; this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Size = new System.Drawing.Size(1200, 22); 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.Size = new System.Drawing.Size(39, 17);
this.toolStripStatusLabel1.Text = "Ready"; this.toolStripStatusLabel1.Text = "Ready";
// //
// sbFile
//
this.sbFile.Name = "sbFile";
this.sbFile.Size = new System.Drawing.Size(17, 17);
this.sbFile.Text = "--";
//
// splitContainer1 // splitContainer1
// //
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; 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.tabPageNodes);
this.tabControl1.Controls.Add(this.tabPage1); this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2); this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Controls.Add(this.tabPage3);
this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tabControl1.Location = new System.Drawing.Point(0, 0); this.tabControl1.Location = new System.Drawing.Point(0, 0);
this.tabControl1.Name = "tabControl1"; this.tabControl1.Name = "tabControl1";
@@ -330,6 +346,47 @@ namespace AGVMapEditor.Forms
this.toolStripButton2.Text = "Clear"; this.toolStripButton2.Text = "Clear";
this.toolStripButton2.Click += new System.EventHandler(this.toolStripButton2_Click); 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 // _propertyGrid
// //
this._propertyGrid.Dock = System.Windows.Forms.DockStyle.Bottom; this._propertyGrid.Dock = System.Windows.Forms.DockStyle.Bottom;
@@ -355,8 +412,8 @@ namespace AGVMapEditor.Forms
this.btnDelete, this.btnDelete,
this.btnEditImage, this.btnEditImage,
this.separator1, this.separator1,
this.btnConnect, this.btnConnNode,
this.btnDeleteConnection, this.btnConnDir,
this.toolStripSeparator1, this.toolStripSeparator1,
this.btnToggleGrid, this.btnToggleGrid,
this.btnFitMap, this.btnFitMap,
@@ -394,6 +451,8 @@ namespace AGVMapEditor.Forms
this.btnAddNode.Size = new System.Drawing.Size(111, 22); this.btnAddNode.Size = new System.Drawing.Size(111, 22);
this.btnAddNode.Text = "노드 추가 (A)"; this.btnAddNode.Text = "노드 추가 (A)";
this.btnAddNode.ToolTipText = "노드 추가 (A)"; this.btnAddNode.ToolTipText = "노드 추가 (A)";
this.btnAddNode.ButtonClick += new System.EventHandler(this.btnAddNode_ButtonClick);
this.btnAddNode.BackColorChanged += new System.EventHandler(this.btnAddNode_BackColorChanged);
// //
// btnAddLabel // btnAddLabel
// //
@@ -438,21 +497,20 @@ namespace AGVMapEditor.Forms
this.separator1.Name = "separator1"; this.separator1.Name = "separator1";
this.separator1.Size = new System.Drawing.Size(6, 25); this.separator1.Size = new System.Drawing.Size(6, 25);
// //
// btnConnect // btnConnNode
// //
this.btnConnect.Image = ((System.Drawing.Image)(resources.GetObject("btnConnect.Image"))); this.btnConnNode.Image = ((System.Drawing.Image)(resources.GetObject("btnConnNode.Image")));
this.btnConnect.Name = "btnConnect"; this.btnConnNode.Name = "btnConnNode";
this.btnConnect.Size = new System.Drawing.Size(95, 22); this.btnConnNode.Size = new System.Drawing.Size(95, 22);
this.btnConnect.Text = "노드연결 (C)"; this.btnConnNode.Text = "노드연결 (C)";
// //
// btnDeleteConnection // btnConnDir
// //
this.btnDeleteConnection.Image = ((System.Drawing.Image)(resources.GetObject("btnDeleteConnection.Image"))); this.btnConnDir.Image = ((System.Drawing.Image)(resources.GetObject("btnConnDir.Image")));
this.btnDeleteConnection.ImageTransparentColor = System.Drawing.Color.Magenta; this.btnConnDir.Name = "btnConnDir";
this.btnDeleteConnection.Name = "btnDeleteConnection"; this.btnConnDir.Size = new System.Drawing.Size(95, 22);
this.btnDeleteConnection.Size = new System.Drawing.Size(94, 22); this.btnConnDir.Text = "방향연결 (C)";
this.btnDeleteConnection.Text = "연결삭제 (X)"; this.btnConnDir.Click += new System.EventHandler(this.btnConnDir_Click);
this.btnDeleteConnection.ToolTipText = "연결 삭제 (X)";
// //
// toolStripSeparator1 // toolStripSeparator1
// //
@@ -475,6 +533,15 @@ namespace AGVMapEditor.Forms
this.btnFitMap.Text = "맵 맞춤"; this.btnFitMap.Text = "맵 맞춤";
this.btnFitMap.ToolTipText = "맵 전체 보기"; 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 // toolStrip2
// //
this.toolStrip2.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 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.Text = "All TurnLeft/Right/Cross On";
this.allTurnLeftRightCrossOnToolStripMenuItem.Click += new System.EventHandler(this.allTurnLeftRightCrossOnToolStripMenuItem_Click); this.allTurnLeftRightCrossOnToolStripMenuItem.Click += new System.EventHandler(this.allTurnLeftRightCrossOnToolStripMenuItem_Click);
// //
// btAddMagnet // toolStripButton3
// //
this.btAddMagnet.Image = ((System.Drawing.Image)(resources.GetObject("btAddMagnet.Image"))); this.toolStripButton3.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
this.btAddMagnet.ImageTransparentColor = System.Drawing.Color.Magenta; this.toolStripButton3.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton3.Image")));
this.btAddMagnet.Name = "btAddMagnet"; this.toolStripButton3.ImageTransparentColor = System.Drawing.Color.Magenta;
this.btAddMagnet.Size = new System.Drawing.Size(87, 22); this.toolStripButton3.Name = "toolStripButton3";
this.btAddMagnet.Text = "마그넷추가"; this.toolStripButton3.Size = new System.Drawing.Size(66, 22);
this.btAddMagnet.Click += new System.EventHandler(this.btAddMagnet_Click); this.toolStripButton3.Text = "Refresh";
this.toolStripButton3.Click += new System.EventHandler(this.toolStripButton3_Click_1);
// //
// MainForm // MainForm
// //
@@ -615,6 +683,10 @@ namespace AGVMapEditor.Forms
this.tableLayoutPanel1.ResumeLayout(false); this.tableLayoutPanel1.ResumeLayout(false);
this.toolStrip4.ResumeLayout(false); this.toolStrip4.ResumeLayout(false);
this.toolStrip4.PerformLayout(); this.toolStrip4.PerformLayout();
this.tabPage3.ResumeLayout(false);
this.tabPage3.PerformLayout();
this.toolStrip5.ResumeLayout(false);
this.toolStrip5.PerformLayout();
this.toolStrip3.ResumeLayout(false); this.toolStrip3.ResumeLayout(false);
this.toolStrip3.PerformLayout(); this.toolStrip3.PerformLayout();
this.toolStrip2.ResumeLayout(false); this.toolStrip2.ResumeLayout(false);
@@ -650,9 +722,8 @@ namespace AGVMapEditor.Forms
private System.Windows.Forms.ToolStripButton btnSelect; private System.Windows.Forms.ToolStripButton btnSelect;
private System.Windows.Forms.ToolStripButton btnMove; private System.Windows.Forms.ToolStripButton btnMove;
private System.Windows.Forms.ToolStripButton btnEditImage; 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 btnDelete;
private System.Windows.Forms.ToolStripButton btnDeleteConnection;
private System.Windows.Forms.ToolStripSeparator separator1; private System.Windows.Forms.ToolStripSeparator separator1;
private System.Windows.Forms.ToolStripButton btnToggleGrid; private System.Windows.Forms.ToolStripButton btnToggleGrid;
private System.Windows.Forms.ToolStripButton btnFitMap; private System.Windows.Forms.ToolStripButton btnFitMap;
@@ -675,5 +746,12 @@ namespace AGVMapEditor.Forms
private System.Windows.Forms.Button button3; private System.Windows.Forms.Button button3;
private System.Windows.Forms.ToolStripButton toolStripButton2; private System.Windows.Forms.ToolStripButton toolStripButton2;
private System.Windows.Forms.ToolStripButton btAddMagnet; 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;
} }
} }

View File

@@ -115,6 +115,7 @@ namespace AGVMapEditor.Forms
_mapCanvas.NodesSelected += OnNodesSelected; // 다중 선택 이벤트 _mapCanvas.NodesSelected += OnNodesSelected; // 다중 선택 이벤트
_mapCanvas.NodeMoved += OnNodeMoved; _mapCanvas.NodeMoved += OnNodeMoved;
_mapCanvas.NodeDeleted += OnNodeDeleted; _mapCanvas.NodeDeleted += OnNodeDeleted;
_mapCanvas.ConnectionCreated += OnConnectionCreated;
_mapCanvas.ConnectionDeleted += OnConnectionDeleted; _mapCanvas.ConnectionDeleted += OnConnectionDeleted;
_mapCanvas.ImageDoubleClicked += OnImageDoubleClicked; _mapCanvas.ImageDoubleClicked += OnImageDoubleClicked;
_mapCanvas.MapChanged += OnMapChanged; _mapCanvas.MapChanged += OnMapChanged;
@@ -144,9 +145,8 @@ namespace AGVMapEditor.Forms
btnAddLabel.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.AddLabel; btnAddLabel.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.AddLabel;
btnAddImage.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.AddImage; 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; 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; btnToggleGrid.Click += (s, e) => _mapCanvas.ShowGrid = !_mapCanvas.ShowGrid;
@@ -182,6 +182,7 @@ namespace AGVMapEditor.Forms
_hasChanges = true; _hasChanges = true;
UpdateTitle(); UpdateTitle();
RefreshNodeList(); RefreshNodeList();
RefreshMagnetList(); // 추가
// RFID 자동 할당 // RFID 자동 할당
} }
@@ -215,6 +216,20 @@ namespace AGVMapEditor.Forms
{ {
// 단일 선택은 기존 방식 사용 // 단일 선택은 기존 방식 사용
_selectedNode = nodes[0]; _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(); UpdateNodeProperties();
UpdateImageEditButton(); UpdateImageEditButton();
} }
@@ -242,17 +257,20 @@ namespace AGVMapEditor.Forms
_hasChanges = true; _hasChanges = true;
UpdateTitle(); UpdateTitle();
RefreshNodeList(); RefreshNodeList();
RefreshMagnetList(); // 추가
ClearNodeProperties(); ClearNodeProperties();
// RFID 자동 할당 // RFID 자동 할당
} }
private void OnConnectionCreated(object sender, (MapNode From, MapNode To) connection) private void OnConnectionCreated(object sender, (MapNode From, MapNode To) connection)
{ {
_hasChanges = true; _hasChanges = true;
UpdateTitle(); UpdateTitle();
RefreshNodeConnectionList();
UpdateNodeProperties(); // 연결 정보 업데이트 UpdateNodeProperties(); // 연결 정보 업데이트
} }
private void OnConnectionDeleted(object sender, (MapNode From, MapNode To) connection) private void OnConnectionDeleted(object sender, (MapNode From, MapNode To) connection)
{ {
_hasChanges = true; _hasChanges = true;
@@ -261,6 +279,8 @@ namespace AGVMapEditor.Forms
UpdateNodeProperties(); // 연결 정보 업데이트 UpdateNodeProperties(); // 연결 정보 업데이트
} }
private void OnImageDoubleClicked(object sender, MapImage image) private void OnImageDoubleClicked(object sender, MapImage image)
{ {
// 이미지 노드 더블클릭 시 이미지 편집창 표시 // 이미지 노드 더블클릭 시 이미지 편집창 표시
@@ -280,6 +300,7 @@ namespace AGVMapEditor.Forms
{ {
_hasChanges = true; _hasChanges = true;
UpdateTitle(); UpdateTitle();
RefreshMagnetDirectionList(); // 방향 정보 업데이트
} }
private void OnBackgroundClicked(object sender, Point location) private void OnBackgroundClicked(object sender, Point location)
@@ -621,7 +642,7 @@ namespace AGVMapEditor.Forms
private void LoadMapFromFile(string filePath) private void LoadMapFromFile(string filePath)
{ {
var result = MapLoader.LoadMapFromFile(filePath); var result = MapLoader.LoadMapFromFile(filePath);
sbFile.Text = filePath;
if (result.Success) if (result.Success)
{ {
// 맵 캔버스에 데이터 설정 // 맵 캔버스에 데이터 설정
@@ -637,6 +658,7 @@ namespace AGVMapEditor.Forms
UpdateTitle(); UpdateTitle();
UpdateNodeList(); UpdateNodeList();
RefreshNodeConnectionList(); RefreshNodeConnectionList();
RefreshMagnetList(); // 추가
@@ -767,6 +789,7 @@ namespace AGVMapEditor.Forms
{ {
RefreshNodeList(); RefreshNodeList();
RefreshNodeConnectionList(); RefreshNodeConnectionList();
RefreshMagnetList(); // 추가
RefreshMapCanvas(); RefreshMapCanvas();
ClearNodeProperties(); ClearNodeProperties();
} }
@@ -829,7 +852,7 @@ namespace AGVMapEditor.Forms
var item = node as MapNode; var item = node as MapNode;
if (item.StationType == StationType.Normal) if (item.StationType == StationType.Normal)
foreColor = Color.DimGray; foreColor = Color.DimGray;
else if (item.StationType == StationType.Charger1 || item.StationType == StationType.Charger2) else if (item.StationType == StationType.Charger)
foreColor = Color.Red; foreColor = Color.Red;
else else
foreColor = Color.DarkGreen; foreColor = Color.DarkGreen;
@@ -945,13 +968,21 @@ namespace AGVMapEditor.Forms
_mapCanvas?.HighlightConnection(connectionInfo.FromNodeId, connectionInfo.ToNodeId); _mapCanvas?.HighlightConnection(connectionInfo.FromNodeId, connectionInfo.ToNodeId);
// 연결된 노드들을 맵에서 하이라이트 표시 (선택적) // 연결된 노드들을 맵에서 하이라이트 표시 (선택적)
var fromNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectionInfo.FromNodeId); //var fromNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectionInfo.FromNodeId);
if (fromNode != null) //if (fromNode != null)
{ //{
_selectedNode = fromNode; // if (_selectedNode != fromNode)
UpdateNodeProperties(); // {
_mapCanvas?.Invalidate(); // _selectedNode = fromNode;
} // _mapCanvas.SelectedNode = fromNode; // 캔버스 선택 상태 동기화
// // 속성창 업데이트 (리스트 리프레시 포함)
// // 주의: RefreshMagnetDirectionList()가 호출되어도 lstNodeConnection에는 영향이 없으므로 안전함
// UpdateNodeProperties();
// _mapCanvas?.Invalidate();
// }
//}
} }
else else
{ {
@@ -1303,9 +1334,9 @@ namespace AGVMapEditor.Forms
{ {
foreach (var node in this._mapCanvas.Nodes) foreach (var node in this._mapCanvas.Nodes)
{ {
node.CanTurnLeft = true; node.CanTurnLeft = false;
node.CanTurnRight = true; node.CanTurnRight = false;
node.DisableCross = false; node.DisableCross = true;
node.ModifiedDate = DateTime.Now; node.ModifiedDate = DateTime.Now;
} }
@@ -1367,6 +1398,9 @@ namespace AGVMapEditor.Forms
private void RefreshMagnetDirectionList() private void RefreshMagnetDirectionList()
{ {
// 이벤트 임시 제거 (DataSource 변경 시 불필요한 이벤트 발생 방지)
lstMagnetDirection.SelectedIndexChanged -= LstMagnetDirection_SelectedIndexChanged;
// 현재 선택된 항목 기억 // 현재 선택된 항목 기억
int selectedIndex = lstMagnetDirection.SelectedIndex; int selectedIndex = lstMagnetDirection.SelectedIndex;
@@ -1374,7 +1408,12 @@ namespace AGVMapEditor.Forms
lstMagnetDirection.DataSource = null; lstMagnetDirection.DataSource = null;
lstMagnetDirection.Items.Clear(); lstMagnetDirection.Items.Clear();
if (this._mapCanvas.Nodes == null) return; if (this._mapCanvas.Nodes == null)
{
// 이벤트 다시 연결 (빠른 리턴 시에도 연결 필요)
lstMagnetDirection.SelectedIndexChanged += LstMagnetDirection_SelectedIndexChanged;
return;
}
var directions = new List<MagnetDirectionInfo>(); var directions = new List<MagnetDirectionInfo>();
@@ -1410,8 +1449,7 @@ namespace AGVMapEditor.Forms
lstMagnetDirection.DataSource = directions; lstMagnetDirection.DataSource = directions;
} }
// 이벤트 연결 // 이벤트 다시 연결
lstMagnetDirection.SelectedIndexChanged -= LstMagnetDirection_SelectedIndexChanged;
lstMagnetDirection.SelectedIndexChanged += LstMagnetDirection_SelectedIndexChanged; lstMagnetDirection.SelectedIndexChanged += LstMagnetDirection_SelectedIndexChanged;
lstMagnetDirection.DoubleClick -= LstMagnetDirection_DoubleClick; lstMagnetDirection.DoubleClick -= LstMagnetDirection_DoubleClick;
@@ -1420,7 +1458,15 @@ namespace AGVMapEditor.Forms
// 선택 항목 복원 (가능한 경우) -> 선택된 객체가 다르게 생성되므로 인덱스로 복원 시도 // 선택 항목 복원 (가능한 경우) -> 선택된 객체가 다르게 생성되므로 인덱스로 복원 시도
if (selectedIndex >= 0 && selectedIndex < lstMagnetDirection.Items.Count) 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); 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) private void btAddMagnet_Click(object sender, EventArgs e)
@@ -1695,11 +1779,11 @@ namespace AGVMapEditor.Forms
string id = _mapCanvas.GenerateUniqueNodeId(); string id = _mapCanvas.GenerateUniqueNodeId();
var magnet = new MapMagnet { Id = id }; var magnet = new MapMagnet { Id = id };
// 점 생성 시 정규화(Snap) 처리 // 점 생성 시 정규화(Snap) 처리
int cx = (int)worldCX; int cx = (int)worldCX;
int cy = (int)worldCY; int cy = (int)worldCY;
magnet.StartPoint = new Point(cx - 50, cy); magnet.StartPoint = new Point(cx - 50, cy);
magnet.EndPoint = new Point(cx + 50, cy); magnet.EndPoint = new Point(cx + 50, cy);
@@ -1711,14 +1795,118 @@ namespace AGVMapEditor.Forms
// 캔버스에 추가 // 캔버스에 추가
_mapCanvas.Magnets.Add(magnet); _mapCanvas.Magnets.Add(magnet);
_hasChanges = true; _hasChanges = true;
UpdateTitle(); UpdateTitle();
RefreshMapCanvas(); RefreshMapCanvas();
RefreshNodeList(); RefreshNodeList();
RefreshMagnetList(); // 추가
// 추가된 마그넷 선택 // 추가된 마그넷 선택
//_mapCanvas.SelectedNode = magnet; //_mapCanvas.SelectedNode = magnet;
UpdateNodeProperties(); 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();
}
} }
} }

View File

@@ -123,7 +123,49 @@
<metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>249, 17</value> <value>249, 17</value>
</metadata> </metadata>
<metadata name="toolStrip4.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 56</value>
</metadata>
<metadata name="toolStrip5.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>123, 56</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="btDelMagnet.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
</value>
</data>
<data name="toolStripButton3.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
</value>
</data>
<metadata name="toolStrip3.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>462, 17</value>
</metadata>
<metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>249, 17</value>
</metadata>
<data name="btNodeRemove.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btNodeRemove.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
@@ -187,21 +229,18 @@
nOccAdABIDXXE1nzAAAAAElFTkSuQmCC nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
</value> </value>
</data> </data>
<metadata name="toolStrip3.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>462, 17</value>
</metadata>
<data name="btnSelect.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btnSelect.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHrSURBVDhPldBLaBNBHMfx/0kUVBBJ0lxWPIhihBJKyAqS YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHsSURBVDhPldBLaBNBHMfx/0kUVBBJ0lxWPIhihBJKyAqS
pHkQIS+9hXg3RhQviicrITnmJqFnQQ8RqiamRqkhj6VCQtuIQaVQc7dg3d3s61Dy0w002KnU9nP67+x8 pHkQIS+9hXg3RhQviicrITnmJqFnQQ8RqiamRqkhj6VCQtuIQaVQc64H6+5mX4eSn26gwU6ltp/Tf2fn
h2GIDmD0kT+mLk/fZNf3pQkznCrM3DFnZflSRG05euast7izcpM72GGqMP1ZFRw1tXm+qq9dg9LiHgwb OwxDdACjj/wxdXn6Jru+L02Y4VRh5o45K8uXImrL0TNnvcWdlZvcwQ5ThenPquCoqc3zVX3tGpQW92DY
dnFYP51i9/6T0r4wp39Kwfh2F8bGI2irEYjvTmo/Gpbj7N4JpXNxShUcdbV1DvpaHMb3HNrP4uiVb2Cj sIvD+ukUu/eflPaFOf1TCsa3uzA2HkFbjUB8d1L70bAcZ/dOKJ2LU6rgqKutc9DX4jC+59B+FkevfAMb
cQtadxbSh6OQ3tM82+6iNLk5rXcd7ecJGIaB0WiE1dcp6F9v41eNvmxV6QzbTMjtKYtct9Wi0Si63S50 jVvQurOQPhyF9J7m2XYXpcnNab3raD9PwDAMjEYjrL5OQf96G79q9GWrSmfYZkJuT1nkuq0WjUbR7Xah
XUe/30fjaQTG+n1IVRpKb4lnuzFtyc4Nl06VE4kE0uk0CoUCSqUSqvOzMNYfYnORtqVFWhEr9JhtJ+Lx 6zr6/T4aTyMw1u9DqtJQeks8241pS3ZuuHSqnEgkkE6nUSgUUCqVUJ2fhbH+EJuLtC0t0opYocdsOxGP
+DjmeR5+vx+xWAzqSgRy3Q65dgJbFeLYZmIndrvd8Pl8sFqt5pWfbL6hbalCl6Uy9cSXlGG7sWQyiXw+ x8cxz/Pw+/2IxWJQVyKQ63bItRPYqhDHNhM7sdvths/ng9VqNa/8ZPMNbUsVuiyVqSe+pAzbjSWTSeTz
P469Xi8sFgvMdblCV6RXVDNnvKAjPxfoKttSOBxGLpfbE+8QFyj09/cugUAA2WwWLpcLHo9nT7yvTCaD +XHs9XphsVhgrssVuiK9opo54wUd+blAV9mWwuEwcrncnniHuEChv793CQQCyGazcLlc8Hg8e+J9ZTIZ
wWAAp9OJUCh0uNhkHtDpdFAsFscPxv7/r2AweM+8ts1mO3z8x29G9wsX9Ki7DgAAAABJRU5ErkJggg== DAYDOJ1OhEKhw8Um84BOp4NisTh+MPb/fwWDwXvmtW022+HjP34DP4sLE797GZoAAAAASUVORK5CYII=
</value> </value>
</data> </data>
<data name="btnMove.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btnMove.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
@@ -217,83 +256,83 @@
YIGDKaeH7rEGFFd1IN1M4c5nAYIcIXLXvmW+uOKfXMvpRO9rFnzJi9lqBKPZYVCedzYsH6SQ2l+Eu2SD YIGDKaeH7rEGFFd1IN1M4c5nAYIcIXLXvmW+uOKfXMvpRO9rFnzJi9lqBKPZYVCedzYsH6SQ2l+Eu2SD
bfNyWeHqqhbxahSCGIM2MwSKrYzDWboBx5sxIsP6yvTPH0lk3YoGI9lhaB8NQZO+gl8Dj7SN1tpAvgAA bfNyWeHqqhbxahSCGIM2MwSKrYzDWboBx5sxIsP6yvTPH0lk3YoGI9lhaB8NQZO+gl8Dj7SN1tpAvgAA
AABJRU5ErkJggg== AABJRU5ErkJggg==
</value>
</data>
<data name="btnAddNode.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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==
</value> </value>
</data> </data>
<data name="btnAddLabel.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btnAddLabel.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHHSURBVDhPnZJva9pQFMb7JfZ2Y9+iIPtke7OujL7sWopr YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHGSURBVDhPnZJfa9pgFMb7JXa7sm9RkH2y3awro5dbS3Et
2bt2bETMi4CJXlCROI0SFP/OJDqXiladgw7cks0ozzhXbopzq2wPhHDPOc/vOSR3r91uo9VqodFooF6v u2vHRsRcBEw0gqJxGiUo/q1JdDYVnVoLLbglm1GecV55U5xbZXsghPe85/k9h+TstFotNJtN1Ot11Go1
o1aroVqtolKp0PvB3i41m02sVqutZzgccoiu6/dDKJkMg8GAJ7uuG0KoZprm/RBamYYpjc7lcrkyn88R VKtVVCoVlMtlej/Z2aZGo4HlcrnxDAYDBtE07XEIJZOh3++zZMdxAgjVDMN4HEIjUzOl0blUKpVnsxl8
BAGvE9AwjL9DKFUM5vP5n4VCYeD7Pj7ffEeBTXmv3+9T788QShYrL5dLkPnL1MerQwcnT228T60hvV4P 32d1Auq6/ncIpfLGbDb7M5/P9z3Pw83X78irE3bX6/WQy+X+DKFkPvJisQCZbyce3h3YOHph4XNiBel2
uVwOmqZtQkzT9GazGR9aLBY8OfrcweVxH2/PXJwfdkOI4zjIZDKbkFKp9MQwDG86XQ9Z9VuefHXyCdL5 u8hkMlAUZR1iGIY7nU5Z03w+Z8nhVzbO3/Tw8cTB6UEngNi2jVQqtQ4pFovPdV13J5NVk1m7Y8kXR1cQ
9RbEtm0wxjYhxWIxkk6nvclkwoe6ra+IHth4c3oHiR440LV1v9PpQFVVSJJ0B9F1PcIY88bj8RbkXdTF Tq83IJZlQVXVdUihUAglk0l3PB6zpk7zHuF9Cx+OHyDhfRuasrpvt9uQZRmCIDxANE0LJRIJdzQabUA+
66MeLo4+IAiWfItkMol4PP4oBJCy2WwklUp5o9EohJw9s3Dxoour0y6+zf3QLEnS4w2zEGOMQ+gm8g/X hR28P+zi7PASvr9gU8TjcUSj0d0AQEqn0wwyHA4DyMlLE2evO7g47uDbzAvMgiA8WzNzqarKILSJ7MM1
vMXly4/44Qe7zUKqqkYSiYRHN1H8YjJrmrbbLKQoyr6iKB5dMMuy/s0sJMvyvizL3n+ZhQgSi8Ue/l4X 7nD+9gt+eP52M5csy6FYLObSJvJfTGZFUbabuSRJ2pMkyaUFM03z38xcoijuiaLo/peZiyCRSOTp73Wu
+gV3kBaRV83skgAAAABJRU5ErkJggg== X1R9FoLvbSO9AAAAAElFTkSuQmCC
</value> </value>
</data> </data>
<data name="btnAddImage.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btnAddImage.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAG4SURBVDhPnZLditpQFIXnJXrb0rcYkD5YL4pMYS7bKaX0 YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAG3SURBVDhPnZLditpQFIXnJXrb0rcYkD5YL4pMYS7bKaX0
HSLmIiV/BzRjIxolKGoM/iAijlbFizLIJOjJYZV9hmSwtiPtghCy917f2pB9EYYh+v0+er0eut0uOp0O HSLmIiW/ghmNaJSgqDH4g4hYrYoXpUgT9OSwyj7DyWBtR9oFIWTvvb61IfsqiiIMBgP0+330ej10u110
2u02Wq0WvV9cnFMQBBBCnDyLxUJCXNd9HkLJZJjP5zJ5NptlEKr5vv88hFamYUqj72az2drtduCcyzoB Oh202216P7u6pDAMwTk/e5bLpYB4nvc0hJLJsFgsRPJ8Ps8gVAuC4GkIrUzDlEbfrVarvd/vwRgTdQL6
Pc/7O4RS08Fqtbqv1WrzOI7Bf/4AD23Zm06n1PszhJLTlZMkAZnF/QaJdYXk21vw0JK9yWSCSqUC0zSP vv93CKXKwWq1eqjX64skScC+fwOLbNGbzWao1Wp/hlCyXDlNU5CZ/9gitW6QfnkNFlmiN51OUalUYJrm
Ib7vR9vtVg4dDgeZnJh5COcDxPfPENZVBhmPxyiXy8eQRqPxxvO8aLPZPELuOjJZ3N5AVL+eQEajERhj KSQIgni324mh4/EoklMzD+6+A69+BLduMshkMkG5XD6FNJvNV77vx9vt9gHytSuS+f0deO3zGWQ8HsNx
x5B6vZ4rlUrRer1+hCwDcCMPcfvpCWLmwQNd9geDAQzDgKIoTxDXdXOMsWi1Wp1C3C8Q7BoPxjVEwuUW nFNIo9HIlUqleLPZPEBWIZiRB7//8Agx82ChLvrD4RCGYUBRlEeI53k527bj9Xp9DvE+gTu3+GncgqdM
lmWhWCy+ygAkx3Fytm1Hy+Uygxz0dxD2e8TlG+yjXWZWFOX1kTkVY0xC6BIlZNEDdz5C7OPz5lSGYeR0 bGFZForF4osMQHJdV0BWq1UGOepvwO23SMp3OMT7zKwoyssTs5TjOAJClyggyz6Y+x78kFw2SxmGkdN1
XY/oEtNfTGbTNM+bU2madqlpWkQHNhwO/82cSlXVS1VVo/8ypyJIoVB4+Xs91S9svB2DRfl8BwAAAABJ PaZLlL+YzKZpXjZLaZp2rWlaTAc2Go3+zSylquq1qqrxf5mlCFIoFJ7/Xpf6BUmpHXRPK0SnAAAAAElF
RU5ErkJggg== TkSuQmCC
</value>
</data>
<data name="btnAddNode.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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=
</value> </value>
</data> </data>
<data name="btnDelete.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btnDelete.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGuSURBVDhP7Y+/TxphGMdp0T+gU3EwhsHZf6BDU9DNxsFE YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVDhP7Y89S1thGIZToz+gk+kg4uDsH3CQJh0VB0EH
B42DHTowdYC2gzg5aFy6kCb4sjiogxsW02pDD+447o7jgGAMUor8Og+FxV+Jhq953wghp1b/AL/JZ3nz pUM7iDh1SNRBnRwqLkKGQvoGnKxDF0lV6gfxJOfk5JyT8xGJlDSmMV+nJ5osVguR3OV9MRKOae0P8IZr
fL7v81gsz2EJBAIOQkiMEIInkvf7/e87BYSQrKIIKJdzjEqFcsCoVvOMWu0vdL0AXf+HdFqhJaXuAqNQ eXmu+30eh+MpLMFg0E0IiRFC8J9kAoHAyH0BISSlKAIKhTSjWKR8Z5RKGUa5fAbTzMI0fyCZVGhJvrXA
yILnIwiHwwyO4xCJRBCNRsHzPGKxGERRhKIorMTn812YCvYgigITzVI8Hocsy0zWtCQMo3S3oFjchyxL ymZT4PkIwuEwg+M4RCIRRKNR8DyPWCwGURShKAor8fv9N7aCU4iiwES7FI/HIcsyk3Vdg2XlHxbkct8g
EASBiVSSJIlJiUQCyWQSmqYhk0mjXq/cLTg8zEFVFSbR36ikqioTU6kU5O9z+DM9iF+OXvye7Meqa/yq yxIEQWAilSRJYlIikYCmadB1HScnSVQqxYcF5+dpqKrCJPoblVRVZaJhGJA/LOJ4qh/77i4cTfRgY2as
LWN39yeOj6s4OaHU0GjoaDaPbjGQW19Cyv0GF8FltLIhnK19guQawoZnCqzgMYJjNpwHl4Fvo4DnFbBg 3pRxePgVFxclXF5SyqhWTdRqP++wkP60CsM7iJvQGhqpXfzafAdpdgBbvkmwgscIjbpwHVoD1ocB33Ng
R33xLbbGXqNzxv8SfNfTaimb6E7TawN9N8/em22ntXS6Mgt4bbh0W9BwW1D8aEVo2Fo2z94bbqJvXpwZ pQ+V90P4MtqN+zP+le2XnY2G8hmtqS25QN/ts22z53Hmrz6+AZZc+O11oOp1IDftxO4rZ8E+2zbc+Itl
uKp67DC+9CL/4QV2RnqufzhffjXPPhhuov/ztsNaoGvTjdryDdsIqhi2ZmdsAAAAAElFTkSuQmCC 8XVvveTrgzXfhczbZzjwdN7ueDoW7LN/DTfeM7fndmbp2nSjpvwHq8ip+rkEjbgAAAAASUVORK5CYII=
</value> </value>
</data> </data>
<data name="btnEditImage.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btnEditImage.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
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
W91zf/4A4AuLF1bhN90AAAAASUVORK5CYII= AbuKiwBr5ZOIAAAAAElFTkSuQmCC
</value> </value>
</data> </data>
<data name="btnConnect.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btnConnNode.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHrSURBVDhPY/j//z8DJRhDgFSMIUAqxhAgFcMZdnZ2 dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHrSURBVDhPY/j//z8DJRhDgFSMIUAqxhAgFcMZdnZ2
@@ -308,19 +347,19 @@
gg== gg==
</value> </value>
</data> </data>
<data name="btnDeleteConnection.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btnConnDir.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHvSURBVDhP7Y7Ni1JhGMVvXadgWrWzRQyzTEFyla0cx6sL dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHrSURBVDhPY/j//z8DJRhDgFSMIUAqxhAgFcMZdnZ2
P6K7USGoRYr1SoJ9zGh3YbgYQZjAC+YqIURoo61slDLQcRORgWCt7gxShBRp0JQ2mSeegS4mzn/QgRce 3FlZWaWtra3v5s2b97+mpuZhTk6OeU5OjkRNTc1dkBhIDqQGpBbFACMjIzYfH5+uiRMn/n/x4sX/u3fv
zvmdw8tx/6XKZrOdYIzdicfj/VwuB0mSuoyxc4wxrSRJCnmUEUPsP2WdTnfM6XSm0uk0er0eFEVBvV6n /j948OD/xsbGe9XV1Ydu3rz5AyR25MiR/yA1ILUgPXADDA0N/YqLiz9cvXr1XURExDZvb+8/qamp/ydN
wo4kSY12uz0ir9lsghhiqaMOGI3GC+Fw+Gun0+n7fL6ngiCMA4EAZFlGLBYD3eRRVigUusRSZ3oglUgk mvS/srLyP4gNEgPJrVix4iFILUgPsgu6mpqavk+ePDnd39+fy97e/vPevXv/nzt37v+hQ4f+r1279j9I
hrIsB10u16LJZPpWq9XQarXQaDRQLBZBHmXEEEsddcBgMFwPBoODTCZzWxTFqsVi2bfb7RBF8eDRTR5l DCQHUgNSC9IDN0BXVzczPT39/bRp00qCgoJ2Ojo6/nJzc/sfGBgIxiA2SAwkB1IDUgvSAzdAU1NT09fX
xBBLHXVAr9efcbvd75LJ5CCfz38plUrIZrOIRCKIRqMHd2WD4cXFpd/PVxfwxL64/2jl+H11gOM43mw2 93pvb+/7VatWvV23bt3/6dOn/y8sLPxfUVEBZvcvbP1fODXmX1qf//+IJse/Lrm6E+EGqKmpMdvb2xt4
n3U4HLt+v38vFAoNGWM/PR7PZa/Xa31wyTx6c+s8huVNTN5W8P3xTbwK6cbPrJob0yOHakvglR/lTUB2 eXndT0lJ+ZKdnf09Ozv7Z3h4eFx4eLhzZm3cz8bVif+3XJn6/8KzXf/7d2f9D+/X/W+eLdmDEa/YcGy7
A+sngY1lfE6toGzhd2bZuSqvaiaT1yVMaxDXgvxZdq6qAv9+7+FVIK7FaI1Df41D9xqPio3/MMvO1bb3 y/eNlyf833ht0n8Q6NmT+r9/TzrIgO8YirFhn2rD/9uuzAFrhoFNl6aCDMBUjA1b5kh/796V9L99VwJY
1L2XV5Z+fVxfxqfYAhT/EdTsmvGWcPTuLHuotr2no1Urv0vfph/9Lf8B0zIZ8W7n4ScAAAAASUVORK5C c/vOBNJcYJ4t2RLco/G/d1cK2GYQDeITHQZQQzrMsyU/gZwNpTtA4gBRO5Y8lpxI5AAAAABJRU5ErkJg
YII= gg==
</value> </value>
</data> </data>
<data name="btnToggleGrid.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btnToggleGrid.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
@@ -335,18 +374,18 @@
<data name="btnFitMap.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btnFitMap.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
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
</value> </value>
</data> </data>
<data name="btAddMagnet.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="btAddMagnet.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">

View File

@@ -374,11 +374,26 @@ namespace AGVNavigationCore.Controls
foreach (var targetNode in node.ConnectedMapNodes) foreach (var targetNode in node.ConnectedMapNodes)
{ {
if (targetNode == null) continue; if (targetNode == null) continue;
// 강조된 연결은 나중에 그리기 위해 건너뜀
if (IsConnectionHighlighted(node.Id, targetNode.Id)) continue;
DrawConnection(g, node, targetNode); 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. 마그넷 그리기 (별도 리스트 사용) // 2. 마그넷 그리기 (별도 리스트 사용)
if (_magnets != null) if (_magnets != null)
{ {
@@ -442,10 +457,17 @@ namespace AGVNavigationCore.Controls
g.DrawLine(_magnetPen, startPoint, endPoint); g.DrawLine(_magnetPen, startPoint, endPoint);
} }
// 호버된 마그넷 강조 // 호버되거나 선택된 마그넷 강조 (선택: Red, 호버: Orange)
if (magnet == _hoveredNode) 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) if (magnet.ControlPoint != null)
{ {
@@ -470,15 +492,6 @@ namespace AGVNavigationCore.Controls
g.DrawLine(highlightPen, startPoint, endPoint); 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: case NodeType.Normal:
var item = _selectedNode as MapNode; var item = _selectedNode as MapNode;
if ( item.StationType == StationType.Charger1 || item.StationType == StationType.Charger2) if ( item.StationType == StationType.Charger)
DrawTriangleGhost(g, ghostBrush); DrawTriangleGhost(g, ghostBrush);
else else
DrawPentagonGhost(g, ghostBrush); DrawPentagonGhost(g, ghostBrush);
@@ -1049,13 +1062,12 @@ namespace AGVNavigationCore.Controls
switch (node.StationType) switch (node.StationType)
{ {
case StationType.Loader: case StationType.Loader:
case StationType.UnLoader: case StationType.Cleaner:
case StationType.Clearner: case StationType.Plating:
case StationType.Buffer: case StationType.Buffer:
DrawPentagonNodeShape(g, node, brush); DrawPentagonNodeShape(g, node, brush);
break; break;
case StationType.Charger1: case StationType.Charger:
case StationType.Charger2:
DrawTriangleNodeShape(g, node, brush); DrawTriangleNodeShape(g, node, brush);
break; break;
case StationType.Limit: case StationType.Limit:
@@ -1470,7 +1482,7 @@ namespace AGVNavigationCore.Controls
// 🔥 노드의 폰트 설정 사용 (0 이하일 경우 기본값 7.0f 사용) // 🔥 노드의 폰트 설정 사용 (0 이하일 경우 기본값 7.0f 사용)
var topFont = new Font("Arial", 9, FontStyle.Bold); 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); var TopSize = g.MeasureString(TopIDText, topFont);
@@ -1496,8 +1508,7 @@ namespace AGVNavigationCore.Controls
Color bgColor = Color.White; Color bgColor = Color.White;
switch (node.StationType) switch (node.StationType)
{ {
case StationType.Charger1: case StationType.Charger:
case StationType.Charger2:
fgColor = Color.White; fgColor = Color.White;
bgColor = Color.Tomato; bgColor = Color.Tomato;
break; break;
@@ -1505,12 +1516,12 @@ namespace AGVNavigationCore.Controls
fgColor = Color.Black; fgColor = Color.Black;
bgColor = Color.White; bgColor = Color.White;
break; break;
case StationType.Clearner: case StationType.Plating:
fgColor = Color.Black; fgColor = Color.Black;
bgColor = Color.DeepSkyBlue; bgColor = Color.DeepSkyBlue;
break; break;
case StationType.Loader: case StationType.Loader:
case StationType.UnLoader: case StationType.Cleaner:
fgColor = Color.Black; fgColor = Color.Black;
bgColor = Color.Gold; bgColor = Color.Gold;
break; break;
@@ -1519,6 +1530,8 @@ namespace AGVNavigationCore.Controls
break; break;
} }
var rectpaddingx = 4; 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 g.DrawString(BottomLabelText, btmFont, descBrush, roundRect, new StringFormat
{ {
@@ -1793,12 +1807,16 @@ namespace AGVNavigationCore.Controls
switch (node.StationType) switch (node.StationType)
{ {
case StationType.Normal: bgColor = Color.DeepSkyBlue; break; case StationType.Normal:
case StationType.Charger1: bgColor = Color.Tomato; break; if(node.CanTurnLeft || node.CanTurnRight)
case StationType.Charger2: bgColor = Color.Tomato; break; bgColor = Color.Violet;
else
bgColor = Color.DeepSkyBlue;
break;
case StationType.Charger: bgColor = Color.Tomato; break;
case StationType.Loader: case StationType.Loader:
case StationType.UnLoader: bgColor = Color.Gold; break; case StationType.Cleaner: bgColor = Color.Gold; break;
case StationType.Clearner: bgColor = Color.DeepSkyBlue; break; case StationType.Plating: bgColor = Color.DeepSkyBlue; break;
case StationType.Buffer: bgColor = Color.WhiteSmoke; break; case StationType.Buffer: bgColor = Color.WhiteSmoke; break;
case StationType.Limit: bgColor = Color.Red; break; case StationType.Limit: bgColor = Color.Red; break;
default: bgColor = Color.White; break; default: bgColor = Color.White; break;

View File

@@ -104,6 +104,10 @@ namespace AGVNavigationCore.Controls
HandleConnectClick(hitNode as MapNode); HandleConnectClick(hitNode as MapNode);
break; break;
case EditMode.ConnectDirection:
HandleConnectDirectionClick(hitNode as MapNode);
break;
case EditMode.Delete: case EditMode.Delete:
HandleDeleteClick(hitNode); HandleDeleteClick(hitNode);
break; break;
@@ -227,6 +231,23 @@ namespace AGVNavigationCore.Controls
if (handleIdx != -1) if (handleIdx != -1)
{ {
_dragHandleIndex = handleIdx; _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; _isDragging = true;
_isPanning = false; _isPanning = false;
Capture = true; Capture = true;
@@ -562,12 +583,11 @@ namespace AGVNavigationCore.Controls
switch (node.StationType) switch (node.StationType)
{ {
case StationType.Loader: case StationType.Loader:
case StationType.UnLoader: case StationType.Cleaner:
case StationType.Clearner: case StationType.Plating:
case StationType.Buffer: case StationType.Buffer:
return IsPointInPentagon(point, node); return IsPointInPentagon(point, node);
case StationType.Charger2: case StationType.Charger:
case StationType.Charger1:
return IsPointInTriangle(point, node); return IsPointInTriangle(point, node);
default: default:
return IsPointInCircle(point, node); return IsPointInCircle(point, node);
@@ -887,7 +907,9 @@ namespace AGVNavigationCore.Controls
var newNode = new MapNode var newNode = new MapNode
{ {
Id = newNodeId, Id = newNodeId,
Position = worldPoint Position = worldPoint,
CanTurnLeft=false,
CanTurnRight= false,
}; };
_nodes.Add(newNode); _nodes.Add(newNode);
@@ -998,6 +1020,31 @@ namespace AGVNavigationCore.Controls
Invalidate(); 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) private void HandleDeleteClick(MapNode hitNode)
{ {
if (hitNode == null) return; if (hitNode == null) return;
@@ -1025,10 +1072,46 @@ namespace AGVNavigationCore.Controls
toNode.ConnectedNodes.Contains(fromNode.Id)) toNode.ConnectedNodes.Contains(fromNode.Id))
return; return;
// 양방향 연결 생성 (AGV가 양쪽 방향으로 이동 가능하도록)
// 양방향 연결 생성 (AGV가 양쪽 방향으로 이동 가능하도록) // 양방향 연결 생성 (AGV가 양쪽 방향으로 이동 가능하도록)
fromNode.AddConnection(toNode.Id); fromNode.AddConnection(toNode.Id);
toNode.AddConnection(fromNode.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<string, MagnetPosition>();
if (fromNode.MagnetDirections.ContainsKey(toNode.Id))
{
fromNode.MagnetDirections[toNode.Id] = direction;
}
else
{
fromNode.MagnetDirections.Add(toNode.Id, direction);
}
MapChanged?.Invoke(this, EventArgs.Empty); MapChanged?.Invoke(this, EventArgs.Empty);
} }

View File

@@ -20,7 +20,7 @@ namespace AGVNavigationCore.Controls
{ {
#region Constants #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 NODE_RADIUS = NODE_SIZE / 2;
private const int GRID_SIZE = 20; private const int GRID_SIZE = 20;
private const float CONNECTION_WIDTH = 1.0f; private const float CONNECTION_WIDTH = 1.0f;
@@ -56,6 +56,7 @@ namespace AGVNavigationCore.Controls
DeleteConnection, // 연결 삭제 모드 DeleteConnection, // 연결 삭제 모드
AddLabel, // 라벨 추가 모드 AddLabel, // 라벨 추가 모드
AddImage, // 이미지 추가 모드 AddImage, // 이미지 추가 모드
ConnectDirection, // 방향 연결 모드
} }
#endregion #endregion
@@ -139,7 +140,7 @@ namespace AGVNavigationCore.Controls
string _alertmesage = ""; string _alertmesage = "";
bool showalert = false; bool showalert = false;
// 깜박임 효과를 위한 타이머 및 상태 // 깜박임 효과를 위한 타이머 및 상태
private Timer _alertBlinkTimer; private Timer _alertBlinkTimer;
private bool _isAlertBlinkOn = true; private bool _isAlertBlinkOn = true;
@@ -176,7 +177,7 @@ namespace AGVNavigationCore.Controls
//_isAlertBlinkOn = !_isAlertBlinkOn; //_isAlertBlinkOn = !_isAlertBlinkOn;
//Invalidate(); //Invalidate();
} }
// 브러쉬 및 펜 // 브러쉬 및 펜
private Brush _normalNodeBrush; private Brush _normalNodeBrush;
@@ -220,6 +221,7 @@ namespace AGVNavigationCore.Controls
public event EventHandler<List<NodeBase>> NodesSelected; // 다중 선택 이벤트 public event EventHandler<List<NodeBase>> NodesSelected; // 다중 선택 이벤트
public event EventHandler<NodeBase> NodeDeleted; public event EventHandler<NodeBase> NodeDeleted;
public event EventHandler<NodeBase> NodeMoved; public event EventHandler<NodeBase> NodeMoved;
public event EventHandler<(MapNode From, MapNode To)> ConnectionCreated; // 연결 생성 이벤트 추가
public event EventHandler<(MapNode From, MapNode To)> ConnectionDeleted; public event EventHandler<(MapNode From, MapNode To)> ConnectionDeleted;
public event EventHandler<MapImage> ImageDoubleClicked; public event EventHandler<MapImage> ImageDoubleClicked;
public event EventHandler<MapLabel> LabelDoubleClicked; public event EventHandler<MapLabel> LabelDoubleClicked;
@@ -266,6 +268,18 @@ namespace AGVNavigationCore.Controls
{ {
if (_nodes != null && _nodes.Contains(node)) 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); _nodes.Remove(node);
Invalidate(); Invalidate();
} }
@@ -336,10 +350,10 @@ namespace AGVNavigationCore.Controls
{ {
// Zoom 값 범위 제한 // Zoom 값 범위 제한
float newZoom = Math.Max(0.1f, Math.Min(5.0f, zoom)); float newZoom = Math.Max(0.1f, Math.Min(5.0f, zoom));
_panOffset = new PointF(panX, panY); _panOffset = new PointF(panX, panY);
_zoomFactor = newZoom; _zoomFactor = newZoom;
Invalidate(); Invalidate();
} }
@@ -408,12 +422,12 @@ namespace AGVNavigationCore.Controls
/// <summary> /// <summary>
/// 선택된 노드 (단일) /// 선택된 노드 (단일)
/// </summary> /// </summary>
public MapNode SelectedNode public NodeBase SelectedNode
{ {
get { return this._selectedNode as MapNode; } get { return this._selectedNode; }
set set
{ {
_selectedNode = value; _selectedNode = value;
if (value != null) if (value != null)
{ {
_selectedNodes.Clear(); _selectedNodes.Clear();
@@ -423,7 +437,7 @@ namespace AGVNavigationCore.Controls
{ {
_selectedNodes.Clear(); _selectedNodes.Clear();
} }
Invalidate(); Invalidate();
} }
} }
@@ -746,8 +760,8 @@ namespace AGVNavigationCore.Controls
_destinationNodePen = new Pen(Color.Orange, 4); _destinationNodePen = new Pen(Color.Orange, 4);
_pathPen = new Pen(Color.Purple, 3); _pathPen = new Pen(Color.Purple, 3);
_agvPen = new Pen(Color.Red, 3); _agvPen = new Pen(Color.Red, 3);
_highlightedConnectionPen = new Pen(Color.Red, 4) { DashStyle = DashStyle.Solid }; _highlightedConnectionPen = new Pen(Color.Red, 6) { DashStyle = DashStyle.Solid };
_magnetPen = new Pen(Color.FromArgb(100, Color.LightSkyBlue), 15) { 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); // 마크는 흰색 선으로 표시 _markPen = new Pen(Color.White, 3); // 마크는 흰색 선으로 표시
} }

View File

@@ -67,15 +67,13 @@ namespace AGVNavigationCore.Models
/// <summary>로더</summary> /// <summary>로더</summary>
Loader, Loader,
/// <summary>클리너</summary> /// <summary>클리너</summary>
Clearner, Plating,
/// <summary>오프로더</summary> /// <summary>오프로더</summary>
UnLoader, Cleaner,
/// <summary>버퍼</summary> /// <summary>버퍼</summary>
Buffer, Buffer,
/// <summary>충전기1</summary> /// <summary>충전기1</summary>
Charger1, Charger,
/// <summary>충전기2</summary>
Charger2,
/// <summary> /// <summary>
/// 끝점(더이상 이동불가) /// 끝점(더이상 이동불가)

View File

@@ -76,5 +76,17 @@ namespace AGVNavigationCore.Models
get => new Point((int)P2.X, (int)P2.Y); get => new Point((int)P2.X, (int)P2.Y);
set { P2.X = value.X; P2.Y = value.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})";
}
}
} }
} }

View File

@@ -15,9 +15,7 @@ namespace AGVNavigationCore.Models
{ {
[Category("라벨 설정")]
[Description("표시할 텍스트입니다.")]
public string Text { get; set; } = "";
public StationType StationType { get; set; } public StationType StationType { get; set; }
@@ -28,10 +26,9 @@ namespace AGVNavigationCore.Models
{ {
if (StationType == StationType.Buffer) return true; if (StationType == StationType.Buffer) return true;
if (StationType == StationType.Loader) return true; if (StationType == StationType.Loader) return true;
if (StationType == StationType.UnLoader) return true; if (StationType == StationType.Cleaner) return true;
if (StationType == StationType.Clearner) return true; if (StationType == StationType.Plating) return true;
if (StationType == StationType.Charger1) return true; if (StationType == StationType.Charger) return true;
if (StationType == StationType.Charger2) return true;
return false; return false;
} }
} }
@@ -56,11 +53,11 @@ namespace AGVNavigationCore.Models
[Category("주행 설정")] [Category("주행 설정")]
[Description("제자리 회전(좌) 가능 여부입니다.")] [Description("제자리 회전(좌) 가능 여부입니다.")]
public bool CanTurnLeft { get; set; } = true; public bool CanTurnLeft { get; set; } = false;
[Category("주행 설정")] [Category("주행 설정")]
[Description("제자리 회전(우) 가능 여부입니다.")] [Description("제자리 회전(우) 가능 여부입니다.")]
public bool CanTurnRight { get; set; } = true; public bool CanTurnRight { get; set; } = false;
[Category("주행 설정")] [Category("주행 설정")]
[Description("교차로 주행 가능 여부입니다.")] [Description("교차로 주행 가능 여부입니다.")]
@@ -73,7 +70,7 @@ namespace AGVNavigationCore.Models
} }
set { _disablecross = value; } set { _disablecross = value; }
} }
private bool _disablecross = false; private bool _disablecross = true;
[Category("주행 설정")] [Category("주행 설정")]
[Description("노드 통과 시 제한 속도입니다.")] [Description("노드 통과 시 제한 속도입니다.")]
@@ -105,6 +102,10 @@ namespace AGVNavigationCore.Models
set => _textFontSize = value > 0 ? value : 7.0f; set => _textFontSize = value > 0 ? value : 7.0f;
} }
[Category("노드 텍스트")]
[Description("표시할 텍스트입니다.")]
public string Text { get; set; } = "";
public MapNode() : base() public MapNode() : base()
{ {
Type = NodeType.Normal; Type = NodeType.Normal;
@@ -121,9 +122,9 @@ namespace AGVNavigationCore.Models
{ {
get get
{ {
if (StationType == StationType.Charger1 || StationType == StationType.Charger2 || StationType == StationType.Buffer || if (StationType == StationType.Charger || StationType == StationType.Buffer ||
StationType == StationType.Clearner || StationType == StationType.Loader || StationType == StationType.Plating || StationType == StationType.Loader ||
StationType == StationType.UnLoader) return true; StationType == StationType.Cleaner) return true;
return false; return false;
} }
} }
@@ -141,6 +142,15 @@ namespace AGVNavigationCore.Models
{ {
if (ConnectedNodes.Remove(nodeId)) 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; ModifiedDate = DateTime.Now;
} }
} }

View File

@@ -240,17 +240,17 @@ namespace AGVNavigationCore.PathFinding.Planning
// 2. Buffer-to-Buffer 예외 처리 // 2. Buffer-to-Buffer 예외 처리
// 05~31 구간 체크 // 05~31 구간 체크
var node05 = _mapNodes.FirstOrDefault(n => n.RfidId == 5); var node_buff_sta = _mapNodes.Where(t => t.StationType == StationType.Buffer).OrderBy(s => s.RfidId).FirstOrDefault();// (n => n.RfidId == 5);
var node31 = _mapNodes.FirstOrDefault(n => n.RfidId == 31); var node_buff_end = _mapNodes.Where(t => t.StationType == StationType.Buffer).OrderByDescending(s => s.RfidId).FirstOrDefault();//
bool fixpath = false; bool fixpath = false;
Retval = null; Retval = null;
MapNode gatewayNode = 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) if (rlt.Success)
{ {
// 버퍼구간내에 시작과 종료가 모두 포함되어있다 // 버퍼구간내에 시작과 종료가 모두 포함되어있다
@@ -265,53 +265,89 @@ namespace AGVNavigationCore.PathFinding.Planning
if (!fixpath) 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); Retval = this.FindBasicPath(startNode, targetNode, prevNode, prevDir);
} }
else else
{ {
// Gateway Node 찾음
// 4. Start -> Gateway 경로 계산 (A*) //목적지까지 그대로 방향을 계산한다.
var pathToGateway = this.FindBasicPath(startNode, gatewayNode, prevNode, prevDir); var pathToTaget = this.FindBasicPath(startNode, targetNode, prevNode, prevDir);
if (pathToGateway.Success == false) if (pathToTaget.Success == false)
return AGVPathResult.CreateFailure($"Gateway({gatewayNode.ID2})까지 경로 실패: {pathToGateway.Message}"); 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]; var reverseDir = prevDir == AgvDirection.Backward ? AgvDirection.Forward : AgvDirection.Backward;
if (predictNext.Id == prevNode.Id) 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) predictNext = pathToGateway.Path[1];
item.MotorDirection = reverseDir; 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 firstnode = Retval.Path.FirstOrDefault();
var firstDet = Retval.DetailedPath.First(); var firstDet = Retval.DetailedPath.First();
var failmessage = $"[{firstnode.ID2}] 노드의 시작모터 방향({firstDet.MotorDirection})이 올바르지 않습니다"; 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); return AGVPathResult.CreateFailure(failmessage);
else if (firstnode.StationType == StationType.Loader && firstDet.MotorDirection != AgvDirection.Backward) else if (firstnode.StationType == StationType.Loader && firstDet.MotorDirection != AgvDirection.Backward)
return AGVPathResult.CreateFailure(failmessage); 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); 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); return AGVPathResult.CreateFailure(failmessage);
else if (firstnode.StationType == StationType.Buffer) else if (firstnode.StationType == StationType.Buffer)
{ {
@@ -621,7 +657,7 @@ namespace AGVNavigationCore.PathFinding.Planning
if (deltaX > 0) isMonitorLeft = PrevDirection == AgvDirection.Backward; if (deltaX > 0) isMonitorLeft = PrevDirection == AgvDirection.Backward;
else isMonitorLeft = PrevDirection == AgvDirection.Forward; 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; deltaX = GTNode.Position.Y - PrevNode.Position.Y;
if (deltaX < 0) isMonitorLeft = PrevDirection == AgvDirection.Backward; if (deltaX < 0) isMonitorLeft = PrevDirection == AgvDirection.Backward;
@@ -631,14 +667,14 @@ namespace AGVNavigationCore.PathFinding.Planning
switch (targetNode.StationType) switch (targetNode.StationType)
{ {
case StationType.Loader: case StationType.Loader:
case StationType.Charger2: case StationType.Charger:
case StationType.Charger1: case StationType.Cleaner:
case StationType.UnLoader: case StationType.Plating:
case StationType.Clearner:
case StationType.Buffer: case StationType.Buffer:
var rlt1 = new AGVPathResult(); var rlt1 = new AGVPathResult();
rlt1.Success = true; rlt1.Success = true;
//단순 경로를 찾는다
var motdir = targetNode.DockDirection == DockingDirection.Backward ? AgvDirection.Backward : AgvDirection.Forward; var motdir = targetNode.DockDirection == DockingDirection.Backward ? AgvDirection.Backward : AgvDirection.Forward;
var pathtarget = this.FindBasicPath(GTNode, targetNode, PrevNode, motdir); 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; var rfid = 0;
if (node.StationType == StationType.UnLoader) rfid = 10; if (node.StationType == StationType.Cleaner) rfid = 3;
else if (node.StationType == StationType.Charger1) rfid = 9; else if (node.StationType == StationType.Charger) rfid = 3;
else if (node.StationType == StationType.Clearner) rfid = 6; else if (node.StationType == StationType.Plating) rfid = 3;
else if (node.StationType == StationType.Charger2) rfid = 13; else if (node.StationType == StationType.Loader) rfid = 3;
else if (node.StationType == StationType.Loader) rfid = 13; else if (node.StationType == StationType.Buffer) rfid = 3;
else if (node.StationType == StationType.Buffer) rfid = 6;
if (rfid == 0) return null; if (rfid == 0) return null;
return _mapNodes.FirstOrDefault(t => t.RfidId == rfid); return _mapNodes.FirstOrDefault(t => t.RfidId == rfid);

View File

@@ -1596,7 +1596,7 @@ namespace AGVSimulator.Forms
MotorDirection = directionName, MotorDirection = directionName,
CurrentPosition = GetNodeDisplayName(currentNode), CurrentPosition = GetNodeDisplayName(currentNode),
TargetPosition = GetNodeDisplayName(targetNode), TargetPosition = GetNodeDisplayName(targetNode),
DockingPosition = (targetNode.StationType == StationType.Charger1 || targetNode.StationType == StationType.Charger2) ? "충전기" : "장비" DockingPosition = (targetNode.StationType == StationType.Charger ) ? "충전기" : "장비"
}; };
if (calcResult.Success) if (calcResult.Success)

View File

@@ -136,8 +136,7 @@ namespace Project
PUB.sm.SetNewRunStep(ERunStep.ERROR); PUB.sm.SetNewRunStep(ERunStep.ERROR);
} }
break; break;
case AGVNavigationCore.Models.StationType.Charger1: case AGVNavigationCore.Models.StationType.Charger:
case AGVNavigationCore.Models.StationType.Charger2:
break; break;
@@ -145,11 +144,11 @@ namespace Project
PUB.XBE.StepMC = Device.eDocStep.ReadyForEnter; PUB.XBE.StepMC = Device.eDocStep.ReadyForEnter;
break; break;
case AGVNavigationCore.Models.StationType.Clearner: case AGVNavigationCore.Models.StationType.Plating:
PUB.XBE.StepMC = Device.eDocStep.ReadyForEnter; PUB.XBE.StepMC = Device.eDocStep.ReadyForEnter;
break; break;
case AGVNavigationCore.Models.StationType.UnLoader: case AGVNavigationCore.Models.StationType.Cleaner:
PUB.XBE.StepMC = Device.eDocStep.ReadyForEnter; PUB.XBE.StepMC = Device.eDocStep.ReadyForEnter;
break; break;

View File

@@ -39,8 +39,8 @@ namespace Project
{ {
//장비 노드 여부 확인 (버퍼가 아닌 도킹 가능 노드여야 함) //장비 노드 여부 확인 (버퍼가 아닌 도킹 가능 노드여야 함)
if (PUB._virtualAGV.CurrentNode.StationType != StationType.Loader && if (PUB._virtualAGV.CurrentNode.StationType != StationType.Loader &&
PUB._virtualAGV.CurrentNode.StationType != StationType.Clearner && PUB._virtualAGV.CurrentNode.StationType != StationType.Plating &&
PUB._virtualAGV.CurrentNode.StationType != StationType.UnLoader) PUB._virtualAGV.CurrentNode.StationType != StationType.Cleaner)
{ {
SetRunStepError(ENIGProtocol.AGVErrorCode.NOT_EQUIPMENTPOINT, $"[{funcname}-{PUB.sm.RunStepSeq}] 현재 위치가 장비 노드가 아닙니다({PUB._virtualAGV.CurrentNode.StationType})"); SetRunStepError(ENIGProtocol.AGVErrorCode.NOT_EQUIPMENTPOINT, $"[{funcname}-{PUB.sm.RunStepSeq}] 현재 위치가 장비 노드가 아닙니다({PUB._virtualAGV.CurrentNode.StationType})");
return false; return false;

View File

@@ -40,8 +40,8 @@ namespace Project
{ {
//장비 노드 여부 확인 (버퍼가 아닌 도킹 가능 노드여야 함) //장비 노드 여부 확인 (버퍼가 아닌 도킹 가능 노드여야 함)
if (PUB._virtualAGV.CurrentNode.StationType != StationType.Loader && if (PUB._virtualAGV.CurrentNode.StationType != StationType.Loader &&
PUB._virtualAGV.CurrentNode.StationType != StationType.Clearner && PUB._virtualAGV.CurrentNode.StationType != StationType.Plating &&
PUB._virtualAGV.CurrentNode.StationType != StationType.UnLoader) PUB._virtualAGV.CurrentNode.StationType != StationType.Cleaner)
{ {
SetRunStepError(ENIGProtocol.AGVErrorCode.NOT_EQUIPMENTPOINT, $"[{funcname}-{PUB.sm.RunStepSeq}] 현재 위치가 장비 노드가 아닙니다({PUB._virtualAGV.CurrentNode.StationType})"); SetRunStepError(ENIGProtocol.AGVErrorCode.NOT_EQUIPMENTPOINT, $"[{funcname}-{PUB.sm.RunStepSeq}] 현재 위치가 장비 노드가 아닙니다({PUB._virtualAGV.CurrentNode.StationType})");
return false; return false;

View File

@@ -114,9 +114,9 @@ namespace Project
switch (StationType) switch (StationType)
{ {
case StationType.Loader: nextStep = ERunStep.LOADER_IN; break; 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.Buffer: nextStep = ERunStep.BUFFER_IN; break;
case StationType.Clearner: nextStep = ERunStep.CLEANER_IN; break; case StationType.Plating: nextStep = ERunStep.CLEANER_IN; break;
default: default:
PUB.log.AddE($"[{logPrefix}-{cmd}] 해당 노드타입({StationType})은 작업을 지원하지 않습니다."); PUB.log.AddE($"[{logPrefix}-{cmd}] 해당 노드타입({StationType})은 작업을 지원하지 않습니다.");
return; return;
@@ -160,9 +160,9 @@ namespace Project
switch (StationType) switch (StationType)
{ {
case StationType.Loader: nextStep = ERunStep.LOADER_OUT; break; 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.Buffer: nextStep = ERunStep.BUFFER_OUT; break;
case StationType.Clearner: nextStep = ERunStep.CLEANER_OUT; break; case StationType.Plating: nextStep = ERunStep.CLEANER_OUT; break;
default: default:
PUB.log.AddE($"[{logPrefix}-{cmd}] 해당 노드타입({StationType})은 작업을 지원하지 않습니다."); PUB.log.AddE($"[{logPrefix}-{cmd}] 해당 노드타입({StationType})은 작업을 지원하지 않습니다.");
return; return;

View File

@@ -34,7 +34,7 @@ namespace Project.ViewForm
private void InitializeMapCanvas() private void InitializeMapCanvas()
{ {
PUB._mapCanvas.NodeSelect += OnNodeSelected;; PUB._mapCanvas.NodeSelect += OnNodeSelected; ;
// 스플리터 패널에 맵 캔버스 추가 // 스플리터 패널에 맵 캔버스 추가
panel1.Controls.Add(PUB._mapCanvas); panel1.Controls.Add(PUB._mapCanvas);
} }
@@ -49,8 +49,8 @@ namespace Project.ViewForm
// [Run Mode] Left Click: AGV Operation // [Run Mode] Left Click: AGV Operation
if (PUB._mapCanvas.Mode == AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run && e.Button == MouseButtons.Left) if (PUB._mapCanvas.Mode == AGVNavigationCore.Controls.UnifiedAGVCanvas.CanvasMode.Run && e.Button == MouseButtons.Left)
{ {
HandleRunModeClick(mapnode); HandleRunModeClick(mapnode);
return; return;
} }
if (e.Button != MouseButtons.Right) return; if (e.Button != MouseButtons.Right) return;
@@ -71,7 +71,7 @@ namespace Project.ViewForm
menu.Items.Add(pickOff); menu.Items.Add(pickOff);
// Charge // Charge
if (mapnode.StationType == StationType.Charger1 || mapnode.StationType == StationType.Charger2) if (mapnode.StationType == StationType.Charger )
{ {
var charge = new ToolStripMenuItem("Charge (Move & Charge)"); var charge = new ToolStripMenuItem("Charge (Move & Charge)");
charge.Click += (s, args) => ExecuteManualCommand(mapnode, ENIGProtocol.AGVCommandHE.Charger); 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})"); PUB.log.AddI($"[Manual Command] {cmd} to ({targetNode.Id})");
// FindPathResult contains DetailedPath already. // FindPathResult contains DetailedPath already.
// PUB._virtualAGV.SetPath(result); // PUB._virtualAGV.SetPath(result);
PUB._virtualAGV.TargetNode = targetNode as MapNode; PUB._virtualAGV.TargetNode = targetNode as MapNode;
// 3. 작업 설정 // 3. 작업 설정
@@ -141,7 +141,7 @@ namespace Project.ViewForm
PUB.AGV.DataReceive += AGV_DataReceive; PUB.AGV.DataReceive += AGV_DataReceive;
} }
private void AGV_DataReceive(object sender, arDev.Narumi.DataEventArgs e) 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) private void fAuto_VisibleChanged(object sender, EventArgs e)
{ {
this.timer1.Enabled = this.Visible; this.timer1.Enabled = this.Visible;
if (timer1.Enabled) if (timer1.Enabled)
{ {
timer1.Start(); timer1.Start();
} }
@@ -194,7 +194,7 @@ namespace Project.ViewForm
private void timer1_Tick_1(object sender, EventArgs e) private void timer1_Tick_1(object sender, EventArgs e)
{ {
} }
private void HandleRunModeClick(MapNode targetNode) private void HandleRunModeClick(MapNode targetNode)
@@ -204,7 +204,7 @@ namespace Project.ViewForm
ENIGProtocol.AGVCommandHE targetCmd = ENIGProtocol.AGVCommandHE.Goto; ENIGProtocol.AGVCommandHE targetCmd = ENIGProtocol.AGVCommandHE.Goto;
string confirmMsg = ""; 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) if (MessageBox.Show($"[{targetNode.Id}] 충전기로 이동하여 충전을 진행하시겠습니까?", "작업 확인", MessageBoxButtons.YesNo) == DialogResult.Yes)
{ {
@@ -217,7 +217,7 @@ namespace Project.ViewForm
{ {
// Loader, Unloader, Buffer, Cleaner - Pick/Drop Selection // Loader, Unloader, Buffer, Cleaner - Pick/Drop Selection
ContextMenuStrip menu = new ContextMenuStrip(); ContextMenuStrip menu = new ContextMenuStrip();
var pickOn = new ToolStripMenuItem("Pick On (Move & Pick)"); var pickOn = new ToolStripMenuItem("Pick On (Move & Pick)");
pickOn.Click += (s, args) => ExecuteManualCommand(targetNode, ENIGProtocol.AGVCommandHE.PickOnEnter); pickOn.Click += (s, args) => ExecuteManualCommand(targetNode, ENIGProtocol.AGVCommandHE.PickOnEnter);
menu.Items.Add(pickOn); menu.Items.Add(pickOn);
@@ -249,14 +249,14 @@ namespace Project.ViewForm
if (PUB.sm.Step < eSMStep.IDLE) return; if (PUB.sm.Step < eSMStep.IDLE) return;
if (PUB._mapCanvas == null) return; if (PUB._mapCanvas == null) return;
tmrun = true; tmrun = true;
var errmsg = string.Empty; var errmsg = string.Empty;
if(PUB.AGV.IsOpen==false) if (PUB.AGV.IsOpen == false)
{ {
errmsg = "AGV컨트롤러 연결실패"; errmsg = "AGV컨트롤러 연결실패";
} }
else if(PUB.XBE.IsOpen==false ) else if (PUB.XBE.IsOpen == false)
{ {
errmsg = "XBEE 연결실패"; errmsg = "XBEE 연결실패";
} }
@@ -264,11 +264,11 @@ namespace Project.ViewForm
{ {
errmsg = "XBEE 통신불가"; errmsg = "XBEE 통신불가";
} }
else if(PUB.AGV.error.Value > 0) else if (PUB.AGV.error.Value > 0)
{ {
errmsg = PUB.AGV.error.ToString(); errmsg = PUB.AGV.error.ToString();
} }
else if(PUB.AGV.system1.stop_by_front_detect) else if (PUB.AGV.system1.stop_by_front_detect)
{ {
errmsg = "전방 물체감지로 인해 정지"; errmsg = "전방 물체감지로 인해 정지";
} }
@@ -276,11 +276,11 @@ namespace Project.ViewForm
{ {
errmsg = "BMS 연결실패"; errmsg = "BMS 연결실패";
} }
else if(VAR.BOOL[eVarBool.FLAG_AUTORUN]==false) else if (VAR.BOOL[eVarBool.FLAG_AUTORUN] == false)
{ {
errmsg = "자동모드가 아닙니다"; errmsg = "자동모드가 아닙니다";
} }
PUB._mapCanvas.SetAlertMessage(errmsg); PUB._mapCanvas.SetAlertMessage(errmsg);

View File

@@ -21,13 +21,15 @@
"ID2": "0070(*N001)", "ID2": "0070(*N001)",
"Id": "N001", "Id": "N001",
"Type": 0, "Type": 0,
"Position": "421, 271" "Position": "448, 249"
}, },
{ {
"StationType": 2, "StationType": 2,
"CanDocking": true, "CanDocking": true,
"DockDirection": 2, "DockDirection": 2,
"MagnetDirections": {}, "MagnetDirections": {
"5": 0
},
"ConnectedNodes": [ "ConnectedNodes": [
"5" "5"
], ],
@@ -44,7 +46,7 @@
"ID2": "0072(*N010)", "ID2": "0072(*N010)",
"Id": "N010", "Id": "N010",
"Type": 0, "Type": 0,
"Position": "341, 487" "Position": "292, 484"
}, },
{ {
"StationType": 4, "StationType": 4,
@@ -68,7 +70,7 @@
"ID2": "0034(*N018)", "ID2": "0034(*N018)",
"Id": "N018", "Id": "N018",
"Type": 0, "Type": 0,
"Position": "283, 623" "Position": "319, 620"
}, },
{ {
"StationType": 4, "StationType": 4,
@@ -92,7 +94,7 @@
"ID2": "0033(*N005)", "ID2": "0033(*N005)",
"Id": "N005", "Id": "N005",
"Type": 0, "Type": 0,
"Position": "223, 625" "Position": "278, 620"
}, },
{ {
"StationType": 4, "StationType": 4,
@@ -116,7 +118,7 @@
"ID2": "0032(*N020)", "ID2": "0032(*N020)",
"Id": "N020", "Id": "N020",
"Type": 0, "Type": 0,
"Position": "164, 626" "Position": "235, 621"
}, },
{ {
"StationType": 4, "StationType": 4,
@@ -140,7 +142,7 @@
"ID2": "0031(*N021)", "ID2": "0031(*N021)",
"Id": "N021", "Id": "N021",
"Type": 0, "Type": 0,
"Position": "108, 626" "Position": "194, 620"
}, },
{ {
"StationType": 0, "StationType": 0,
@@ -164,7 +166,7 @@
"ID2": "0009(*1)", "ID2": "0009(*1)",
"Id": "1", "Id": "1",
"Type": 0, "Type": 0,
"Position": "338, 625" "Position": "363, 620"
}, },
{ {
"StationType": 0, "StationType": 0,
@@ -188,7 +190,7 @@
"ID2": "0008(*2)", "ID2": "0008(*2)",
"Id": "2", "Id": "2",
"Type": 0, "Type": 0,
"Position": "395, 622" "Position": "404, 620"
}, },
{ {
"StationType": 0, "StationType": 0,
@@ -196,12 +198,12 @@
"DockDirection": 0, "DockDirection": 0,
"MagnetDirections": { "MagnetDirections": {
"N033": 1, "N033": 1,
"N034": 2 "N035": 2
}, },
"ConnectedNodes": [ "ConnectedNodes": [
"2", "2",
"N033", "N033",
"N034" "N035"
], ],
"CanTurnLeft": false, "CanTurnLeft": false,
"CanTurnRight": false, "CanTurnRight": false,
@@ -216,18 +218,19 @@
"ID2": "0007(*4)", "ID2": "0007(*4)",
"Id": "4", "Id": "4",
"Type": 0, "Type": 0,
"Position": "444, 602" "Position": "443, 620"
}, },
{ {
"StationType": 0, "StationType": 0,
"CanDocking": false, "CanDocking": false,
"DockDirection": 0, "DockDirection": 0,
"MagnetDirections": { "MagnetDirections": {
"N031": 1 "N034": 1,
"N010": 0
}, },
"ConnectedNodes": [ "ConnectedNodes": [
"N010", "N010",
"N031" "N034"
], ],
"CanTurnLeft": false, "CanTurnLeft": false,
"CanTurnRight": false, "CanTurnRight": false,
@@ -242,7 +245,7 @@
"ID2": "0005(*5)", "ID2": "0005(*5)",
"Id": "5", "Id": "5",
"Type": 0, "Type": 0,
"Position": "449, 485" "Position": "441, 485"
}, },
{ {
"StationType": 1, "StationType": 1,
@@ -265,17 +268,18 @@
"ID2": "0071(*6)", "ID2": "0071(*6)",
"Id": "6", "Id": "6",
"Type": 0, "Type": 0,
"Position": "523, 585" "Position": "571, 507"
}, },
{ {
"StationType": 0, "StationType": 0,
"CanDocking": false, "CanDocking": false,
"DockDirection": 0, "DockDirection": 0,
"MagnetDirections": {}, "MagnetDirections": {
"N031": 0
},
"ConnectedNodes": [ "ConnectedNodes": [
"6", "6",
"N031", "N031"
"N034"
], ],
"CanTurnLeft": false, "CanTurnLeft": false,
"CanTurnRight": false, "CanTurnRight": false,
@@ -290,7 +294,7 @@
"ID2": "0004(*7)", "ID2": "0004(*7)",
"Id": "7", "Id": "7",
"Type": 0, "Type": 0,
"Position": "524, 530" "Position": "571, 448"
}, },
{ {
"StationType": 5, "StationType": 5,
@@ -313,7 +317,7 @@
"ID2": "0073(*8)", "ID2": "0073(*8)",
"Id": "8", "Id": "8",
"Type": 0, "Type": 0,
"Position": "367, 389" "Position": "327, 381"
}, },
{ {
"StationType": 0, "StationType": 0,
@@ -337,7 +341,7 @@
"ID2": "0002(*9)", "ID2": "0002(*9)",
"Id": "9", "Id": "9",
"Type": 0, "Type": 0,
"Position": "515, 312" "Position": "565, 297"
}, },
{ {
"StationType": 0, "StationType": 0,
@@ -361,10 +365,10 @@
"ID2": "0001(*10)", "ID2": "0001(*10)",
"Id": "10", "Id": "10",
"Type": 0, "Type": 0,
"Position": "479, 270" "Position": "514, 247"
}, },
{ {
"StationType": 7, "StationType": 6,
"CanDocking": false, "CanDocking": false,
"DockDirection": 0, "DockDirection": 0,
"MagnetDirections": {}, "MagnetDirections": {},
@@ -384,7 +388,7 @@
"ID2": "0091(*N027)", "ID2": "0091(*N027)",
"Id": "N027", "Id": "N027",
"Type": 0, "Type": 0,
"Position": "-59, 627" "Position": "70, 620"
}, },
{ {
"StationType": 4, "StationType": 4,
@@ -408,7 +412,7 @@
"ID2": "0035(*N028)", "ID2": "0035(*N028)",
"Id": "N028", "Id": "N028",
"Type": 0, "Type": 0,
"Position": "47, 627" "Position": "153, 620"
}, },
{ {
"StationType": 4, "StationType": 4,
@@ -432,20 +436,20 @@
"ID2": "0036(*N029)", "ID2": "0036(*N029)",
"Id": "N029", "Id": "N029",
"Type": 0, "Type": 0,
"Position": "-7, 629" "Position": "110, 619"
}, },
{ {
"StationType": 0, "StationType": 0,
"CanDocking": false, "CanDocking": false,
"DockDirection": 0, "DockDirection": 0,
"MagnetDirections": { "MagnetDirections": {
"5": 2, "7": 0,
"7": 0 "N034": 2
}, },
"ConnectedNodes": [ "ConnectedNodes": [
"7",
"9", "9",
"5" "7",
"N034"
], ],
"CanTurnLeft": true, "CanTurnLeft": true,
"CanTurnRight": true, "CanTurnRight": true,
@@ -460,13 +464,15 @@
"ID2": "0003(*N031)", "ID2": "0003(*N031)",
"Id": "N031", "Id": "N031",
"Type": 0, "Type": 0,
"Position": "517, 358" "Position": "568, 354"
}, },
{ {
"StationType": 0, "StationType": 0,
"CanDocking": false, "CanDocking": false,
"DockDirection": 0, "DockDirection": 0,
"MagnetDirections": {}, "MagnetDirections": {
"N033": 0
},
"ConnectedNodes": [ "ConnectedNodes": [
"8", "8",
"N033" "N033"
@@ -484,13 +490,16 @@
"ID2": "0006(*N032)", "ID2": "0006(*N032)",
"Id": "N032", "Id": "N032",
"Type": 0, "Type": 0,
"Position": "405, 452" "Position": "368, 445"
}, },
{ {
"StationType": 0, "StationType": 0,
"CanDocking": false, "CanDocking": false,
"DockDirection": 0, "DockDirection": 0,
"MagnetDirections": {}, "MagnetDirections": {
"4": 0,
"N032": 0
},
"ConnectedNodes": [ "ConnectedNodes": [
"N032", "N032",
"4" "4"
@@ -508,19 +517,21 @@
"ID2": "0010(*N033)", "ID2": "0010(*N033)",
"Id": "N033", "Id": "N033",
"Type": 0, "Type": 0,
"Position": "426, 522" "Position": "413, 525"
}, },
{ {
"StationType": 0, "StationType": 0,
"CanDocking": false, "CanDocking": false,
"DockDirection": 0, "DockDirection": 0,
"MagnetDirections": { "MagnetDirections": {
"4": 2, "N035": 0,
"7": 0 "5": 2,
"N031": 1
}, },
"ConnectedNodes": [ "ConnectedNodes": [
"4", "N031",
"7" "N035",
"5"
], ],
"CanTurnLeft": false, "CanTurnLeft": false,
"CanTurnRight": false, "CanTurnRight": false,
@@ -535,7 +546,34 @@
"ID2": "0011(*N034)", "ID2": "0011(*N034)",
"Id": "N034", "Id": "N034",
"Type": 0, "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": [], "Labels": [],
@@ -689,12 +727,12 @@
"Magnets": [ "Magnets": [
{ {
"P1": { "P1": {
"X": 440.0, "X": 439.0,
"Y": 623.0 "Y": 623.0
}, },
"P2": { "P2": {
"X": -56.0, "X": 69.0,
"Y": 627.0 "Y": 621.0
}, },
"ControlPoint": null, "ControlPoint": null,
"Id": "5a0edec2-7ac3-4c99-bbb4-8debde0c1d07", "Id": "5a0edec2-7ac3-4c99-bbb4-8debde0c1d07",
@@ -702,67 +740,67 @@
}, },
{ {
"P1": { "P1": {
"X": 525.0, "X": 569.0,
"Y": 616.0 "Y": 509.0
}, },
"P2": { "P2": {
"X": 516.16556848250036, "X": 565.16556848250036,
"Y": 309.44573330767145 "Y": 295.44573330767145
}, },
"ControlPoint": null, "ControlPoint": null,
"Id": "a5424add-e8c9-483c-a5db-733dca1b8f57", "Id": "a5424add-e8c9-483c-a5db-733dca1b8f57",
"Type": 4 "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": { "P1": {
"X": 481.0, "X": 481.0,
"Y": 486.0 "Y": 486.0
}, },
"P2": { "P2": {
"X": 315.0, "X": 565.0,
"Y": 487.0 "Y": 388.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
}, },
"ControlPoint": null, "ControlPoint": null,
"Id": "N032", "Id": "N032",
@@ -771,11 +809,11 @@
{ {
"P1": { "P1": {
"X": 443.0, "X": 443.0,
"Y": 631.0 "Y": 622.0
}, },
"P2": { "P2": {
"X": 443.0, "X": 442.0,
"Y": 568.0 "Y": 579.0
}, },
"ControlPoint": null, "ControlPoint": null,
"Id": "N031", "Id": "N031",
@@ -784,11 +822,11 @@
{ {
"P1": { "P1": {
"X": 442.0, "X": 442.0,
"Y": 574.0 "Y": 579.0
}, },
"P2": { "P2": {
"X": 397.0, "X": 368.0,
"Y": 432.0 "Y": 446.0
}, },
"ControlPoint": null, "ControlPoint": null,
"Id": "N030", "Id": "N030",
@@ -796,12 +834,12 @@
}, },
{ {
"P1": { "P1": {
"X": 360.0, "X": 327.0,
"Y": 408.0 "Y": 413.0
}, },
"P2": { "P2": {
"X": 402.0, "X": 369.0,
"Y": 439.0 "Y": 444.0
}, },
"ControlPoint": null, "ControlPoint": null,
"Id": "N032", "Id": "N032",
@@ -809,12 +847,12 @@
}, },
{ {
"P1": { "P1": {
"X": 366.0, "X": 327.0,
"Y": 333.0 "Y": 329.0
}, },
"P2": { "P2": {
"X": 366.0, "X": 327.0,
"Y": 415.0 "Y": 411.0
}, },
"ControlPoint": null, "ControlPoint": null,
"Id": "N033", "Id": "N033",
@@ -822,12 +860,12 @@
}, },
{ {
"P1": { "P1": {
"X": 528.0, "X": 484.0,
"Y": 483.0 "Y": 481.0
}, },
"P2": { "P2": {
"X": 446.0, "X": 443.0,
"Y": 591.0 "Y": 578.0
}, },
"ControlPoint": null, "ControlPoint": null,
"Id": "N035", "Id": "N035",
@@ -838,6 +876,6 @@
"BackgroundColorArgb": -14671840, "BackgroundColorArgb": -14671840,
"ShowGrid": false "ShowGrid": false
}, },
"CreatedDate": "2026-02-11T13:07:50.7796603+09:00", "CreatedDate": "2026-02-12T09:53:53.0138602+09:00",
"Version": "1.3" "Version": "1.3"
} }