fix: RFID duplicate validation and correct magnet direction calculation

- Add real-time RFID duplicate validation in map editor with automatic rollback
- Remove RFID auto-assignment to maintain data consistency between editor and simulator
- Fix magnet direction calculation to use actual forward direction angles instead of arbitrary assignment
- Add node names to simulator combo boxes for better identification
- Improve UI layout by drawing connection lines before text for better visibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ChiKyun Kim
2025-09-15 16:31:40 +09:00
parent 1add9ed59a
commit 7f48253770
41 changed files with 4827 additions and 3649 deletions

View File

@@ -70,6 +70,7 @@ namespace AGVSimulator.Forms
this.startSimulationToolStripButton = new System.Windows.Forms.ToolStripButton();
this.stopSimulationToolStripButton = new System.Windows.Forms.ToolStripButton();
this.resetToolStripButton = new System.Windows.Forms.ToolStripButton();
this.btAllReset = new System.Windows.Forms.ToolStripButton();
this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator();
this.fitToMapToolStripButton = new System.Windows.Forms.ToolStripButton();
this.resetZoomToolStripButton = new System.Windows.Forms.ToolStripButton();
@@ -85,6 +86,7 @@ namespace AGVSimulator.Forms
this._clearPathButton = new System.Windows.Forms.Button();
this._startPathButton = new System.Windows.Forms.Button();
this._calculatePathButton = new System.Windows.Forms.Button();
this._avoidRotationCheckBox = new System.Windows.Forms.CheckBox();
this._targetNodeCombo = new System.Windows.Forms.ComboBox();
this.targetNodeLabel = new System.Windows.Forms.Label();
this._startNodeCombo = new System.Windows.Forms.ComboBox();
@@ -93,13 +95,19 @@ namespace AGVSimulator.Forms
this._setPositionButton = new System.Windows.Forms.Button();
this._rfidTextBox = new System.Windows.Forms.TextBox();
this._rfidLabel = new System.Windows.Forms.Label();
this._directionCombo = new System.Windows.Forms.ComboBox();
this._directionLabel = new System.Windows.Forms.Label();
this._stopSimulationButton = new System.Windows.Forms.Button();
this._startSimulationButton = new System.Windows.Forms.Button();
this._removeAgvButton = new System.Windows.Forms.Button();
this._addAgvButton = new System.Windows.Forms.Button();
this._agvListCombo = new System.Windows.Forms.ComboBox();
this._canvasPanel = new System.Windows.Forms.Panel();
this.btAllReset = new System.Windows.Forms.ToolStripButton();
this._agvInfoPanel = new System.Windows.Forms.Panel();
this._agvInfoTitleLabel = new System.Windows.Forms.Label();
this._liftDirectionLabel = new System.Windows.Forms.Label();
this._motorDirectionLabel = new System.Windows.Forms.Label();
this._pathDebugLabel = new System.Windows.Forms.Label();
this._menuStrip.SuspendLayout();
this._toolStrip.SuspendLayout();
this._statusStrip.SuspendLayout();
@@ -107,6 +115,7 @@ namespace AGVSimulator.Forms
this._statusGroup.SuspendLayout();
this._pathGroup.SuspendLayout();
this._agvControlGroup.SuspendLayout();
this._agvInfoPanel.SuspendLayout();
this.SuspendLayout();
//
// _menuStrip
@@ -139,7 +148,7 @@ namespace AGVSimulator.Forms
//
this.openMapToolStripMenuItem.Name = "openMapToolStripMenuItem";
this.openMapToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O)));
this.openMapToolStripMenuItem.Size = new System.Drawing.Size(183, 22);
this.openMapToolStripMenuItem.Size = new System.Drawing.Size(221, 22);
this.openMapToolStripMenuItem.Text = "맵 열기(&O)...";
this.openMapToolStripMenuItem.Click += new System.EventHandler(this.OnOpenMap_Click);
//
@@ -147,33 +156,33 @@ namespace AGVSimulator.Forms
//
this.reloadMapToolStripMenuItem.Name = "reloadMapToolStripMenuItem";
this.reloadMapToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.R)));
this.reloadMapToolStripMenuItem.Size = new System.Drawing.Size(183, 22);
this.reloadMapToolStripMenuItem.Size = new System.Drawing.Size(221, 22);
this.reloadMapToolStripMenuItem.Text = "맵 다시열기(&R)";
this.reloadMapToolStripMenuItem.Click += new System.EventHandler(this.OnReloadMap_Click);
//
// toolStripSeparator1
//
this.toolStripSeparator1.Name = "toolStripSeparator1";
this.toolStripSeparator1.Size = new System.Drawing.Size(180, 6);
this.toolStripSeparator1.Size = new System.Drawing.Size(218, 6);
//
// launchMapEditorToolStripMenuItem
//
this.launchMapEditorToolStripMenuItem.Name = "launchMapEditorToolStripMenuItem";
this.launchMapEditorToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.M)));
this.launchMapEditorToolStripMenuItem.Size = new System.Drawing.Size(183, 22);
this.launchMapEditorToolStripMenuItem.Size = new System.Drawing.Size(221, 22);
this.launchMapEditorToolStripMenuItem.Text = "MapEditor 실행(&M)";
this.launchMapEditorToolStripMenuItem.Click += new System.EventHandler(this.OnLaunchMapEditor_Click);
//
// toolStripSeparator4
//
this.toolStripSeparator4.Name = "toolStripSeparator4";
this.toolStripSeparator4.Size = new System.Drawing.Size(180, 6);
this.toolStripSeparator4.Size = new System.Drawing.Size(218, 6);
//
// exitToolStripMenuItem
//
this.exitToolStripMenuItem.Name = "exitToolStripMenuItem";
this.exitToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Alt | System.Windows.Forms.Keys.F4)));
this.exitToolStripMenuItem.Size = new System.Drawing.Size(183, 22);
this.exitToolStripMenuItem.Size = new System.Drawing.Size(221, 22);
this.exitToolStripMenuItem.Text = "종료(&X)";
this.exitToolStripMenuItem.Click += new System.EventHandler(this.OnExit_Click);
//
@@ -284,7 +293,7 @@ namespace AGVSimulator.Forms
//
this.reloadMapToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.reloadMapToolStripButton.Name = "reloadMapToolStripButton";
this.reloadMapToolStripButton.Size = new System.Drawing.Size(63, 22);
this.reloadMapToolStripButton.Size = new System.Drawing.Size(59, 22);
this.reloadMapToolStripButton.Text = "다시열기";
this.reloadMapToolStripButton.ToolTipText = "현재 맵을 다시 로드합니다";
this.reloadMapToolStripButton.Click += new System.EventHandler(this.OnReloadMap_Click);
@@ -293,7 +302,7 @@ namespace AGVSimulator.Forms
//
this.launchMapEditorToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.launchMapEditorToolStripButton.Name = "launchMapEditorToolStripButton";
this.launchMapEditorToolStripButton.Size = new System.Drawing.Size(71, 22);
this.launchMapEditorToolStripButton.Size = new System.Drawing.Size(66, 22);
this.launchMapEditorToolStripButton.Text = "MapEditor";
this.launchMapEditorToolStripButton.ToolTipText = "MapEditor를 실행합니다";
this.launchMapEditorToolStripButton.Click += new System.EventHandler(this.OnLaunchMapEditor_Click);
@@ -330,6 +339,15 @@ namespace AGVSimulator.Forms
this.resetToolStripButton.ToolTipText = "시뮬레이션을 초기화합니다";
this.resetToolStripButton.Click += new System.EventHandler(this.OnReset_Click);
//
// btAllReset
//
this.btAllReset.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.btAllReset.Name = "btAllReset";
this.btAllReset.Size = new System.Drawing.Size(71, 22);
this.btAllReset.Text = "전체초기화";
this.btAllReset.ToolTipText = "시뮬레이션을 초기화합니다";
this.btAllReset.Click += new System.EventHandler(this.btAllReset_Click);
//
// toolStripSeparator3
//
this.toolStripSeparator3.Name = "toolStripSeparator3";
@@ -382,9 +400,9 @@ namespace AGVSimulator.Forms
this._controlPanel.Controls.Add(this._pathGroup);
this._controlPanel.Controls.Add(this._agvControlGroup);
this._controlPanel.Dock = System.Windows.Forms.DockStyle.Right;
this._controlPanel.Location = new System.Drawing.Point(950, 49);
this._controlPanel.Location = new System.Drawing.Point(967, 49);
this._controlPanel.Name = "_controlPanel";
this._controlPanel.Size = new System.Drawing.Size(250, 729);
this._controlPanel.Size = new System.Drawing.Size(233, 729);
this._controlPanel.TabIndex = 3;
//
// _statusGroup
@@ -392,9 +410,10 @@ namespace AGVSimulator.Forms
this._statusGroup.Controls.Add(this._pathLengthLabel);
this._statusGroup.Controls.Add(this._agvCountLabel);
this._statusGroup.Controls.Add(this._simulationStatusLabel);
this._statusGroup.Location = new System.Drawing.Point(10, 356);
this._statusGroup.Dock = System.Windows.Forms.DockStyle.Top;
this._statusGroup.Location = new System.Drawing.Point(0, 404);
this._statusGroup.Name = "_statusGroup";
this._statusGroup.Size = new System.Drawing.Size(230, 100);
this._statusGroup.Size = new System.Drawing.Size(233, 100);
this._statusGroup.TabIndex = 3;
this._statusGroup.TabStop = false;
this._statusGroup.Text = "상태 정보";
@@ -431,20 +450,22 @@ namespace AGVSimulator.Forms
this._pathGroup.Controls.Add(this._clearPathButton);
this._pathGroup.Controls.Add(this._startPathButton);
this._pathGroup.Controls.Add(this._calculatePathButton);
this._pathGroup.Controls.Add(this._avoidRotationCheckBox);
this._pathGroup.Controls.Add(this._targetNodeCombo);
this._pathGroup.Controls.Add(this.targetNodeLabel);
this._pathGroup.Controls.Add(this._startNodeCombo);
this._pathGroup.Controls.Add(this.startNodeLabel);
this._pathGroup.Location = new System.Drawing.Point(10, 200);
this._pathGroup.Dock = System.Windows.Forms.DockStyle.Top;
this._pathGroup.Location = new System.Drawing.Point(0, 214);
this._pathGroup.Name = "_pathGroup";
this._pathGroup.Size = new System.Drawing.Size(230, 150);
this._pathGroup.Size = new System.Drawing.Size(233, 190);
this._pathGroup.TabIndex = 1;
this._pathGroup.TabStop = false;
this._pathGroup.Text = "경로 제어";
//
// _clearPathButton
//
this._clearPathButton.Location = new System.Drawing.Point(150, 120);
this._clearPathButton.Location = new System.Drawing.Point(150, 148);
this._clearPathButton.Name = "_clearPathButton";
this._clearPathButton.Size = new System.Drawing.Size(70, 25);
this._clearPathButton.TabIndex = 6;
@@ -454,7 +475,7 @@ namespace AGVSimulator.Forms
//
// _startPathButton
//
this._startPathButton.Location = new System.Drawing.Point(80, 120);
this._startPathButton.Location = new System.Drawing.Point(80, 148);
this._startPathButton.Name = "_startPathButton";
this._startPathButton.Size = new System.Drawing.Size(65, 25);
this._startPathButton.TabIndex = 5;
@@ -464,7 +485,7 @@ namespace AGVSimulator.Forms
//
// _calculatePathButton
//
this._calculatePathButton.Location = new System.Drawing.Point(10, 120);
this._calculatePathButton.Location = new System.Drawing.Point(10, 148);
this._calculatePathButton.Name = "_calculatePathButton";
this._calculatePathButton.Size = new System.Drawing.Size(65, 25);
this._calculatePathButton.TabIndex = 4;
@@ -472,10 +493,20 @@ namespace AGVSimulator.Forms
this._calculatePathButton.UseVisualStyleBackColor = true;
this._calculatePathButton.Click += new System.EventHandler(this.OnCalculatePath_Click);
//
// _avoidRotationCheckBox
//
this._avoidRotationCheckBox.AutoSize = true;
this._avoidRotationCheckBox.Location = new System.Drawing.Point(10, 126);
this._avoidRotationCheckBox.Name = "_avoidRotationCheckBox";
this._avoidRotationCheckBox.Size = new System.Drawing.Size(104, 16);
this._avoidRotationCheckBox.TabIndex = 7;
this._avoidRotationCheckBox.Text = "회전 구간 회피";
this._avoidRotationCheckBox.UseVisualStyleBackColor = true;
//
// _targetNodeCombo
//
this._targetNodeCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this._targetNodeCombo.Location = new System.Drawing.Point(10, 95);
this._targetNodeCombo.Location = new System.Drawing.Point(10, 97);
this._targetNodeCombo.Name = "_targetNodeCombo";
this._targetNodeCombo.Size = new System.Drawing.Size(210, 20);
this._targetNodeCombo.TabIndex = 3;
@@ -511,14 +542,17 @@ namespace AGVSimulator.Forms
this._agvControlGroup.Controls.Add(this._setPositionButton);
this._agvControlGroup.Controls.Add(this._rfidTextBox);
this._agvControlGroup.Controls.Add(this._rfidLabel);
this._agvControlGroup.Controls.Add(this._directionCombo);
this._agvControlGroup.Controls.Add(this._directionLabel);
this._agvControlGroup.Controls.Add(this._stopSimulationButton);
this._agvControlGroup.Controls.Add(this._startSimulationButton);
this._agvControlGroup.Controls.Add(this._removeAgvButton);
this._agvControlGroup.Controls.Add(this._addAgvButton);
this._agvControlGroup.Controls.Add(this._agvListCombo);
this._agvControlGroup.Location = new System.Drawing.Point(10, 10);
this._agvControlGroup.Dock = System.Windows.Forms.DockStyle.Top;
this._agvControlGroup.Location = new System.Drawing.Point(0, 0);
this._agvControlGroup.Name = "_agvControlGroup";
this._agvControlGroup.Size = new System.Drawing.Size(230, 180);
this._agvControlGroup.Size = new System.Drawing.Size(233, 214);
this._agvControlGroup.TabIndex = 0;
this._agvControlGroup.TabStop = false;
this._agvControlGroup.Text = "AGV 제어";
@@ -527,7 +561,7 @@ namespace AGVSimulator.Forms
//
this._setPositionButton.Location = new System.Drawing.Point(160, 138);
this._setPositionButton.Name = "_setPositionButton";
this._setPositionButton.Size = new System.Drawing.Size(60, 25);
this._setPositionButton.Size = new System.Drawing.Size(60, 67);
this._setPositionButton.TabIndex = 7;
this._setPositionButton.Text = "위치설정";
this._setPositionButton.UseVisualStyleBackColor = true;
@@ -550,6 +584,24 @@ namespace AGVSimulator.Forms
this._rfidLabel.TabIndex = 5;
this._rfidLabel.Text = "RFID 현재위치:";
//
// _directionCombo
//
this._directionCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this._directionCombo.FormattingEnabled = true;
this._directionCombo.Location = new System.Drawing.Point(10, 185);
this._directionCombo.Name = "_directionCombo";
this._directionCombo.Size = new System.Drawing.Size(140, 20);
this._directionCombo.TabIndex = 8;
//
// _directionLabel
//
this._directionLabel.AutoSize = true;
this._directionLabel.Location = new System.Drawing.Point(10, 165);
this._directionLabel.Name = "_directionLabel";
this._directionLabel.Size = new System.Drawing.Size(85, 12);
this._directionLabel.TabIndex = 9;
this._directionLabel.Text = "모터 구동방향:";
//
// _stopSimulationButton
//
this._stopSimulationButton.Location = new System.Drawing.Point(120, 85);
@@ -602,19 +654,65 @@ namespace AGVSimulator.Forms
// _canvasPanel
//
this._canvasPanel.Dock = System.Windows.Forms.DockStyle.Fill;
this._canvasPanel.Location = new System.Drawing.Point(0, 49);
this._canvasPanel.Location = new System.Drawing.Point(0, 109);
this._canvasPanel.Name = "_canvasPanel";
this._canvasPanel.Size = new System.Drawing.Size(950, 729);
this._canvasPanel.Size = new System.Drawing.Size(967, 669);
this._canvasPanel.TabIndex = 4;
//
// btAllReset
// _agvInfoPanel
//
this.btAllReset.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.btAllReset.Name = "btAllReset";
this.btAllReset.Size = new System.Drawing.Size(71, 22);
this.btAllReset.Text = "전체초기화";
this.btAllReset.ToolTipText = "시뮬레이션을 초기화합니다";
this.btAllReset.Click += new System.EventHandler(this.btAllReset_Click);
this._agvInfoPanel.BackColor = System.Drawing.Color.LightBlue;
this._agvInfoPanel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this._agvInfoPanel.Controls.Add(this._agvInfoTitleLabel);
this._agvInfoPanel.Controls.Add(this._liftDirectionLabel);
this._agvInfoPanel.Controls.Add(this._motorDirectionLabel);
this._agvInfoPanel.Controls.Add(this._pathDebugLabel);
this._agvInfoPanel.Dock = System.Windows.Forms.DockStyle.Top;
this._agvInfoPanel.Location = new System.Drawing.Point(0, 49);
this._agvInfoPanel.Name = "_agvInfoPanel";
this._agvInfoPanel.Size = new System.Drawing.Size(967, 60);
this._agvInfoPanel.TabIndex = 5;
//
// _agvInfoTitleLabel
//
this._agvInfoTitleLabel.AutoSize = true;
this._agvInfoTitleLabel.Font = new System.Drawing.Font("맑은 고딕", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this._agvInfoTitleLabel.Location = new System.Drawing.Point(10, 12);
this._agvInfoTitleLabel.Name = "_agvInfoTitleLabel";
this._agvInfoTitleLabel.Size = new System.Drawing.Size(91, 15);
this._agvInfoTitleLabel.TabIndex = 0;
this._agvInfoTitleLabel.Text = "AGV 상태 정보:";
//
// _liftDirectionLabel
//
this._liftDirectionLabel.AutoSize = true;
this._liftDirectionLabel.Font = new System.Drawing.Font("맑은 고딕", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this._liftDirectionLabel.Location = new System.Drawing.Point(120, 12);
this._liftDirectionLabel.Name = "_liftDirectionLabel";
this._liftDirectionLabel.Size = new System.Drawing.Size(83, 15);
this._liftDirectionLabel.TabIndex = 1;
this._liftDirectionLabel.Text = "리프트 방향: -";
//
// _motorDirectionLabel
//
this._motorDirectionLabel.AutoSize = true;
this._motorDirectionLabel.Font = new System.Drawing.Font("맑은 고딕", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this._motorDirectionLabel.Location = new System.Drawing.Point(250, 12);
this._motorDirectionLabel.Name = "_motorDirectionLabel";
this._motorDirectionLabel.Size = new System.Drawing.Size(71, 15);
this._motorDirectionLabel.TabIndex = 2;
this._motorDirectionLabel.Text = "모터 방향: -";
//
// _pathDebugLabel
//
this._pathDebugLabel.AutoSize = true;
this._pathDebugLabel.Font = new System.Drawing.Font("맑은 고딕", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this._pathDebugLabel.ForeColor = System.Drawing.Color.DarkBlue;
this._pathDebugLabel.Location = new System.Drawing.Point(10, 30);
this._pathDebugLabel.Name = "_pathDebugLabel";
this._pathDebugLabel.Size = new System.Drawing.Size(114, 15);
this._pathDebugLabel.TabIndex = 3;
this._pathDebugLabel.Text = "경로: 설정되지 않음";
//
// SimulatorForm
//
@@ -622,6 +720,7 @@ namespace AGVSimulator.Forms
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1200, 800);
this.Controls.Add(this._canvasPanel);
this.Controls.Add(this._agvInfoPanel);
this.Controls.Add(this._controlPanel);
this.Controls.Add(this._statusStrip);
this.Controls.Add(this._toolStrip);
@@ -644,6 +743,8 @@ namespace AGVSimulator.Forms
this._pathGroup.PerformLayout();
this._agvControlGroup.ResumeLayout(false);
this._agvControlGroup.PerformLayout();
this._agvInfoPanel.ResumeLayout(false);
this._agvInfoPanel.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
@@ -692,6 +793,7 @@ namespace AGVSimulator.Forms
private System.Windows.Forms.Button _calculatePathButton;
private System.Windows.Forms.Button _startPathButton;
private System.Windows.Forms.Button _clearPathButton;
private System.Windows.Forms.CheckBox _avoidRotationCheckBox;
private System.Windows.Forms.GroupBox _statusGroup;
private System.Windows.Forms.Label _simulationStatusLabel;
private System.Windows.Forms.Label _agvCountLabel;
@@ -700,11 +802,18 @@ namespace AGVSimulator.Forms
private System.Windows.Forms.Label _rfidLabel;
private System.Windows.Forms.TextBox _rfidTextBox;
private System.Windows.Forms.Button _setPositionButton;
private System.Windows.Forms.ComboBox _directionCombo;
private System.Windows.Forms.Label _directionLabel;
private System.Windows.Forms.ToolStripButton btAllReset;
private System.Windows.Forms.ToolStripMenuItem reloadMapToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem launchMapEditorToolStripMenuItem;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator4;
private System.Windows.Forms.ToolStripButton reloadMapToolStripButton;
private System.Windows.Forms.ToolStripButton launchMapEditorToolStripButton;
private System.Windows.Forms.Panel _agvInfoPanel;
private System.Windows.Forms.Label _liftDirectionLabel;
private System.Windows.Forms.Label _motorDirectionLabel;
private System.Windows.Forms.Label _agvInfoTitleLabel;
private System.Windows.Forms.Label _pathDebugLabel;
}
}

View File

@@ -25,6 +25,7 @@ namespace AGVSimulator.Forms
private List<MapNode> _mapNodes;
private NodeResolver _nodeResolver;
private PathCalculator _pathCalculator;
private AdvancedAGVPathfinder _advancedPathfinder;
private List<VirtualAGV> _agvList;
private SimulationState _simulationState;
private Timer _simulationTimer;
@@ -50,6 +51,9 @@ namespace AGVSimulator.Forms
{
InitializeComponent();
InitializeForm();
// Load 이벤트 연결
this.Load += SimulatorForm_Load;
}
#endregion
@@ -80,6 +84,8 @@ namespace AGVSimulator.Forms
// 초기 상태 설정
UpdateUI();
// 마지막 맵 파일 자동 로드 확인은 Form_Load에서 수행
}
@@ -121,6 +127,12 @@ namespace AGVSimulator.Forms
#region Event Handlers
private void SimulatorForm_Load(object sender, EventArgs e)
{
// 폼이 완전히 로드된 후 마지막 맵 파일 자동 로드 확인
CheckAndLoadLastMapFile();
}
private void OnOpenMap_Click(object sender, EventArgs e)
{
using (var openDialog = new OpenFileDialog())
@@ -279,7 +291,39 @@ namespace AGVSimulator.Forms
_pathCalculator.SetMapData(_mapNodes);
}
var agvResult = _pathCalculator.FindAGVPath(startNode.NodeId, targetNode.NodeId);
if (_advancedPathfinder == null)
{
_advancedPathfinder = new AdvancedAGVPathfinder(_mapNodes);
}
// 현재 AGV 방향 가져오기
var selectedAGV = _agvListCombo.SelectedItem as VirtualAGV;
var currentDirection = selectedAGV?.CurrentDirection ?? AgvDirection.Forward;
// 고급 경로 계획 사용
var advancedResult = _advancedPathfinder.FindAdvancedPath(startNode.NodeId, targetNode.NodeId, currentDirection);
if (advancedResult.Success)
{
// 고급 경로 결과를 기존 AGVPathResult 형태로 변환
var agvResult1 = ConvertToAGVPathResult(advancedResult);
_simulatorCanvas.CurrentPath = agvResult1.ToPathResult();
_pathLengthLabel.Text = $"경로 길이: {advancedResult.TotalDistance:F1}";
_statusLabel.Text = $"고급 경로 계산 완료 ({advancedResult.CalculationTimeMs}ms)";
// 고급 경로 디버깅 정보 표시
UpdateAdvancedPathDebugInfo(advancedResult);
return;
}
// 고급 경로 실패시 기존 방식으로 fallback
// 회전 회피 옵션 설정
var options = _avoidRotationCheckBox.Checked
? PathfindingOptions.AvoidRotation
: PathfindingOptions.Default;
var agvResult = _pathCalculator.FindAGVPath(startNode.NodeId, targetNode.NodeId, null, options);
if (agvResult.Success)
{
@@ -434,8 +478,12 @@ namespace AGVSimulator.Forms
if (nodesWithRfid.Count == 0)
return "RFID가 할당된 노드가 없습니다.";
// 처음 10개의 RFID만 표시
var rfidList = nodesWithRfid.Take(10).Select(n => $"- {n.RfidId} → {n.NodeId}");
// 처음 10개의 RFID만 표시 (노드 이름 포함)
var rfidList = nodesWithRfid.Take(10).Select(n =>
{
var nodeNamePart = !string.IsNullOrEmpty(n.Name) ? $" {n.Name}" : "";
return $"- {n.RfidId} → {n.NodeId}{nodeNamePart}";
});
var result = string.Join("\n", rfidList);
if (nodesWithRfid.Count > 10)
@@ -457,8 +505,7 @@ namespace AGVSimulator.Forms
_mapNodes = result.Nodes;
_currentMapFilePath = filePath;
// RFID가 없는 노드들에 자동 할당
MapLoader.AssignAutoRfidIds(_mapNodes);
// RFID 자동 할당 제거 - 에디터에서 설정한 값 그대로 사용
// 시뮬레이터 캔버스에 맵 설정
_simulatorCanvas.Nodes = _mapNodes;
@@ -488,6 +535,35 @@ namespace AGVSimulator.Forms
}
}
/// <summary>
/// 마지막 맵 파일이 있는지 확인하고 사용자에게 로드할지 물어봄
/// </summary>
private void CheckAndLoadLastMapFile()
{
if (_config.AutoLoadLastMapFile && _config.HasValidLastMapFile())
{
string fileName = Path.GetFileName(_config.LastMapFilePath);
var result = MessageBox.Show(
$"마지막으로 사용한 맵 파일을 찾았습니다:\n\n{fileName}\n\n이 파일을 열까요?",
"마지막 맵 파일 로드",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
try
{
LoadMapFile(_config.LastMapFilePath);
}
catch (Exception ex)
{
MessageBox.Show($"맵 파일 로드 중 오류가 발생했습니다:\n{ex.Message}",
"맵 파일 로드 오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
private void UpdateNodeComboBoxes()
{
_startNodeCombo.Items.Clear();
@@ -499,8 +575,9 @@ namespace AGVSimulator.Forms
{
if (node.IsActive && node.HasRfid())
{
// {rfid} - [{node}] 형식으로 ComboBoxItem 생성
var displayText = $"{node.RfidId} - [{node.NodeId}]";
// {rfid} - [{node}] {name} 형식으로 ComboBoxItem 생성
var nodeNamePart = !string.IsNullOrEmpty(node.Name) ? $" {node.Name}" : "";
var displayText = $"{node.RfidId} - [{node.NodeId}]{nodeNamePart}";
var item = new ComboBoxItem<MapNode>(node, displayText);
_startNodeCombo.Items.Add(item);
@@ -709,6 +786,111 @@ namespace AGVSimulator.Forms
return node?.HasRfid() == true ? node.RfidId : nodeId;
}
/// <summary>
/// 고급 경로 결과를 기존 AGVPathResult 형태로 변환
/// </summary>
private AGVPathResult ConvertToAGVPathResult(AdvancedAGVPathfinder.AdvancedPathResult advancedResult)
{
var agvResult = new AGVPathResult();
agvResult.Success = advancedResult.Success;
agvResult.Path = advancedResult.GetSimplePath();
agvResult.NodeMotorInfos = advancedResult.DetailedPath;
agvResult.TotalDistance = advancedResult.TotalDistance;
agvResult.CalculationTimeMs = advancedResult.CalculationTimeMs;
agvResult.ExploredNodes = advancedResult.ExploredNodeCount;
agvResult.ErrorMessage = advancedResult.ErrorMessage;
return agvResult;
}
/// <summary>
/// 고급 경로 디버깅 정보 업데이트
/// </summary>
private void UpdateAdvancedPathDebugInfo(AdvancedAGVPathfinder.AdvancedPathResult advancedResult)
{
if (advancedResult == null || !advancedResult.Success)
{
_pathDebugLabel.Text = "고급 경로: 설정되지 않음";
return;
}
// 노드 ID를 RFID로 변환한 경로 생성
var pathWithRfid = advancedResult.GetSimplePath().Select(nodeId => GetRfidByNodeId(nodeId)).ToList();
// 콘솔 디버그 정보 출력
Program.WriteLine($"[ADVANCED DEBUG] 고급 경로 계산 완료:");
Program.WriteLine($" 전체 경로 (RFID): [{string.Join(" ", pathWithRfid)}]");
Program.WriteLine($" 전체 경로 (NodeID): [{string.Join(" ", advancedResult.GetSimplePath())}]");
Program.WriteLine($" 경로 노드 수: {advancedResult.DetailedPath.Count}");
Program.WriteLine($" 방향 전환 필요: {advancedResult.RequiredDirectionChange}");
if (advancedResult.RequiredDirectionChange && !string.IsNullOrEmpty(advancedResult.DirectionChangeNode))
{
Program.WriteLine($" 방향 전환 노드: {advancedResult.DirectionChangeNode}");
}
Program.WriteLine($" 설명: {advancedResult.PlanDescription}");
// 상세 경로 정보 출력
for (int i = 0; i < advancedResult.DetailedPath.Count; i++)
{
var info = advancedResult.DetailedPath[i];
var rfidId = GetRfidByNodeId(info.NodeId);
var nextRfidId = info.NextNodeId != null ? GetRfidByNodeId(info.NextNodeId) : "END";
var flags = new List<string>();
if (info.CanRotate) flags.Add("회전가능");
if (info.IsDirectionChangePoint) flags.Add("방향전환");
if (info.RequiresSpecialAction) flags.Add($"특수동작:{info.SpecialActionDescription}");
if (info.MagnetDirection != MagnetDirection.Straight) flags.Add($"마그넷:{info.MagnetDirection}");
var flagsStr = flags.Count > 0 ? $" [{string.Join(", ", flags)}]" : "";
Program.WriteLine($" {i}: {rfidId}({info.NodeId}) → {info.MotorDirection} → {nextRfidId}{flagsStr}");
}
// 경로 문자열 구성 (마그넷 방향 포함)
var pathWithDetails = new List<string>();
for (int i = 0; i < advancedResult.DetailedPath.Count; i++)
{
var motorInfo = advancedResult.DetailedPath[i];
var rfidId = GetRfidByNodeId(motorInfo.NodeId);
string motorSymbol = motorInfo.MotorDirection == AgvDirection.Forward ? "[전진]" : "[후진]";
// 마그넷 방향 표시
if (motorInfo.MagnetDirection != MagnetDirection.Straight)
{
string magnetSymbol = motorInfo.MagnetDirection == MagnetDirection.Left ? "[←]" : "[→]";
motorSymbol += magnetSymbol;
}
// 특수 동작 표시
if (motorInfo.RequiresSpecialAction)
motorSymbol += "[🔄]";
else if (motorInfo.IsDirectionChangePoint && motorInfo.CanRotate)
motorSymbol += "[↻]";
pathWithDetails.Add($"{rfidId}{motorSymbol}");
}
string pathString = string.Join(" → ", pathWithDetails);
// UI에 표시 (길이 제한)
if (pathString.Length > 100)
{
pathString = pathString.Substring(0, 97) + "...";
}
// 통계 정보
var forwardCount = advancedResult.DetailedPath.Count(m => m.MotorDirection == AgvDirection.Forward);
var backwardCount = advancedResult.DetailedPath.Count(m => m.MotorDirection == AgvDirection.Backward);
var magnetDirectionChanges = advancedResult.DetailedPath.Count(m => m.MagnetDirection != MagnetDirection.Straight);
string stats = $"전진: {forwardCount}, 후진: {backwardCount}";
if (magnetDirectionChanges > 0)
stats += $", 마그넷제어: {magnetDirectionChanges}";
_pathDebugLabel.Text = $"고급경로: {pathString} (총 {advancedResult.DetailedPath.Count}개 노드, {advancedResult.TotalDistance:F1}px, {stats})";
}
/// <summary>
/// 경로 디버깅 정보 업데이트 (RFID 값 표시, 모터방향 정보 포함)
/// </summary>
@@ -736,7 +918,14 @@ namespace AGVSimulator.Forms
var info = agvResult.NodeMotorInfos[i];
var rfidId = GetRfidByNodeId(info.NodeId);
var nextRfidId = info.NextNodeId != null ? GetRfidByNodeId(info.NextNodeId) : "END";
Program.WriteLine($" {i}: {rfidId}({info.NodeId}) → {info.MotorDirection} → {nextRfidId}");
var flags = new List<string>();
if (info.CanRotate) flags.Add("회전가능");
if (info.IsDirectionChangePoint) flags.Add("방향전환");
if (info.RequiresSpecialAction) flags.Add($"특수동작:{info.SpecialActionDescription}");
var flagsStr = flags.Count > 0 ? $" [{string.Join(", ", flags)}]" : "";
Program.WriteLine($" {i}: {rfidId}({info.NodeId}) → {info.MotorDirection} → {nextRfidId}{flagsStr}");
}
}
@@ -751,6 +940,13 @@ namespace AGVSimulator.Forms
var motorInfo = agvResult.NodeMotorInfos[i];
var rfidId = GetRfidByNodeId(motorInfo.NodeId);
string motorSymbol = motorInfo.MotorDirection == AgvDirection.Forward ? "[전진]" : "[후진]";
// 특수 동작 표시 추가
if (motorInfo.RequiresSpecialAction)
motorSymbol += "[🔄]";
else if (motorInfo.IsDirectionChangePoint && motorInfo.CanRotate)
motorSymbol += "[↻]";
pathWithMotorInfo.Add($"{rfidId}{motorSymbol}");
}
pathString = string.Join(" → ", pathWithMotorInfo);