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:
@@ -43,13 +43,14 @@ namespace AGVNavigationCore.Controls
|
||||
/// </summary>
|
||||
public enum EditMode
|
||||
{
|
||||
Select, // 선택 모드
|
||||
Move, // 이동 모드
|
||||
AddNode, // 노드 추가 모드
|
||||
Connect, // 연결 모드
|
||||
Delete, // 삭제 모드
|
||||
AddLabel, // 라벨 추가 모드
|
||||
AddImage // 이미지 추가 모드
|
||||
Select, // 선택 모드
|
||||
Move, // 이동 모드
|
||||
AddNode, // 노드 추가 모드
|
||||
Connect, // 연결 모드
|
||||
Delete, // 삭제 모드
|
||||
DeleteConnection, // 연결 삭제 모드
|
||||
AddLabel, // 라벨 추가 모드
|
||||
AddImage // 이미지 추가 모드
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -98,6 +99,11 @@ namespace AGVNavigationCore.Controls
|
||||
// 자동 증가 카운터
|
||||
private int _nodeCounter = 1;
|
||||
|
||||
// 강조 연결
|
||||
private (string FromNodeId, string ToNodeId)? _highlightedConnection = null;
|
||||
|
||||
// RFID 중복 검사
|
||||
private HashSet<string> _duplicateRfidNodes = new HashSet<string>();
|
||||
|
||||
// 브러쉬 및 펜
|
||||
private Brush _normalNodeBrush;
|
||||
@@ -118,6 +124,7 @@ namespace AGVNavigationCore.Controls
|
||||
private Pen _destinationNodePen;
|
||||
private Pen _pathPen;
|
||||
private Pen _agvPen;
|
||||
private Pen _highlightedConnectionPen;
|
||||
|
||||
// 컨텍스트 메뉴
|
||||
private ContextMenuStrip _contextMenu;
|
||||
@@ -131,6 +138,7 @@ namespace AGVNavigationCore.Controls
|
||||
public event EventHandler<MapNode> NodeSelected;
|
||||
public event EventHandler<MapNode> NodeDeleted;
|
||||
public event EventHandler<MapNode> NodeMoved;
|
||||
public event EventHandler<(MapNode From, MapNode To)> ConnectionDeleted;
|
||||
public event EventHandler MapChanged;
|
||||
|
||||
// AGV 이벤트
|
||||
@@ -215,6 +223,13 @@ namespace AGVNavigationCore.Controls
|
||||
set
|
||||
{
|
||||
_nodes = value ?? new List<MapNode>();
|
||||
|
||||
// 기존 노드들의 최대 번호를 찾아서 _nodeCounter 설정
|
||||
UpdateNodeCounter();
|
||||
|
||||
// RFID 중복값 검사
|
||||
DetectDuplicateRfidNodes();
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
@@ -286,6 +301,45 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Connection Highlighting
|
||||
|
||||
/// <summary>
|
||||
/// 특정 연결을 강조 표시
|
||||
/// </summary>
|
||||
/// <param name="fromNodeId">시작 노드 ID</param>
|
||||
/// <param name="toNodeId">끝 노드 ID</param>
|
||||
public void HighlightConnection(string fromNodeId, string toNodeId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fromNodeId) || string.IsNullOrEmpty(toNodeId))
|
||||
{
|
||||
_highlightedConnection = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 사전순으로 정렬하여 저장 (연결이 단일 방향으로 저장되므로)
|
||||
if (string.Compare(fromNodeId, toNodeId, StringComparison.Ordinal) <= 0)
|
||||
{
|
||||
_highlightedConnection = (fromNodeId, toNodeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_highlightedConnection = (toNodeId, fromNodeId);
|
||||
}
|
||||
}
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 연결 강조 표시 해제
|
||||
/// </summary>
|
||||
public void ClearHighlightedConnection()
|
||||
{
|
||||
_highlightedConnection = null;
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -347,6 +401,7 @@ namespace AGVNavigationCore.Controls
|
||||
_destinationNodePen = new Pen(Color.Orange, 4);
|
||||
_pathPen = new Pen(Color.Purple, 3);
|
||||
_agvPen = new Pen(Color.Red, 3);
|
||||
_highlightedConnectionPen = new Pen(Color.Red, 4) { DashStyle = DashStyle.Solid };
|
||||
}
|
||||
|
||||
private void CreateContextMenu()
|
||||
@@ -513,6 +568,7 @@ namespace AGVNavigationCore.Controls
|
||||
_destinationNodePen?.Dispose();
|
||||
_pathPen?.Dispose();
|
||||
_agvPen?.Dispose();
|
||||
_highlightedConnectionPen?.Dispose();
|
||||
|
||||
// 컨텍스트 메뉴 정리
|
||||
_contextMenu?.Dispose();
|
||||
@@ -525,40 +581,74 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// RFID 중복값을 가진 노드들을 감지하고 표시
|
||||
/// 나중에 추가된 노드(인덱스가 더 큰)를 중복으로 간주
|
||||
/// </summary>
|
||||
private void DetectDuplicateRfidNodes()
|
||||
{
|
||||
_duplicateRfidNodes.Clear();
|
||||
|
||||
if (_nodes == null || _nodes.Count == 0)
|
||||
return;
|
||||
|
||||
// RFID값과 해당 노드의 인덱스를 저장
|
||||
var rfidToNodeIndex = new Dictionary<string, List<int>>();
|
||||
|
||||
// 모든 노드의 RFID값 수집
|
||||
for (int i = 0; i < _nodes.Count; i++)
|
||||
{
|
||||
var node = _nodes[i];
|
||||
if (!string.IsNullOrEmpty(node.RfidId))
|
||||
{
|
||||
if (!rfidToNodeIndex.ContainsKey(node.RfidId))
|
||||
{
|
||||
rfidToNodeIndex[node.RfidId] = new List<int>();
|
||||
}
|
||||
rfidToNodeIndex[node.RfidId].Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
// 중복된 RFID를 가진 노드들을 찾아서 나중에 추가된 것들을 표시
|
||||
foreach (var kvp in rfidToNodeIndex)
|
||||
{
|
||||
if (kvp.Value.Count > 1)
|
||||
{
|
||||
// 첫 번째 노드는 원본으로 유지, 나머지는 중복으로 표시
|
||||
for (int i = 1; i < kvp.Value.Count; i++)
|
||||
{
|
||||
int duplicateNodeIndex = kvp.Value[i];
|
||||
_duplicateRfidNodes.Add(_nodes[duplicateNodeIndex].NodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 기존 노드들의 최대 번호를 찾아서 _nodeCounter를 업데이트
|
||||
/// </summary>
|
||||
private void UpdateNodeCounter()
|
||||
{
|
||||
if (_nodes == null || _nodes.Count == 0)
|
||||
{
|
||||
_nodeCounter = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
int maxNumber = 0;
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
// NodeId에서 숫자 부분 추출 (예: "N001" -> 1)
|
||||
if (node.NodeId.StartsWith("N") && int.TryParse(node.NodeId.Substring(1), out int number))
|
||||
{
|
||||
maxNumber = Math.Max(maxNumber, number);
|
||||
}
|
||||
}
|
||||
|
||||
_nodeCounter = maxNumber + 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#region Interfaces
|
||||
|
||||
/// <summary>
|
||||
/// AGV 인터페이스 (가상/실제 AGV 통합)
|
||||
/// </summary>
|
||||
public interface IAGV
|
||||
{
|
||||
string AgvId { get; }
|
||||
Point CurrentPosition { get; }
|
||||
AgvDirection CurrentDirection { get; }
|
||||
AGVState CurrentState { get; }
|
||||
float BatteryLevel { get; }
|
||||
|
||||
// 이동 경로 정보 추가
|
||||
Point? TargetPosition { get; }
|
||||
string CurrentNodeId { get; }
|
||||
string TargetNodeId { get; }
|
||||
DockingDirection DockingDirection { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AGV 상태 열거형
|
||||
/// </summary>
|
||||
public enum AGVState
|
||||
{
|
||||
Idle, // 대기
|
||||
Moving, // 이동 중
|
||||
Rotating, // 회전 중
|
||||
Docking, // 도킹 중
|
||||
Charging, // 충전 중
|
||||
Error // 오류
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user