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

@@ -49,6 +49,8 @@ namespace AGVMapEditor.Models
_astarPathfinder.SetMapNodes(mapNodes);
// RfidPathfinder는 MapNode의 RFID 정보를 직접 사용
_rfidPathfinder.SetMapNodes(mapNodes);
// 도킹 조건 검색용 내부 노드 목록 업데이트
UpdateInternalMapNodes(mapNodes);
}
/// <summary>
@@ -63,6 +65,32 @@ namespace AGVMapEditor.Models
return _agvPathfinder.FindAGVPath(startNodeId, endNodeId, targetDirection);
}
/// <summary>
/// AGV 경로 계산 (옵션 지정 가능)
/// </summary>
/// <param name="startNodeId">시작 노드 ID</param>
/// <param name="endNodeId">목적지 노드 ID</param>
/// <param name="targetDirection">목적지 도착 방향</param>
/// <param name="options">경로 탐색 옵션</param>
/// <returns>AGV 경로 계산 결과</returns>
public AGVPathResult FindAGVPath(string startNodeId, string endNodeId, AgvDirection? targetDirection, PathfindingOptions options)
{
return _agvPathfinder.FindAGVPath(startNodeId, endNodeId, targetDirection, options);
}
/// <summary>
/// AGV 경로 계산 (현재 방향 및 PathfindingOptions 지원)
/// </summary>
/// <param name="startNodeId">시작 노드 ID</param>
/// <param name="endNodeId">목적지 노드 ID</param>
/// <param name="currentDirection">현재 AGV 방향</param>
/// <param name="targetDirection">목적지 도착 방향</param>
/// <param name="options">경로 탐색 옵션</param>
/// <returns>AGV 경로 계산 결과</returns>
public AGVPathResult FindAGVPath(string startNodeId, string endNodeId, AgvDirection? currentDirection, AgvDirection? targetDirection, PathfindingOptions options)
{
return _agvPathfinder.FindAGVPath(startNodeId, endNodeId, currentDirection, targetDirection, options);
}
/// <summary>
/// 충전 스테이션으로의 경로 찾기
@@ -264,5 +292,141 @@ namespace AGVMapEditor.Models
{
_rfidPathfinder.RotationCostWeight = weight;
}
#region
// 내부 노드 목록 저장
private List<MapNode> _mapNodes;
/// <summary>
/// 맵 노드 설정 (도킹 조건 검색용)
/// </summary>
private void UpdateInternalMapNodes(List<MapNode> mapNodes)
{
_mapNodes = mapNodes;
}
/// <summary>
/// 도킹 방향 기반 노드 검색
/// </summary>
/// <param name="dockingDirection">도킹 방향</param>
/// <returns>해당 도킹 방향의 노드 목록</returns>
public List<MapNode> GetNodesByDockingDirection(DockingDirection dockingDirection)
{
if (_mapNodes == null) return new List<MapNode>();
var result = new List<MapNode>();
foreach (var node in _mapNodes)
{
if (!node.IsActive) continue;
var nodeDockingDirection = GetNodeDockingDirection(node);
if (nodeDockingDirection == dockingDirection)
{
result.Add(node);
}
}
return result;
}
/// <summary>
/// 노드의 도킹 방향 결정
/// </summary>
/// <param name="node">노드</param>
/// <returns>도킹 방향</returns>
public DockingDirection GetNodeDockingDirection(MapNode node)
{
switch (node.Type)
{
case NodeType.Charging:
return DockingDirection.Forward; // 충전기: 전진 도킹
case NodeType.Docking:
return DockingDirection.Backward; // 장비: 후진 도킹
default:
return DockingDirection.Forward; // 기본값: 전진
}
}
/// <summary>
/// 도킹 방향과 장비 타입에 맞는 노드들로의 경로 검색
/// </summary>
/// <param name="startRfidId">시작 RFID</param>
/// <param name="dockingDirection">필요한 도킹 방향</param>
/// <param name="stationType">장비 타입 (선택사항)</param>
/// <returns>경로 계산 결과 목록 (거리 순 정렬)</returns>
public List<RfidPathResult> FindPathsByDockingCondition(string startRfidId, DockingDirection dockingDirection, StationType? stationType = null)
{
var targetNodes = GetNodesByDockingDirection(dockingDirection);
var results = new List<RfidPathResult>();
// 장비 타입 필터링 (필요시)
if (stationType.HasValue && dockingDirection == DockingDirection.Backward)
{
// 후진 도킹이면서 특정 장비 타입이 지정된 경우
// 이 부분은 추후 StationMapping 정보가 있을 때 구현
// 현재는 모든 도킹 노드를 대상으로 함
}
foreach (var targetNode in targetNodes)
{
if (!targetNode.HasRfid()) continue;
try
{
var pathResult = _rfidPathfinder.FindAGVPath(startRfidId, targetNode.RfidId);
if (pathResult.Success)
{
results.Add(pathResult);
}
}
catch (Exception ex)
{
// 개별 경로 계산 실패는 무시하고 계속 진행
System.Diagnostics.Debug.WriteLine($"Path calculation failed from {startRfidId} to {targetNode.RfidId}: {ex.Message}");
}
}
// 거리 순으로 정렬
return results.OrderBy(r => r.TotalDistance).ToList();
}
/// <summary>
/// 가장 가까운 충전기 경로 찾기 (전진 도킹)
/// </summary>
/// <param name="startRfidId">시작 RFID</param>
/// <returns>가장 가까운 충전기로의 경로</returns>
public RfidPathResult FindNearestChargingStationPath(string startRfidId)
{
var chargingPaths = FindPathsByDockingCondition(startRfidId, DockingDirection.Forward);
var chargingNodes = chargingPaths.Where(p => p.Success).ToList();
return chargingNodes.FirstOrDefault() ?? new RfidPathResult
{
Success = false,
ErrorMessage = "충전 가능한 충전기를 찾을 수 없습니다."
};
}
/// <summary>
/// 가장 가까운 장비 도킹 경로 찾기 (후진 도킹)
/// </summary>
/// <param name="startRfidId">시작 RFID</param>
/// <param name="stationType">장비 타입 (선택사항)</param>
/// <returns>가장 가까운 장비로의 경로</returns>
public RfidPathResult FindNearestEquipmentPath(string startRfidId, StationType? stationType = null)
{
var equipmentPaths = FindPathsByDockingCondition(startRfidId, DockingDirection.Backward, stationType);
var equipmentNodes = equipmentPaths.Where(p => p.Success).ToList();
return equipmentNodes.FirstOrDefault() ?? new RfidPathResult
{
Success = false,
ErrorMessage = $"도킹 가능한 장비를 찾을 수 없습니다. ({stationType?.ToString() ?? " "})"
};
}
#endregion
}
}