"refactor:Improve-map-loading-and-use-canvas-nodes"

This commit is contained in:
2025-12-14 22:55:09 +09:00
parent 764fbbd204
commit 3c8eae889c
13 changed files with 218 additions and 199 deletions

View File

@@ -108,7 +108,6 @@ namespace AGVSimulator.Forms
}
private UnifiedAGVCanvas _simulatorCanvas;
private List<MapNode> _mapNodes;
private AGVPathfinder _advancedPathfinder;
private List<VirtualAGV> _agvList;
private SimulationState _simulationState;
@@ -158,7 +157,7 @@ namespace AGVSimulator.Forms
_config = SimulatorConfig.Load();
// 데이터 초기화
_mapNodes = new List<MapNode>();
_agvList = new List<VirtualAGV>();
_simulationState = new SimulationState();
_currentMapFilePath = string.Empty;
@@ -305,14 +304,14 @@ namespace AGVSimulator.Forms
private void OnAddAGV_Click(object sender, EventArgs e)
{
if (_mapNodes == null || _mapNodes.Count == 0)
if (_simulatorCanvas.Nodes == null || _simulatorCanvas.Nodes.Count == 0)
{
MessageBox.Show("먼저 맵을 로드해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
var agvId = $"AGV{_agvList.Count + 1:D2}";
var startPosition = _mapNodes.First().Position; // 첫 번째 노드에서 시작
var startPosition = _simulatorCanvas.Nodes.First().Position; // 첫 번째 노드에서 시작
var newAGV = new VirtualAGV(agvId, startPosition);
_agvList.Add(newAGV);
@@ -396,7 +395,7 @@ namespace AGVSimulator.Forms
if (_advancedPathfinder == null)
{
_advancedPathfinder = new AGVPathfinder(_mapNodes);
_advancedPathfinder = new AGVPathfinder(_simulatorCanvas.Nodes);
}
// 현재 AGV 방향 가져오기
@@ -420,11 +419,11 @@ namespace AGVSimulator.Forms
// 도킹 검증이 없는 경우 추가 검증 수행
if (advancedResult.DockingValidation == null || !advancedResult.DockingValidation.IsValidationRequired)
{
advancedResult.DockingValidation = DockingValidator.ValidateDockingDirection(advancedResult, _mapNodes);
advancedResult.DockingValidation = DockingValidator.ValidateDockingDirection(advancedResult, _simulatorCanvas.Nodes);
}
//마지막대상이 버퍼라면 시퀀스처리를 해야한다
if(targetNode.Type == NodeType.Buffer)
if(targetNode.StationType == StationType.Buffer)
{
var lastDetailPath = advancedResult.DetailedPath.Last();
if(lastDetailPath.NodeId == targetNode.Id) //마지막노드 재확인
@@ -598,13 +597,13 @@ namespace AGVSimulator.Forms
/// </summary>
private MapNode FindClosestNode(Point position)
{
if (_mapNodes == null || _mapNodes.Count == 0)
if (_simulatorCanvas.Nodes == null || _simulatorCanvas.Nodes.Count == 0)
return null;
MapNode closestNode = null;
double closestDistance = double.MaxValue;
foreach (var node in _mapNodes)
foreach (var node in _simulatorCanvas.Nodes)
{
var distance = Math.Sqrt(Math.Pow(node.Position.X - position.X, 2) +
Math.Pow(node.Position.Y - position.Y, 2));
@@ -645,7 +644,7 @@ namespace AGVSimulator.Forms
var currentDirection = directionItem?.Direction ?? AgvDirection.Forward;
// 중복 RFID 확인
var existingNode = _mapNodes?.FirstOrDefault(n => n.RfidId.Equals(rfidId, StringComparison.OrdinalIgnoreCase));
var existingNode = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.RfidId.Equals(rfidId, StringComparison.OrdinalIgnoreCase));
if (existingNode != null)
{
// 이미 존재하는 노드로 이동
@@ -723,15 +722,14 @@ namespace AGVSimulator.Forms
Id = newNodeId,
RfidId = rfidId,
Position = new Point(newX, newY),
Type = NodeType.Normal,
IsActive = true
};
// 맵에 추가
if (_mapNodes == null)
_mapNodes = new List<MapNode>();
if (_simulatorCanvas.Nodes == null)
_simulatorCanvas.Nodes = new List<MapNode>();
_mapNodes.Add(newNode);
_simulatorCanvas.Nodes.Add(newNode);
// 이전 노드와 연결 생성
if (_lastScannedNode != null)
@@ -748,7 +746,7 @@ namespace AGVSimulator.Forms
selectedAGV.SetPosition(newNode, currentDirection);
// 캔버스 업데이트
_simulatorCanvas.Nodes = _mapNodes;
_simulatorCanvas.Nodes = _simulatorCanvas.Nodes;
// 화면을 새 노드 위치로 이동
_simulatorCanvas.PanToNode(newNode.Id);
@@ -763,7 +761,7 @@ namespace AGVSimulator.Forms
// UI 업데이트
UpdateNodeComboBoxes();
_statusLabel.Text = $"노드 생성: {newNode.Id} (RFID: {rfidId}) [{GetDirectionSymbol(currentDirection)}] - 총 {_mapNodes.Count}개";
_statusLabel.Text = $"노드 생성: {newNode.Id} (RFID: {rfidId}) [{GetDirectionSymbol(currentDirection)}] - 총 {_simulatorCanvas.Nodes.Count}개";
_rfidTextBox.Text = "";
Program.WriteLine($"[맵 스캔] 노드 생성 완료: {newNode.Id} (RFID: {rfidId}) at ({newX}, {newY}), 방향: {currentDirection}");
@@ -864,7 +862,7 @@ namespace AGVSimulator.Forms
}
// RFID에 해당하는 노드 직접 찾기
var targetNode = _mapNodes?.FirstOrDefault(n => n.RfidId.Equals(rfidId, StringComparison.OrdinalIgnoreCase));
var targetNode = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.RfidId.Equals(rfidId, StringComparison.OrdinalIgnoreCase));
if (targetNode == null)
{
MessageBox.Show($"RFID '{rfidId}'에 해당하는 노드를 찾을 수 없습니다.\n\n사용 가능한 RFID 목록:\n{GetAvailableRfidList()}",
@@ -947,10 +945,10 @@ namespace AGVSimulator.Forms
private string GetAvailableRfidList()
{
if (_mapNodes == null || _mapNodes.Count == 0)
if (_simulatorCanvas.Nodes == null || _simulatorCanvas.Nodes.Count == 0)
return "매핑된 RFID가 없습니다.";
var nodesWithRfid = _mapNodes.Where(n => n.HasRfid()).ToList();
var nodesWithRfid = _simulatorCanvas.Nodes.Where(n => n.HasRfid()).ToList();
if (nodesWithRfid.Count == 0)
return "RFID가 할당된 노드가 없습니다.";
@@ -977,13 +975,13 @@ namespace AGVSimulator.Forms
{
Console.WriteLine($"Map File Load : {filePath}");
_mapNodes = result.Nodes;
_simulatorCanvas.Nodes = result.Nodes;
_currentMapFilePath = filePath;
// RFID 자동 할당 제거 - 에디터에서 설정한 값 그대로 사용
// 시뮬레이터 캔버스에 맵 설정
_simulatorCanvas.Nodes = _mapNodes;
_simulatorCanvas.SetMapLoadResult(result);//.Nodes = _simulatorCanvas.Nodes;
// 맵 설정 적용 (배경색, 그리드 표시)
if (result.Settings != null)
@@ -1051,9 +1049,9 @@ namespace AGVSimulator.Forms
_startNodeCombo.Items.Clear();
_targetNodeCombo.Items.Clear();
if (_mapNodes != null)
if (_simulatorCanvas.Nodes != null)
{
foreach (var node in _mapNodes)
foreach (var node in _simulatorCanvas.Nodes)
{
if (node.IsActive && node.HasRfid())
{
@@ -1109,7 +1107,7 @@ namespace AGVSimulator.Forms
// RFID 위치 설정 관련
var hasSelectedAGV = _agvListCombo.SelectedItem != null;
var hasRfidNodes = _mapNodes != null && _mapNodes.Any(n => n.HasRfid());
var hasRfidNodes = _simulatorCanvas.Nodes != null && _simulatorCanvas.Nodes.Any(n => n.HasRfid());
_setPositionButton.Enabled = hasSelectedAGV && hasRfidNodes;
_rfidTextBox.Enabled = hasSelectedAGV && hasRfidNodes;
@@ -1182,7 +1180,7 @@ namespace AGVSimulator.Forms
// 경로 예측 기반 LiftCalculator를 사용하여 리프트 방향 계산
var liftInfo = AGVNavigationCore.Utils.LiftCalculator.CalculateLiftInfoWithPathPrediction(
currentPos, prevPos.Value, agv.CurrentDirection, _mapNodes);
currentPos, prevPos.Value, agv.CurrentDirection, _simulatorCanvas.Nodes);
// 이동 각도 계산 (표시용)
var moveAngleRad = Math.Atan2(dy, dx);
@@ -1226,7 +1224,7 @@ namespace AGVSimulator.Forms
// 경로 예측 기반 LiftCalculator를 사용하여 리프트 방향 계산
var liftInfo = AGVNavigationCore.Utils.LiftCalculator.CalculateLiftInfoWithPathPrediction(
currentPos, targetPos.Value, agv.CurrentDirection, _mapNodes);
currentPos, targetPos.Value, agv.CurrentDirection, _simulatorCanvas.Nodes);
// 도킹 방향 정보 추가
string dockingInfo = dockingDirection == DockingDirection.Forward ? "전진도킹" : "후진도킹";
@@ -1259,7 +1257,7 @@ namespace AGVSimulator.Forms
/// </summary>
private string GetRfidByNodeId(string nodeId)
{
var node = _mapNodes?.FirstOrDefault(n => n.Id == nodeId);
var node = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.Id == nodeId);
return node?.HasRfid() == true ? node.RfidId : nodeId;
}
@@ -1268,7 +1266,7 @@ namespace AGVSimulator.Forms
/// </summary>
private string GetDisplayName(string nodeId)
{
var node = _mapNodes?.FirstOrDefault(n => n.Id == nodeId);
var node = _simulatorCanvas.Nodes?.FirstOrDefault(n => n.Id == nodeId);
if (node != null && !string.IsNullOrEmpty(node.RfidId))
{
return node.RfidId;
@@ -1541,7 +1539,7 @@ namespace AGVSimulator.Forms
private async void toolStripButton1_Click(object sender, EventArgs e)
{
// 맵과 AGV 확인
if (_mapNodes == null || _mapNodes.Count == 0)
if (_simulatorCanvas.Nodes == null || _simulatorCanvas.Nodes.Count == 0)
{
MessageBox.Show("맵 데이터가 없습니다. 먼저 맵을 로드해주세요.", "알림",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
@@ -1557,8 +1555,7 @@ namespace AGVSimulator.Forms
}
// 도킹 타겟 노드 찾기
var dockingTargets = _mapNodes.Where(n =>
n.Type == NodeType.Charging || n.Type == NodeType.Loader || n.Type == NodeType.UnLoader || n.Type == NodeType.Clearner || n.Type == NodeType.Buffer).ToList();
var dockingTargets = _simulatorCanvas.Nodes.Where(n => n.isDockingNode).ToList();
if (dockingTargets.Count == 0)
{
@@ -1666,7 +1663,7 @@ namespace AGVSimulator.Forms
private PathTestLogItem CreateTestResultFromUI(MapNode prevNode, MapNode targetNode,
string directionName, (bool result, string message) calcResult)
{
var currentNode = _mapNodes.FirstOrDefault(n => n.Id ==
var currentNode = _simulatorCanvas.Nodes.FirstOrDefault(n => n.Id ==
(_agvListCombo.SelectedItem as VirtualAGV)?.CurrentNodeId);
var logItem = new PathTestLogItem
@@ -1675,7 +1672,7 @@ namespace AGVSimulator.Forms
MotorDirection = directionName,
CurrentPosition = GetNodeDisplayName(currentNode),
TargetPosition = GetNodeDisplayName(targetNode),
DockingPosition = targetNode.Type == NodeType.Charging ? "충전기" : "장비"
DockingPosition = targetNode.StationType == StationType.Charger ? "충전기" : "장비"
};
if (calcResult.result)
@@ -1685,7 +1682,7 @@ namespace AGVSimulator.Forms
if (currentPath != null && currentPath.Success)
{
// 도킹 검증
var dockingValidation = DockingValidator.ValidateDockingDirection(currentPath, _mapNodes);
var dockingValidation = DockingValidator.ValidateDockingDirection(currentPath, _simulatorCanvas.Nodes);
if (dockingValidation.IsValid)
{
@@ -1726,7 +1723,7 @@ namespace AGVSimulator.Forms
var pairs = new List<(MapNode, MapNode)>();
var processedPairs = new HashSet<string>();
foreach (var nodeA in _mapNodes)
foreach (var nodeA in _simulatorCanvas.Nodes)
{
if (nodeA.ConnectedMapNodes == null || nodeA.ConnectedMapNodes.Count == 0)
continue;
@@ -1952,9 +1949,9 @@ namespace AGVSimulator.Forms
if (result == DialogResult.Yes)
{
// 기존 맵 데이터 삭제
_mapNodes?.Clear();
_mapNodes = new List<MapNode>();
_simulatorCanvas.Nodes = _mapNodes;
_simulatorCanvas.Nodes?.Clear();
_simulatorCanvas.Nodes = new List<MapNode>();
_simulatorCanvas.Nodes = _simulatorCanvas.Nodes;
_currentMapFilePath = string.Empty;
UpdateNodeComboBoxes();
_statusLabel.Text = "맵 초기화 완료 - 스캔 모드 시작";
@@ -1978,16 +1975,16 @@ namespace AGVSimulator.Forms
_isMapScanMode = false;
btMakeMap.Text = "맵 생성";
btMakeMap.BackColor = SystemColors.Control;
_statusLabel.Text = $"맵 스캔 완료 - {_mapNodes?.Count ?? 0}개 노드 생성됨";
_statusLabel.Text = $"맵 스캔 완료 - {_simulatorCanvas.Nodes?.Count ?? 0}개 노드 생성됨";
Program.WriteLine($"[맵 스캔] 스캔 모드 종료 - 총 {_mapNodes?.Count ?? 0}개 노드");
Program.WriteLine($"[맵 스캔] 스캔 모드 종료 - 총 {_simulatorCanvas.Nodes?.Count ?? 0}개 노드");
// 맵 저장 권장
if (_mapNodes != null && _mapNodes.Count > 0)
if (_simulatorCanvas.Nodes != null && _simulatorCanvas.Nodes.Count > 0)
{
var saveResult = MessageBox.Show(
$"맵 스캔이 완료되었습니다.\n\n" +
$"생성된 노드: {_mapNodes.Count}개\n\n" +
$"생성된 노드: {_simulatorCanvas.Nodes.Count}개\n\n" +
"맵을 저장하시겠습니까?",
"맵 저장",
MessageBoxButtons.YesNo,
@@ -2011,11 +2008,11 @@ namespace AGVSimulator.Forms
try
{
// MapLoader의 표준 저장 메서드 사용 (AGVMapEditor와 동일한 형식)
bool success = MapLoader.SaveMapToFile(filePath, _mapNodes);
bool success = MapLoader.SaveMapToFile(filePath, _simulatorCanvas.Nodes);
if (success)
{
Program.WriteLine($"[맵 저장] 파일 저장 완료: {filePath} ({_mapNodes.Count}개 노드)");
Program.WriteLine($"[맵 저장] 파일 저장 완료: {filePath} ({_simulatorCanvas.Nodes.Count}개 노드)");
}
else
{
@@ -2032,7 +2029,7 @@ namespace AGVSimulator.Forms
private void btMapSaveAs_Click(object sender, EventArgs e)
{
// 맵 데이터 확인
if (_mapNodes == null || _mapNodes.Count == 0)
if (_simulatorCanvas.Nodes == null || _simulatorCanvas.Nodes.Count == 0)
{
MessageBox.Show("저장할 맵 데이터가 없습니다.", "알림",
MessageBoxButtons.OK, MessageBoxIcon.Information);