enhance: Display RFID values in path UI and improve AGV lift direction visualization
- Replace NodeID with RFID values in path display for better field mapping
- Add ComboBoxItem<T> class for {rfid} - [{node}] format in combo boxes
- Implement GetRfidByNodeId helper method for NodeID to RFID conversion
- Enhanced UpdatePathDebugInfo to show both RFID and NodeID information
- Improved path visualization with RFID-based route display
- Users can now easily match displayed paths with physical RFID tags
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ using System.Windows.Forms;
|
||||
using AGVMapEditor.Models;
|
||||
using AGVNavigationCore.Models;
|
||||
using AGVNavigationCore.Controls;
|
||||
using AGVNavigationCore.PathFinding;
|
||||
using AGVSimulator.Models;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@@ -59,21 +60,24 @@ namespace AGVSimulator.Forms
|
||||
{
|
||||
// 설정 로드
|
||||
_config = SimulatorConfig.Load();
|
||||
|
||||
|
||||
// 데이터 초기화
|
||||
_mapNodes = new List<MapNode>();
|
||||
_agvList = new List<VirtualAGV>();
|
||||
_simulationState = new SimulationState();
|
||||
_currentMapFilePath = string.Empty;
|
||||
|
||||
|
||||
// 시뮬레이터 캔버스 생성 (중앙 패널에만)
|
||||
CreateSimulatorCanvas();
|
||||
|
||||
|
||||
// 타이머 초기화
|
||||
_simulationTimer = new Timer();
|
||||
_simulationTimer.Interval = 100; // 100ms 간격
|
||||
_simulationTimer.Tick += OnSimulationTimer_Tick;
|
||||
|
||||
|
||||
// 방향 콤보박스 초기화
|
||||
InitializeDirectionCombo();
|
||||
|
||||
// 초기 상태 설정
|
||||
UpdateUI();
|
||||
}
|
||||
@@ -85,7 +89,7 @@ namespace AGVSimulator.Forms
|
||||
_simulatorCanvas = new UnifiedAGVCanvas();
|
||||
_simulatorCanvas.Dock = DockStyle.Fill;
|
||||
_simulatorCanvas.Mode = UnifiedAGVCanvas.CanvasMode.ViewOnly;
|
||||
|
||||
|
||||
_canvasPanel.Controls.Add(_simulatorCanvas);
|
||||
}
|
||||
|
||||
@@ -95,6 +99,24 @@ namespace AGVSimulator.Forms
|
||||
_canvasPanel.BringToFront();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모터 구동방향 콤보박스 초기화
|
||||
/// </summary>
|
||||
private void InitializeDirectionCombo()
|
||||
{
|
||||
_directionCombo.Items.Clear();
|
||||
|
||||
// AgvDirection enum 값들을 콤보박스에 추가
|
||||
_directionCombo.Items.Add(new DirectionItem(AgvDirection.Forward, "전진 (모니터쪽)"));
|
||||
_directionCombo.Items.Add(new DirectionItem(AgvDirection.Backward, "후진 (리프트쪽)"));
|
||||
_directionCombo.Items.Add(new DirectionItem(AgvDirection.Left, "좌회전"));
|
||||
_directionCombo.Items.Add(new DirectionItem(AgvDirection.Right, "우회전"));
|
||||
_directionCombo.Items.Add(new DirectionItem(AgvDirection.Stop, "정지"));
|
||||
|
||||
// 기본 선택: 전진
|
||||
_directionCombo.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
@@ -105,7 +127,7 @@ namespace AGVSimulator.Forms
|
||||
{
|
||||
openDialog.Filter = "AGV Map Files (*.agvmap)|*.agvmap|모든 파일 (*.*)|*.*";
|
||||
openDialog.Title = "맵 파일 열기";
|
||||
|
||||
|
||||
if (openDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
try
|
||||
@@ -115,7 +137,7 @@ namespace AGVSimulator.Forms
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"맵 파일을 로드할 수 없습니다:\n{ex.Message}", "오류",
|
||||
MessageBox.Show($"맵 파일을 로드할 수 없습니다:\n{ex.Message}", "오류",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
@@ -131,10 +153,11 @@ namespace AGVSimulator.Forms
|
||||
{
|
||||
if (_simulationState.IsRunning)
|
||||
return;
|
||||
|
||||
|
||||
_simulationState.IsRunning = true;
|
||||
_simulationTimer.Start();
|
||||
_statusLabel.Text = "시뮬레이션 실행 중";
|
||||
Console.WriteLine("시뮬레이션 실행");
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
@@ -142,16 +165,17 @@ namespace AGVSimulator.Forms
|
||||
{
|
||||
if (!_simulationState.IsRunning)
|
||||
return;
|
||||
|
||||
|
||||
_simulationState.IsRunning = false;
|
||||
_simulationTimer.Stop();
|
||||
_statusLabel.Text = "시뮬레이션 정지";
|
||||
Console.WriteLine("시뮬레이션 정지");
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
private void OnReset_Click(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void OnFitToMap_Click(object sender, EventArgs e)
|
||||
@@ -166,7 +190,7 @@ namespace AGVSimulator.Forms
|
||||
|
||||
private void OnAbout_Click(object sender, EventArgs e)
|
||||
{
|
||||
MessageBox.Show("AGV 시뮬레이터 v1.0\n\nENIG AGV 시스템용 시뮬레이터", "정보",
|
||||
MessageBox.Show("AGV 시뮬레이터 v1.0\n\nENIG AGV 시스템용 시뮬레이터", "정보",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
|
||||
@@ -177,17 +201,26 @@ namespace AGVSimulator.Forms
|
||||
MessageBox.Show("먼저 맵을 로드해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var agvId = $"AGV{_agvList.Count + 1:D2}";
|
||||
var startPosition = _mapNodes.First().Position; // 첫 번째 노드에서 시작
|
||||
|
||||
|
||||
var newAGV = new VirtualAGV(agvId, startPosition);
|
||||
_agvList.Add(newAGV);
|
||||
_simulatorCanvas.AGVList = new List<IAGV>(_agvList.Cast<IAGV>());
|
||||
|
||||
|
||||
// 콘솔 출력
|
||||
|
||||
Program.WriteLine($"[SYSTEM] AGV 추가:");
|
||||
Program.WriteLine($" AGV ID: {agvId}");
|
||||
Program.WriteLine($" 시작 위치: ({startPosition.X}, {startPosition.Y})");
|
||||
Program.WriteLine($" 총 AGV 수: {_agvList.Count}");
|
||||
Program.WriteLine("");
|
||||
|
||||
|
||||
UpdateAGVComboBox();
|
||||
UpdateUI();
|
||||
|
||||
|
||||
_statusLabel.Text = $"{agvId} 추가됨";
|
||||
}
|
||||
|
||||
@@ -195,16 +228,23 @@ namespace AGVSimulator.Forms
|
||||
{
|
||||
if (_agvListCombo.SelectedItem == null)
|
||||
return;
|
||||
|
||||
|
||||
var selectedAGV = _agvListCombo.SelectedItem as VirtualAGV;
|
||||
if (selectedAGV != null)
|
||||
{
|
||||
_agvList.Remove(selectedAGV);
|
||||
_simulatorCanvas.AGVList = new List<IAGV>(_agvList.Cast<IAGV>());
|
||||
|
||||
|
||||
// 콘솔 출력
|
||||
Console.WriteLine($"[SYSTEM] AGV 제거:");
|
||||
Console.WriteLine($" AGV ID: {selectedAGV.AgvId}");
|
||||
Console.WriteLine($" 남은 AGV 수: {_agvList.Count}");
|
||||
Console.WriteLine("");
|
||||
|
||||
|
||||
UpdateAGVComboBox();
|
||||
UpdateUI();
|
||||
|
||||
|
||||
_statusLabel.Text = $"{selectedAGV.AgvId} 제거됨";
|
||||
}
|
||||
}
|
||||
@@ -221,27 +261,41 @@ namespace AGVSimulator.Forms
|
||||
MessageBox.Show("시작 RFID와 목표 RFID를 선택해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var startItem = _startNodeCombo.SelectedItem as ComboBoxItem<MapNode>;
|
||||
var targetItem = _targetNodeCombo.SelectedItem as ComboBoxItem<MapNode>;
|
||||
var startNode = startItem?.Value;
|
||||
var targetNode = targetItem?.Value;
|
||||
|
||||
var startNode = _startNodeCombo.SelectedItem as MapNode;
|
||||
var targetNode = _targetNodeCombo.SelectedItem as MapNode;
|
||||
|
||||
if (startNode == null || targetNode == null)
|
||||
{
|
||||
MessageBox.Show("선택한 노드 정보가 올바르지 않습니다.", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pathCalculator == null)
|
||||
{
|
||||
_pathCalculator = new PathCalculator();
|
||||
_pathCalculator.SetMapData(_mapNodes);
|
||||
}
|
||||
|
||||
|
||||
var agvResult = _pathCalculator.FindAGVPath(startNode.NodeId, targetNode.NodeId);
|
||||
|
||||
|
||||
if (agvResult.Success)
|
||||
{
|
||||
_simulatorCanvas.CurrentPath = agvResult.ToPathResult();
|
||||
_pathLengthLabel.Text = $"경로 길이: {agvResult.TotalDistance:F1}";
|
||||
_statusLabel.Text = $"경로 계산 완료 ({agvResult.CalculationTimeMs}ms)";
|
||||
|
||||
// 경로 디버깅 정보 표시
|
||||
UpdatePathDebugInfo(agvResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show($"경로를 찾을 수 없습니다:\n{agvResult.ErrorMessage}", "경로 계산 실패",
|
||||
// 경로 실패시 디버깅 정보 초기화
|
||||
_pathDebugLabel.Text = $"경로: 실패 - {agvResult.ErrorMessage}";
|
||||
|
||||
MessageBox.Show($"경로를 찾을 수 없습니다:\n{agvResult.ErrorMessage}", "경로 계산 실패",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
}
|
||||
@@ -254,13 +308,13 @@ namespace AGVSimulator.Forms
|
||||
MessageBox.Show("AGV를 선택해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (_simulatorCanvas.CurrentPath == null || !_simulatorCanvas.CurrentPath.Success)
|
||||
{
|
||||
MessageBox.Show("먼저 경로를 계산해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
selectedAGV.StartPath(_simulatorCanvas.CurrentPath, _mapNodes);
|
||||
_statusLabel.Text = $"{selectedAGV.AgvId} 경로 시작";
|
||||
}
|
||||
@@ -275,6 +329,7 @@ namespace AGVSimulator.Forms
|
||||
private void OnSetPosition_Click(object sender, EventArgs e)
|
||||
{
|
||||
SetAGVPositionByRfid();
|
||||
_simulatorCanvas.FitToNodes();
|
||||
}
|
||||
|
||||
private void OnRfidTextBox_KeyPress(object sender, KeyPressEventArgs e)
|
||||
@@ -282,6 +337,7 @@ namespace AGVSimulator.Forms
|
||||
if (e.KeyChar == (char)Keys.Enter)
|
||||
{
|
||||
SetAGVPositionByRfid();
|
||||
_simulatorCanvas.FitToNodes();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -318,17 +374,53 @@ namespace AGVSimulator.Forms
|
||||
var targetNode = _mapNodes?.FirstOrDefault(n => n.RfidId.Equals(rfidId, StringComparison.OrdinalIgnoreCase));
|
||||
if (targetNode == null)
|
||||
{
|
||||
MessageBox.Show($"RFID '{rfidId}'에 해당하는 노드를 찾을 수 없습니다.\n\n사용 가능한 RFID 목록:\n{GetAvailableRfidList()}",
|
||||
MessageBox.Show($"RFID '{rfidId}'에 해당하는 노드를 찾을 수 없습니다.\n\n사용 가능한 RFID 목록:\n{GetAvailableRfidList()}",
|
||||
"RFID 찾기 실패", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// AGV 위치 설정
|
||||
// 선택된 방향 확인
|
||||
var selectedDirectionItem = _directionCombo.SelectedItem as DirectionItem;
|
||||
var selectedDirection = selectedDirectionItem?.Direction ?? AgvDirection.Forward;
|
||||
|
||||
// 콘솔 출력 (상세한 리프트 방향 계산 과정)
|
||||
|
||||
Program.WriteLine($"[AGV-{selectedAGV.AgvId}] 위치 설정:");
|
||||
Program.WriteLine($" RFID: {rfidId} → 노드: {targetNode.NodeId}");
|
||||
Program.WriteLine($" 새로운 위치: ({targetNode.Position.X}, {targetNode.Position.Y})");
|
||||
Program.WriteLine($" 모터 방향: {selectedDirectionItem?.DisplayText ?? "전진"} ({selectedDirection})");
|
||||
|
||||
// SetPosition 호출 전 상태
|
||||
var oldTargetPos = selectedAGV.TargetPosition;
|
||||
var oldCurrentPos = selectedAGV.CurrentPosition;
|
||||
Program.WriteLine($" [BEFORE] 현재 CurrentPosition: ({oldCurrentPos.X}, {oldCurrentPos.Y})");
|
||||
Program.WriteLine($" [BEFORE] 이전 TargetPosition: {(oldTargetPos.HasValue ? $"({oldTargetPos.Value.X}, {oldTargetPos.Value.Y})" : "None")}");
|
||||
|
||||
|
||||
// AGV 위치 및 방향 설정
|
||||
_simulatorCanvas.SetAGVPosition(selectedAGV.AgvId, targetNode.Position);
|
||||
|
||||
_statusLabel.Text = $"{selectedAGV.AgvId} 위치를 RFID '{rfidId}' (노드: {targetNode.NodeId})로 설정했습니다.";
|
||||
_simulatorCanvas.UpdateAGVDirection(selectedAGV.AgvId, selectedDirection);
|
||||
|
||||
// VirtualAGV 객체의 위치와 방향 업데이트
|
||||
selectedAGV.SetPosition(targetNode.Position); // 이전 위치 기억하도록
|
||||
selectedAGV.SetDirection(selectedDirection);
|
||||
|
||||
// SetPosition 호출 후 상태 확인 및 리프트 계산
|
||||
|
||||
var newTargetPos = selectedAGV.TargetPosition;
|
||||
var newCurrentPos = selectedAGV.CurrentPosition;
|
||||
Program.WriteLine($" [AFTER] 새로운 CurrentPosition: ({newCurrentPos.X}, {newCurrentPos.Y})");
|
||||
Program.WriteLine($" [AFTER] 새로운 TargetPosition: {(newTargetPos.HasValue ? $"({newTargetPos.Value.X}, {newTargetPos.Value.Y})" : "None")}");
|
||||
|
||||
// 리프트 방향 계산 과정 상세 출력
|
||||
Program.WriteLine($" [LIFT] 리프트 방향 계산:");
|
||||
CalculateLiftDirectionDetailed(selectedAGV);
|
||||
Program.WriteLine("");
|
||||
|
||||
|
||||
_statusLabel.Text = $"{selectedAGV.AgvId} 위치를 RFID '{rfidId}' (노드: {targetNode.NodeId}), 방향: {selectedDirectionItem?.DisplayText ?? "전진"}로 설정했습니다.";
|
||||
_rfidTextBox.Text = ""; // 입력 필드 초기화
|
||||
|
||||
|
||||
// 시뮬레이터 캔버스의 해당 노드로 이동
|
||||
_simulatorCanvas.PanToNode(targetNode.NodeId);
|
||||
}
|
||||
@@ -345,10 +437,10 @@ namespace AGVSimulator.Forms
|
||||
// 처음 10개의 RFID만 표시
|
||||
var rfidList = nodesWithRfid.Take(10).Select(n => $"- {n.RfidId} → {n.NodeId}");
|
||||
var result = string.Join("\n", rfidList);
|
||||
|
||||
|
||||
if (nodesWithRfid.Count > 10)
|
||||
result += $"\n... 외 {nodesWithRfid.Count - 10}개";
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -357,29 +449,31 @@ namespace AGVSimulator.Forms
|
||||
try
|
||||
{
|
||||
var result = MapLoader.LoadMapFromFile(filePath);
|
||||
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
Console.WriteLine($"Map File Load : {filePath}");
|
||||
|
||||
_mapNodes = result.Nodes;
|
||||
_currentMapFilePath = filePath;
|
||||
|
||||
|
||||
// RFID가 없는 노드들에 자동 할당
|
||||
MapLoader.AssignAutoRfidIds(_mapNodes);
|
||||
|
||||
|
||||
// 시뮬레이터 캔버스에 맵 설정
|
||||
_simulatorCanvas.Nodes = _mapNodes;
|
||||
|
||||
|
||||
// 설정에 마지막 맵 파일 경로 저장
|
||||
_config.LastMapFilePath = filePath;
|
||||
if (_config.AutoSave)
|
||||
{
|
||||
_config.Save();
|
||||
}
|
||||
|
||||
|
||||
// UI 업데이트
|
||||
UpdateNodeComboBoxes();
|
||||
UpdateUI();
|
||||
|
||||
|
||||
// 맵에 맞춤
|
||||
_simulatorCanvas.FitToNodes();
|
||||
}
|
||||
@@ -398,27 +492,31 @@ namespace AGVSimulator.Forms
|
||||
{
|
||||
_startNodeCombo.Items.Clear();
|
||||
_targetNodeCombo.Items.Clear();
|
||||
|
||||
|
||||
if (_mapNodes != null)
|
||||
{
|
||||
foreach (var node in _mapNodes)
|
||||
{
|
||||
if (node.IsActive && node.HasRfid())
|
||||
{
|
||||
_startNodeCombo.Items.Add(node);
|
||||
_targetNodeCombo.Items.Add(node);
|
||||
// {rfid} - [{node}] 형식으로 ComboBoxItem 생성
|
||||
var displayText = $"{node.RfidId} - [{node.NodeId}]";
|
||||
var item = new ComboBoxItem<MapNode>(node, displayText);
|
||||
|
||||
_startNodeCombo.Items.Add(item);
|
||||
_targetNodeCombo.Items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_startNodeCombo.DisplayMember = "RfidId";
|
||||
_targetNodeCombo.DisplayMember = "RfidId";
|
||||
|
||||
_startNodeCombo.DisplayMember = "DisplayText";
|
||||
_targetNodeCombo.DisplayMember = "DisplayText";
|
||||
}
|
||||
|
||||
private void UpdateAGVComboBox()
|
||||
{
|
||||
_agvListCombo.Items.Clear();
|
||||
|
||||
|
||||
if (_agvList != null)
|
||||
{
|
||||
foreach (var agv in _agvList)
|
||||
@@ -426,9 +524,9 @@ namespace AGVSimulator.Forms
|
||||
_agvListCombo.Items.Add(agv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_agvListCombo.DisplayMember = "AgvId";
|
||||
|
||||
|
||||
if (_agvListCombo.Items.Count > 0)
|
||||
{
|
||||
_agvListCombo.SelectedIndex = 0;
|
||||
@@ -439,47 +537,260 @@ namespace AGVSimulator.Forms
|
||||
{
|
||||
// 시뮬레이션 상태
|
||||
_simulationStatusLabel.Text = _simulationState.IsRunning ? "시뮬레이션: 실행 중" : "시뮬레이션: 정지";
|
||||
|
||||
|
||||
// AGV 수
|
||||
_agvCountLabel.Text = $"AGV 수: {_agvList?.Count ?? 0}";
|
||||
|
||||
|
||||
// 버튼 상태
|
||||
_startSimulationButton.Enabled = !_simulationState.IsRunning && _agvList?.Count > 0;
|
||||
_stopSimulationButton.Enabled = _simulationState.IsRunning;
|
||||
|
||||
|
||||
_removeAgvButton.Enabled = _agvListCombo.SelectedItem != null;
|
||||
_startPathButton.Enabled = _agvListCombo.SelectedItem != null &&
|
||||
_simulatorCanvas.CurrentPath != null &&
|
||||
_startPathButton.Enabled = _agvListCombo.SelectedItem != null &&
|
||||
_simulatorCanvas.CurrentPath != null &&
|
||||
_simulatorCanvas.CurrentPath.Success;
|
||||
|
||||
_calculatePathButton.Enabled = _startNodeCombo.SelectedItem != null &&
|
||||
|
||||
_calculatePathButton.Enabled = _startNodeCombo.SelectedItem != null &&
|
||||
_targetNodeCombo.SelectedItem != null;
|
||||
|
||||
|
||||
// RFID 위치 설정 관련
|
||||
var hasSelectedAGV = _agvListCombo.SelectedItem != null;
|
||||
var hasRfidNodes = _mapNodes != null && _mapNodes.Any(n => n.HasRfid());
|
||||
|
||||
|
||||
_setPositionButton.Enabled = hasSelectedAGV && hasRfidNodes;
|
||||
_rfidTextBox.Enabled = hasSelectedAGV && hasRfidNodes;
|
||||
|
||||
|
||||
// AGV 정보 패널 업데이트
|
||||
UpdateAGVInfoPanel();
|
||||
|
||||
// 맵 다시열기 버튼
|
||||
var hasCurrentMap = !string.IsNullOrEmpty(_currentMapFilePath);
|
||||
reloadMapToolStripMenuItem.Enabled = hasCurrentMap;
|
||||
reloadMapToolStripButton.Enabled = hasCurrentMap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AGV 정보 패널 업데이트
|
||||
/// </summary>
|
||||
private void UpdateAGVInfoPanel()
|
||||
{
|
||||
var selectedAGV = _agvListCombo.SelectedItem as VirtualAGV;
|
||||
|
||||
if (selectedAGV == null)
|
||||
{
|
||||
_liftDirectionLabel.Text = "리프트 방향: -";
|
||||
_motorDirectionLabel.Text = "모터 방향: -";
|
||||
_agvInfoTitleLabel.Text = "AGV 상태 정보: (AGV 선택 안됨)";
|
||||
return;
|
||||
}
|
||||
|
||||
// AGV 선택됨
|
||||
_agvInfoTitleLabel.Text = $"AGV 상태 정보: {selectedAGV.AgvId}";
|
||||
|
||||
// 리프트 방향 계산
|
||||
var liftDirection = CalculateLiftDirection(selectedAGV);
|
||||
_liftDirectionLabel.Text = $"리프트 방향: {liftDirection}";
|
||||
|
||||
// 모터 방향
|
||||
var motorDirection = GetMotorDirectionString(selectedAGV.CurrentDirection);
|
||||
_motorDirectionLabel.Text = $"모터 방향: {motorDirection}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AGV의 리프트 방향 계산 (상세 출력 버전)
|
||||
/// </summary>
|
||||
private void CalculateLiftDirectionDetailed(VirtualAGV agv)
|
||||
{
|
||||
var currentPos = agv.CurrentPosition;
|
||||
var targetPos = agv.TargetPosition;
|
||||
var dockingDirection = agv.DockingDirection;
|
||||
|
||||
Program.WriteLine($" 입력값: CurrentPos=({currentPos.X}, {currentPos.Y})");
|
||||
Program.WriteLine($" 입력값: TargetPos={(!targetPos.HasValue ? "None" : $"({targetPos.Value.X}, {targetPos.Value.Y})")}");
|
||||
Program.WriteLine($" 입력값: DockingDirection={dockingDirection}");
|
||||
|
||||
if (!targetPos.HasValue || targetPos.Value == currentPos)
|
||||
{
|
||||
Program.WriteLine($" 결과: 방향을 알 수 없음 (TargetPos 없음 또는 같은 위치)");
|
||||
return;
|
||||
}
|
||||
|
||||
// 이동 방향 계산 (이전 → 현재 = TargetPos → CurrentPos)
|
||||
var dx = currentPos.X - targetPos.Value.X;
|
||||
var dy = currentPos.Y - targetPos.Value.Y;
|
||||
Program.WriteLine($" 이동 벡터: dx={dx}, dy={dy}");
|
||||
|
||||
if (Math.Abs(dx) < 1 && Math.Abs(dy) < 1)
|
||||
{
|
||||
Program.WriteLine($" 결과: 정지 상태 (이동거리 < 1픽셀)");
|
||||
return;
|
||||
}
|
||||
|
||||
// 경로 예측 기반 LiftCalculator를 사용하여 리프트 방향 계산
|
||||
var liftInfo = AGVNavigationCore.Utils.LiftCalculator.CalculateLiftInfoWithPathPrediction(
|
||||
currentPos, targetPos.Value, agv.CurrentDirection, _mapNodes);
|
||||
|
||||
// 이동 각도 계산 (표시용)
|
||||
var moveAngleRad = Math.Atan2(dy, dx);
|
||||
var moveAngleDeg = moveAngleRad * 180.0 / Math.PI;
|
||||
while (moveAngleDeg < 0) moveAngleDeg += 360;
|
||||
while (moveAngleDeg >= 360) moveAngleDeg -= 360;
|
||||
|
||||
Program.WriteLine($" 이동 각도: {moveAngleDeg:F1}도 (라디안: {moveAngleRad:F3})");
|
||||
Program.WriteLine($" 모터 방향: {GetMotorDirectionString(liftInfo.MotorDirection)}");
|
||||
Program.WriteLine($" 리프트 각도: {liftInfo.AngleDegrees:F1}도 ({liftInfo.CalculationMethod})");
|
||||
|
||||
// 도킹 방향 정보 추가
|
||||
string dockingInfo = dockingDirection == DockingDirection.Forward ? "전진도킹" : "후진도킹";
|
||||
|
||||
Program.WriteLine($" 최종 결과: {liftInfo.DirectionString} ({liftInfo.AngleDegrees:F0}°) [{dockingInfo}]");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AGV의 리프트 방향 계산 (간단한 버전)
|
||||
/// </summary>
|
||||
private string CalculateLiftDirection(VirtualAGV agv)
|
||||
{
|
||||
var currentPos = agv.CurrentPosition;
|
||||
var targetPos = agv.TargetPosition;
|
||||
var dockingDirection = agv.DockingDirection;
|
||||
|
||||
if (!targetPos.HasValue || targetPos.Value == currentPos)
|
||||
{
|
||||
// 방향을 알 수 없는 경우
|
||||
return "알 수 없음 (?)";
|
||||
}
|
||||
|
||||
// 이동 방향 계산 (현재 → 타겟, 실제로는 이전 → 현재)
|
||||
var dx = currentPos.X - targetPos.Value.X;
|
||||
var dy = currentPos.Y - targetPos.Value.Y;
|
||||
|
||||
if (Math.Abs(dx) < 1 && Math.Abs(dy) < 1)
|
||||
{
|
||||
return "정지 상태";
|
||||
}
|
||||
|
||||
// 경로 예측 기반 LiftCalculator를 사용하여 리프트 방향 계산
|
||||
var liftInfo = AGVNavigationCore.Utils.LiftCalculator.CalculateLiftInfoWithPathPrediction(
|
||||
currentPos, targetPos.Value, agv.CurrentDirection, _mapNodes);
|
||||
|
||||
// 도킹 방향 정보 추가
|
||||
string dockingInfo = dockingDirection == DockingDirection.Forward ? "전진도킹" : "후진도킹";
|
||||
|
||||
return $"{liftInfo.DirectionString} ({liftInfo.AngleDegrees:F0}°) [{dockingInfo}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모터 방향을 문자열로 변환
|
||||
/// </summary>
|
||||
private string GetMotorDirectionString(AgvDirection direction)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case AgvDirection.Forward:
|
||||
return "전진 (F)";
|
||||
case AgvDirection.Backward:
|
||||
return "후진 (B)";
|
||||
case AgvDirection.Left:
|
||||
return "좌회전 (L)";
|
||||
case AgvDirection.Right:
|
||||
return "우회전 (R)";
|
||||
default:
|
||||
return "알 수 없음";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 노드 ID를 RFID 값으로 변환 (NodeResolver 사용)
|
||||
/// </summary>
|
||||
private string GetRfidByNodeId(string nodeId)
|
||||
{
|
||||
var node = _mapNodes?.FirstOrDefault(n => n.NodeId == nodeId);
|
||||
return node?.HasRfid() == true ? node.RfidId : nodeId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 경로 디버깅 정보 업데이트 (RFID 값 표시, 모터방향 정보 포함)
|
||||
/// </summary>
|
||||
private void UpdatePathDebugInfo(AGVPathResult agvResult)
|
||||
{
|
||||
if (agvResult == null || !agvResult.Success)
|
||||
{
|
||||
_pathDebugLabel.Text = "경로: 설정되지 않음";
|
||||
return;
|
||||
}
|
||||
|
||||
// 노드 ID를 RFID로 변환한 경로 생성
|
||||
var pathWithRfid = agvResult.Path.Select(nodeId => GetRfidByNodeId(nodeId)).ToList();
|
||||
|
||||
// 콘솔 디버그 정보 출력 (RFID 기준)
|
||||
Program.WriteLine($"[DEBUG] 경로 계산 완료:");
|
||||
Program.WriteLine($" 전체 경로 (RFID): [{string.Join(" → ", pathWithRfid)}]");
|
||||
Program.WriteLine($" 전체 경로 (NodeID): [{string.Join(" → ", agvResult.Path)}]");
|
||||
Program.WriteLine($" 경로 노드 수: {agvResult.Path.Count}");
|
||||
if (agvResult.NodeMotorInfos != null)
|
||||
{
|
||||
Program.WriteLine($" 모터정보 수: {agvResult.NodeMotorInfos.Count}");
|
||||
for (int i = 0; i < agvResult.NodeMotorInfos.Count; i++)
|
||||
{
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
// 모터방향 정보가 있다면 이를 포함하여 경로 문자열 구성 (RFID 기준)
|
||||
string pathString;
|
||||
if (agvResult.NodeMotorInfos != null && agvResult.NodeMotorInfos.Count > 0)
|
||||
{
|
||||
// RFID별 모터방향 정보를 포함한 경로 문자열 생성
|
||||
var pathWithMotorInfo = new List<string>();
|
||||
for (int i = 0; i < agvResult.NodeMotorInfos.Count; i++)
|
||||
{
|
||||
var motorInfo = agvResult.NodeMotorInfos[i];
|
||||
var rfidId = GetRfidByNodeId(motorInfo.NodeId);
|
||||
string motorSymbol = motorInfo.MotorDirection == AgvDirection.Forward ? "[전진]" : "[후진]";
|
||||
pathWithMotorInfo.Add($"{rfidId}{motorSymbol}");
|
||||
}
|
||||
pathString = string.Join(" → ", pathWithMotorInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 기본 경로 정보만 표시 (RFID 기준)
|
||||
pathString = string.Join(" → ", pathWithRfid);
|
||||
}
|
||||
|
||||
// UI에 표시 (길이 제한)
|
||||
if (pathString.Length > 120)
|
||||
{
|
||||
pathString = pathString.Substring(0, 117) + "...";
|
||||
}
|
||||
|
||||
// 모터방향 통계 추가
|
||||
string motorStats = "";
|
||||
if (agvResult.NodeMotorInfos != null && agvResult.NodeMotorInfos.Count > 0)
|
||||
{
|
||||
var forwardCount = agvResult.NodeMotorInfos.Count(m => m.MotorDirection == AgvDirection.Forward);
|
||||
var backwardCount = agvResult.NodeMotorInfos.Count(m => m.MotorDirection == AgvDirection.Backward);
|
||||
motorStats = $", 전진: {forwardCount}, 후진: {backwardCount}";
|
||||
}
|
||||
|
||||
_pathDebugLabel.Text = $"경로: {pathString} (총 {agvResult.Path.Count}개 노드, {agvResult.TotalDistance:F1}px{motorStats})";
|
||||
}
|
||||
|
||||
private void OnReloadMap_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_currentMapFilePath))
|
||||
{
|
||||
MessageBox.Show("다시 로드할 맵 파일이 없습니다. 먼저 맵을 열어주세요.", "알림",
|
||||
MessageBox.Show("다시 로드할 맵 파일이 없습니다. 먼저 맵을 열어주세요.", "알림",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!File.Exists(_currentMapFilePath))
|
||||
{
|
||||
MessageBox.Show($"맵 파일을 찾을 수 없습니다:\n{_currentMapFilePath}", "오류",
|
||||
MessageBox.Show($"맵 파일을 찾을 수 없습니다:\n{_currentMapFilePath}", "오류",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
@@ -491,7 +802,7 @@ namespace AGVSimulator.Forms
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"맵 파일을 다시 로드할 수 없습니다:\n{ex.Message}", "오류",
|
||||
MessageBox.Show($"맵 파일을 다시 로드할 수 없습니다:\n{ex.Message}", "오류",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
@@ -515,7 +826,7 @@ namespace AGVSimulator.Forms
|
||||
if (openDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
mapEditorPath = openDialog.FileName;
|
||||
|
||||
|
||||
// 설정에 저장
|
||||
_config.MapEditorExecutablePath = mapEditorPath;
|
||||
if (_config.AutoSave)
|
||||
@@ -548,7 +859,7 @@ namespace AGVSimulator.Forms
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"MapEditor를 실행할 수 없습니다:\n{ex.Message}", "오류",
|
||||
MessageBox.Show($"MapEditor를 실행할 수 없습니다:\n{ex.Message}", "오류",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
@@ -578,4 +889,45 @@ namespace AGVSimulator.Forms
|
||||
_statusLabel.Text = "초기화 완료";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 방향 콤보박스용 아이템 클래스
|
||||
/// </summary>
|
||||
public class DirectionItem
|
||||
{
|
||||
public AgvDirection Direction { get; }
|
||||
public string DisplayText { get; }
|
||||
|
||||
public DirectionItem(AgvDirection direction, string displayText)
|
||||
{
|
||||
Direction = direction;
|
||||
DisplayText = displayText;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return DisplayText;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 제네릭 콤보박스 아이템 클래스
|
||||
/// </summary>
|
||||
/// <typeparam name="T">값의 타입</typeparam>
|
||||
public class ComboBoxItem<T>
|
||||
{
|
||||
public T Value { get; }
|
||||
public string DisplayText { get; }
|
||||
|
||||
public ComboBoxItem(T value, string displayText)
|
||||
{
|
||||
Value = value;
|
||||
DisplayText = displayText;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return DisplayText;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user