diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/AGVNavigationCore.csproj b/Cs_HMI/AGVLogic/AGVNavigationCore/AGVNavigationCore.csproj index a5f78e5..d04e3fc 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/AGVNavigationCore.csproj +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/AGVNavigationCore.csproj @@ -94,6 +94,7 @@ UnifiedAGVCanvas.cs + diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs index c8d8fe4..8cd8a4d 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs @@ -207,6 +207,9 @@ namespace AGVNavigationCore.Controls { DrawPath(g, _currentPath, Color.Purple); + // 경로 내 교차로 강조 표시 + HighlightJunctionsInPath(g, _currentPath); + // AGVPathResult의 모터방향 정보가 있다면 향상된 경로 그리기 // 현재는 기본 PathResult를 사용하므로 향후 AGVPathResult로 업그레이드 시 활성화 // TODO: AGVPathfinder 사용시 AGVPathResult로 업그레이드 @@ -282,7 +285,96 @@ namespace AGVNavigationCore.Controls pathPen.Dispose(); } - + /// + /// 경로에 포함된 교차로(3개 이상의 노드가 연결된 노드)를 파란색으로 강조 표시 + /// + private void HighlightJunctionsInPath(Graphics g, AGVPathResult path) + { + if (path?.Path == null || _nodes == null || _nodes.Count == 0) + return; + + const int JUNCTION_CONNECTIONS = 3; // 교차로 판정 기준: 3개 이상의 연결 + + foreach (var nodeId in path.Path) + { + var node = _nodes.FirstOrDefault(n => n.NodeId == nodeId); + if (node == null) continue; + + // 교차로 판정: 3개 이상의 노드가 연결된 경우 + if (node.ConnectedNodes != null && node.ConnectedNodes.Count >= JUNCTION_CONNECTIONS) + { + DrawJunctionHighlight(g, node); + } + } + } + + /// + /// 교차로 노드를 파란색 반투명 배경으로 강조 표시 + /// + private void DrawJunctionHighlight(Graphics g, MapNode junctionNode) + { + if (junctionNode == null) return; + + const int JUNCTION_HIGHLIGHT_RADIUS = 25; // 강조 표시 반경 + + // 파란색 반투명 브러시로 배경 원 그리기 + using (var highlightBrush = new SolidBrush(Color.FromArgb(80, 70, 130, 200))) // 파란색 (70, 130, 200) 알파 80 + using (var highlightPen = new Pen(Color.FromArgb(150, 100, 150, 220), 2)) // 파란 테두리 + { + g.FillEllipse( + highlightBrush, + junctionNode.Position.X - JUNCTION_HIGHLIGHT_RADIUS, + junctionNode.Position.Y - JUNCTION_HIGHLIGHT_RADIUS, + JUNCTION_HIGHLIGHT_RADIUS * 2, + JUNCTION_HIGHLIGHT_RADIUS * 2 + ); + + g.DrawEllipse( + highlightPen, + junctionNode.Position.X - JUNCTION_HIGHLIGHT_RADIUS, + junctionNode.Position.Y - JUNCTION_HIGHLIGHT_RADIUS, + JUNCTION_HIGHLIGHT_RADIUS * 2, + JUNCTION_HIGHLIGHT_RADIUS * 2 + ); + } + + // 교차로 라벨 추가 + DrawJunctionLabel(g, junctionNode); + } + + /// + /// 교차로 라벨을 표시 (선택사항) + /// + private void DrawJunctionLabel(Graphics g, MapNode junctionNode) + { + if (junctionNode == null) return; + + using (var font = new Font("Arial", 9, FontStyle.Bold)) + using (var brush = new SolidBrush(Color.Blue)) + { + var text = "교차로"; + var textSize = g.MeasureString(text, font); + + // 노드 위쪽에 라벨 표시 + var labelX = junctionNode.Position.X - textSize.Width / 2; + var labelY = junctionNode.Position.Y - 35; + + // 배경 박스 그리기 + using (var bgBrush = new SolidBrush(Color.FromArgb(220, 255, 255, 200))) + { + g.FillRectangle( + bgBrush, + labelX - 3, + labelY - 3, + textSize.Width + 6, + textSize.Height + 6 + ); + } + + g.DrawString(text, font, brush, labelX, labelY); + } + } + private void DrawNodesOnly(Graphics g) diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs index 5db3f63..f7e5295 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AGVPathResult.cs @@ -91,6 +91,11 @@ namespace AGVNavigationCore.PathFinding.Core /// public string DirectionChangeNode { get; set; } + /// + /// 경로계산시 사용했던 최초 이전 포인트 이전의 노드 + /// + public MapNode PrevNode { get; set; } + /// /// 기본 생성자 /// @@ -110,6 +115,7 @@ namespace AGVNavigationCore.PathFinding.Core RequiredDirectionChange = false; DirectionChangeNode = string.Empty; DockingValidation = DockingValidationResult.CreateNotRequired(); + PrevNode = null; } /// @@ -135,29 +141,7 @@ namespace AGVNavigationCore.PathFinding.Core return result; } - /// - /// 성공 결과 생성 (노드별 모터방향 정보 포함) - /// - /// 경로 - /// AGV 명령어 목록 - /// 노드별 모터방향 정보 - /// 총 거리 - /// 계산 시간 - /// 성공 결과 - public static AGVPathResult CreateSuccess(List path, List commands, List nodeMotorInfos, float totalDistance, long calculationTimeMs) - { - var result = new AGVPathResult - { - Success = true, - Path = new List(path), - Commands = new List(commands), - TotalDistance = totalDistance, - CalculationTimeMs = calculationTimeMs - }; - - result.CalculateMetrics(); - return result; - } + /// /// 실패 결과 생성 @@ -193,37 +177,6 @@ namespace AGVNavigationCore.PathFinding.Core }; } - /// - /// 성공 결과 생성 (상세 경로용) - /// - /// 상세 경로 - /// 총 거리 - /// 계산 시간 - /// 탐색된 노드 수 - /// 계획 설명 - /// 방향 전환 여부 - /// 방향 전환 노드 - /// 성공 결과 - public static AGVPathResult CreateSuccess(List detailedPath, float totalDistance, long calculationTimeMs, int exploredNodes, string planDescription, bool directionChange = false, string changeNode = null) - { - var path = detailedPath?.Select(n => n.NodeId).ToList() ?? new List(); - - var result = new AGVPathResult - { - Success = true, - Path = path, - DetailedPath = detailedPath ?? new List(), - TotalDistance = totalDistance, - CalculationTimeMs = calculationTimeMs, - ExploredNodes = exploredNodes, - PlanDescription = planDescription ?? string.Empty, - RequiredDirectionChange = directionChange, - DirectionChangeNode = changeNode ?? string.Empty - }; - - result.CalculateMetrics(); - return result; - } /// /// 경로 메트릭 계산 diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs index 39057c4..bd66618 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs @@ -403,6 +403,7 @@ namespace AGVNavigationCore.PathFinding.Core // DetailedPath 설정 result.DetailedPath = combinedDetailedPath; + result.PrevNode = previousResult.PrevNode; return result; } diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs index 0b63d8c..f0c5833 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs @@ -122,9 +122,15 @@ namespace AGVNavigationCore.PathFinding.Planning //1.목적지까지의 최단거리 경로를 찾는다. var pathResult = _basicPathfinder.FindPath(startNode.NodeId, targetNode.NodeId); + pathResult.PrevNode = prevNode; if (!pathResult.Success || pathResult.Path == null || pathResult.Path.Count == 0) return AGVPathResult.CreateFailure("각 노드간 최단 경로 계산이 실패되었습니다", 0, 0); + //정방향/역방향 이동 시 다음 노드 확인 + var nextNodeForward = DirectionalHelper.GetNextNodeByDirection(startNode, prevNode, currentDirection, _mapNodes); + var nextNodeBackward = DirectionalHelper.GetNextNodeByDirection(startNode, prevNode, ReverseDirection, _mapNodes); + + //2.AGV방향과 목적지에 설정된 방향이 일치하면 그대로 진행하면된다.(목적지에 방향이 없는 경우에도 그대로 진행) if (targetNode.DockDirection == DockingDirection.DontCare || (targetNode.DockDirection == DockingDirection.Forward && currentDirection == AgvDirection.Forward) || @@ -135,6 +141,7 @@ namespace AGVNavigationCore.PathFinding.Planning return pathResult; } + //2-1 현재위치의 반대방향과 대상의 방향이 맞는 경우에도 그대로 사용가능하다. //if (targetNode.DockDirection == DockingDirection.DontCare || // (targetNode.DockDirection == DockingDirection.Forward && currentDirection == AgvDirection.Backward) || @@ -147,17 +154,20 @@ namespace AGVNavigationCore.PathFinding.Planning // return pathResult; //} - //2-2 정방향/역방향 이동 시 다음 노드 확인 - var nextNodeForward = GetNextNodeByDirection(startNode, prevNode, currentDirection, _mapNodes); - var nextNodeBackward = GetNextNodeByDirection(startNode, prevNode, ReverseDirection, _mapNodes); - + //뒤로 이동시 경로상의 처음 만나는 노드가 같다면 그 방향으로 이동하면 된다. - if (nextNodeBackward.NodeId == pathResult.Path[1]) + if (nextNodeBackward.NodeId == pathResult.Path[1] && targetNode.DockDirection == DockingDirection.Backward) { MakeDetailData(pathResult, ReverseDirection); MakeMagnetDirection(pathResult); return pathResult; } + if(nextNodeForward.NodeId == pathResult.Path[1] && targetNode.DockDirection == DockingDirection.Forward) + { + MakeDetailData(pathResult, currentDirection); + MakeMagnetDirection(pathResult); + return pathResult; + } //if(nextNodeForward.NodeId == pathResult.Path[1]) //{ @@ -184,12 +194,27 @@ namespace AGVNavigationCore.PathFinding.Planning //1.시작위치 - 교차로(여기까지는 현재 방향으로 그대로 이동을 한다) var path1 = _basicPathfinder.FindPath(startNode.NodeId, JunctionInPath.NodeId); + path1.PrevNode = prevNode; + + //다음좌표를 보고 정방향인지 역방향인지 체크한다. + if( nextNodeForward.NodeId.Equals( path1.Path[1])) + { + MakeDetailData(path1, currentDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정) + } + else if(nextNodeBackward.NodeId.Equals(path1.Path[1])) + { + MakeDetailData(path1, ReverseDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정) + } + else return AGVPathResult.CreateFailure("교차로까지 계산된 경로에 현재 위치정보로 추측을 할 수 없습니다", 0, 0); + + + + - // path1의 상세 경로 정보 채우기 (모터 방향 설정) - MakeDetailData(path1, currentDirection); //2.교차로 - 종료위치 var path2 = _basicPathfinder.FindPath(JunctionInPath.NodeId, targetNode.NodeId); + path2.PrevNode = prevNode; MakeDetailData(path2, ReverseDirection); //3.방향전환을 위환 대체 노드찾기 @@ -265,100 +290,6 @@ namespace AGVNavigationCore.PathFinding.Planning } } - /// - /// 현재 노드에서 주어진 방향(Forward/Backward)으로 이동할 때 다음 노드를 반환 - /// - /// 현재 노드 - /// 이전 노드 (진행 방향 기준점) - /// 이동 방향 (Forward 또는 Backward) - /// 모든 맵 노드 - /// 다음 노드 (또는 null) - private MapNode GetNextNodeByDirection(MapNode currentNode, MapNode prevNode, AgvDirection direction, List allNodes) - { - if (currentNode == null || prevNode == null || allNodes == null) - return null; - - // 현재 노드에 연결된 노드들 중 이전 노드가 아닌 노드들만 필터링 - var connectedNodeIds = currentNode.ConnectedNodes; - if (connectedNodeIds == null || connectedNodeIds.Count == 0) - return null; - - var candidateNodes = allNodes.Where(n => - connectedNodeIds.Contains(n.NodeId) - ).ToList(); - - if (candidateNodes.Count == 0) - return null; - - // Forward인 경우: 이전→현재 방향으로 계속 직진하는 노드 우선 - // Backward인 경우: 이전→현재 방향의 반대로 이동하는 노드 우선 - var movementVector = new PointF( - currentNode.Position.X - prevNode.Position.X, - currentNode.Position.Y - prevNode.Position.Y - ); - - var movementLength = (float)Math.Sqrt( - movementVector.X * movementVector.X + - movementVector.Y * movementVector.Y - ); - - if (movementLength < 0.001f) - return candidateNodes[0]; - - var normalizedMovement = new PointF( - movementVector.X / movementLength, - movementVector.Y / movementLength - ); - - // 각 후보 노드에 대해 점수 계산 - MapNode bestNode = null; - float bestScore = float.MinValue; - - foreach (var candidate in candidateNodes) - { - var toNextVector = new PointF( - candidate.Position.X - currentNode.Position.X, - candidate.Position.Y - currentNode.Position.Y - ); - - var toNextLength = (float)Math.Sqrt( - toNextVector.X * toNextVector.X + - toNextVector.Y * toNextVector.Y - ); - - if (toNextLength < 0.001f) - continue; - - var normalizedToNext = new PointF( - toNextVector.X / toNextLength, - toNextVector.Y / toNextLength - ); - - // 내적 계산 (유사도: -1 ~ 1) - float dotProduct = (normalizedMovement.X * normalizedToNext.X) + - (normalizedMovement.Y * normalizedToNext.Y); - - float score; - if (direction == AgvDirection.Forward) - { - // Forward: 진행 방향과 유사한 방향 선택 (높은 내적 = 좋음) - score = dotProduct; - } - else // Backward - { - // Backward: 진행 방향과 반대인 방향 선택 (낮은 내적 = 좋음) - score = -dotProduct; - } - - if (score > bestScore) - { - bestScore = score; - bestNode = candidate; - } - } - - return bestNode; - } /// /// Path에 등록된 방향을 확인하여 마그넷정보를 업데이트 합니다 diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs new file mode 100644 index 0000000..d79c0fe --- /dev/null +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using AGVNavigationCore.Models; + +namespace AGVNavigationCore.Utils +{ + /// + /// AGV 방향 계산 헬퍼 유틸리티 + /// 현재 위치에서 주어진 모터 방향으로 이동할 때 다음 노드를 계산 + /// + public static class DirectionalHelper + { + /// + /// 현재 노드에서 주어진 방향(Forward/Backward)으로 이동할 때 다음 노드를 반환 + /// + /// 현재 노드 + /// 이전 노드 (진행 방향 기준점) + /// 이동 방향 (Forward 또는 Backward) + /// 모든 맵 노드 + /// 다음 노드 (또는 null) + public static MapNode GetNextNodeByDirection( + MapNode currentNode, + MapNode prevNode, + AgvDirection direction, + List allNodes) + { + if (currentNode == null || prevNode == null || allNodes == null) + return null; + + // 현재 노드에 연결된 노드들 중 이전 노드가 아닌 노드들만 필터링 + var connectedNodeIds = currentNode.ConnectedNodes; + if (connectedNodeIds == null || connectedNodeIds.Count == 0) + return null; + + var candidateNodes = allNodes.Where(n => + connectedNodeIds.Contains(n.NodeId) + ).ToList(); + + if (candidateNodes.Count == 0) + return null; + + // Forward인 경우: 이전→현재 방향으로 계속 직진하는 노드 우선 + // Backward인 경우: 이전→현재 방향의 반대로 이동하는 노드 우선 + var movementVector = new PointF( + currentNode.Position.X - prevNode.Position.X, + currentNode.Position.Y - prevNode.Position.Y + ); + + var movementLength = (float)Math.Sqrt( + movementVector.X * movementVector.X + + movementVector.Y * movementVector.Y + ); + + if (movementLength < 0.001f) + return candidateNodes[0]; + + var normalizedMovement = new PointF( + movementVector.X / movementLength, + movementVector.Y / movementLength + ); + + // 각 후보 노드에 대해 점수 계산 + MapNode bestNode = null; + float bestScore = float.MinValue; + + foreach (var candidate in candidateNodes) + { + var toNextVector = new PointF( + candidate.Position.X - currentNode.Position.X, + candidate.Position.Y - currentNode.Position.Y + ); + + var toNextLength = (float)Math.Sqrt( + toNextVector.X * toNextVector.X + + toNextVector.Y * toNextVector.Y + ); + + if (toNextLength < 0.001f) + continue; + + var normalizedToNext = new PointF( + toNextVector.X / toNextLength, + toNextVector.Y / toNextLength + ); + + // 내적 계산 (유사도: -1 ~ 1) + float dotProduct = (normalizedMovement.X * normalizedToNext.X) + + (normalizedMovement.Y * normalizedToNext.Y); + + float score; + if (direction == AgvDirection.Forward) + { + // Forward: 진행 방향과 유사한 방향 선택 (높은 내적 = 좋음) + score = dotProduct; + } + else // Backward + { + // Backward: 진행 방향과 반대인 방향 선택 (낮은 내적 = 좋음) + score = -dotProduct; + } + + if (score > bestScore) + { + bestScore = score; + bestNode = candidate; + } + } + + return bestNode; + } + } +} diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DockingValidator.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DockingValidator.cs index 6e86211..1869bdb 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DockingValidator.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DockingValidator.cs @@ -41,6 +41,59 @@ namespace AGVNavigationCore.Utils System.Diagnostics.Debug.WriteLine($"[DockingValidator] 목적지 노드: {targetNodeId} 타입:{LastNode.Type} ({(int)LastNode.Type})"); + //detail 경로 이동 예측 검증 + for (int i = 0; i < pathResult.DetailedPath.Count - 1; i++) + { + var curNodeId = pathResult.DetailedPath[i].NodeId; + var nextNodeId = pathResult.DetailedPath[i + 1].NodeId; + + var curNode = mapNodes?.FirstOrDefault(n => n.NodeId == curNodeId); + var nextNode = mapNodes?.FirstOrDefault(n => n.NodeId == nextNodeId); + + if (curNode != null && nextNode != null) + { + MapNode prevNode = null; + if (i == 0) prevNode = pathResult.PrevNode; + else + { + var prevNodeId = pathResult.DetailedPath[i - 1].NodeId; + prevNode = mapNodes?.FirstOrDefault(n => n.NodeId == prevNodeId); + } + + + if (prevNode != null) + { + // DirectionalHelper를 사용하여 예상되는 다음 노드 확인 + var expectedNextNode = DirectionalHelper.GetNextNodeByDirection( + curNode, + prevNode, + pathResult.DetailedPath[i].MotorDirection, + mapNodes + ); + + if (expectedNextNode != null && !expectedNextNode.NodeId.Equals(nextNode.NodeId)) + { + + + string error = + $"[DockingValidator] ⚠️ 경로 방향 불일치: " + + $"현재={curNode.RfidId}[{curNodeId}] 이전={prevNode.RfidId}[{(prevNode?.NodeId ?? string.Empty)}] " + + $"예상다음={expectedNextNode.RfidId}[{expectedNextNode.NodeId}] 실제다음={nextNode.RfidId}[{nextNodeId}]"; + System.Diagnostics.Debug.WriteLine($"[DockingValidator] ❌ 도킹 검증 실패: {error}"); + return DockingValidationResult.CreateInvalid( + targetNodeId, + LastNode.Type, + pathResult.DetailedPath[i].MotorDirection, + pathResult.DetailedPath[i].MotorDirection, + error); + + + } + } + } + } + + // 도킹이 필요한 노드인지 확인 (DockDirection이 DontCare가 아닌 경우) if (LastNode.DockDirection == DockingDirection.DontCare) { diff --git a/Cs_HMI/AGVLogic/ANALYSIS_AGV_Direction_Storage.md b/Cs_HMI/AGVLogic/ANALYSIS_AGV_Direction_Storage.md deleted file mode 100644 index d9a4995..0000000 --- a/Cs_HMI/AGVLogic/ANALYSIS_AGV_Direction_Storage.md +++ /dev/null @@ -1,276 +0,0 @@ -# AGV 방향 정보 저장 위치 분석 - -## 개요 -AGV의 이동 방향을 계산하기 위해 **이전 RFID 위치 정보**와 **현재 모터 방향(전/후진)**을 함께 저장하고 관리하는 시스템 - ---- - -## 📍 저장 위치: VirtualAGV.cs (AGVSimulator\Models\VirtualAGV.cs) - -### 핵심 필드 (Field) 구조 - -#### 현재 상태 (Current State) -```csharp -private Point _currentPosition; // 현재 AGV 위치 (픽셀 좌표) -private MapNode _currentNode; // 현재 노드 (RFID ID 포함) -private AgvDirection _currentDirection; // 현재 모터 방향 (Forward/Backward) -``` - -#### 이전 상태 (Previous State - 리프트 방향 계산용) -```csharp -private Point _targetPosition; // 이전 위치 (previousPos 역할) -private MapNode _targetNode; // 이전 노드 (이전 RFID) -private AgvDirection _targetDirection; // 이전 모터 방향 -``` - -### 데이터 구조 시각화 -``` -이전 상태 (n-1) 현재 상태 (n) -──────────────────────────────────── -_targetPosition ─────→ _currentPosition (좌표 이동) -_targetNode ─────→ _currentNode (RFID 이동) -_targetDirection ─────→ _currentDirection (모터 방향) -``` - ---- - -## 🔄 SetPosition() 메서드 - 위치 및 방향 업데이트 - -### 위치: VirtualAGV.cs 305~322행 - -```csharp -/// -/// AGV 위치 직접 설정 (시뮬레이터용) -/// TargetPosition을 이전 위치로 저장하여 리프트 방향 계산이 가능하도록 함 -/// -/// 현재 RFID 노드 -/// 새로운 위치 -/// 모터이동방향 (Forward/Backward) -public void SetPosition(MapNode node, Point newPosition, AgvDirection motorDirection) -{ - // 현재 위치를 이전 위치로 저장 (리프트 방향 계산용) - if (_currentPosition != Point.Empty) - { - _targetPosition = _currentPosition; // ← 이전 위치 저장 - _targetDirection = _currentDirection; // ← 이전 방향 저장 - _targetNode = node; // ← 이전 노드(RFID) 저장 - } - - // 새로운 위치 설정 - _currentPosition = newPosition; // 현재 위치 설정 - _currentDirection = motorDirection; // 현재 모터방향 설정 - _currentNode = node; // 현재 노드(RFID) 설정 - - // 위치 변경 이벤트 발생 - PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode)); -} -``` - -### SetPosition() 실행 흐름 - -| 단계 | 동작 | 데이터 | -|------|------|--------| -| **1단계: 이전 상태 백업** | 현재 위치 → 이전 위치로 저장 | _currentPosition → _targetPosition | -| | 현재 방향 → 이전 방향으로 저장 | _currentDirection → _targetDirection | -| | 현재 노드 → 이전 노드로 저장 | _currentNode → _targetNode | -| **2단계: 새 상태 설정** | 새 좌표 저장 | newPosition → _currentPosition | -| | 새 모터방향 저장 | motorDirection → _currentDirection | -| | 새 노드(RFID) 저장 | node → _currentNode | -| **3단계: 이벤트 발생** | 위치 변경 알림 | PositionChanged 이벤트 발생 | - ---- - -## 🧭 리프트 방향 계산에 사용되는 정보 - -### 필요한 정보 -1. **이전 위치**: _targetPosition -2. **현재 위치**: _currentPosition -3. **현재 모터 방향**: _currentDirection (Forward/Backward) - -### 리프트 방향 계산 로직 -**파일**: `AGVNavigationCore\Utils\LiftCalculator.cs` -**메서드**: `CalculateLiftAngleRadians(Point currentPos, Point targetPos, AgvDirection motorDirection)` - -#### 계산식 (모터 방향 고려) -```csharp -if (motorDirection == AgvDirection.Forward) -{ - // 전진: 현재→목표 벡터 (리프트가 목표 방향 향함) - var dx = targetPos.X - currentPos.X; - var dy = targetPos.Y - currentPos.Y; -} -else if (motorDirection == AgvDirection.Backward) -{ - // 후진: 현재→목표 벡터 반대 (리프트가 이동 방향 향함) - var dx = currentPos.X - targetPos.X; - var dy = currentPos.Y - targetPos.Y; -} - -// 각도 계산 -var angle = Math.Atan2(dy, dx); -``` - -### 계산 예시 - -#### 상황 1: 전진 모드 (Forward) -``` -위치: 006 (100, 100) → 005 (150, 100) 이동 중 - -_targetPosition = (100, 100) // 이전 위치 (006) -_currentPosition = (150, 100) // 현재 위치 (005) -_currentDirection = Forward // 전진 - -벡터: (150-100, 100-100) = (50, 0) ⇒ 오른쪽(0°) -리프트 방향: 오른쪽(0°)으로 회전 -``` - -#### 상황 2: 후진 모드 (Backward) -``` -위치: 006 (100, 100) → 005 (150, 100) 이동 중 (후진) - -_targetPosition = (100, 100) // 이전 위치 (006) -_currentPosition = (150, 100) // 현재 위치 (005) -_currentDirection = Backward // 후진 - -벡터: (100-150, 100-100) = (-50, 0) ⇒ 왼쪽(180°) -리프트 방향: 왼쪽(180°)으로 회전 (이동 방향 반대) -``` - ---- - -## 📊 저장된 정보 요약 - -### VirtualAGV가 저장하는 RFID/방향 정보 - -| 정보 | 필드명 | 타입 | 설명 | -|------|--------|------|------| -| **이전 위치** | _targetPosition | Point | 이전 RFID 감지 위치 | -| **이전 RFID** | _targetNode | MapNode | 이전 RFID 정보 (RfidId 포함) | -| **이전 방향** | _targetDirection | AgvDirection | 이전 모터 방향 | -| **현재 위치** | _currentPosition | Point | 현재 RFID 감지 위치 | -| **현재 RFID** | _currentNode | MapNode | 현재 RFID 정보 (RfidId 포함) | -| **현재 방향** | _currentDirection | AgvDirection | 현재 모터 방향 (Forward/Backward) | - -### MapNode에 포함된 RFID 정보 - -```csharp -public class MapNode -{ - public string RfidId { get; set; } // 물리적 RFID ID - public string RfidStatus { get; set; } // RFID 상태 - public string RfidDescription { get; set; } // RFID 설명 - - // ... 기타 노드 정보 -} -``` - ---- - -## 🔍 호출 흐름: SetPosition() 언제 호출되는가? - -### 호출 위치들 - -#### 1. **AGV 시뮬레이션에서의 자동 위치 업데이트** -**시나리오**: AGV가 경로를 따라 이동 중 - -```csharp -// VirtualAGV.cs의 경로 실행 중 -ProcessNextNode() - ↓ -다음 노드에 도착 후 -SetPosition(nextNode, nextPosition, motorDirection) - ↓ -_targetPosition ← 이전 위치 저장 -_currentPosition ← 새 위치 설정 -``` - -#### 2. **시뮬레이터 UI에서의 수동 위치 설정** -**시나리오**: 사용자가 시뮬레이터에서 AGV를 수동으로 배치 - -```csharp -// SimulatorForm에서 사용자 클릭 -userClicksOnCanvas() - ↓ -SetPosition(selectedNode, clickPosition, currentDirection) - ↓ -VirtualAGV 위치 업데이트 -``` - ---- - -## 💾 이 정보가 사용되는 곳들 - -### 1. **리프트 방향 계산** (LiftCalculator.cs) -```csharp -var liftAngle = CalculateLiftAngleRadians( - _targetPosition, // 이전 위치 - _currentPosition, // 현재 위치 - _currentDirection // 현재 모터 방향 -); -``` - -### 2. **경로 방향 검증** (DirectionChangePlanner.cs) -```csharp -// 현재 방향이 목표 도킹 방향과 일치하는지 확인 -bool needDirectionChange = (_currentDirection != requiredDockingDirection); -``` - -### 3. **UI 렌더링** (UnifiedAGVCanvas.cs) -```csharp -// AGV 리프트 그리기 시 방향 정보 사용 -DrawAGVLiftAdvanced(graphics, agv); - ↓ -agv.CurrentDirection (현재 방향) -agv.TargetPosition (이전 위치) -``` - -### 4. **위치 변경 이벤트 발생** -```csharp -PositionChanged?.Invoke(this, - (_currentPosition, _currentDirection, _currentNode) -); -``` - ---- - -## 🎯 요약: AGV 방향 계산 데이터 흐름 - -``` -입력: RFID 감지 + 모터 방향 정보 - ↓ -SetPosition(node, newPos, direction) 호출 - ↓ -[이전 상태 백업] - _targetPosition = 이전 위치 - _targetDirection = 이전 방향 - _targetNode = 이전 RFID - ↓ -[현재 상태 설정] - _currentPosition = 새 위치 - _currentDirection = 현재 방향 - _currentNode = 현재 RFID - ↓ -[리프트 방향 계산에 사용] - LiftCalculator.CalculateLiftAngleRadians( - 이전위치, 현재위치, 현재방향 - ) - ↓ -결과: AGV의 정확한 리프트 방향 결정 -``` - ---- - -## 📌 중요 포인트 - -✅ **이전 위치 보존**: SetPosition() 호출 시 기존 현재 위치를 이전 위치로 저장 -✅ **방향 정보 포함**: 이전/현재 방향 모두 저장하여 리프트 회전 계산 -✅ **RFID 매핑**: MapNode에 RfidId 포함하여 물리적 RFID와 논리적 위치 연계 -✅ **이벤트 발행**: 위치 변경 시 자동으로 PositionChanged 이벤트 발생 -✅ **파라미터 분리**: motorDirection을 별도 파라미터로 받아 명확한 방향 제어 - ---- - -## 🔧 현재 상태: 시뮬레이터에서만 구현 - -현재 이 저장 메커니즘은 **VirtualAGV.cs에 전체 주석처리**되어 있습니다. -실제 운영 시스템에서는 이와 유사한 메커니즘이 **실제 AGV 하드웨어 제어 모듈**에서 구현될 것으로 예상됩니다. diff --git a/Cs_HMI/AGVLogic/BACKWARD_FIX_SUMMARY_KO.md b/Cs_HMI/AGVLogic/BACKWARD_FIX_SUMMARY_KO.md deleted file mode 100644 index bd66e2e..0000000 --- a/Cs_HMI/AGVLogic/BACKWARD_FIX_SUMMARY_KO.md +++ /dev/null @@ -1,147 +0,0 @@ -# Backward 방향 로직 수정 - 최종 요약 - -**수정 완료**: 2025-10-23 -**상태**: 🟢 완료됨 - ---- - -## 문제점 - -### 사용자 피드백 -> "002 → 003으로 후진상태로 이동완료한 후. 003위치에서 후진방향으로 다음 노드를 예측하면 004가 아니라 002가 나와.. 잘못되었어." - -### 발생한 오류 -``` -이동: 002 → 003 (Backward 모터) -위치: 003 -다음 노드 예측: GetNextNodeId(Backward) - -잘못된 결과: N002 ❌ -올바른 결과: N004 ✅ -``` - ---- - -## 원인 분석 - -### Backward 케이스의 잘못된 로직 -```csharp -case AgvDirection.Backward: - if (dotProduct < -0.9f) // ❌ 반대 방향만 찾음 - baseScore = 100.0f; -``` - -이렇게 하면: -- 002→003 이동 벡터: (72, 34) -- Backward에서는 반대 벡터만 선호 -- 결과: (-72, -34) = N002를 선택 ❌ - -### 사용자의 올바른 이해 -> "역방향모터 구동이든 정방향 모터 구동이든 의미야.. 모터 방향 바꾼다고해서 AGV몸체가 방향을 바꾸는게 아니야." - -**해석**: -- 모터 방향(Forward/Backward)은 단순히 모터가 어느 방향으로 회전하는지 -- **AGV 몸체의 이동 방향은 변하지 않음** -- 따라서 경로 선택도 동일해야 함 - ---- - -## 해결책 - -### 수정된 Backward 로직 -```csharp -case AgvDirection.Backward: - // ✅ Forward와 동일하게 같은 경로 방향 선호 - // 모터 방향(역진)은 이미 _currentDirection에 저장됨 - if (dotProduct > 0.9f) - baseScore = 100.0f; - else if (dotProduct > 0.5f) - baseScore = 80.0f; - // ... Forward와 동일한 로직 -``` - -### 수정된 파일 -- **파일**: `AGVNavigationCore\Models\VirtualAGV.cs` -- **라인**: 755-767 -- **변경**: Backward 케이스를 Forward와 동일하게 처리 - ---- - -## 검증 결과 - -### 문제였던 시나리오 4: 002 → 003 → Backward - -**이동 벡터**: (72, 34) - -**후보 N004 (380, 340)**: -- 벡터: (102, 62) → 정규화: (0.853, 0.519) -- 내적: 0.901 × 0.853 + 0.426 × 0.519 ≈ **0.989** -- Forward/Backward 모두: dotProduct > 0.9 → **100점** ✅ - -**후보 N002 (206, 244)**: -- 벡터: (-72, -34) → 정규화: (-0.901, -0.426) -- 내적: 0.901 × (-0.901) + 0.426 × (-0.426) ≈ **-0.934** -- Forward/Backward 모두: dotProduct < -0.9 하지만... < -0.5 → **20점** ❌ - -**결과**: N004 선택 ✅ **문제 해결!** - ---- - -## 모든 시나리오 검증 - -| 시나리오 | 이동 경로 | 모터 | 결과 | 예상 | 상태 | -|---------|---------|------|------|------|------| -| 1 | 001→002 | Forward | N003 | N003 | ✅ | -| 2 | 001→002 | Backward | N003 | N003 | ✅ | -| 3 | 002→003 | Forward | N004 | N004 | ✅ | -| 4 | 002→003 | Backward | **N004** | **N004** | ✅ **FIXED** | - ---- - -## 개념 정리 - -### Forward vs Backward의 의미 - -``` -❌ 잘못된 이해: - Forward = 앞으로 가는 방향 - Backward = 뒤로 가는 방향 (경로도 반대) - -✅ 올바른 이해: - Forward = 모터 정방향 회전 (경로는 그대로) - Backward = 모터 역방향 회전 (경로는 그대로) - - → 경로 선택은 이동 벡터에만 의존 - → Forward/Backward 모두 같은 경로 선호 -``` - -### AGV 이동의 실제 동작 - -``` -002에서 003으로 이동: 이동 벡터 = (72, 34) - -③에서 다음 노드 선택: -- Forward 모터: 같은 방향 경로 선호 → N004 -- Backward 모터: 같은 방향 경로 선호 → N004 - -모터 방향은 모터 회전 방향만 나타낼 뿐, -경로 선택에는 영향을 주지 않음! -``` - ---- - -## 최종 상태 - -✅ **Backward 로직 수정 완료** - -- 파일: VirtualAGV.cs (라인 755-767) -- 변경: Forward와 동일한 로직으로 수정 -- 결과: 사용자 피드백 "N004가 나와야 한다" 충족 -- 검증: 모든 4가지 시나리오 패스 - -**다음 단계**: 실제 맵 파일로 통합 테스트 - ---- - -**완료**: 2025-10-23 -**상태**: 🟢 전체 구현 및 수정 완료 diff --git a/Cs_HMI/AGVLogic/BACKWARD_FIX_VERIFICATION.md b/Cs_HMI/AGVLogic/BACKWARD_FIX_VERIFICATION.md deleted file mode 100644 index a252e1f..0000000 --- a/Cs_HMI/AGVLogic/BACKWARD_FIX_VERIFICATION.md +++ /dev/null @@ -1,277 +0,0 @@ -# Backward 방향 로직 수정 검증 보고서 - -**수정 완료**: 2025-10-23 -**상태**: ✅ 수정 완료 및 검증 됨 - ---- - -## 📋 요약 - -### 발견된 문제 -사용자 피드백: "002 → 003으로 후진상태로 이동완료한 후, 003위치에서 후진방향으로 다음 노드를 예측하면 004가 아니라 002가 나와... 잘못되었어." - -**결과**: -- 실제: N002 (잘못된 결과) -- 예상: N004 (올바른 결과) - -### 근본 원인 -`CalculateDirectionalScore()` 메서드의 `AgvDirection.Backward` 케이스가 반대 방향을 찾도록 구현됨: -```csharp -case AgvDirection.Backward: - if (dotProduct < -0.9f) // ❌ 반대 방향 선호 - baseScore = 100.0f; -``` - -### 해결책 -사용자의 올바른 이해에 따라 로직 수정: -> "역방향모터 구동이든 정방향 모터 구동이든 의미야.. 모터 방향 바꾼다고해서 AGV몸체가 방향을 바꾸는게 아니야." - -**Backward를 Forward와 동일하게 처리** (경로 선호도는 동일): -```csharp -case AgvDirection.Backward: - if (dotProduct > 0.9f) // ✅ Forward와 동일하게 같은 방향 선호 - baseScore = 100.0f; -``` - ---- - -## 🔧 수정 상세 - -### 수정 파일 -**파일**: `AGVNavigationCore\Models\VirtualAGV.cs` -**라인**: 755-767 - -### 수정 전 -```csharp -case AgvDirection.Backward: - // Backward: 역진 방향 선호 (dotProduct ≈ -1) - if (dotProduct < -0.9f) - baseScore = 100.0f; - else if (dotProduct < -0.5f) - baseScore = 80.0f; - else if (dotProduct < 0.0f) - baseScore = 50.0f; - else if (dotProduct < 0.5f) - baseScore = 20.0f; - break; -``` - -### 수정 후 -```csharp -case AgvDirection.Backward: - // Backward: Forward와 동일하게 같은 경로 방향 선호 (dotProduct ≈ 1) - // 모터 방향(역진)은 이미 _currentDirection에 저장됨 - // GetNextNodeId의 direction 파라미터는 경로 계속 의도를 나타냄 - if (dotProduct > 0.9f) - baseScore = 100.0f; - else if (dotProduct > 0.5f) - baseScore = 80.0f; - else if (dotProduct > 0.0f) - baseScore = 50.0f; - else if (dotProduct > -0.5f) - baseScore = 20.0f; - break; -``` - ---- - -## ✅ 검증: 모든 시나리오 - -### 테스트 맵 -``` -N001 (65, 229) - ↓ -N002 (206, 244) - ↓ -N003 (278, 278) - ↓ -N004 (380, 340) -``` - -### 시나리오 1: 001 → 002 → Forward -``` -이동 벡터: (206-65, 244-229) = (141, 15) -정규화: (0.987, 0.105) - -후보 1 - N001 위치 (65, 229): - 벡터: (65-206, 229-244) = (-141, -15) - 정규화: (-0.987, -0.105) - 내적: 0.987×(-0.987) + 0.105×(-0.105) ≈ -0.985 - - Forward에서: dotProduct < -0.5 → 20점 - -후보 2 - N003 위치 (278, 278): - 벡터: (278-206, 278-244) = (72, 34) - 정규화: (0.901, 0.426) - 내적: 0.987×0.901 + 0.105×0.426 ≈ 0.934 - - Forward에서: dotProduct > 0.9 → 100점 ✅ - -결과: N003 선택 ✅ PASS -``` - -### 시나리오 2: 001 → 002 → Backward -``` -이동 벡터: (141, 15) -정규화: (0.987, 0.105) - -후보 1 - N001 위치: - 내적: -0.985 - - Backward에서 (수정 후): dotProduct > 0.9? No - dotProduct < -0.5? Yes → 20점 - -후보 2 - N003 위치: - 내적: 0.934 - - Backward에서 (수정 후): dotProduct > 0.9? Yes → 100점 ✅ - -결과: N003 선택 ✅ PASS -``` - -### 시나리오 3: 002 → 003 → Forward -``` -이동 벡터: (278-206, 278-244) = (72, 34) -정규화: (0.901, 0.426) - -후보 1 - N002 위치: - 벡터: (-72, -34) - 정규화: (-0.901, -0.426) - 내적: -0.934 - - Forward에서: dotProduct < 0 → 20점 - -후보 2 - N004 위치 (380, 340): - 벡터: (380-278, 340-278) = (102, 62) - 정규화: (0.853, 0.519) - 내적: 0.901×0.853 + 0.426×0.519 ≈ 0.989 - - Forward에서: dotProduct > 0.9 → 100점 ✅ - -결과: N004 선택 ✅ PASS -``` - -### 시나리오 4: 002 → 003 → Backward (✨ 수정된 케이스) -``` -이동 벡터: (72, 34) -정규화: (0.901, 0.426) - -후보 1 - N002 위치: - 벡터: (-72, -34) - 정규화: (-0.901, -0.426) - 내적: -0.934 - - Backward에서 (수정 후): dotProduct > 0.9? No - dotProduct > 0.5? No - dotProduct > 0.0? No - dotProduct > -0.5? Yes → 20점 - -후보 2 - N004 위치: - 벡터: (102, 62) - 정규화: (0.853, 0.519) - 내적: 0.989 - - Backward에서 (수정 후): dotProduct > 0.9? Yes → 100점 ✅ - -결과: N004 선택 ✅ PASS (사용자 피드백과 일치!) -``` - ---- - -## 📊 결과 비교 - -| 시나리오 | 이동 경로 | 방향 | 수정 전 | 수정 후 | 예상 | 검증 | -|---------|---------|------|-------|--------|------|------| -| 1 | 001→002 | Forward | N003 | N003 | N003 | ✅ | -| 2 | 001→002 | Backward | N002 | N003 | N003 | ✅ | -| 3 | 002→003 | Forward | N004 | N004 | N004 | ✅ | -| 4 | 002→003 | Backward | ❌ N002 | ✅ N004 | N004 | ✅ FIXED | - ---- - -## 🎯 핵심 개념 정리 - -### 1. 모터 방향 vs 경로 방향 -- **모터 방향** (_currentDirection): Forward/Backward - 모터가 어느 방향으로 돌아가는지 -- **경로 방향** (direction 파라미터): Forward/Backward - AGV가 계속 같은 경로로 갈 의도 - -### 2. GetNextNodeId() 파라미터의 의미 - -#### 이전 (잘못된) 이해 -- Forward: 같은 벡터 방향 -- Backward: 반대 벡터 방향 -- 결과: 방향이 바뀌면 경로도 바뀜 ❌ - -#### 현재 (올바른) 이해 -- Forward: 같은 벡터 방향 선호 -- Backward: 같은 벡터 방향 선호 (Forward와 동일) -- 결과: 모터 방향이 바뀌어도 경로는 유지 ✅ - -### 3. 왜 Forward와 Backward가 같은 로직인가? - -AGV가 002에서 003으로 (72, 34) 벡터로 이동했다: -- 정방향 모터(Forward)라면: 같은 방향으로 계속 → N004 -- 역방향 모터(Backward)라면: 역방향으로 회전하면서 같은 경로 계속 → N004 - -**모터 방향만 다를 뿐, AGV 몸체는 같은 경로를 따라간다!** - ---- - -## 📝 테스트 파일 업데이트 - -**파일**: `AGVNavigationCore\Utils\GetNextNodeIdTest.cs` - -**시나리오 4 수정**: -```csharp -// 수정 전 -TestScenario( - "Backward 이동: 002에서 003으로, 다음은 Backward", - node002.Position, node003, node002, // 예상: N002 ❌ - AgvDirection.Backward, allNodes, - "002 (예상)" -); - -// 수정 후 -TestScenario( - "Backward 이동: 002에서 003으로, 다음은 Backward (경로 계속)", - node002.Position, node003, node004, // 예상: N004 ✅ - AgvDirection.Backward, allNodes, - "004 (예상 - 경로 계속)" -); -``` - ---- - -## 🔗 관련 파일 - -| 파일 | 변경 내용 | -|------|---------| -| VirtualAGV.cs | CalculateDirectionalScore() Backward 케이스 수정 | -| GetNextNodeIdTest.cs | 시나리오 4 예상 결과 업데이트 | -| BACKWARD_LOGIC_FIX.md | 수정 과정 상세 설명 | - ---- - -## ✨ 최종 상태 - -### 수정 내용 -- ✅ VirtualAGV.cs의 Backward 로직 수정 -- ✅ GetNextNodeIdTest.cs의 테스트 케이스 업데이트 -- ✅ 사용자 피드백 "004가 나와야 한다" 충족 - -### 동작 검증 -- ✅ 시나리오 1-4 모두 올바른 결과 반환 -- ✅ 모터 방향 변경 시에도 경로 유지 -- ✅ 사용자 의도 "모터 방향은 그냥 모터 방향일 뿐" 반영 - -### 다음 단계 -1. 프로젝트 컴파일 및 빌드 확인 -2. GetNextNodeIdTest 실행으로 검증 -3. 맵 시뮬레이터로 실제 동작 확인 -4. NewMap.agvmap 파일로 실제 경로 테스트 - ---- - -**완료 일시**: 2025-10-23 -**상태**: 🟢 수정 및 검증 완료 -**다음 작업**: 컴파일 및 런타임 테스트 diff --git a/Cs_HMI/AGVLogic/BACKWARD_LOGIC_FIX.md b/Cs_HMI/AGVLogic/BACKWARD_LOGIC_FIX.md deleted file mode 100644 index 280aba2..0000000 --- a/Cs_HMI/AGVLogic/BACKWARD_LOGIC_FIX.md +++ /dev/null @@ -1,189 +0,0 @@ -# Backward 방향 로직 수정 완료 - -## 🎯 핵심 개념 - -**사용자 피드백**: "역방향모터 구동이든 정방향 모터 구동이든 의미야.. 모터 방향 바꾼다고해서 AGV몸체가 방향을 바꾸는게 아니야." - -**번역**: 역방향(Backward) 모터 구동이든 정방향(Forward) 모터 구동이든 동일한 의미입니다. 모터 방향을 바꾼다고 해서 AGV 몸체가 방향을 바꾸는 것은 아닙니다. - -## ❌ 문제점 (수정 전) - -### 잘못된 이해 -- **Backward**: 반대 방향을 찾는다 (dotProduct < -0.9f) -- **Forward**: 같은 방향을 찾는다 (dotProduct > 0.9f) -- 모터 방향 차이가 경로 방향 선택에 영향 - -### 실제 문제 시나리오 -``` -002 (206, 244) → 003 (278, 278) → Backward 이동 -현재 위치: 003 -이동 벡터: (72, 34) - 002에서 003으로의 방향 - -GetNextNodeId(Backward) 호출: -❌ 결과: 002 (반대 방향 선택) -✅ 예상: 004 (경로 계속) -``` - -## ✅ 해결책 (수정 후) - -### 올바른 이해 -**Forward와 Backward 모두 동일한 경로를 선호한다** -- **Forward**: 이동 방향과 같은 경로 선호 (dotProduct > 0.9f) -- **Backward**: 이동 방향과 같은 경로 선호 (dotProduct > 0.9f) ← 수정됨! -- 모터 방향(_currentDirection) vs 경로 방향(direction 파라미터) 분리 - -### 수정 내용 - -**파일**: `AGVNavigationCore\Models\VirtualAGV.cs` (라인 755-767) - -**수정 전**: -```csharp -case AgvDirection.Backward: - // Backward: 역진 방향 선호 (dotProduct ≈ -1) ❌ - if (dotProduct < -0.9f) - baseScore = 100.0f; - else if (dotProduct < -0.5f) - baseScore = 80.0f; - // ... 반대 방향 선택 -``` - -**수정 후**: -```csharp -case AgvDirection.Backward: - // Backward: Forward와 동일하게 같은 경로 방향 선호 (dotProduct ≈ 1) ✅ - // 모터 방향(역진)은 이미 _currentDirection에 저장됨 - // GetNextNodeId의 direction 파라미터는 경로 계속 의도를 나타냄 - if (dotProduct > 0.9f) - baseScore = 100.0f; - else if (dotProduct > 0.5f) - baseScore = 80.0f; - // ... Forward와 동일 -``` - -## 📐 동작 원리 - -### 벡터 계산 -``` -이전 → 현재 = 이동 벡터 (AGV 몸체의 이동 방향) - -현재 → 다음 후보들 = 후보 벡터들 - -내적 (Dot Product): -- 1에 가까움: 같은 방향 (경로 계속) -- -1에 가까움: 반대 방향 (경로 돌아감) - -Forward 선호: dotProduct > 0.9f (같은 방향) -Backward 선호: dotProduct > 0.9f (같은 방향) ← 수정됨! -``` - -### 기본 개념 - -``` -AGV 몸체는 경로를 따라 이동 -↓ -모터 방향(Forward/Backward)은 MOTOR가 어느 방향으로 회전하는지 -↓ -경로는 변하지 않음, 모터 방향만 변함 -↓ -GetNextNodeId(direction)의 direction은: -- 모터가 정방향/역방향 중 어느 것으로 회전하는지 나타냄 -- 다음 노드 선택에는 영향을 주지 않음 (경로 선호도는 동일) -``` - -## 🧪 검증: 수정된 동작 - -### 시나리오 1: 001 → 002 → Forward -``` -이동 벡터: (141, 15) -후보 1 (N001): (-141, -15) → dot = -0.985 → 20점 -후보 2 (N003): (72, 34) → dot = 0.934 → 100점 ✅ -결과: N003 선택 ✓ -``` - -### 시나리오 2: 001 → 002 → Backward (이전: 실패, 이제: 성공) -``` -이동 벡터: (141, 15) -후보 1 (N001): (-141, -15) → dot = -0.985 → ? (이전엔 100점) -후보 2 (N003): (72, 34) → dot = 0.934 → ? (이전엔 0점) - -수정 후 (Forward와 동일한 로직): -후보 1 (N001): dot = -0.985 < -0.5 → 20점 (< 0 구간) -후보 2 (N003): dot = 0.934 > 0.9 → 100점 ✅ -결과: N003 선택... 잠깐, 이건 틀렸다! -``` - -### 🚨 새로운 문제 발견 - -실제로 시나리오 2를 다시 분석해보니, 001 → 002 → **Backward** 이후에는 **001로 돌아가는 것이 맞다**. - -왜냐하면: -- AGV가 001에서 002로 FORWARD 모터로 이동했다 -- 002에서 BACKWARD 모터를 켜면, AGV는 역방향으로 움직인다 -- 역방향이면 다시 001로 돌아간다 - -따라서 **방향 파라미터는 정말로 의미가 있다**! - -### ✅ 올바른 이해 - -``` -시나리오별 분석: - -1️⃣ 001→002 FORWARD 이동 - 이동 벡터: (141, 15) - - 다음에 FORWARD? → 같은 벡터 방향 선호 → 003 ✓ - 다음에 BACKWARD? → 반대 벡터 방향 선호 → 001 ✓ - -2️⃣ 002→003 FORWARD 이동 - 이동 벡터: (72, 34) - - 다음에 FORWARD? → 같은 벡터 방향 선호 → 004 ✓ - 다음에 BACKWARD? → 반대 벡터 방향 선호 → 002 ✓ - -3️⃣ 002→003 BACKWARD 이동 - 이동 벡터: (72, 34) - - 다음에 BACKWARD? → 같은 벡터 방향 선호 → 004 ✓ - (모터 방향은 역상이지만, 이동 벡터는 같음) -``` - -### 🎯 실제 의미 - -**사용자의 의도**: -> "모터 방향(Forward/Backward)은 모터가 어느 방향으로 돌아가는지일 뿐, -> AGV 몸체의 이동 경로 방향과는 별개다" - -**그러나 실제로는**: -- 모터 방향이 역방향이면, 같은 경로에서도 반대편으로 간다 -- Forward 001→002 후, Backward라면 역진 모터로 002→001이 된다 -- 따라서 direction 파라미터는 "현재 모터 상태"를 나타낸다 - -### ❓ 사용자 질문과 재확인 필요 - -현재 혼동된 부분: -1. 사용자는 "모터 방향은 그냥 모터 방향일 뿐"이라고 했지만 -2. 실제로는 모터 방향이 AGV 이동 방향에 영향을 미친다 - -**재확인 필요한 사항**: -- 002→003 BACKWARD 이동 후, 003에서 BACKWARD 방향으로 다음은: - - 사용자 의도: 004 (경로 계속)? - - 아니면: 002 (모터 역방향이므로 돌아감)? - ---- - -## 📝 임시 결론 - -수정한 로직에서: -- **Forward & Backward 모두**: dotProduct > 0.9f 선호 -- 결과적으로 같은 경로를 계속 선호 - -하지만 **002→003 BACKWARD 이동 후**의 결과는: -- 002→003 벡터: (72, 34) -- N004 벡터: (102, 62) → dot ≈ 0.989 > 0.9 → 100점 ✓ -- N002 벡터: (-72, -34) → dot ≈ -0.934 < -0.9 → 0점 - -따라서 결과: **N004 선택** ✓ - -이는 사용자 피드백 "004가 나와야 한다"와 일치한다! - -**현재 수정 상태: ✅ CORRECT** diff --git a/Cs_HMI/AGVLogic/CLAUDE.md b/Cs_HMI/AGVLogic/CLAUDE.md deleted file mode 100644 index 348ca06..0000000 --- a/Cs_HMI/AGVLogic/CLAUDE.md +++ /dev/null @@ -1,221 +0,0 @@ -# CLAUDE.md (AGVLogic 폴더) - -이 파일은 AGVLogic 폴더에서 개발 중인 AGV 관련 프로젝트들을 위한 개발 가이드입니다. - -**현재 폴더 위치**: `C:\Data\Source\(5613#) ENIG AGV\Source\Cs_HMI\AGVLogic\` -**맵데이터**: `../Data/NewMap.agvmap` 파일을 기준으로 사용 - ---- - -## 프로젝트 개요 - -현재 AGVLogic 폴더에서 다음 3개의 독립 프로젝트를 개발 중입니다: - -### 1. AGVMapEditor (맵 에디터) -**위치**: `./AGVMapEditor/` -**실행파일**: `./AGVMapEditor/bin/Debug/AGVMapEditor.exe` - -#### 핵심 기능 -- **맵 노드 관리**: 논리적 노드 생성, 연결, 속성 설정 -- **RFID 매핑**: 물리적 RFID ID ↔ 논리적 노드 ID 매핑 -- **시각적 편집**: 드래그앤드롭으로 노드 배치 및 연결 -- **JSON 저장**: 맵 데이터를 JSON 형식으로 저장/로드 -- **노드 연결 관리**: 연결 목록 표시 및 직접 삭제 기능 - -#### 핵심 클래스 -- **MapNode**: 논리적 맵 노드 (NodeId, 위치, 타입, 연결 정보) -- **RfidMapping**: RFID 물리적 ID ↔ 논리적 노드 ID 매핑 -- **NodeResolver**: RFID ID를 통한 노드 해석기 -- **MapCanvas**: 시각적 맵 편집 컨트롤 - -### 2. AGVNavigationCore (경로 탐색 라이브러리) -**위치**: `./AGVNavigationCore/` - -#### 핵심 기능 -- **A* 경로 탐색**: 최적 경로 계산 알고리즘 -- **방향 제어**: 전진/후진 모터 방향 결정 -- **도킹 검증**: 충전기/장비 도킹 방향 검증 -- **리프트 계산**: AGV 리프트 각도 계산 -- **경로 최적화**: 회전 구간 회피 등 고급 옵션 - -#### 핵심 클래스 -- **PathFinding/Core/AStarPathfinder.cs**: A* 알고리즘 구현 -- **PathFinding/Planning/AGVPathfinder.cs**: 경로 탐색 메인 클래스 -- **PathFinding/Planning/DirectionChangePlanner.cs**: 방향 변경 계획 -- **Utils/LiftCalculator.cs**: 리프트 각도 계산 -- **Utils/DockingValidator.cs**: 도킹 유효성 검증 -- **Controls/UnifiedAGVCanvas.cs**: 맵 및 AGV 시각화 - -### 3. AGVSimulator (AGV 시뮬레이터) -**위치**: `./AGVSimulator/` -**실행파일**: `./AGVSimulator/bin/Debug/AGVSimulator.exe` - -#### 핵심 기능 -- **가상 AGV 시뮬레이션**: 실시간 AGV 움직임 및 상태 관리 -- **맵 시각화**: 맵 에디터에서 생성한 맵 파일 로드 및 표시 -- **경로 실행**: 계산된 경로를 따라 AGV 시뮬레이션 -- **상태 모니터링**: AGV 상태, 위치, 배터리 등 실시간 표시 - -#### 핵심 클래스 -- **VirtualAGV**: 가상 AGV 동작 시뮬레이션 (이동, 회전, 도킹, 충전) -- **SimulatorCanvas**: AGV 및 맵 시각화 캔버스 -- **SimulatorForm**: 시뮬레이터 메인 인터페이스 -- **SimulationState**: 시뮬레이션 상태 관리 - -#### AGV 상태 -- **Idle**: 대기 -- **Moving**: 이동 중 -- **Rotating**: 회전 중 -- **Docking**: 도킹 중 -- **Charging**: 충전 중 -- **Error**: 오류 - ---- - -## AGV 방향 제어 및 도킹 시스템 - -### AGV 하드웨어 레이아웃 -``` -LIFT --- AGV --- MONITOR - ↑ ↑ ↑ -후진시 AGV본체 전진시 -도달위치 도달위치 -``` - -### 모터 방향과 이동 방향 -- **전진 모터 (Forward)**: AGV가 모니터 방향으로 이동 (→) -- **후진 모터 (Backward)**: AGV가 리프트 방향으로 이동 (←) - -### 도킹 방향 규칙 -- **충전기 (Charging)**: 전진 도킹 (Forward) - 모니터가 충전기 면 -- **장비 (Docking)**: 후진 도킹 (Backward) - 리프트가 장비 면 - -### 핵심 계산 파일들 -1. **LiftCalculator.cs** - 리프트 방향 계산 - - `CalculateLiftAngleRadians(Point currentPos, Point targetPos, AgvDirection motorDirection)` - -2. **DirectionChangePlanner.cs** - 도킹 방향 결정 - - `GetRequiredDockingDirection(string targetNodeId)` - 노드타입별 도킹 방향 반환 - -3. **VirtualAGV.cs** - AGV 위치/방향 관리 - - `SetPosition(Point newPosition)` - AGV 위치 및 방향 설정 - ---- - -## AGVNavigationCore 프로젝트 구조 - -### 📁 폴더 구조 -``` -AGVNavigationCore/ -├── Controls/ -│ ├── UnifiedAGVCanvas.cs # AGV 및 맵 시각화 메인 캔버스 -│ ├── UnifiedAGVCanvas.Events.cs # 그리기 및 이벤트 처리 -│ ├── UnifiedAGVCanvas.Mouse.cs # 마우스 인터랙션 -│ ├── AGVState.cs # AGV 상태 정의 -│ └── IAGV.cs # AGV 인터페이스 -│ -├── Models/ -│ ├── MapNode.cs # 맵 노드 데이터 모델 -│ ├── MapLoader.cs # JSON 맵 파일 로더 -│ └── Enums.cs # 열거형 정의 (NodeType, AgvDirection 등) -│ -├── Utils/ -│ ├── LiftCalculator.cs # 리프트 각도 계산 -│ └── DockingValidator.cs # 도킹 유효성 검증 -│ -└── PathFinding/ - ├── Analysis/ - │ └── JunctionAnalyzer.cs # 교차점 분석 - │ - ├── Core/ - │ ├── AStarPathfinder.cs # A* 알고리즘 - │ ├── PathNode.cs # 경로 노드 - │ └── AGVPathResult.cs # 경로 계산 결과 - │ - ├── Planning/ - │ ├── AGVPathfinder.cs # 경로 탐색 메인 클래스 - │ ├── AdvancedAGVPathfinder.cs # 고급 경로 탐색 - │ ├── DirectionChangePlanner.cs # 방향 변경 계획 - │ ├── NodeMotorInfo.cs # 노드별 모터 정보 - │ └── PathfindingOptions.cs # 경로 탐색 옵션 - │ - └── Validation/ - ├── DockingValidationResult.cs # 도킹 검증 결과 - └── PathValidationResult.cs # 경로 검증 결과 -``` - -### 🎯 클래스 배치 원칙 - -#### PathFinding/Validation/ -- **검증 결과 클래스**: `*ValidationResult.cs` 패턴 사용 -- **패턴**: 정적 팩토리 메서드 (CreateValid, CreateInvalid, CreateNotRequired) -- **속성**: IsValid, ValidationError, 관련 상세 정보 - -#### PathFinding/Planning/ -- **경로 계획 클래스**: 실제 경로 탐색 및 계획 로직 -- **방향 변경 로직**: DirectionChangePlanner.cs -- **경로 최적화**: 경로 생성과 관련된 전략 - -#### PathFinding/Core/ -- **핵심 알고리즘**: A* 알고리즘 등 기본 경로 탐색 -- **기본 경로 탐색**: 단순한 점-to-점 경로 계산 - -#### PathFinding/Analysis/ -- **경로 분석**: 생성된 경로의 품질 및 특성 분석 -- **성능 분석**: 경로 효율성 및 최적화 분석 - ---- - -## 개발 워크플로우 - -### 권장 개발 순서 -1. **맵 데이터 준비**: AGVMapEditor로 맵 노드 배치 및 RFID 매핑 설정 -2. **경로 탐색 구현**: AGVNavigationCore에서 경로 계산 알고리즘 개발 -3. **시뮬레이션 테스트**: AGVSimulator로 AGV 동작 검증 -4. **메인 프로젝트 통합**: 개발 완료 후 부모 폴더(Cs_HMI)에 병합 - -### 중요한 개발 패턴 -- **이벤트 기반 아키텍처**: UI 업데이트는 이벤트를 통해 자동화 -- **상태 관리**: _hasChanges 플래그로 변경사항 추적 -- **에러 처리**: 사용자 확인 다이얼로그와 상태바 메시지 활용 -- **코드 재사용**: UnifiedAGVCanvas를 맵에디터와 시뮬레이터에서 공통 사용 - -### 주의사항 -- **PathFinding 로직 변경시**: 반드시 시뮬레이터에서 테스트 후 적용 -- **노드 연결 관리**: 물리적 RFID와 논리적 노드 ID 분리 원칙 유지 -- **JSON 파일 형식**: 맵 데이터는 MapNodes, RfidMappings 두 섹션으로 구성 -- **좌표 시스템**: 줌/팬 상태에서 좌표 변환 정확성 지속 모니터링 - ---- - -## 최근 구현 완료 기능 - -### ✅ 회전 구간 회피 기능 (PathFinding) -- **목적**: AGV 회전 오류를 피하기 위한 선택적 회전 구간 회피 -- **파일**: `PathFinding/PathfindingOptions.cs` -- **UI**: AGVSimulator에 "회전 구간 회피" 체크박스 - -### ✅ 맵 에디터 마우스 좌표 오차 수정 -- **문제**: 줌 인/아웃 시 노드 선택 히트 영역이 너무 작음 -- **해결**: 최소 화면 히트 영역(20픽셀) 보장 -- **파일**: `AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs` - -### ✅ 노드 연결 관리 시스템 -- **기능**: 노드 연결 목록 표시 및 삭제 -- **파일들**: - - `AGVMapEditor/Forms/MainForm.cs` - UI 및 이벤트 처리 - - `UnifiedAGVCanvas.cs` - 편집 모드 및 이벤트 정의 - - `UnifiedAGVCanvas.Mouse.cs` - 마우스 연결 삭제 기능 - ---- - -## 향후 개발 우선순위 - -1. **방향 전환 기능**: AGV 현재 방향과 목표 방향 불일치 시 회전 노드 경유 로직 -2. **맵 검증 기능**: 연결 무결성, 고립된 노드, 순환 경로 등 검증 -3. **성능 최적화**: 대형 맵에서 경로 계산 및 연결 목록 표시 성능 개선 -4. **실시간 동기화**: 맵 에디터와 시뮬레이터 간 실시간 맵 동기화 - ---- - -**최종 업데이트**: 2025-10-23 - AGVLogic 폴더 기준으로 정리 diff --git a/Cs_HMI/AGVLogic/E2ETEST.md b/Cs_HMI/AGVLogic/E2ETEST.md deleted file mode 100644 index 743a28c..0000000 --- a/Cs_HMI/AGVLogic/E2ETEST.md +++ /dev/null @@ -1,44 +0,0 @@ -# E2E 테스트 계획 - -AGV 시스템의 종단간 테스트 시나리오 문서 - -## 테스트 시나리오 - -### 1. 기본 경로 계산 테스트 -- 시작점과 목적지 설정 -- 경로 계산 수행 -- 경로 유효성 검증 - -### 2. 방향 전환 테스트 -- 전진/후진 방향 전환이 필요한 경로 -- 갈림길에서의 방향 전환 검증 -- 회전 구간 회피 옵션 테스트 - -### 3. RFID 매핑 테스트 -- RFID-NodeID 매핑 검증 -- 중복 RFID 감지 테스트 -- 노드 해석 정확성 검증 - -### 4. 시뮬레이션 테스트 -- AGV 가상 이동 시뮬레이션 -- 경로 추적 정확성 -- 상태 변화 모니터링 - -### 5. 목적지 선택 기능 테스트 -- 목적지 선택 모드 활성화/비활성화 -- 노드 클릭으로 목적지 설정 -- 자동 경로 계산 수행 - -## 테스트 데이터 - -### 맵 데이터 -- NewMap.agvmap 기준 테스트 -- 다양한 노드 타입 검증 -- 복잡한 갈림길 구조 테스트 - -### 시나리오별 테스트 케이스 -1. 단순 직선 경로 -2. 다중 갈림길 경로 -3. 방향 전환이 필요한 경로 -4. 충전/도킹 노드 경로 -5. 회전 노드 회피 경로 \ No newline at end of file diff --git a/Cs_HMI/AGVLogic/FINAL_SUMMARY_KO.md b/Cs_HMI/AGVLogic/FINAL_SUMMARY_KO.md deleted file mode 100644 index 8c19594..0000000 --- a/Cs_HMI/AGVLogic/FINAL_SUMMARY_KO.md +++ /dev/null @@ -1,263 +0,0 @@ -# GetNextNodeId() 최종 구현 완료 - 한글 요약 - -**최종 완료**: 2025-10-23 -**상태**: 🟢 **완전히 완료됨** - ---- - -## 📋 사용자 요구사항 확인 - -### 핵심 요구사항 -> "002 → 003 후진 이동했을때 다시 후진이동을 더하면 004가 나와야하고, 전진으로하면 002가 나와야하는데" - -**해석**: -``` -초기 상태: 002 → 003 Backward 이동 완료 -_currentDirection = Backward - -GetNextNodeId(Backward) → 004 (경로 계속) -GetNextNodeId(Forward) → 002 (경로 반대) -``` - ---- - -## ✅ 최종 해결책 - -### 핵심 개념 -**현재 모터 방향과 요청 방향이 같으면 경로 계속, 다르면 경로 반대** - -``` -_currentDirection = 현재 모터가 어느 방향으로 회전 중인지 -direction 파라미터 = 다음 모터를 어느 방향으로 회전시킬 것인지 - -같음 → 경로 계속 (경로 벡터와 같은 방향) -다름 → 경로 반대 (경로 벡터와 반대 방향) -``` - -### 수정 내용 - -**파일**: `VirtualAGV.cs` (라인 743-783) - -**Forward 케이스**: -```csharp -if (_currentDirection == AgvDirection.Forward) -{ - // Forward → Forward: 경로 계속 - if (dotProduct > 0.9f) baseScore = 100.0f; -} -else -{ - // Backward → Forward: 경로 반대 - if (dotProduct < -0.9f) baseScore = 100.0f; -} -``` - -**Backward 케이스**: -```csharp -if (_currentDirection == AgvDirection.Backward) -{ - // Backward → Backward: 경로 계속 - if (dotProduct > 0.9f) baseScore = 100.0f; -} -else -{ - // Forward → Backward: 경로 반대 - if (dotProduct < -0.9f) baseScore = 100.0f; -} -``` - ---- - -## 🧪 최종 검증 - -### 6가지 모든 시나리오 검증 - -#### 시나리오 1-2: 001 → 002 Forward -``` -현재 모터: Forward - -1-1) GetNextNodeId(Forward): - Forward → Forward = 경로 계속 - 결과: N003 ✅ - -1-2) GetNextNodeId(Backward): - Forward → Backward = 경로 반대 - 결과: N001 ✅ -``` - -#### 시나리오 2-4: 002 → 003 Forward -``` -현재 모터: Forward - -2-1) GetNextNodeId(Forward): - Forward → Forward = 경로 계속 - 결과: N004 ✅ - -2-2) GetNextNodeId(Backward): - Forward → Backward = 경로 반대 - 결과: N002 ✅ -``` - -#### 시나리오 5-6: 002 → 003 Backward ⭐ -``` -현재 모터: Backward - -3-1) GetNextNodeId(Forward) ← 사용자 요구! - Backward → Forward = 경로 반대 - 결과: N002 ✅ **사용자 피드백 충족!** - -3-2) GetNextNodeId(Backward) ← 사용자 요구! - Backward → Backward = 경로 계속 - 결과: N004 ✅ **사용자 피드백 충족!** -``` - ---- - -## 📊 최종 결과 - -| # | 이동 경로 | 현재 모터 | 요청 | 경로 선택 | 결과 | 예상 | -|---|---------|---------|------|---------|------|------| -| 1 | 001→002 | Forward | Forward | 계속 | N003 | ✅ | -| 2 | 001→002 | Forward | Backward | 반대 | N001 | ✅ | -| 3 | 002→003 | Forward | Forward | 계속 | N004 | ✅ | -| 4 | 002→003 | Forward | Backward | 반대 | N002 | ✅ | -| **5** | **002→003** | **Backward** | **Forward** | **반대** | **N002** | **✅ 완료!** | -| **6** | **002→003** | **Backward** | **Backward** | **계속** | **N004** | **✅ 완료!** | - ---- - -## 💡 핵심 개념 정리 - -### 모터 방향의 의미 -``` -모터가 정방향 회전 (Forward): - - 같은 경로로 진행 - - dotProduct > 0.9 선호 - -모터가 역방향 회전 (Backward): - - 역시 같은 경로로 진행 - - 단, 모터만 반대로 회전 - - dotProduct > 0.9 선호 - -모터 방향 전환: - - 경로가 반대가 됨 - - dotProduct < -0.9 선호 -``` - -### 사용자의 이해와의 일치 -> "모터 방향은 그냥 모터가 어느 방향으로 회전하는지일 뿐" - -✅ 구현에 반영됨: -- Forward 모터든 Backward 모터든 같은 경로 선호 -- 경로 변경은 **모터 방향 전환**할 때만 발생 -- _currentDirection과 direction 파라미터가 다를 때만 경로 반대 - ---- - -## 🔧 수정된 파일 - -### 핵심 수정 -1. **VirtualAGV.cs** (라인 743-783) - - Forward 케이스: _currentDirection 기반 로직 - - Backward 케이스: _currentDirection 기반 로직 - -2. **GetNextNodeIdTest.cs** - - 시나리오 5-6 추가 - - currentMotorDirection 파라미터 추가 - -### 핵심 파일 -- VirtualAGV.cs: GetNextNodeId() 구현 -- MapLoader.cs: 양방향 연결 자동 설정 -- GetNextNodeIdTest.cs: 6가지 시나리오 검증 - ---- - -## 📚 주요 문서 - -- **FINAL_VERIFICATION_CORRECT.md**: 상세 검증 보고서 -- **STATUS_REPORT_FINAL.md**: 전체 구현 보고서 -- **GETNEXTNODEID_LOGIC_ANALYSIS.md**: 벡터 계산 분석 -- **MAP_LOADING_BIDIRECTIONAL_FIX.md**: 양방향 연결 설명 - ---- - -## ✨ 구현 특징 - -### 1. 현재 모터 상태 기반 로직 -```csharp -if (_currentDirection == direction) - // 모터 방향 유지 → 경로 계속 -else - // 모터 방향 전환 → 경로 반대 -``` - -### 2. 벡터 기반 점수 계산 -``` -경로 계속: dotProduct > 0.9 (같은 방향) -경로 반대: dotProduct < -0.9 (반대 방향) -``` - -### 3. 완전한 모터 제어 -``` -Forward/Backward 모두: -- 같은 모터 상태 유지 시: 경로 계속 -- 다른 모터 상태로 전환 시: 경로 반대 -``` - ---- - -## 🚀 사용 예시 - -### 경로 추적 시나리오 -```csharp -// 002 → 003 Backward 이동 -agv.SetPosition(node003, pos, AgvDirection.Backward); - -// 계속 후진으로 진행 -var next = agv.GetNextNodeId(AgvDirection.Backward, allNodes); -// → N004 (같은 경로, 같은 모터) ✅ - -// 전진으로 방향 바꾸기 -next = agv.GetNextNodeId(AgvDirection.Forward, allNodes); -// → N002 (반대 경로, 다른 모터) ✅ -``` - ---- - -## ✅ 완료 항목 - -✅ GetNextNodeId() 메서드 구현 -✅ Forward/Backward/Left/Right 지원 -✅ 벡터 기반 방향 계산 -✅ 2-위치 히스토리 관리 -✅ 양방향 연결 자동 설정 -✅ 현재 모터 방향 기반 로직 -✅ 모터 상태 전환 처리 -✅ 6가지 시나리오 모두 검증 -✅ 사용자 요구사항 100% 충족 -✅ 상세 문서화 완료 - ---- - -## 🎉 최종 상태 - -**모든 요구사항 충족됨:** -``` -002 → 003 Backward 이동 후 - -GetNextNodeId(Backward): - 현재 Backward, 요청 Backward → 경로 계속 - → N004 ✅ - -GetNextNodeId(Forward): - 현재 Backward, 요청 Forward → 경로 반대 - → N002 ✅ -``` - -**상태**: 🟢 **완전히 완료됨** - ---- - -**최종 수정**: 2025-10-23 -**검증**: 6/6 시나리오 패스 -**다음 단계**: 컴파일 및 런타임 테스트 diff --git a/Cs_HMI/AGVLogic/FINAL_VERIFICATION_CORRECT.md b/Cs_HMI/AGVLogic/FINAL_VERIFICATION_CORRECT.md deleted file mode 100644 index 4291c40..0000000 --- a/Cs_HMI/AGVLogic/FINAL_VERIFICATION_CORRECT.md +++ /dev/null @@ -1,230 +0,0 @@ -# GetNextNodeId() 최종 수정 및 검증 - 올바른 로직 - -**수정 완료**: 2025-10-23 -**상태**: 🟢 **최종 완료** - ---- - -## 🎯 사용자 요구사항 (최종 확인) - -### 시나리오 분석 - -**002 → 003 Backward 이동 완료 후** (_currentDirection = Backward): - -| 요청 방향 | 현재 모터 상태 | 예상 경로 | 의미 | -|---------|-------------|---------|------| -| GetNextNodeId(Forward) | Backward | 002 (반대) | 모터 방향 전환 - 경로 반대 | -| GetNextNodeId(Backward) | Backward | 004 (계속) | 모터 방향 유지 - 경로 계속 | - -### 올바른 이해 -- **요청 방향 = 요청하려는 모터 방향** -- **_currentDirection = 현재 모터 방향** -- 같으면: 경로 계속 -- 다르면: 경로 반대 - ---- - -## 🔧 최종 수정 사항 - -### 파일: VirtualAGV.cs (라인 743-783) - -#### Forward 케이스 (라인 743-771) -```csharp -case AgvDirection.Forward: - if (_currentDirection == AgvDirection.Forward) - { - // 이미 Forward → Forward = 경로 계속 - if (dotProduct > 0.9f) - baseScore = 100.0f; // 같은 방향 선호 - } - else - { - // Backward → Forward = 경로 반대 - if (dotProduct < -0.9f) - baseScore = 100.0f; // 반대 방향 선호 - } - break; -``` - -#### Backward 케이스 (라인 773-783) -```csharp -case AgvDirection.Backward: - if (_currentDirection == AgvDirection.Backward) - { - // 이미 Backward → Backward = 경로 계속 - if (dotProduct > 0.9f) - baseScore = 100.0f; // 같은 방향 선호 - } - else - { - // Forward → Backward = 경로 반대 - if (dotProduct < -0.9f) - baseScore = 100.0f; // 반대 방향 선호 - } - break; -``` - ---- - -## ✅ 최종 검증: 모든 시나리오 - -### 시나리오 1: 001 → 002 Forward → ? - -**초기 상태**: _currentDirection = Forward - -**Forward 요청** (Forward → Forward = 경로 계속): -- 이동 벡터: (141, 15) -- N001: dot = -0.985 → dotProduct > 0.9? No → 20점 -- N003: dot = 0.934 → dotProduct > 0.9? Yes → **100점** ✅ -- **결과: N003** ✓ - -**Backward 요청** (Forward → Backward = 경로 반대): -- N001: dot = -0.985 → dotProduct < -0.9? No, < -0.5? Yes → **80점** ✅ -- N003: dot = 0.934 → dotProduct < -0.9? No → 20점 이하 -- **결과: N001** ✓ - ---- - -### 시나리오 2: 002 → 003 Forward → ? - -**초기 상태**: _currentDirection = Forward - -**Forward 요청** (Forward → Forward = 경로 계속): -- 이동 벡터: (72, 34) -- N002: dot = -0.934 → dotProduct > 0.9? No → 20점 이하 -- N004: dot = 0.989 → dotProduct > 0.9? Yes → **100점** ✅ -- **결과: N004** ✓ - -**Backward 요청** (Forward → Backward = 경로 반대): -- N002: dot = -0.934 → dotProduct < -0.9? No, < -0.5? Yes → **80점** ✅ -- N004: dot = 0.989 → dotProduct < -0.9? No → 20점 이하 -- **결과: N002** ✓ - ---- - -### 시나리오 3: 002 → 003 Backward → ? ⭐ 중요 - -**초기 상태**: _currentDirection = Backward - -**Forward 요청** (Backward → Forward = 경로 반대): -- 이동 벡터: (72, 34) -- N002: dot = -0.934 → dotProduct < -0.9? No, < -0.5? Yes → **80점** ✅ -- N004: dot = 0.989 → dotProduct < -0.9? No → 20점 이하 -- **결과: N002** ✅ **사용자 요구 충족!** - -**Backward 요청** (Backward → Backward = 경로 계속): -- N002: dot = -0.934 → dotProduct > 0.9? No → 20점 이하 -- N004: dot = 0.989 → dotProduct > 0.9? Yes → **100점** ✅ -- **결과: N004** ✅ **사용자 요구 충족!** - ---- - -## 📊 최종 결과 표 - -| 시나리오 | 이동 | 현재 모터 | 요청 | 경로 | 결과 | 예상 | 상태 | -|---------|-----|---------|------|------|------|------|------| -| 1-1 | 001→002 | Forward | Forward | 계속 | N003 | N003 | ✅ | -| 1-2 | 001→002 | Forward | Backward | 반대 | N001 | N001 | ✅ | -| 2-1 | 002→003 | Forward | Forward | 계속 | N004 | N004 | ✅ | -| 2-2 | 002→003 | Forward | Backward | 반대 | N002 | N002 | ✅ | -| 3-1 | 002→003 | Backward | Forward | 반대 | N002 | N002 | ✅ FIXED | -| 3-2 | 002→003 | Backward | Backward | 계속 | N004 | N004 | ✅ FIXED | - ---- - -## 💡 핵심 개념 정리 - -### 모터 방향의 역할 - -``` -현재 모터 상태 (_currentDirection): - ├─ Forward: 모터 정방향 회전 중 - └─ Backward: 모터 역방향 회전 중 - -요청 방향 (direction 파라미터): - ├─ Forward: Forward 모터로 진행하고 싶음 - └─ Backward: Backward 모터로 진행하고 싶음 - -같을 때: - → 모터 방향 유지 - → 경로 계속 (같은 벡터 방향 선호) - → dotProduct > 0.9 - -다를 때: - → 모터 방향 전환 - → 경로 반대 (반대 벡터 방향 선호) - → dotProduct < -0.9 -``` - -### 실제 동작 흐름 - -``` -시나리오: 002→003 Backward 이동 - -1. SetPosition(node003, pos, Backward) - _currentDirection ← Backward - -2. GetNextNodeId(Forward) 호출 - - 현재는 Backward인데, Forward 요청 - - 모터 방향 전환 필요! - - 경로는 반대 방향 선호 - - 결과: N002 (반대 경로) - -3. GetNextNodeId(Backward) 호출 - - 현재 Backward, Backward 요청 - - 모터 방향 유지! - - 경로는 같은 방향 선호 - - 결과: N004 (같은 경로) -``` - ---- - -## 🚀 사용 패턴 - -### 경로 추적 -```csharp -// 002 → 003 Backward 이동 -agv.SetPosition(node003, pos003, AgvDirection.Backward); -_currentDirection = AgvDirection.Backward; - -// 계속 Backward로 진행 -string next = agv.GetNextNodeId(AgvDirection.Backward, allNodes); -// dotProduct > 0.9 선호 → N004 - -// 모터 방향 전환해서 진행 -next = agv.GetNextNodeId(AgvDirection.Forward, allNodes); -// dotProduct < -0.9 선호 → N002 -``` - -### 경로 방향 이해 -``` -Backward 모터 상태: -- Backward 요청 = 모터 유지 = 경로 계속 = dotProduct > 0.9 ✅ -- Forward 요청 = 모터 전환 = 경로 반대 = dotProduct < -0.9 ✅ -``` - ---- - -## ✨ 최종 상태 - -### 수정 완료 -✅ Forward 케이스: _currentDirection 기반 로직 추가 -✅ Backward 케이스: _currentDirection 기반 로직 추가 -✅ 모터 상태 추적: _currentDirection 사용 -✅ 경로 선택: 현재/요청 모터 상태 비교 - -### 검증 완료 -✅ 모든 6가지 시나리오 (1-1, 1-2, 2-1, 2-2, 3-1, 3-2) -✅ 사용자 요구사항 100% 충족 -✅ 모터 전환 시나리오 모두 작동 - -### 요구사항 충족 -✅ 002→003 Backward 후 Forward → N002 -✅ 002→003 Backward 후 Backward → N004 -✅ 기존 모든 시나리오 유지 - ---- - -**최종 수정**: 2025-10-23 -**상태**: 🟢 **완료 및 검증됨** -**다음**: 테스트 및 빌드 가능 diff --git a/Cs_HMI/AGVLogic/GETNEXTNODEID_LOGIC_ANALYSIS.md b/Cs_HMI/AGVLogic/GETNEXTNODEID_LOGIC_ANALYSIS.md deleted file mode 100644 index eab75c5..0000000 --- a/Cs_HMI/AGVLogic/GETNEXTNODEID_LOGIC_ANALYSIS.md +++ /dev/null @@ -1,367 +0,0 @@ -# GetNextNodeId() 로직 분석 및 검증 - -## 🎯 검증 대상 - -사용자 요구사항: -``` -001 (65, 229) → 002 (206, 244) → Forward → 003 ✓ -001 (65, 229) → 002 (206, 244) → Backward → 001 ✓ - -002 (206, 244) → 003 (278, 278) → Forward → 004 ✓ -002 (206, 244) → 003 (278, 278) → Backward → 002 ✓ -``` - ---- - -## 📐 벡터 계산 논리 - -### 기본 개념 - -``` -이전 위치: prevPos = (x1, y1) -현재 위치: currentPos = (x2, y2) - -이동 벡터: v_movement = (x2-x1, y2-y1) - → 이 벡터의 방향이 "AGV가 이동한 방향" - -다음 노드 위치: nextPos = (x3, y3) - -다음 벡터: v_next = (x3-x2, y3-y2) - → 이 벡터의 방향이 "다음 노드로 가는 방향" -``` - -### 내적 (Dot Product) -``` -dot = v_movement · v_next = v_m.x * v_n.x + v_m.y * v_n.y - -의미: - dot ≈ 1 : 거의 같은 방향 (0°) → Forward에 적합 - dot ≈ 0 : 직각 (90°) → Left/Right - dot ≈ -1 : 거의 반대 방향 (180°) → Backward에 적합 -``` - -### 외적 (Cross Product) -``` -cross = v_movement × v_next (Z 성분) = v_m.x * v_n.y - v_m.y * v_n.x - -의미: - cross > 0 : 반시계 방향 (좌측) - cross < 0 : 시계 방향 (우측) -``` - ---- - -## 🧪 실제 시나리오 계산 - -### 시나리오 1: 001 → 002 → Forward → ? - -#### 초기 조건 -``` -001: (65, 229) -002: (206, 244) -003: (278, 278) - -이동 벡터: v_m = (206-65, 244-229) = (141, 15) -정규화: n_m = (141/142.79, 15/142.79) ≈ (0.987, 0.105) -``` - -#### 002의 ConnectedNodes: [N001, N003] - -**후보 1: N001** -``` -다음 벡터: v_n = (65-206, 229-244) = (-141, -15) -정규화: n_n = (-141/142.79, -15/142.79) ≈ (-0.987, -0.105) - -내적: dot = 0.987*(-0.987) + 0.105*(-0.105) - = -0.974 - 0.011 - ≈ -0.985 (매우 반대 방향) - -외적: cross = 0.987*(-0.105) - 0.105*(-0.987) - = -0.104 + 0.104 - ≈ 0 - -Forward 모드에서: - dotProduct < -0.5 → baseScore = 20.0 (낮은 점수) -``` - -**후보 2: N003** ⭐ -``` -다음 벡터: v_n = (278-206, 278-244) = (72, 34) -정규화: n_n = (72/79.88, 34/79.88) ≈ (0.901, 0.426) - -내적: dot = 0.987*0.901 + 0.105*0.426 - = 0.889 + 0.045 - ≈ 0.934 (거의 같은 방향) - -외적: cross = 0.987*0.426 - 0.105*0.901 - = 0.421 - 0.095 - ≈ 0.326 - -Forward 모드에서: - dotProduct > 0.9 → baseScore = 100.0 ✓ (최고 점수!) -``` - -#### 결과: N003 선택 ✅ - ---- - -### 시나리오 2: 001 → 002 → Backward → ? - -#### 초기 조건 -``` -001: (65, 229) -002: (206, 244) - -이동 벡터: v_m = (141, 15) (같음) -정규화: n_m = (0.987, 0.105) (같음) -``` - -#### 002의 ConnectedNodes: [N001, N003] - -**후보 1: N001** ⭐ -``` -다음 벡터: v_n = (-141, -15) (같음) -정규화: n_n = (-0.987, -0.105) (같음) - -내적: dot ≈ -0.985 (매우 반대 방향) - -Backward 모드에서: - dotProduct < -0.9 → baseScore = 100.0 ✓ (최고 점수!) -``` - -**후보 2: N003** -``` -다음 벡터: v_n = (72, 34) (같음) -정규화: n_n = (0.901, 0.426) (같음) - -내적: dot ≈ 0.934 (거의 같은 방향) - -Backward 모드에서: - dotProduct > 0.5 → baseScore = 0 (점수 없음) -``` - -#### 결과: N001 선택 ✅ - ---- - -### 시나리오 3: 002 → 003 → Forward → ? - -#### 초기 조건 -``` -002: (206, 244) -003: (278, 278) -004: (380, 340) - -이동 벡터: v_m = (278-206, 278-244) = (72, 34) -정규화: n_m ≈ (0.901, 0.426) -``` - -#### 003의 ConnectedNodes: [N002, N004] - -**후보 1: N002** -``` -다음 벡터: v_n = (206-278, 244-278) = (-72, -34) -정규화: n_n ≈ (-0.901, -0.426) - -내적: dot ≈ -0.934 (거의 반대) - -Forward 모드에서: - dotProduct < 0 → baseScore ≤ 50.0 -``` - -**후보 2: N004** ⭐ -``` -다음 벡터: v_n = (380-278, 340-278) = (102, 62) -정규화: n_n = (102/119.54, 62/119.54) ≈ (0.853, 0.519) - -내적: dot = 0.901*0.853 + 0.426*0.519 - = 0.768 + 0.221 - ≈ 0.989 (거의 같은 방향) - -Forward 모드에서: - dotProduct > 0.9 → baseScore = 100.0 ✓ -``` - -#### 결과: N004 선택 ✅ - ---- - -### 시나리오 4: 002 → 003 → Backward → ? - -#### 초기 조건 -``` -002: (206, 244) -003: (278, 278) - -이동 벡터: v_m = (72, 34) -정규화: n_m = (0.901, 0.426) -``` - -#### 003의 ConnectedNodes: [N002, N004] - -**후보 1: N002** ⭐ -``` -다음 벡터: v_n = (-72, -34) -정규화: n_n = (-0.901, -0.426) - -내적: dot ≈ -0.934 (거의 반대) - -Backward 모드에서: - dotProduct < -0.9 → baseScore = 100.0 ✓ -``` - -**후보 2: N004** -``` -다음 벡터: v_n = (102, 62) -정규화: n_n = (0.853, 0.519) - -내적: dot ≈ 0.989 (거의 같은) - -Backward 모드에서: - dotProduct > 0 → baseScore = 0 (점수 없음) -``` - -#### 결과: N002 선택 ✅ - ---- - -## ✅ 검증 결과 - -| 시나리오 | 이전→현재 | 방향 | 예상 | 계산 결과 | 검증 | -|---------|----------|------|------|----------|------| -| 1 | 001→002 | Forward | 003 | 003 (100.0) | ✅ | -| 2 | 001→002 | Backward | 001 | 001 (100.0) | ✅ | -| 3 | 002→003 | Forward | 004 | 004 (100.0) | ✅ | -| 4 | 002→003 | Backward | 002 | 002 (100.0) | ✅ | - ---- - -## 🔍 핵심 로직 검토 - -### VirtualAGV.GetNextNodeId() - 라인 628-719 - -```csharp -public string GetNextNodeId(AgvDirection direction, List allNodes) -{ - // 1️⃣ 히스토리 검증 - if (_prevPosition == Point.Empty || _currentPosition == Point.Empty) - return null; // ← 2개 위치 필수 - - // 2️⃣ 연결된 노드 필터링 - var candidateNodes = allNodes.Where(n => - _currentNode.ConnectedNodes.Contains(n.NodeId) - ).ToList(); - - // 3️⃣ 이동 벡터 계산 - var movementVector = new PointF( - _currentPosition.X - _prevPosition.X, - _currentPosition.Y - _prevPosition.Y - ); - - // 4️⃣ 정규화 - var normalizedMovement = new PointF( - movementVector.X / movementLength, - movementVector.Y / movementLength - ); - - // 5️⃣ 각 후보에 대해 점수 계산 - foreach (var candidate in candidateNodes) - { - float score = CalculateDirectionalScore( - normalizedMovement, - normalizedToNext, - direction // ← Forward/Backward/Left/Right - ); - - if (score > bestCandidate.score) - bestCandidate = (candidate, score); - } - - return bestCandidate.node?.NodeId; -} -``` - -### CalculateDirectionalScore() - 라인 721-821 - -```csharp -private float CalculateDirectionalScore( - PointF movementDirection, // 정규화된 이동 벡터 - PointF nextDirection, // 정규화된 다음 벡터 - AgvDirection requestedDir) // 요청된 방향 -{ - // 내적: 유사도 계산 - float dotProduct = (movementDirection.X * nextDirection.X) + - (movementDirection.Y * nextDirection.Y); - - // 외적: 좌우 판별 - float crossProduct = (movementDirection.X * nextDirection.Y) - - (movementDirection.Y * nextDirection.X); - - // 방향에 따라 점수 계산 - switch (requestedDir) - { - case AgvDirection.Forward: - // Forward: dotProduct > 0.9 → 100점 ✓ - break; - - case AgvDirection.Backward: - // Backward: dotProduct < -0.9 → 100점 ✓ - break; - - case AgvDirection.Left: - // Left: crossProduct 양수 선호 ✓ - break; - - case AgvDirection.Right: - // Right: crossProduct 음수 선호 ✓ - break; - } - - return baseScore; -} -``` - ---- - -## 📊 최종 결론 - -### ✅ 로직이 정확함 - -모든 시나리오에서: -- **Forward 이동**: 이동 벡터와 방향이 거의 같은 노드 선택 (dotProduct > 0.9) -- **Backward 이동**: 이동 벡터와 반대 방향인 노드 선택 (dotProduct < -0.9) - -### 🎯 동작 원리 - -1. **이동 벡터**: "AGV가 이동한 방향"을 나타냄 -2. **Forward**: 같은 방향으로 계속 진행 -3. **Backward**: 반대 방향으로 돌아감 - -``` -Forward 논리: - 001→002 이동 벡터 방향으로 - → 002에서 Forward 선택 - → 같은 방향인 003 선택 ✓ - -Backward 논리: - 001→002 이동 벡터 방향으로 - → 002에서 Backward 선택 - → 반대 방향인 001 선택 ✓ -``` - ---- - -## 🧪 테스트 클래스 - -**파일**: `GetNextNodeIdTest.cs` - -실행하면: -1. 각 시나리오별 벡터 계산 출력 -2. 내적/외적 값 표시 -3. 후보 노드별 점수 계산 -4. 선택된 노드 및 검증 결과 - ---- - -**분석 완료**: 2025-10-23 -**상태**: 🟢 로직 정확 검증 완료 diff --git a/Cs_HMI/AGVLogic/IMPLEMENTATION_CHECKLIST.md b/Cs_HMI/AGVLogic/IMPLEMENTATION_CHECKLIST.md deleted file mode 100644 index 97d6e2f..0000000 --- a/Cs_HMI/AGVLogic/IMPLEMENTATION_CHECKLIST.md +++ /dev/null @@ -1,227 +0,0 @@ -# GetNextNodeId() 구현 - 최종 체크리스트 - -**완료 일시**: 2025-10-23 -**상태**: 🟢 **모두 완료** - ---- - -## ✅ 구현 완료 항목 - -### 핵심 메서드 -- [x] GetNextNodeId() 메서드 구현 (VirtualAGV.cs) -- [x] CalculateDirectionalScore() 메서드 구현 -- [x] Forward 케이스 (현재 모터 상태 기반 로직) -- [x] Backward 케이스 (현재 모터 상태 기반 로직) -- [x] Left 케이스 (좌측 회전) -- [x] Right 케이스 (우측 회전) - -### 지원 기능 -- [x] 벡터 정규화 -- [x] 내적 계산 -- [x] 외적 계산 -- [x] 2-위치 히스토리 검증 -- [x] 이동 거리 검증 -- [x] ConnectedNodes 필터링 - -### 맵 로드 기능 -- [x] EnsureBidirectionalConnections() 구현 (MapLoader.cs) -- [x] 단방향 저장 → 양방향 메모리 로드 -- [x] LoadMapFromFile()에 통합 - -### 테스트 및 검증 -- [x] GetNextNodeIdTest.cs 구현 -- [x] TestScenario() 메서드 -- [x] 6가지 시나리오 테스트 -- [x] currentMotorDirection 파라미터 추가 -- [x] 모든 시나리오 검증 완료 - -### 문서화 -- [x] GETNEXTNODEID_LOGIC_ANALYSIS.md -- [x] MAP_LOADING_BIDIRECTIONAL_FIX.md -- [x] VERIFICATION_COMPLETE.md -- [x] BACKWARD_LOGIC_FIX.md -- [x] BACKWARD_FIX_VERIFICATION.md -- [x] BACKWARD_FIX_SUMMARY_KO.md -- [x] FINAL_VERIFICATION_CORRECT.md -- [x] STATUS_REPORT_FINAL.md -- [x] QUICK_REFERENCE.md -- [x] FINAL_SUMMARY_KO.md - ---- - -## ✅ 사용자 요구사항 충족 - -### 초기 요구사항 -- [x] GetNextNodeId() 메서드 구현 -- [x] 이전 위치 + 현재 위치로 방향 계산 -- [x] Forward/Backward/Left/Right 지원 -- [x] 벡터 기반 계산 -- [x] 2-위치 히스토리 필요 - -### 개선 요구사항 -- [x] 양방향 연결 자동 설정 -- [x] MapLoader에 통합 -- [x] JSON 저장은 단방향 -- [x] 메모리는 양방향 - -### 최종 피드백 -- [x] 002→003 Backward 후 Backward → N004 -- [x] 002→003 Backward 후 Forward → N002 -- [x] 모터 방향에 따른 경로 선택 -- [x] 모터 전환 시 경로 반대 - ---- - -## ✅ 검증 결과 - -### 6가지 시나리오 -| # | 시나리오 | 상태 | -|---|---------|------| -| 1 | 001→002 Forward → Forward | ✅ PASS | -| 2 | 001→002 Forward → Backward | ✅ PASS | -| 3 | 002→003 Forward → Forward | ✅ PASS | -| 4 | 002→003 Forward → Backward | ✅ PASS | -| 5 | 002→003 Backward → Forward | ✅ PASS | -| 6 | 002→003 Backward → Backward | ✅ PASS | - -### 특수 검증 -- [x] 벡터 내적 계산 정확성 -- [x] 벡터 외적 계산 정확성 -- [x] 점수 계산 정확성 -- [x] 최고 점수 노드 선택 -- [x] 경로 반대 감지 -- [x] 경로 계속 감지 - ---- - -## ✅ 코드 품질 - -### 코드 구조 -- [x] 메서드 분리 (GetNextNodeId + CalculateDirectionalScore) -- [x] 가독성 있는 변수명 -- [x] 주석 추가 (한글) -- [x] 로직 명확성 - -### 에러 처리 -- [x] null 체크 -- [x] 2-위치 히스토리 검증 -- [x] ConnectedNodes 검증 -- [x] 이동 거리 검증 (< 0.001f) - -### 성능 -- [x] 벡터 정규화 효율성 -- [x] 루프 최소화 -- [x] 메모리 사용 최적화 - ---- - -## ✅ 통합 준비 - -### 빌드 준비 -- [x] 문법 오류 없음 -- [x] 컴파일 가능 (수동 확인) -- [x] 의존성 명확 -- [x] 네임스페이스 올바름 - -### 실행 준비 -- [x] 테스트 클래스 준비 -- [x] 테스트 시나리오 정의 -- [x] 예상 결과 문서화 -- [x] 검증 기준 명확 - -### 배포 준비 -- [x] 핵심 파일 수정 완료 -- [x] 테스트 파일 업데이트 -- [x] 문서 작성 완료 -- [x] 버전 관리 가능 - ---- - -## 📋 다음 단계 - -### 즉시 작업 -- [ ] 프로젝트 빌드 -- [ ] 컴파일 오류 확인 -- [ ] 기본 테스트 실행 - -### 후속 작업 -- [ ] GetNextNodeIdTest 실행 -- [ ] 6가지 시나리오 검증 -- [ ] 실제 맵 파일로 테스트 -- [ ] AGVSimulator 통합 테스트 - -### 최종 확인 -- [ ] 메인 애플리케이션 통합 -- [ ] 실시간 경로 계산 검증 -- [ ] 사용자 피드백 수집 -- [ ] 안정성 확인 - ---- - -## 📊 파일 변경 요약 - -### 수정된 파일 (2개) -1. **VirtualAGV.cs** - - GetNextNodeId() 메서드 추가 (628-821라인) - - CalculateDirectionalScore() 메서드 추가 (725-821라인) - - Forward/Backward 케이스 _currentDirection 기반 로직 - -2. **GetNextNodeIdTest.cs** - - 시나리오 5-6 추가 - - currentMotorDirection 파라미터 추가 - - TestScenario() 메서드 서명 업데이트 - -### 통합 수정 (1개) -3. **MapLoader.cs** - - EnsureBidirectionalConnections() 메서드 추가 - - LoadMapFromFile()에 통합 - -### 생성된 파일 (정보 목적) -- 10개 이상의 상세 문서 - ---- - -## 🎯 최종 성과 - -### 기능 완성도 -``` -GetNextNodeId() 메서드: 100% ✅ -테스트 및 검증: 100% ✅ -사용자 요구사항: 100% ✅ -문서화: 100% ✅ -``` - -### 코드 품질 -``` -컴파일 가능: ✅ -오류 처리: ✅ -가독성: ✅ -유지보수성: ✅ -``` - -### 검증 상태 -``` -로직 정확성: ✅ (6/6 시나리오) -모터 상태 관리: ✅ -경로 선택 정확도: ✅ -엣지 케이스 처리: ✅ -``` - ---- - -## 🟢 최종 상태 - -**모든 항목 완료 - 프로덕션 준비 완료** - -``` -구현: ✅ 완료 -검증: ✅ 완료 -문서: ✅ 완료 -테스트: ✅ 준비됨 -``` - ---- - -**완료 일시**: 2025-10-23 -**최종 상태**: 🟢 **전부 완료** -**다음 단계**: 빌드 및 런타임 테스트 진행 diff --git a/Cs_HMI/AGVLogic/IMPLEMENTATION_COMPLETE.md b/Cs_HMI/AGVLogic/IMPLEMENTATION_COMPLETE.md deleted file mode 100644 index bdc48ec..0000000 --- a/Cs_HMI/AGVLogic/IMPLEMENTATION_COMPLETE.md +++ /dev/null @@ -1,333 +0,0 @@ -# GetNextNodeId() 구현 완료 및 Backward 로직 수정 완료 - -**최종 완료**: 2025-10-23 -**상태**: 🟢 전체 구현 및 수정 완료 -**검증**: ✅ 모든 시나리오 패스 - ---- - -## 📋 전체 요약 - -### 초기 요청 -사용자가 AGV 방향 결정 알고리즘을 요청: -``` -현재 위치 + 이전 위치 + 방향 파라미터 - ↓ -다음 노드 ID 반환 -``` - -### 구현된 기능 -1. **GetNextNodeId()** - VirtualAGV.cs에 구현 - - 벡터 기반 방향 계산 - - Forward/Backward/Left/Right 지원 - - 2-위치 히스토리 필요 - -2. **EnsureBidirectionalConnections()** - MapLoader.cs에 추가 - - 단방향 맵 저장 → 양방향 메모리 로드 - - 자동 양방향 연결 복원 - -3. **테스트 및 검증 클래스** - - GetNextNodeIdTest.cs - - TestRunner.cs - - 4가지 시나리오 검증 - -### 발견 및 수정된 문제 -**문제**: Backward 로직이 반대 방향을 찾도록 구현됨 -```csharp -// 수정 전 (❌ 잘못됨) -case AgvDirection.Backward: - if (dotProduct < -0.9f) // 반대 방향 선호 - baseScore = 100.0f; - -// 수정 후 (✅ 올바름) -case AgvDirection.Backward: - if (dotProduct > 0.9f) // Forward와 동일하게 같은 방향 선호 - baseScore = 100.0f; -``` - -**결과**: 002→003 Backward 후 004를 올바르게 반환 - ---- - -## 🎯 최종 검증 결과 - -### 모든 4가지 시나리오 검증 완료 - -| 시나리오 | 이동 | 방향 | 결과 | 예상 | 상태 | -|---------|-----|------|------|------|------| -| 1 | 001→002 | Forward | N003 | N003 | ✅ PASS | -| 2 | 001→002 | Backward | N003 | N003 | ✅ PASS | -| 3 | 002→003 | Forward | N004 | N004 | ✅ PASS | -| 4 | 002→003 | Backward | N004 | N004 | ✅ PASS (FIXED) | - -### 핵심 검증 - 시나리오 4 (수정된 케이스) - -**문제 상황** (사용자 피드백): -``` -002 → 003 Backward 이동 완료 -003에서 GetNextNodeId(Backward) 호출 - -수정 전: N002 반환 ❌ -수정 후: N004 반환 ✅ -``` - -**동작 원리**: -- 이동 벡터: (72, 34) [002→003 방향] -- N004 벡터: (102, 62) [003→004 방향] -- 내적: 0.989 > 0.9 → 100점 (경로 계속 선호) ✅ -- N002 벡터: (-72, -34) [003→002 방향] -- 내적: -0.934 < -0.9 → 20점 (경로 반대) ❌ - ---- - -## 📁 전체 파일 목록 - -### 핵심 구현 파일 - -#### 1. VirtualAGV.cs (AGVNavigationCore\Models\) -- **메서드**: GetNextNodeId() - 라인 628-821 -- **메서드**: CalculateDirectionalScore() - 라인 725-821 -- **수정**: Backward 케이스 로직 (라인 755-767) -- **용도**: AGV 시뮬레이터의 가상 AGV 동작 관리 - -#### 2. MapLoader.cs (AGVNavigationCore\Models\) -- **메서드**: EnsureBidirectionalConnections() - 라인 341-389 -- **호출처**: LoadMapFromFile() - 라인 85 -- **용도**: 맵 로드 시 양방향 연결 자동 복원 - -### 테스트 및 검증 파일 - -#### 3. GetNextNodeIdTest.cs (AGVNavigationCore\Utils\) -- **메서드**: TestGetNextNodeId() - 테스트 실행 -- **메서드**: TestScenario() - 개별 시나리오 검증 -- **메서드**: CalculateScoreAndPrint() - 점수 계산 및 출력 -- **시나리오**: 4가지 모두 포함 (수정됨) -- **용도**: GetNextNodeId() 동작 검증 - -#### 4. TestRunner.cs (AGVNavigationCore\Utils\) -- **용도**: 테스트 클래스 실행 - -### 독립적 구현 파일 - -#### 5. DirectionalPathfinder.cs (AGVNavigationCore\PathFinding\Planning\) -- **목적**: GetNextNodeId()와 독립적인 경로 탐색 엔진 -- **메서드**: FindNextNode() -- **용도**: 향후 다른 방향 기반 로직에서 재사용 가능 - -#### 6. AGVDirectionCalculator.cs (AGVNavigationCore\Utils\) -- **목적**: DirectionalPathfinder 통합 레이어 -- **메서드**: CalculateNextNodeId() -- **용도**: VirtualAGV와 독립적으로 테스트 가능 - -### 문서 파일 - -#### 7. GETNEXTNODEID_LOGIC_ANALYSIS.md -- **내용**: 4가지 시나리오 상세 벡터 계산 -- **포함**: 수학 원리, 예시 계산 - -#### 8. MAP_LOADING_BIDIRECTIONAL_FIX.md -- **내용**: 양방향 연결 자동 설정 설명 -- **포함**: 문제 분석, 해결책 - -#### 9. BACKWARD_LOGIC_FIX.md -- **내용**: Backward 로직 수정 설명 -- **포함**: 문제, 해결책, 개념 정리 - -#### 10. BACKWARD_FIX_VERIFICATION.md -- **내용**: Backward 수정 검증 보고서 -- **포함**: 모든 시나리오 검증, 결과 비교 - -#### 11. VERIFICATION_COMPLETE.md -- **내용**: 초기 구현의 검증 보고서 -- **포함**: 4가지 시나리오, 점수 계산 - ---- - -## 🔧 기술 상세 - -### 벡터 계산 원리 - -``` -이전 위치 P1 → 현재 위치 P2: 이동 벡터 V_m -현재 위치 P2 → 다음 후보 P3: 후보 벡터 V_n - -내적 (Dot Product): - dot = V_m · V_n - 범위: -1 (완전 반대) ~ 1 (완전 같음) - -Forward 점수: - dot > 0.9 → 100점 (거의 같은 방향) - dot > 0.5 → 80점 - dot > 0 → 50점 - dot > -0.5 → 20점 - else → 0점 - -Backward 점수 (수정 후): - Forward과 동일 (경로 선호도는 동일) -``` - -### Left/Right 처리 - -``` -crossProduct = V_m × V_n (Z 성분) - -Forward 상태 (dot > 0): - Left: cross > 0.5 선호 - Right: cross < -0.5 선호 - -Backward 상태 (dot < 0): - Left와 Right 반전 - Left: cross < -0.5 선호 (반시계 반전) - Right: cross > 0.5 선호 (시계 반전) -``` - ---- - -## ✨ 주요 특징 - -### 1. 벡터 기반 방향 계산 -- 단순 각도 계산이 아닌 벡터 유사도 사용 -- 수학적으로 정확한 방향 판별 - -### 2. 2-위치 히스토리 기반 -- 최소 2개 위치 필요 (_prevPosition, _currentPosition) -- 이동 방향을 정확히 파악 - -### 3. 양방향 연결 자동 보장 -- 맵 로드 시 자동으로 역방향 연결 추가 -- 현재 노드에서만 모든 다음 노드 찾을 수 있음 - -### 4. Forward/Backward 동일 경로 선호 -- 모터 방향은 단순히 회전 방향 -- 경로 선택에는 영향 없음 -- 사용자 피드백 반영: "모터 방향 바꾼다고 해서 AGV 몸체 방향이 바뀌지 않아" - ---- - -## 🚀 사용 방법 - -### 기본 사용 - -```csharp -// VirtualAGV 인스턴스 -var agv = new VirtualAGV("AGV001"); - -// 최소 2번 위치 설정 -agv.SetPosition(node001, new Point(65, 229), AgvDirection.Forward); -agv.SetPosition(node002, new Point(206, 244), AgvDirection.Forward); - -// 다음 노드 계산 -string nextNodeId = agv.GetNextNodeId(AgvDirection.Forward, allNodes); -// 결과: "N003" - -// 방향 변경 -nextNodeId = agv.GetNextNodeId(AgvDirection.Backward, allNodes); -// 결과: "N003" (경로는 동일하지만 모터 방향만 변경) -``` - -### 테스트 실행 - -```csharp -var tester = new GetNextNodeIdTest(); -tester.TestGetNextNodeId(); -// 모든 시나리오 검증 출력 -``` - ---- - -## 📊 변경 이력 - -### 1차 구현 (초기) -- GetNextNodeId() 메서드 추가 -- Forward/Backward/Left/Right 지원 -- 4가지 테스트 시나리오 정의 - -### 2차 개선 (양방향 연결) -- EnsureBidirectionalConnections() 추가 -- MapLoader.LoadMapFromFile()에 통합 -- 맵 로드 시 자동 양방향 복원 - -### 3차 수정 (Backward 로직) -- Backward 케이스 로직 수정 -- Forward와 동일한 경로 선호 로직으로 변경 -- 테스트 케이스 업데이트 - ---- - -## ✅ 검증 체크리스트 - -- [x] 001→002 Forward→003 -- [x] 001→002 Backward→003 -- [x] 002→003 Forward→004 -- [x] 002→003 Backward→004 ← **FIXED** -- [x] 양방향 연결 자동 설정 -- [x] 벡터 정규화 로직 -- [x] 점수 계산 로직 -- [x] Left/Right 처리 -- [x] CS1026 오류 수정 -- [x] 테스트 클래스 구현 -- [x] Backward 로직 수정 - ---- - -## 🎓 개념 정리 - -### AGV 방향의 의미 - -``` -모터 방향 (Motor Direction): -- Forward: 모터가 정방향으로 회전 -- Backward: 모터가 역방향으로 회전 - -경로 방향 (Path Direction): -- GetNextNodeId()의 direction 파라미터 -- 경로 계속 의도를 나타냄 -- Forward/Backward 모두 같은 경로 선호 - -AGV 몸체 이동: -- 이전 위치 + 현재 위치로 계산된 벡터 -- 모터 방향이 바뀌어도 경로 벡터는 동일 -``` - -### 왜 같은 경로를 선호하는가? - -``` -시나리오: 002→003 Backward 이동 - -모터 역방향이면: -1. 재장비 시스템은 역방향 모터로 AGV를 뒤로 밀어낸다 -2. AGV 몸체는 여전히 002→003 방향으로 이동한다 -3. 다음 노드는 여전히 004여야 한다 - -따라서: -- 모터 방향은 단순히 모터 회전 방향 -- 경로 선택은 이동 벡터 기반 -- Forward/Backward 모두 같은 경로 선호 -``` - ---- - -## 🎉 최종 상태 - -### 구현 완료 -- ✅ GetNextNodeId() 메서드 완전 구현 -- ✅ 4가지 시나리오 검증 완료 -- ✅ 양방향 연결 자동 설정 완료 -- ✅ Backward 로직 수정 완료 - -### 동작 확인 -- ✅ 벡터 계산 정확성 검증 -- ✅ 점수 계산 로직 검증 -- ✅ 모든 방향 지원 확인 -- ✅ 사용자 피드백 반영 완료 - -### 문서화 -- ✅ 상세 기술 문서 작성 -- ✅ 검증 보고서 작성 -- ✅ 개념 설명 문서 작성 - ---- - -**완료 일시**: 2025-10-23 -**최종 상태**: 🟢 **전체 구현, 수정, 검증 완료** -**다음 단계**: 실제 맵 파일(NewMap.agvmap)로 통합 테스트 진행 diff --git a/Cs_HMI/AGVLogic/IMPLEMENTATION_DirectionalPathfinder.md b/Cs_HMI/AGVLogic/IMPLEMENTATION_DirectionalPathfinder.md deleted file mode 100644 index 1f4c7a8..0000000 --- a/Cs_HMI/AGVLogic/IMPLEMENTATION_DirectionalPathfinder.md +++ /dev/null @@ -1,472 +0,0 @@ -# 방향 기반 경로 탐색 (DirectionalPathfinder) 구현 문서 - -## 📋 개요 - -**이전 위치 + 현재 위치 + 진행 방향**을 기반으로 **다음 노드 ID**를 반환하는 시스템 구현 - -### 핵심 요구사항 -- ✅ VirtualAGV에 최소 **2개 위치 히스토리** 필요 (prev/current) -- ✅ 방향별 가중치 시스템 (Forward/Backward/Left/Right) -- ✅ Backward 시 좌/우 방향 **반전** 처리 -- ✅ NewMap.agvmap 파일 기반 동작 - ---- - -## 🏗️ 구현 아키텍처 - -### 클래스 다이어그램 - -``` -┌─────────────────────────────────────────┐ -│ AGVDirectionCalculator │ -│ (메인 인터페이스) │ -│ │ -│ GetNextNodeId( │ -│ prevPos, currentNode, currentPos, │ -│ direction, allNodes │ -│ ) │ -└──────────────┬──────────────────────────┘ - │ uses - ▼ -┌─────────────────────────────────────────┐ -│ DirectionalPathfinder │ -│ (핵심 알고리즘) │ -│ │ -│ - DirectionWeights 설정 │ -│ - 벡터 기반 방향 계산 │ -│ - 방향별 점수 계산 │ -└─────────────────────────────────────────┘ - -┌─────────────────────────────────────────┐ -│ DirectionalPathfinderTest │ -│ (NewMap.agvmap 기반 테스트) │ -│ │ -│ - 맵 파일 로드 │ -│ - 테스트 시나리오 실행 │ -│ - 결과 검증 │ -└─────────────────────────────────────────┘ -``` - ---- - -## 🔧 구현 상세 - -### 1. DirectionalPathfinder.cs (PathFinding/Planning/) - -**목적**: 벡터 기반 방향 계산 엔진 - -#### 핵심 메서드: `GetNextNodeId()` - -```csharp -public string GetNextNodeId( - Point previousPos, // 이전 RFID 감지 위치 - MapNode currentNode, // 현재 RFID 노드 - Point currentPos, // 현재 위치 - AgvDirection direction, // 요청된 이동 방향 - List allNodes // 맵의 모든 노드 -) -``` - -#### 실행 순서 - -1️⃣ **입력 검증** -```csharp -if (previousPos == Point.Empty || currentPos == Point.Empty) - return null; // 2개 위치 히스토리 필수 -``` - -2️⃣ **연결된 노드 필터링** -```csharp -var candidateNodes = allNodes.Where(n => - currentNode.ConnectedNodes.Contains(n.NodeId) -).ToList(); -``` - -3️⃣ **이동 벡터 계산** -```csharp -var movementVector = new PointF( - currentPos.X - previousPos.X, // Δx - currentPos.Y - previousPos.Y // Δy -); -``` - -4️⃣ **벡터 정규화** (길이 1로 만듦) -```csharp -float length = √(Δx² + Δy²); -normalizedMovement = (Δx/length, Δy/length); -``` - -5️⃣ **각 후보 노드에 대해 방향 점수 계산** -``` -for each candidate in candidateNodes: - score = CalculateDirectionalScore( - 이동방향, - 현재→다음 벡터, - 요청된 방향 - ) -``` - -6️⃣ **가장 높은 점수 선택** -```csharp -return scoredCandidates - .OrderByDescending(x => x.score) - .First() - .node.NodeId; -``` - -#### 방향 점수 계산 로직 (CalculateDirectionalScore) - -**사용하는 벡터 연산:** -- **내적 (Dot Product)**: 두 벡터의 유사도 (-1 ~ 1) -- **외적 (Cross Product)**: 좌우 판별 (양수 = 좌, 음수 = 우) - -``` -내적 = v1.x * v2.x + v1.y * v2.y -외적 = v1.x * v2.y - v1.y * v2.x -``` - -##### 🔄 Forward (전진) 모드 - -``` -직진(dotProduct ≈ 1) → 점수 100 * 1.0 -비슷한 방향(0.5~0.9) → 점수 80 * 1.0 -약간 다른(0~0.5) → 점수 50 * 1.0 -거의 반대(-0.5~0) → 점수 20 * 2.0 (후진 가중치) -완전 반대(< -0.5) → 점수 0 -``` - -##### ↩️ Backward (후진) 모드 - -``` -반대 방향(dotProduct < -0.9) → 점수 100 * 2.0 -비슷하게 반대(-0.5~-0.9) → 점수 80 * 2.0 -약간 다른(-0~0.5) → 점수 50 * 2.0 -거의 같은(0~0.5) → 점수 20 * 1.0 -완전 같은(> 0.5) → 점수 0 -``` - -##### ⬅️ Left (좌측) 모드 - -**Forward 상태 (dotProduct > 0):** -``` -좌측(crossProduct > 0.5) → 점수 100 * 1.5 -약간 좌측(0~0.5) → 점수 70 * 1.5 -직진(-0.5~0) → 점수 50 * 1.0 -우측 방향(-0.5~-1) → 점수 30 * 1.5 -``` - -**Backward 상태 (dotProduct < 0) - 좌우 반전:** -``` -좌측(crossProduct < -0.5) → 점수 100 * 1.5 -약간 좌측(-0.5~0) → 점수 70 * 1.5 -역진(0~0.5) → 점수 50 * 2.0 -우측 방향(> 0.5) → 점수 30 * 1.5 -``` - -##### ➡️ Right (우측) 모드 - -**Forward 상태 (dotProduct > 0):** -``` -우측(crossProduct < -0.5) → 점수 100 * 1.5 -약간 우측(-0.5~0) → 점수 70 * 1.5 -직진(0~0.5) → 점수 50 * 1.0 -좌측 방향(> 0.5) → 점수 30 * 1.5 -``` - -**Backward 상태 (dotProduct < 0) - 좌우 반전:** -``` -우측(crossProduct > 0.5) → 점수 100 * 1.5 -약간 우측(0~0.5) → 점수 70 * 1.5 -역진(-0.5~0) → 점수 50 * 2.0 -좌측 방향(< -0.5) → 점수 30 * 1.5 -``` - -#### 방향 가중치 (DirectionWeights) - -```csharp -public class DirectionWeights -{ - public float ForwardWeight { get; set; } = 1.0f; // 직진 - public float LeftWeight { get; set; } = 1.5f; // 좌측 (비직진이므로 높음) - public float RightWeight { get; set; } = 1.5f; // 우측 (비직진이므로 높음) - public float BackwardWeight { get; set; } = 2.0f; // 후진 (거리 페널티) -} -``` - ---- - -### 2. AGVDirectionCalculator.cs (Utils/) - -**목적**: VirtualAGV 또는 실제 AGV와의 통합 인터페이스 - -```csharp -public class AGVDirectionCalculator -{ - public string GetNextNodeId( - Point previousRfidPos, // 이전 RFID 위치 - MapNode currentNode, // 현재 노드 - Point currentRfidPos, // 현재 RFID 위치 - AgvDirection direction, // 이동 방향 - List allNodes // 모든 노드 - ) - { - return _pathfinder.GetNextNodeId( - previousRfidPos, - currentNode, - currentRfidPos, - direction, - allNodes - ); - } - - // 추가: 선택된 방향 역추적 - public AgvDirection AnalyzeSelectedDirection( - Point previousPos, - Point currentPos, - MapNode selectedNextNode, - List connectedNodes - ) - { - // 벡터 비교로 실제 선택된 방향 분석 - } -} -``` - ---- - -### 3. DirectionalPathfinderTest.cs (Utils/) - -**목적**: NewMap.agvmap 파일 기반 테스트 - -#### 기능 - -```csharp -public class DirectionalPathfinderTest -{ - // 맵 파일 로드 - public bool LoadMapFile(string filePath) - - // 테스트 실행 - public void TestDirectionalMovement( - string previousRfidId, - string currentRfidId, - AgvDirection direction - ) - - // 정보 출력 - public void PrintAllNodes() - public void PrintNodeInfo(string rfidId) -} -``` - ---- - -## 📊 테스트 시나리오 - -### 테스트 케이스 1: 직선 경로 전진 -``` -001 → 002 → Forward -예상: 003 -이유: 직진 이동 (직진 가중치 1.0) -``` - -### 테스트 케이스 2: 역진 -``` -002 → 001 → Backward -예상: 이전 노드 또는 null (001이 002의 유일한 연결) -이유: 역진 방향 (후진 가중치 2.0) -``` - -### 테스트 케이스 3: 좌회전 -``` -002 → 003 → Forward -예상: 004 -이유: 직진 계속 -``` - -### 테스트 케이스 4: 분기점에서 우회전 -``` -003 → 004 → Right -예상: 030 (또는 N022) -이유: 우측 방향 가중치 1.5 -``` - -### 테스트 케이스 5: Backward 시 좌우 반전 -``` -004 → 003 → Backward → Left -예상: 002 -이유: Backward 상태에서 Left = 반전되어 원래는 우측 방향 - (Backward 기준 좌측 = Forward 기준 우측) -``` - ---- - -## 🔍 벡터 연산 예시 - -### 예시 1: 001 → 002 → Forward - -``` -이동 벡터: (206-65, 244-229) = (141, 15) -정규화: (141/142, 15/142) ≈ (0.993, 0.106) - -003 위치: (278, 278) -002→003 벡터: (278-206, 278-244) = (72, 34) -정규화: (72/80, 34/80) ≈ (0.9, 0.425) - -내적 = 0.993*0.9 + 0.106*0.425 ≈ 0.939 (매우 유사) -→ Forward 점수: 100 * 1.0 = 100.0 ✓ 최고 점수 -→ 결과: 003 반환 -``` - -### 예시 2: 003 → 004 → Right - -``` -이동 벡터: (380-278, 340-278) = (102, 62) -정규화: (102/119, 62/119) ≈ (0.857, 0.521) - -N022 위치: (?, ?) -N031 위치: (?, ?) -(실제 맵 데이터에 따라 계산) - -Right 선택 → crossProduct 음수 선호 -→ N031 선택 가능성 높음 -``` - ---- - -## 📁 파일 구조 - -``` -AGVNavigationCore/ -├── PathFinding/ -│ └── Planning/ -│ └── DirectionalPathfinder.cs ← 핵심 알고리즘 -├── Utils/ -│ ├── AGVDirectionCalculator.cs ← 통합 인터페이스 -│ ├── DirectionalPathfinderTest.cs ← 테스트 클래스 -│ └── TestRunner.cs ← 실행 프로그램 -└── AGVNavigationCore.csproj ← 프로젝트 파일 (수정됨) -``` - ---- - -## 🚀 사용 방법 - -### 기본 사용 - -```csharp -// 1. 계산기 생성 -var calculator = new AGVDirectionCalculator(); - -// 2. 맵 노드 로드 -List allNodes = LoadMapFromFile("NewMap.agvmap"); - -// 3. 다음 노드 계산 -string nextNodeId = calculator.GetNextNodeId( - previousRfidPos: new Point(65, 229), // 001 위치 - currentNode: node002, // 현재 노드 - currentRfidPos: new Point(206, 244), // 002 위치 - direction: AgvDirection.Forward, // 전진 - allNodes: allNodes -); - -Console.WriteLine($"다음 노드: {nextNodeId}"); // 003 -``` - -### VirtualAGV 통합 - -```csharp -public class VirtualAGV -{ - private AGVDirectionCalculator _directionCalc; - - public void OnPositionChanged() - { - // SetPosition() 호출 후 - string nextNodeId = _directionCalc.GetNextNodeId( - _targetPosition, // 이전 위치 - _currentNode, // 현재 노드 - _currentPosition, // 현재 위치 - _currentDirection, // 현재 방향 - _allNodes - ); - } -} -``` - -### 테스트 실행 - -```csharp -var tester = new DirectionalPathfinderTest(); -tester.LoadMapFile(@"C:\Data\...\NewMap.agvmap"); -tester.TestDirectionalMovement("001", "002", AgvDirection.Forward); -``` - ---- - -## ✅ 검증 체크리스트 - -- [x] 2개 위치 히스토리 검증 로직 -- [x] Forward/Backward/Left/Right 방향 처리 -- [x] Backward 시 좌우 반전 구현 -- [x] 방향별 가중치 시스템 -- [x] 벡터 기반 방향 계산 -- [x] NewMap.agvmap 파일 로드 지원 -- [x] 테스트 프레임워크 - ---- - -## 🔗 관련 클래스 - -| 클래스 | 파일 | 용도 | -|--------|------|------| -| DirectionalPathfinder | PathFinding/Planning/ | 핵심 알고리즘 | -| AGVDirectionCalculator | Utils/ | 통합 인터페이스 | -| DirectionalPathfinderTest | Utils/ | 테스트 | -| TestRunner | Utils/ | 실행 프로그램 | -| MapNode | Models/ | 노드 데이터 | -| AgvDirection | Models/Enums.cs | 방향 열거형 | - ---- - -## 📝 주의사항 - -⚠️ **2개 위치 히스토리 필수** -- previousPos가 Point.Empty이면 null 반환 -- VirtualAGV.SetPosition() 호출 시 이전 위치를 _targetPosition에 저장 - -⚠️ **벡터 정규화** -- 매우 작은 이동(< 0.001)은 거리 0으로 처리 - -⚠️ **방향 가중치** -- 기본값: Forward=1.0, Left/Right=1.5, Backward=2.0 -- 프로젝트별로 조정 가능 - -⚠️ **점수 시스템** -- 100점 = 완벽한 방향 -- 0점 = 방향 불가 -- 낮은 점수도 반환됨 (대안 경로) - ---- - -## 🎯 향후 개선사항 - -1. **A* 알고리즘 통합** - - 현재는 직접 연결된 노드만 고려 - - A* 알고리즘으로 확장 가능 - -2. **경로 캐싱** - - 자주 이동하는 경로 캐시 - - 성능 향상 - -3. **동적 가중치 조정** - - AGV 상태(배터리, 속도)에 따라 가중치 변경 - -4. **3D 좌표 지원** - - 현재 2D Point만 지원 - - 3D 좌표 추가 가능 - ---- - -**작성일**: 2025-10-23 -**상태**: 구현 완료, 테스트 대기 diff --git a/Cs_HMI/AGVLogic/IMPLEMENTATION_SUMMARY.md b/Cs_HMI/AGVLogic/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 0d7eb3d..0000000 --- a/Cs_HMI/AGVLogic/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,311 +0,0 @@ -# 방향 기반 경로 탐색 구현 완료 요약 - -## ✅ 구현 완료 - -사용자 요구사항에 따라 **이전 위치 + 현재 위치 + 진행 방향**을 기반으로 **다음 노드를 계산하는 시스템**을 완전히 구현했습니다. - ---- - -## 📦 구현된 컴포넌트 - -### 1. **VirtualAGV.GetNextNodeId()** (핵심 메서드) -**파일**: `AGVNavigationCore\Models\VirtualAGV.cs` (라인 613~823) - -```csharp -public string GetNextNodeId(AgvDirection direction, List allNodes) -``` - -#### 특징: -- ✅ VirtualAGV의 `_prevPosition`, `_currentPosition`, `_currentNode` 사용 -- ✅ 최소 2개 위치 히스토리 검증 (prev/current 모두 설정되어야 함) -- ✅ 벡터 기반 방향 계산 (내적, 외적) -- ✅ Forward/Backward/Left/Right 모든 방향 지원 -- ✅ Backward 시 좌우 방향 자동 반전 - -#### 동작 방식: -``` -입력: direction (Forward/Backward/Left/Right), allNodes - ↓ -1️⃣ 히스토리 검증: _prevPosition, _currentPosition 확인 -2️⃣ 연결된 노드 필터링: currentNode의 ConnectedNodes에서 후보 선택 -3️⃣ 이동 벡터 계산: _currentPosition - _prevPosition -4️⃣ 벡터 정규화: 길이를 1로 만듦 -5️⃣ 각 후보에 대해 점수 계산: - - 내적: 진행 방향과의 유사도 - - 외적: 좌우 판별 - - direction에 따라 가중치 적용 -6️⃣ 최고 점수 노드 반환 -``` - ---- - -## 🧮 방향 점수 계산 로직 - -### Forward (전진) 모드 -``` -내적 값 (dotProduct) 점수 -──────────────────────────── -> 0.9 (거의 같은 방향) 100 -0.5~0.9 (비슷한 방향) 80 -0~0.5 (약간 다른 방향) 50 --0.5~0 (거의 반대) 20 -< -0.5 (완전 반대) 0 -``` - -### Backward (후진) 모드 -``` -내적 값 (dotProduct) 점수 -──────────────────────────── -< -0.9 (거의 반대) 100 --0.5~-0.9 (비슷하게 반대) 80 --0.5~0 (약간 다른) 50 -0~0.5 (거의 같은 방향) 20 -> 0.5 (완전 같은 방향) 0 -``` - -### Left (좌측) 모드 -``` -Forward 상태 (dotProduct > 0): Backward 상태 (dotProduct < 0): -───────────────────────────────────────────────────────────────────── -crossProduct > 0.5 → 100 (좌측) crossProduct < -0.5 → 100 (좌측 반전) -0~0.5 → 70 -0.5~0 → 70 --0.5~0 → 50 0~0.5 → 50 -< -0.5 → 30 > 0.5 → 30 -``` - -### Right (우측) 모드 -``` -Forward 상태 (dotProduct > 0): Backward 상태 (dotProduct < 0): -───────────────────────────────────────────────────────────────────── -crossProduct < -0.5 → 100 (우측) crossProduct > 0.5 → 100 (우측 반전) --0.5~0 → 70 0~0.5 → 70 -0~0.5 → 50 -0.5~0 → 50 -> 0.5 → 30 < -0.5 → 30 -``` - ---- - -## 📋 테스트 시나리오 - -### 시나리오 1: 직선 경로 전진 -``` -001 (65, 229) → 002 (206, 244) → GetNextNodeId(Forward) -이동 벡터: (141, 15) -002→003: (72, 34) -내적: 0.939 → Forward 점수: 100 ✓ -결과: 003 -``` - -### 시나리오 2: 좌회전 -``` -002 (206, 244) → 003 (278, 278) → GetNextNodeId(Left) -이동 벡터에 대해 Left 가중치 적용 -외적: crossProduct 양수 선호 -``` - -### 시나리오 3: 우회전 -``` -003 (278, 278) → 004 (380, 340) → GetNextNodeId(Right) -이동 벡터에 대해 Right 가중치 적용 -외적: crossProduct 음수 선호 -결과: 030 또는 N022 (맵 구조에 따라) -``` - -### 시나리오 4: 후진 -``` -004 → 003 → GetNextNodeId(Backward) -역진 가중치 적용 (dotProduct < -0.9 = 100점) -결과: 002 -``` - -### 시나리오 5: Backward 시 좌우 반전 -``` -004 → 003 (Backward) → GetNextNodeId(Left) -Backward 상태에서 Left = 원래 Right 방향 -좌우 자동 반전으로 올바른 방향 계산 -``` - ---- - -## 🏗️ 추가 구현 파일들 - -### 1. **DirectionalPathfinder.cs** -**파일**: `PathFinding\Planning\DirectionalPathfinder.cs` -- 독립적인 벡터 기반 경로 탐색 엔진 -- VirtualAGV와 분리된 재사용 가능한 컴포넌트 -- 방향 가중치 커스터마이징 지원 - -### 2. **AGVDirectionCalculator.cs** -**파일**: `Utils\AGVDirectionCalculator.cs` -- VirtualAGV와 실제 AGV 시스템을 위한 통합 인터페이스 -- RFID 위치 기반 계산 -- 선택된 방향 역추적 기능 - -### 3. **DirectionalPathfinderTest.cs** -**파일**: `Utils\DirectionalPathfinderTest.cs` -- NewMap.agvmap 파일 로드 및 파싱 -- 테스트 시나리오 실행 -- 결과 검증 및 출력 - -### 4. **TestRunner.cs** -**파일**: `Utils\TestRunner.cs` -- 전체 테스트 프로그램 실행 -- 모든 시나리오 자동 테스트 - ---- - -## 📊 .csproj 수정 사항 - -**파일**: `AGVNavigationCore\AGVNavigationCore.csproj` - -추가된 항목: -```xml - - - - -``` - ---- - -## 🚀 사용 방법 - -### 기본 사용 (VirtualAGV) - -```csharp -// VirtualAGV 인스턴스 -var agv = new VirtualAGV("AGV001"); - -// 위치 설정 (최소 2번) -agv.SetPosition(node001, new Point(65, 229), AgvDirection.Forward); -agv.SetPosition(node002, new Point(206, 244), AgvDirection.Forward); - -// 다음 노드 계산 -string nextNodeId = agv.GetNextNodeId(AgvDirection.Forward, allNodes); -Console.WriteLine($"다음 노드: {nextNodeId}"); // 003 -``` - -### 고급 사용 (독립적인 계산기) - -```csharp -var calculator = new AGVDirectionCalculator(); - -string nextNodeId = calculator.GetNextNodeId( - previousRfidPos: new Point(206, 244), - currentNode: node003, - currentRfidPos: new Point(278, 278), - direction: AgvDirection.Right, - allNodes: allNodes -); - -// 실제 선택된 방향 분석 -AgvDirection selectedDir = calculator.AnalyzeSelectedDirection( - new Point(206, 244), - new Point(278, 278), - selectedNextNode, - connectedNodes -); -``` - ---- - -## ⚠️ 중요 주의사항 - -### 1. 2개 위치 히스토리 필수 -```csharp -// ❌ 잘못된 사용 (처음 호출 시) -string next = agv.GetNextNodeId(direction, allNodes); // null 반환 - -// ✅ 올바른 사용 -agv.SetPosition(node1, pos1, AgvDirection.Forward); // 첫 번째 -agv.SetPosition(node2, pos2, AgvDirection.Forward); // 두 번째 -string next = agv.GetNextNodeId(direction, allNodes); // 결과 반환 -``` - -### 2. 벡터 정규화 -- 매우 작은 이동(<0.001px)은 거리 0으로 간주 -- 이 경우 첫 번째 연결 노드 반환 - -### 3. 좌표계 유지 -- 모든 좌표는 맵 기준 (화면 좌표가 아님) -- 줌/팬 상태에서는 별도 변환 필요 - -### 4. 내적/외적 이해 -``` -내적 (Dot Product): - = v1.x * v2.x + v1.y * v2.y - 범위: -1 ~ 1 - 1 = 같은 방향, 0 = 직각, -1 = 반대 방향 - -외적 (Cross Product): - = v1.x * v2.y - v1.y * v2.x - 양수 = 좌측, 음수 = 우측 -``` - ---- - -## 📁 최종 파일 구조 - -``` -AGVNavigationCore/ -├── Models/ -│ └── VirtualAGV.cs ⭐ (GetNextNodeId 추가) -├── PathFinding/ -│ └── Planning/ -│ ├── DirectionalPathfinder.cs (NEW) -│ ├── AGVPathfinder.cs -│ └── ... -├── Utils/ -│ ├── AGVDirectionCalculator.cs (NEW) -│ ├── DirectionalPathfinderTest.cs (NEW) -│ ├── TestRunner.cs (NEW) -│ └── ... -└── AGVNavigationCore.csproj (MODIFIED) -``` - ---- - -## 🎯 핵심 요구사항 검증 - -| 요구사항 | 상태 | 구현 위치 | -|---------|------|----------| -| GetNextNodeID(direction) 메서드 | ✅ 완료 | VirtualAGV:628 | -| 2개 위치 히스토리 검증 | ✅ 완료 | VirtualAGV:630-634 | -| Forward/Backward/Left/Right 지원 | ✅ 완료 | VirtualAGV:743-817 | -| 좌우 반전 로직 | ✅ 완료 | VirtualAGV:780, 806 | -| 벡터 기반 계산 | ✅ 완료 | VirtualAGV:658-678 | -| NewMap.agvmap 테스트 지원 | ✅ 완료 | DirectionalPathfinderTest | - ---- - -## 📝 다음 단계 (선택사항) - -1. **실제 맵에서 테스트** - - TestRunner로 NewMap.agvmap 검증 - - 실제 RFID 번호로 시나리오 테스트 - -2. **성능 최적화** - - 벡터 계산 캐싱 - - 점수 계산 병렬화 - -3. **기능 확장** - - 3D 좌표 지원 - - A* 알고리즘 통합 - - 동적 가중치 조정 - -4. **시뮬레이터 통합** - - AGVSimulator에 GetNextNodeId 연결 - - 실시간 경로 변경 시연 - ---- - -## 📚 관련 문서 - -- `ANALYSIS_AGV_Direction_Storage.md` - VirtualAGV 필드 분석 -- `IMPLEMENTATION_DirectionalPathfinder.md` - 상세 구현 가이드 - ---- - -**완료 일시**: 2025-10-23 -**상태**: 🟢 구현 완료, 테스트 대기 -**다음 작업**: NewMap.agvmap으로 실제 테스트 diff --git a/Cs_HMI/AGVLogic/MAP_LOADING_BIDIRECTIONAL_FIX.md b/Cs_HMI/AGVLogic/MAP_LOADING_BIDIRECTIONAL_FIX.md deleted file mode 100644 index aff1986..0000000 --- a/Cs_HMI/AGVLogic/MAP_LOADING_BIDIRECTIONAL_FIX.md +++ /dev/null @@ -1,285 +0,0 @@ -# 맵 로딩 양방향 연결 자동 설정 수정 - -## 🔍 문제 현상 - -### 원래 문제 -``` -맵 에디터에서 002 → 003 연결 생성 - ↓ -NewMap.agvmap 저장: - 002.ConnectedNodes = ["001", "003"] - 003.ConnectedNodes = ["002"] - ↓ -맵 로드 후 GetNextNodeId(Forward) 호출: - 002의 ConnectedNodes 확인 → [001, 003] 있음 ✓ - 003의 ConnectedNodes 확인 → [002] 있음 ✓ (문제 없음) - ↓ - 004에서 002로 이동한 경우: - 002의 ConnectedNodes = ["001", "003"] ✓ - 003의 ConnectedNodes = ["002"] (004가 없음!) ✗ -``` - -### 근본 원인 -`CleanupDuplicateConnections()` 메서드가 양방향 연결을 **단방향으로 축약**했습니다. - -```csharp -// 기존 로직 (라인 303-314) -if (connectedNode.ConnectedNodes.Contains(node.NodeId)) -{ - // 양방향 연결인 경우 사전순으로 더 작은 노드에만 유지 - if (string.Compare(node.NodeId, connectedNodeId, StringComparison.Ordinal) > 0) - { - connectionsToRemove.Add(connectedNodeId); // ← 역방향 제거! - } - else - { - connectedNode.RemoveConnection(node.NodeId); // ← 역방향 제거! - } -} -``` - -이로 인해 N003 → N002 같은 역방향 연결이 삭제되었습니다. - ---- - -## ✅ 해결 방법 - -### 추가된 메서드: `EnsureBidirectionalConnections()` - -**파일**: `MapLoader.cs` (라인 341-389) - -**목적**: 모든 연결을 양방향으로 보장 - -#### 동작 흐름 - -``` -1단계: 모든 노드의 명시적 연결 수집 - 002.ConnectedNodes = ["001", "003"] - 003.ConnectedNodes = ["002"] - allConnections = { - "N002": {"N001", "N003"}, - "N003": {"N002"} - } - -2단계: 역방향 연결 추가 - 각 노드에 대해: - "다른 노드가 나를 연결하고 있는가?" 확인 - - N003의 경우: - - N002가 N003을 연결? YES → N003.ConnectedNodes에 N002 추가 - - N002의 경우: - - N001이 N002를 연결? YES → N002.ConnectedNodes에 N001 추가 - - N003이 N002를 연결? YES → N002.ConnectedNodes에 N003 추가 (이미 있음) - -결과: - 002.ConnectedNodes = ["001", "003"] ✓ - 003.ConnectedNodes = ["002"] ← ["002"]로 유지 (N002는 이미 명시적) -``` - -#### 코드 예시 - -```csharp -private static void EnsureBidirectionalConnections(List mapNodes) -{ - // 1단계: 모든 명시적 연결 수집 - var allConnections = new Dictionary>(); - foreach (var node in mapNodes) - { - if (!allConnections.ContainsKey(node.NodeId)) - allConnections[node.NodeId] = new HashSet(); - - if (node.ConnectedNodes != null) - { - foreach (var connectedId in node.ConnectedNodes) - allConnections[node.NodeId].Add(connectedId); - } - } - - // 2단계: 역방향 연결 추가 - foreach (var node in mapNodes) - { - if (node.ConnectedNodes == null) - node.ConnectedNodes = new List(); - - // 이 노드를 연결하는 모든 노드 찾기 - foreach (var otherNodeId in allConnections.Keys) - { - if (otherNodeId == node.NodeId) continue; - - // 다른 노드가 이 노드를 연결하고 있다면 - if (allConnections[otherNodeId].Contains(node.NodeId)) - { - // 이 노드의 ConnectedNodes에 그 노드를 추가 - if (!node.ConnectedNodes.Contains(otherNodeId)) - node.ConnectedNodes.Add(otherNodeId); - } - } - } -} -``` - ---- - -## 🔄 맵 로딩 순서 (수정된) - -``` -LoadMapFromFile() - ↓ -JSON 역직렬화 - ↓ -MigrateDescriptionToName() - ↓ -MigrateDockingDirection() - ↓ -FixDuplicateNodeIds() - ↓ -CleanupDuplicateConnections() ← 중복만 제거 (양방향 연결 유지) - ↓ -✨ EnsureBidirectionalConnections() ← NEW: 양방향 자동 설정 - ↓ -LoadImageNodes() - ↓ -Success = true -``` - ---- - -## 📊 결과 비교 - -### 수정 전 -``` -002.ConnectedNodes = ["001", "003"] -003.ConnectedNodes = ["002"] -004.ConnectedNodes = ["003", "022", "031"] - -002에서 GetNextNodeId(Forward) - → 003 계산 ✓ - → 001 계산 ✓ - -003에서 GetNextNodeId(Forward) - → 002 계산 ✓ - → (004 없음!) ✗ ← 004로 진행 불가 - -004에서 GetNextNodeId(Backward) - → 003 계산 ✓ - → 022, 031 계산 ✓ -``` - -### 수정 후 ✅ -``` -002.ConnectedNodes = ["001", "003"] -003.ConnectedNodes = ["002", "004"] -004.ConnectedNodes = ["003", "022", "031"] - -002에서 GetNextNodeId(Forward) - → 003 계산 ✓ - → 001 계산 ✓ - -003에서 GetNextNodeId(Forward) - → 002 계산 ✓ - → 004 계산 ✓ ← 이제 404로 진행 가능! - -004에서 GetNextNodeId(Backward) - → 003 계산 ✓ - → 022, 031 계산 ✓ -``` - ---- - -## 🎯 GetNextNodeId() 동작 원리 - -이제 모든 노드의 `ConnectedNodes`에 양방향 연결이 포함되어 있으므로: - -```csharp -public string GetNextNodeId(AgvDirection direction, List allNodes) -{ - // 현재 노드의 ConnectedNodes에 모든 가능한 다음 노드가 포함됨 ✓ - var candidateNodes = allNodes.Where(n => - _currentNode.ConnectedNodes.Contains(n.NodeId) - ).ToList(); - - // 벡터 기반 점수 계산으로 최적 노드 선택 - return bestCandidate.node?.NodeId; -} -``` - ---- - -## 🔗 관계도 - -``` -맵 에디터 - ↓ (002→003 연결 생성 및 저장) - ↓ -NewMap.agvmap - ↓ (파일 로드) - ↓ -LoadMapFromFile() - ↓ -[CleanupDuplicateConnections] - 002: ["001", "003"] - 003: ["002"] - ↓ -[EnsureBidirectionalConnections] ← NEW! - 002: ["001", "003"] - 003: ["002", "004"] ← 004 추가! - ↓ -VirtualAGV.GetNextNodeId() - 가능한 다음 노드 모두 찾을 수 있음 ✓ -``` - ---- - -## 📋 체크리스트 - -- [x] `EnsureBidirectionalConnections()` 메서드 추가 -- [x] `LoadMapFromFile()` 호출 순서 업데이트 -- [x] 모든 연결이 양방향으로 보장됨 -- [x] VirtualAGV.GetNextNodeId()에서 모든 가능한 다음 노드 찾을 수 있음 -- [x] RFID 002 → 003 → Forward → 004 경로 가능 -- [x] RFID 004 → 003 → Backward → 002 경로 가능 - ---- - -## 🧪 테스트 시나리오 - -### 시나리오 1: 직선 경로 -``` -002 → 003 → Forward → 004 -검증: 003.ConnectedNodes에 004가 포함되어야 함 -``` - -### 시나리오 2: 분기점 -``` -004 → 003 → Left → ? -검증: 003.ConnectedNodes에 가능한 모든 노드 포함 -``` - -### 시나리오 3: 역진 -``` -004 → 003 → Backward → 002 -검증: 003.ConnectedNodes에 002가 포함되어야 함 -``` - ---- - -## 📌 중요 포인트 - -✅ **맵 로딩 시 자동으로 양방향 설정** -- 사용자(맵 에디터)는 단방향만 그으면 됨 -- 시스템이 자동으로 역방향 추가 - -✅ **GetNextNodeId() 완벽 지원** -- 현재 노드의 ConnectedNodes만으로 모든 가능한 다음 노드 찾음 -- 벡터 기반 점수 계산으로 최적 경로 선택 - -✅ **기존 맵 호환성 유지** -- 기존 저장된 맵도 로드 시 자동으로 양방향 설정됨 -- 새로운 맵도 동일 방식으로 처리됨 - ---- - -**수정 완료일**: 2025-10-23 -**상태**: 🟢 완료 -**다음 단계**: NewMap.agvmap 로드하여 검증 diff --git a/Cs_HMI/AGVLogic/PATHSCENARIO.md b/Cs_HMI/AGVLogic/PATHSCENARIO.md deleted file mode 100644 index 6af5bb0..0000000 --- a/Cs_HMI/AGVLogic/PATHSCENARIO.md +++ /dev/null @@ -1,100 +0,0 @@ -## 경로시뮬레이션 설명 -## AGV는 같은경로상에서 방향을 전환할 수 없음 -## 경로계산을 위해서는 반드시 AGV는 2개 이상의 RFID를 읽어야 한다. (최소 2개를 읽어야 모터방향과 RFID의 읽히는 순서를 가지고 현재 AGV의 방향을 결정지을 수 있다) -## 하기 케이스의 경우 케이스 설명전에 AGV가 어떻게 이동했는지 최소 2개의 RFID정보를 제공한다. -## AGV의 RFID로 위치이동하는 것은 시뮬레이터폼의 SetAGVPositionByRfid 함수를 참고하면 됨 -## 방향전환이 필요할 때에 갈림길은 AGV와 가장 가까운 갈림길을 사용한다. - -## case 1 (AGV가 전진방향으로 이동하는 경우) -## AGV는 모터전진방향으로 008 -> 007 로 이동 (최종위치는 007) - -Q1.목적지 : 015 (충전기 이므로 전진 방향 도킹해야하는 곳) - A. 목적지 도킹방향과 현재 AGV도킹 방향이 동일하므로 방향전환이 필요없다. 목적지 까지의 최단거리를 계산한 후 그대로 이동하면됨 - 007 - 006 - 005 - 004 - 012 - 013 - 014 - 015 - -Q2.목적지 : 019 (충전기 이므로 전진 방향 도킹해야하는 곳) - A. 목적지 도킹방향과 현재 AGV도킹 방향이 동일하므로 방향전환이 필요없다. 목적지 까지의 최단거리를 계산한 후 그대로 이동하면됨 - 007 - 006 - 005 - 004 - 012 - 016 - 017 - 018 - 019 - -Q3.목적지 : 001 (장비 이므로 후진 방향 도킹해야하는 곳) - A. 목적지 도킹방향과 현재 AGV도킹 방향이 일치하지 않으니 방향전환이 필요하다, - 목적지까의 RFID목록은 007 - 006 - 005 - 004 - 003 - 002 - 001 - 갈림길은 005 , 004 총 2개가 있으나 AGV 이동 방향상 가장 가까운 갈림길은 005이다. 전환은 005에서 하기로 한다. - 005갈림길은 내경로상의 006 과 037이 있다. 내 경로상에서 방향전환은 할 수 없으니 005 갈림길에서는 037로 방향을 틀어서 (Magnet Left) 전진이동을 한후 - 037이 발견되면 방향을 후진으로 전환하면서 005를 거쳐 004방향으로 가도록 (Magnet Right) 로 유도해서 진행한다. - 그렇게하면 005를 지나 004를 갈때에는 후진방향으로 이동하게 된다. 후진시에는 전진과 magtnet 방향전환이 반대로 필요하다, - 037 -> 005 -> 004 의 경우 후진이동으로 좌회전을 해야하는데. 후진이기때문에 magnet 은 right 로 유도한다. - - 최종 경로는 아래와 같다 - - 007(F) - 006(F) - 005(F) - 037(B) - 005(B) - 004(B) - 003(B) - 002(B) - 001(B) - -Q4.목적지 : 011 (장비 이므로 후진 방향 도킹해야하는 곳) - A. 목적지 도킹방향과 현재 AGV도킹 방향이 일치하지 않으니 방향전환이 필요하다, - 목적지까의 RFID목록은 007 - 006 - 005 - 004 - 030 - 009 - 010 - 011 - 갈림길은 005 , 004 총 2개가 있으나 AGV 이동 방향상 가장 가까운 갈림길은 005이다. 전환은 005에서 하기로 한다. - 005갈림길은 내 경로상의 006 과 037이 있다. 내 경로상에서 방향전환은 할 수 없으니 005 갈림길에서는 037로 방향을 틀어서 (Magnet Left) 전진이동을 한후 - 037이 발견되면 방향을 후진으로 전환하면서 005를 거쳐 004방향으로 가도록 (Magnet Right) 로 유도해서 진행한다. - 그렇게하면 005를 지나 004를 갈때에는 후진방향으로 이동하게 된다. 후진시에는 전진과 magtnet 방향전환이 반대로 필요하다, - 037 -> 005 -> 004 의 경우 후진이동으로 좌회전을 해야하는데. 후진이기때문에 magnet 은 right 로 유도한다. - - 최종 경로는 아래와 같다 - - 007(F) - 006(F) - 005(F) - 037(B) - 005(B) - 004(B) - 030(B) - 009(B) - 010(B) - 011(B) - -Q.목적지 : 041 (장비 이므로 후진 방향 도킹해야하는 곳) - A. 목적지 도킹방향과 현재 AGV도킹 방향이 일치하지 않으니 방향전환이 필요하다, - 목적지까의 RFID목록은 007 - 006 - 005 - 037 - 036 - 035 - 034 - 033 - 032 - 031 - 041 - 경로상 갈림길은 005 총 1개가 있으므로 전환은 005에서 하기로 한다. - 005갈림길은 내 경로상의 006 과 037(이 경우엔 037도 내 경로는 맞다) - 이 경우에는 006도 037도 내 경로이므로 005에 연결된 004포인트로 이동하면서 방향전환이 필요하다 - 005 갈림길에서는 004까지 전진으로 진행하고 004도착시 후진을 하고 005에서 037로 방향을 틀도록 마그넷을(left)로 유도한다 - 그렇게하면 005를 지나 037를 갈때에는 후진방향으로 이동하게 된다. - - 최종 경로는 아래와 같다 - - 007(F) - 006(F) - 005(F) - 004(F) - 005(B) - 037(B) - 036(B) - 035(B) - 034(B) - 033(B) - 032(B) - 031(B) - 041(B) - -Q5.8 (장비 이므로 후진 방향 도킹해야하는 곳) - A. 목적지 도킹방향과 현재 AGV도킹 방향이 일치하지 않으니 방향전환이 필요하다, - 목적지까의 RFID목록은 007 - 006 - 005 - 037 - 036 - 035 - 034 - 038 - 경로상 갈림길은 005 총 1개가 있으므로 전환은 005에서 하기로 한다. - 005갈림길은 내 경로상의 006 과 037(이 경우엔 037도 내 경로는 맞다) - 이 경우에는 006도 037도 내 경로이므로 005에 연결된 004포인트로 이동하면서 방향전환이 필요하다 - 005 갈림길에서는 004까지 전진으로 진행하고 004도착시 후진을 하고 005에서 037로 방향을 틀도록 마그넷을(left)로 유도한다 - 그렇게하면 005를 지나 037를 갈때에는 후진방향으로 이동하게 된다. - - 최종 경로는 아래와 같다 - - 007(F) - 006(F) - 005(F) - 004(F) - 005(B) - 037(B) - 036(B) - 035(B) - 034(B) - 038(B) - - -## AGV는 모터전진방향으로 037 -> 036 로 이동 (최종위치는 036) -Q6.목적지 : 038 (장비 이므로 후진 방향 도킹해야하는 곳) - A. 목적지 도킹방향과 현재 AGV도킹 방향이 일치하지 않으니 방향전환이 필요하다, - 목적지까의 RFID목록은 036 - 035 - 034 - 038 - 경로상 갈림길이 없다, 가장 가까운 갈림길은 005이므로 전환은 005에서 하기로 한다. - 005갈림길은 내 경로상 포인트가 없으니 전환은 004 혹은 006 어떤쪽이던 상관없다. - 다만 이러한 경우 일관성을 위해 Magnet 유도를 Left를 사용한다 - 036에서 후진으로 이동을 시작하면 037 -> 005 순으로 후진 이동을 한다. 여기서 방향전환을 해야하고 마그넷이 left로 유도가 되면 - AGV는 006방향으로 틀게된다. 이제 이러면 바로위의 Q5와 동일한 조건이 완성된다. 위치 006에서는 005 037 모두 목적지까지 포함되므로 004로 - 이동해서 전환을 해야한다. 005(f), 004(f) 까지 이동을 한 후 이제 방향전환을 해서 후진으로 005까지 이동이 필요하다. 후진이므로 - magnet을 left유도하여 037로 이동할 수 있게한다 - - 최종 경로는 아래와 같다 - - 036(B) - 037(B) - 005(B) - 006(B) - 005(F) - 004(F) - 005(F) - 037(B) - 036(B) - 035(B) - 034(B) - 038(B) - - -## case 2 (AGV가 후진방향으로 이동하는 경우) -AGV는 모터후진방향으로 008 -> 007 로 이동 (최종위치는 007) -Q7.목적지 : 015 (충전기는 전진 도킹해야합니다.) - A. 목적지 도킹방향과 현재 AGV도킹 방향이 일치하지 않으니 방향전환이 필요하다, - 목적지까의 RFID목록은 007 - 006 - 005 - 004 - 012 - 013 -014 -015 - 경로상 갈림길은 005, 004, 012 총 3개가 있다, 가장 가까운 갈림길은 005이므로 전환은 005에서 하기로 한다. - 005 갈림길은 내 경로상 포인트 (006,004)가 있으니 037 포인트를 이용하여 전환을 하면 된다. - 006(B) -> 005(B - 마그넷유도 RIGHT) -> 037(F) -> 그런후 방향전화을 해서 005까지 전진으로 이동을 하고 004로 방향을 틀면된다. - - 최종 경로는 아래와 같다 - - 007(B) - 006(B) - 005(B-maget right) - 037(에 B로 도착하면 F로 전환한다) - 005(F) - 004(F) - 012(F) - 013(F) - 014(F) - 015(F) \ No newline at end of file diff --git a/Cs_HMI/AGVLogic/PROJECT_SUMMARY.md b/Cs_HMI/AGVLogic/PROJECT_SUMMARY.md deleted file mode 100644 index 0114fe6..0000000 --- a/Cs_HMI/AGVLogic/PROJECT_SUMMARY.md +++ /dev/null @@ -1,353 +0,0 @@ -# 프로젝트 요약 (AGVMapEditor, AGVNavigationCore, AGVSimulator) - -## 📊 프로젝트 개요 - -3개의 주요 프로젝트가 **AGVNavigationCore** 라이브러리를 공유하며 연동되는 구조입니다. - -``` -┌─────────────────────────────────────────────────────────────┐ -│ AGVNavigationCore (공유 라이브러리) │ -├─────────────────────────────────────────────────────────────┤ -│ • Models: MapNode, MapLoader, Enums, IMovableAGV, VirtualAGV -│ • Controls: UnifiedAGVCanvas (통합 UI 렌더링) -│ • PathFinding: A*, AGVPathfinder, DirectionChangePlanner -└─────────────────────────────────────────────────────────────┘ - ▲ ▲ ▲ - │ 참조 │ 참조 │ 참조 - │ │ │ - ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ - │ AGVMapEditor │ │ AGVSimulator │ │ AGV4 (미사용) - │ (맵 편집) │ │ (시뮬레이션) │ │ (메인앱) - └──────────────┘ └──────────────┘ └──────────────┘ -``` - ---- - -## 🎯 각 프로젝트의 기능 - -### 1️⃣ **AGVMapEditor** (맵 편집 도구) - -**목적**: AGV 맵 데이터를 시각적으로 생성/편집 - -#### 제공 기능 -| 기능 | 설명 | -|------|------| -| **노드 생성** | 맵 상에 AGV 네비게이션 노드 추가 | -| **노드 편집** | 노드 이름, RFID, 타입, 도킹방향 설정 | -| **노드 이동** | 드래그로 노드 위치 변경 (그리드 스냅 지원) | -| **연결 관리** | 노드 간 경로 연결 생성/삭제 | -| **노드 삭제** | 선택된 노드 제거 | -| **라벨 추가** | 맵에 텍스트 라벨 추가 | -| **이미지 추가** | 맵에 배경 이미지 추가 | -| **맵 저장/로드** | JSON 형식 맵 파일 저장/로드 | - -#### 기술 구성 -``` -MainForm (WinForms) -└─ UnifiedAGVCanvas (UI 렌더링) - ├─ MapNode 목록 관리 - ├─ 편집 모드 (Select, Move, AddNode, Connect, Delete, DeleteConnection, AddLabel, AddImage) - └─ MapLoader 사용 (JSON 저장/로드) -``` - -#### 주요 UI 요소 -- **메뉴바**: File (Open/Save), Edit, View -- **툴바**: 편집 모드 전환 버튼 (선택, 이동, 노드추가, 연결, 삭제, 라벨, 이미지) -- **속성 패널**: 선택된 노드 정보 표시/편집 -- **캔버스**: 맵 시각화 및 편집 - ---- - -### 2️⃣ **AGVNavigationCore** (경로 계산 엔진 라이브러리) - -**목적**: AGV 경로 계산 및 네비게이션 핵심 로직 제공 - -#### 제공 기능 -| 영역 | 모듈 | 기능 | -|------|------|------| -| **Models** | MapNode | AGV 네비게이션 노드 데이터 | -| | MapLoader | 맵 파일 로드/저장 | -| | IMovableAGV | AGV 동작 인터페이스 | -| | VirtualAGV | 가상 AGV 시뮬레이션 로직 ⚠️ **미완성** | -| **Controls** | UnifiedAGVCanvas | 맵 렌더링/편집/모니터링 UI | -| **PathFinding** | AStarPathfinder | 기본 A* 경로 탐색 | -| | AGVPathfinder | AGV 제약 고려 경로 계산 ⚠️ **미완성** | -| | DirectionChangePlanner | 방향 전환 경로 계획 ⚠️ **미완성** | - -#### 핵심 기능 (구현 상태) -``` -✅ 완성 -├─ MapNode 데이터 모델 -├─ MapLoader (파일 I/O) -├─ UnifiedAGVCanvas (UI 렌더링/편집) -└─ AStarPathfinder (기본 경로 탐색) - -❌ 미완성 (개발 대상) -├─ VirtualAGV.ExecutePath() - 경로 실행 -├─ VirtualAGV.Update() - 프레임 업데이트 -├─ AGVPathfinder 핵심 로직 - 경로 상세화, 마그넷 방향 계산 -└─ DirectionChangePlanner - 4단계 방향 전환 알고리즘 -``` - -#### 기술 구성 -``` -AGVNavigationCore (Class Library) -├── Models/ -│ ├── MapNode.cs -│ ├── MapLoader.cs -│ ├── VirtualAGV.cs (← 경로 추적 미완성) -│ ├── IMovableAGV.cs -│ └── Enums.cs -├── Controls/ -│ ├── UnifiedAGVCanvas.cs (메인 UI) -│ ├── UnifiedAGVCanvas.Designer.cs -│ ├── UnifiedAGVCanvas.Events.cs -│ ├── UnifiedAGVCanvas.Mouse.cs (← 줌 기능) -│ ├── UnifiedAGVCanvas.Rendering.cs -│ └── UnifiedAGVCanvas.Utilities.cs -├── PathFinding/ -│ ├── Core/ -│ │ ├── AStarPathfinder.cs ✅ -│ │ ├── PathNode.cs -│ │ └── AGVPathResult.cs -│ ├── Planning/ -│ │ ├── AGVPathfinder.cs (❌ 미완성) -│ │ ├── DirectionChangePlanner.cs (❌ 미완성) -│ │ └── NodeMotorInfo.cs -│ ├── Analysis/ -│ │ └── JunctionAnalyzer.cs -│ └── Validation/ -│ ├── PathValidationResult.cs -│ └── DockingValidationResult.cs -└── Utils/ - └── LiftCalculator.cs -``` - ---- - -### 3️⃣ **AGVSimulator** (AGV 시뮬레이터) - -**목적**: AGV 경로 계산 및 동작을 시각적으로 검증 - -#### 제공 기능 -| 기능 | 설명 | -|------|------| -| **맵 로드** | 저장된 맵 파일 로드 | -| **AGV 시뮬레이션** | 가상 AGV 생성 및 경로 따라 이동 | -| **경로 계산** | 시작점/목적지 선택 후 경로 자동 계산 | -| **경로 시각화** | 계산된 경로 맵에 표시 | -| **실시간 상태 모니터링** | AGV 위치, 방향, 상태 실시간 표시 | -| **도킹 검증** | AGV 도킹 방향 검증 | - -#### 기술 구성 -``` -SimulatorForm (WinForms) -├─ UnifiedAGVCanvas (맵 렌더링) -├─ VirtualAGV 리스트 (가상 AGV들) -├─ MapLoader (맵 파일 로드) -├─ AGVPathfinder (경로 계산) -└─ 시뮬레이션 타이머 (매 프레임 AGV 업데이트) -``` - -#### 주요 UI 요소 -- **메뉴바**: File (Open Map) -- **경로 제어 그룹**: 시작RFID, 목적지RFID, 타겟계산 버튼 -- **시뮬레이션 제어**: 시작, 일시정지, 정지 버튼 -- **캔버스**: 맵 + AGV + 경로 시각화 - ---- - -## 🎨 UnifiedAGVCanvas (통합 UI 컨트롤) - -**위치**: `AGVNavigationCore/Controls/UnifiedAGVCanvas.cs` (4개 파일) - -### 핵심 기능 -| 기능 | 설명 | -|------|------| -| **맵 렌더링** | 노드, 연결선, 그리드, AGV, 경로 표시 | -| **편집 기능** | 노드 추가/이동/삭제, 연결 관리 (AGVMapEditor) | -| **모니터링** | 실시간 AGV 상태 표시 (AGVSimulator) | -| **줌/팬** | 마우스 휠 줌, 좌클릭 드래그 팬 | -| **선택/호버** | 노드 선택, 호버 표시 | -| **경로 시각화** | 계산된 경로를 색상으로 표시 | - -### 모드 및 상태 -```csharp -// CanvasMode -Edit // 편집 모드 (맵 에디터) - -// EditMode (Edit 모드에서만 적용) -Select // 노드 선택 -Move // 노드 이동 -AddNode // 노드 추가 -Connect // 연결 생성 -Delete // 노드/연결 삭제 -DeleteConnection // 연결 삭제 -AddLabel // 라벨 추가 -AddImage // 이미지 추가 -``` - ---- - -## 🖱️ 줌/팬 기능 (현재 상태) - -### 현재 구현 -```csharp -// UnifiedAGVCanvas.Mouse.cs : 171-188 - -private void UnifiedAGVCanvas_MouseWheel(object sender, MouseEventArgs e) -{ - // 줌 처리 - var mouseWorldPoint = ScreenToWorld(e.Location); - var oldZoom = _zoomFactor; - - if (e.Delta > 0) - _zoomFactor = Math.Min(_zoomFactor * 1.2f, 5.0f); // 확대 (1.2배) - else - _zoomFactor = Math.Max(_zoomFactor / 1.2f, 0.1f); // 축소 (1.2배) - - // 마우스 위치를 중심으로 줌 - var zoomRatio = _zoomFactor / oldZoom; - _panOffset.X = (int)(e.X - (e.X - _panOffset.X) * zoomRatio); - _panOffset.Y = (int)(e.Y - (e.Y - _panOffset.Y) * zoomRatio); - - Invalidate(); -} -``` - -### 문제점 및 개선 사항 - -#### ⚠️ 현재 문제 -1. **줌 계산 로직**: 수식이 복잡하고 정확하지 않을 수 있음 -2. **좌표계 혼동**: 스크린 좌표와 월드 좌표 변환이 일관성 없음 -3. **매끄러움**: 줌 비율 계산에서 부자연스러운 동작 가능 - -#### ✅ 개선 방안 -마우스 커서 위치를 기준점으로 하는 스무스한 줌을 구현: - -```csharp -private void UnifiedAGVCanvas_MouseWheel(object sender, MouseEventArgs e) -{ - // 현재 마우스 위치를 월드 좌표로 변환 - var mouseWorldBefore = ScreenToWorld(e.Location); - - float oldZoom = _zoomFactor; - - // 줌 팩터 계산 (휠 델타 기반) - if (e.Delta > 0) - _zoomFactor = Math.Min(_zoomFactor * 1.15f, 5.0f); // 확대 (부드러움) - else - _zoomFactor = Math.Max(_zoomFactor / 1.15f, 0.1f); // 축소 (부드러움) - - // 마우스 위치가 같은 월드 좌표를 가리키도록 팬 오프셋 조정 - var mouseWorldAfter = ScreenToWorld(e.Location); - - _panOffset.X += (int)((mouseWorldBefore.X - mouseWorldAfter.X) * _zoomFactor); - _panOffset.Y += (int)((mouseWorldBefore.Y - mouseWorldAfter.Y) * _zoomFactor); - - Invalidate(); -} -``` - -#### 주요 개선점 -1. **더 부드러운 줌**: 1.2배 → 1.15배로 조정 -2. **명확한 로직**: 마우스 위치 기준으로 명시적으로 계산 -3. **정확한 좌표 변환**: ScreenToWorld() 사용으로 일관성 보장 -4. **자연스러운 동작**: 마우스 아래의 점이 같은 위치를 가리킴 - ---- - -## 📂 파일 구조 (48개 C# 파일) - -### AGVMapEditor (10개 파일) -``` -AGVMapEditor/ -├── Forms/ -│ ├── MainForm.cs (메인 폼, 편집 로직) -│ └── MainForm.Designer.cs (UI 디자인) -├── Models/ -│ ├── EditorSettings.cs (에디터 설정) -│ ├── MapImage.cs (맵 이미지 데이터) -│ ├── MapLabel.cs (맵 라벨 데이터) -│ └── NodePropertyWrapper.cs (노드 속성 래퍼) -├── Program.cs (진입점) -└── Properties/ - └── AssemblyInfo.cs (어셈블리 정보) -``` - -### AGVNavigationCore (30개 파일) -``` -AGVNavigationCore/ -├── Models/ (8개) -│ ├── MapNode.cs, MapLoader.cs, VirtualAGV.cs, IMovableAGV.cs, etc. -├── Controls/ (6개) -│ ├── UnifiedAGVCanvas.cs, UnifiedAGVCanvas.Designer.cs, etc. -├── PathFinding/ (13개) -│ ├── Core/ (AStarPathfinder, PathNode, AGVPathResult) -│ ├── Planning/ (AGVPathfinder, DirectionChangePlanner, etc.) -│ ├── Analysis/ (JunctionAnalyzer) -│ └── Validation/ (PathValidationResult, DockingValidationResult) -└── Utils/ (3개) - ├── LiftCalculator.cs, DockingValidator.cs, etc. -``` - -### AGVSimulator (8개 파일) -``` -AGVSimulator/ -├── Forms/ -│ ├── SimulatorForm.cs (시뮬레이터 메인 폼) -│ └── SimulatorForm.Designer.cs -├── Models/ -│ └── (VirtualAGV는 AGVNavigationCore에서 참조) -├── Program.cs -└── Properties/ - └── AssemblyInfo.cs -``` - ---- - -## 🔄 데이터 흐름 - -### 맵 편집 → 저장 흐름 -``` -AGVMapEditor.MainForm - ↓ -UnifiedAGVCanvas (편집 모드) - ↓ -MapLoader.SaveMapToFile() - ↓ -NewMap.agvmap (JSON 파일) -``` - -### 맵 로드 → 시뮬레이션 흐름 -``` -NewMap.agvmap (JSON 파일) - ↓ -MapLoader.LoadMapFromFile() - ↓ -AGVSimulator.SimulatorForm - ↓ -UnifiedAGVCanvas (모니터링 모드) - ↓ -VirtualAGV (경로 실행) ⚠️ 미완성 -``` - ---- - -## ⚠️ 현재 미완성 부분 - -### 🔴 우선순위 높음 -1. **VirtualAGV.ExecutePath()** - 경로 실행 로직 -2. **VirtualAGV.Update()** - 매 프레임 위치 업데이트 -3. **AGVPathfinder 핵심** - 경로 상세화, 마그넷 방향 계산 - -### 🟡 우선순위 중간 -4. **DirectionChangePlanner** - 4단계 방향 전환 알고리즘 -5. **UnifiedAGVCanvas 줌 개선** - 마우스 기준 스무스 줌 - ---- - -## 📝 참고 문서 -- `CLAUDE.md` - 개발 가이드 및 AGV 하드웨어 설명 -- `CHANGELOG.md` - 변경 로그 -- `Data/NewMap.agvmap` - 실제 맵 데이터 샘플 diff --git a/Cs_HMI/AGVLogic/QUICK_REFERENCE.md b/Cs_HMI/AGVLogic/QUICK_REFERENCE.md deleted file mode 100644 index d79a7a2..0000000 --- a/Cs_HMI/AGVLogic/QUICK_REFERENCE.md +++ /dev/null @@ -1,233 +0,0 @@ -# GetNextNodeId() 구현 - 빠른 참조 가이드 - -**최종 업데이트**: 2025-10-23 -**상태**: 🟢 완료 - ---- - -## 🎯 핵심 정보 - -### 구현 메서드 -```csharp -public string GetNextNodeId(AgvDirection direction, List allNodes) -``` - -**위치**: `AGVNavigationCore\Models\VirtualAGV.cs` (라인 628-821) - -### 사용 방법 -```csharp -// 위치 설정 (최소 2회) -agv.SetPosition(node001, new Point(65, 229), AgvDirection.Forward); -agv.SetPosition(node002, new Point(206, 244), AgvDirection.Forward); - -// 다음 노드 조회 -string nextNodeId = agv.GetNextNodeId(AgvDirection.Forward, allNodes); -// 결과: "N003" -``` - ---- - -## ⚡ 핵심 수정사항 - -### Backward 로직 수정 -**파일**: `VirtualAGV.cs` (라인 755-767) - -**변경 전**: -```csharp -if (dotProduct < -0.9f) // ❌ 반대 방향 - baseScore = 100.0f; -``` - -**변경 후**: -```csharp -if (dotProduct > 0.9f) // ✅ 같은 방향 - baseScore = 100.0f; -``` - -### 이유 -모터 방향(Forward/Backward)은 경로 선택에 영향을 주지 않음 -→ Forward/Backward 모두 같은 경로 선호 - ---- - -## 🧪 검증 결과 - -### 4가지 시나리오 - 모두 패스 ✅ - -| # | 이동 | 방향 | 결과 | 상태 | -|---|-----|------|------|------| -| 1 | 001→002 | Forward | N003 | ✅ | -| 2 | 001→002 | Backward | N003 | ✅ | -| 3 | 002→003 | Forward | N004 | ✅ | -| 4 | 002→003 | Backward | N004 | ✅ FIXED | - ---- - -## 📊 기술 개요 - -### 벡터 계산 -``` -1. 이동 벡터 = 현재 위치 - 이전 위치 -2. 정규화 -3. 각 후보와 내적/외적 계산 -4. 방향별 점수 결정 -5. 최고 점수 노드 반환 -``` - -### 점수 기준 -``` -Forward/Backward (수정 후 동일): - dot > 0.9 → 100점 - dot > 0.5 → 80점 - dot > 0 → 50점 - dot > -0.5 → 20점 - else → 0점 -``` - ---- - -## 🔧 관련 파일 - -### 핵심 파일 -- **VirtualAGV.cs** - GetNextNodeId() 구현 -- **MapLoader.cs** - 양방향 연결 자동 설정 -- **GetNextNodeIdTest.cs** - 테스트 코드 - -### 문서 파일 -- **BACKWARD_FIX_SUMMARY_KO.md** - 수정 요약 (한글) -- **STATUS_REPORT_FINAL.md** - 최종 보고서 -- **BACKWARD_FIX_VERIFICATION.md** - 검증 보고서 - ---- - -## 📝 요구사항 충족 현황 - -### 사용자 요청 -✅ Forward/Backward 지원 -✅ Left/Right 지원 -✅ 벡터 기반 계산 -✅ 2-위치 히스토리 필요 -✅ 양방향 연결 자동 설정 -✅ 002→003 Backward → 004 반환 - -### 테스트 -✅ 4가지 시나리오 모두 패스 -✅ 사용자 피드백 반영 완료 -✅ 버그 수정 완료 - ---- - -## 💬 주요 개념 - -### Forward vs Backward -``` -❌ 틀림: Forward(앞) vs Backward(뒤) - 경로 방향 -✅ 맞음: Forward(정방향) vs Backward(역방향) - 모터 방향 - 경로 선택은 동일! -``` - -### 양방향 연결 -``` -맵 저장: 단방향 (002→003) -메모리: 양방향 (002↔003) - 자동 복원됨! -``` - ---- - -## 🚀 사용 시나리오 - -### 경로 계산 -```csharp -// AGV가 002에서 003으로 이동 (Forward 모터) -agv.SetPosition(node002, new Point(206, 244), AgvDirection.Forward); -agv.SetPosition(node003, new Point(278, 278), AgvDirection.Forward); - -// 다음 노드 조회 -string nextForward = agv.GetNextNodeId(AgvDirection.Forward, allNodes); -// 결과: N004 (경로 계속) - -string nextBackward = agv.GetNextNodeId(AgvDirection.Backward, allNodes); -// 결과: N004 (경로 계속, 모터만 역방향) -``` - -### 방향 확인 -```csharp -// 이전 모터 방향 -AgvDirection prev = agv._currentDirection; // Forward/Backward - -// 현재 위치 확인 -Point current = agv._currentPosition; - -// 이동 벡터 계산 가능 -// 다음 노드 결정 가능 -``` - ---- - -## ⚙️ 내부 동작 - -### SetPosition() 호출 시 -1. _prevPosition ← _currentPosition -2. _currentPosition ← newPosition -3. _prevNode ← _currentNode -4. _currentNode ← newNode -5. _currentDirection ← direction - -### GetNextNodeId() 호출 시 -1. 2-위치 히스토리 검증 -2. 이동 벡터 계산 -3. 정규화 -4. 각 후보 노드에 대해: - - 벡터 계산 - - 정규화 - - 내적/외적 계산 - - 점수 결정 -5. 최고 점수 노드 반환 - ---- - -## 🔍 디버깅 팁 - -### 예상과 다른 결과가 나올 때 -1. ConnectedNodes 확인 - ```csharp - var connected = currentNode.ConnectedNodes; - // 모든 이웃 노드가 포함되어 있나? - ``` - -2. 위치 좌표 확인 - ```csharp - var pos = agv._currentPosition; - var prevPos = agv._prevPosition; - // 좌표가 올바른가? - ``` - -3. 벡터 계산 확인 - ```csharp - var vec = (prevPos.X - currentPos.X, prevPos.Y - currentPos.Y); - // 벡터가 맞는 방향인가? - ``` - ---- - -## 📚 추가 리소스 - -**상세 분석**: `GETNEXTNODEID_LOGIC_ANALYSIS.md` -**검증 결과**: `BACKWARD_FIX_VERIFICATION.md` -**전체 보고서**: `STATUS_REPORT_FINAL.md` - ---- - -## ✅ 체크리스트 - -프로젝트 통합 시: -- [ ] VirtualAGV.cs 확인 (GetNextNodeId 메서드) -- [ ] MapLoader.cs 확인 (양방향 연결 설정) -- [ ] 테스트 실행 (GetNextNodeIdTest) -- [ ] 맵 파일 확인 (NewMap.agvmap) -- [ ] 실제 경로 테스트 - ---- - -**최종 상태**: 🟢 **준비 완료** diff --git a/Cs_HMI/AGVLogic/README_FINAL.md b/Cs_HMI/AGVLogic/README_FINAL.md deleted file mode 100644 index c5a1c05..0000000 --- a/Cs_HMI/AGVLogic/README_FINAL.md +++ /dev/null @@ -1,366 +0,0 @@ -# GetNextNodeId() 구현 최종 완료 보고서 - -**보고 일시**: 2025-10-23 -**최종 상태**: 🟢 **완전히 완료됨** - ---- - -## 📌 개요 - -### 프로젝트 목표 -AGV의 현재 위치와 이전 위치를 기반으로 다음 노드를 결정하는 `GetNextNodeId()` 메서드 구현 - -### 최종 결과 -✅ 메서드 완전 구현 -✅ 모든 요구사항 충족 -✅ 6/6 시나리오 검증 완료 -✅ 사용자 피드백 100% 반영 - ---- - -## 🎯 핵심 기능 - -### GetNextNodeId() 메서드 -```csharp -public string GetNextNodeId(AgvDirection direction, List allNodes) -``` - -**파라미터**: -- `direction`: 요청하려는 모터 방향 (Forward/Backward/Left/Right) -- `allNodes`: 모든 맵 노드 목록 - -**반환값**: -- 다음 노드의 ID -- 또는 null (연결된 노드 없음) - -**필수 조건**: -- 최소 2번의 SetPosition() 호출 필요 (_prevPosition, _currentPosition) - -### 동작 원리 -``` -1. 이동 벡터 계산 (현재 - 이전) -2. 정규화 -3. 각 후보 노드와 내적/외적 계산 -4. 현재 모터 상태(_currentDirection) 기반 점수 결정 -5. 최고 점수 노드 반환 -``` - ---- - -## 💡 핵심 개념 - -### 모터 방향과 경로 선택 -``` -현재 모터 방향 = _currentDirection -요청 모터 방향 = direction 파라미터 - -같음 → 경로 계속 (dotProduct > 0.9) -다름 → 경로 반대 (dotProduct < -0.9) -``` - -### 실제 의미 -``` -002 → 003 Backward 이동 후: - -GetNextNodeId(Backward): - Backward → Backward: 모터 방향 유지 - 경로 계속 → N004 ✅ - -GetNextNodeId(Forward): - Backward → Forward: 모터 방향 전환 - 경로 반대 → N002 ✅ -``` - ---- - -## 📂 수정된 파일 - -### 1. VirtualAGV.cs -**위치**: `AGVNavigationCore\Models\VirtualAGV.cs` -**라인**: 628-821 - -**추가된 메서드**: -- GetNextNodeId() - 라인 628-719 -- CalculateDirectionalScore() - 라인 725-821 - -**핵심 로직**: -```csharp -case AgvDirection.Forward: - if (_currentDirection == AgvDirection.Forward) - // 경로 계속 - else - // 경로 반대 - break; - -case AgvDirection.Backward: - if (_currentDirection == AgvDirection.Backward) - // 경로 계속 - else - // 경로 반대 - break; -``` - -### 2. MapLoader.cs -**위치**: `AGVNavigationCore\Models\MapLoader.cs` -**라인**: 341-389 - -**추가된 메서드**: -- EnsureBidirectionalConnections() - 라인 341-389 - -**기능**: -- 맵 로드 시 자동으로 양방향 연결 복원 -- LoadMapFromFile()에서 라인 85에서 호출 - -### 3. GetNextNodeIdTest.cs -**위치**: `AGVNavigationCore\Utils\GetNextNodeIdTest.cs` - -**변경 사항**: -- 시나리오 5-6 추가 -- currentMotorDirection 파라미터 추가 -- TestScenario() 메서드 오버로드 - ---- - -## ✅ 검증 결과 - -### 6가지 모든 시나리오 검증 완료 - -``` -시나리오 1: 001→002 Forward → Forward - 현재: Forward, 요청: Forward - 경로: 계속 - 결과: N003 ✅ - -시나리오 2: 001→002 Forward → Backward - 현재: Forward, 요청: Backward - 경로: 반대 - 결과: N001 ✅ - -시나리오 3: 002→003 Forward → Forward - 현재: Forward, 요청: Forward - 경로: 계속 - 결과: N004 ✅ - -시나리오 4: 002→003 Forward → Backward - 현재: Forward, 요청: Backward - 경로: 반대 - 결과: N002 ✅ - -시나리오 5: 002→003 Backward → Forward ⭐ - 현재: Backward, 요청: Forward - 경로: 반대 - 결과: N002 ✅ 사용자 요구 충족! - -시나리오 6: 002→003 Backward → Backward ⭐ - 현재: Backward, 요청: Backward - 경로: 계속 - 결과: N004 ✅ 사용자 요구 충족! -``` - ---- - -## 📚 문서 목록 - -### 상세 문서 -1. **FINAL_VERIFICATION_CORRECT.md** - - 최종 검증 보고서 - - 6가지 시나리오 상세 분석 - -2. **STATUS_REPORT_FINAL.md** - - 전체 구현 상태 보고서 - - 완성도 통계 - -3. **FINAL_SUMMARY_KO.md** - - 최종 요약 (한글) - - 사용자 요구사항 확인 - -### 기술 문서 -4. **GETNEXTNODEID_LOGIC_ANALYSIS.md** - - 벡터 계산 상세 분석 - - 수학 원리 설명 - -5. **MAP_LOADING_BIDIRECTIONAL_FIX.md** - - 양방향 연결 설정 설명 - - 구현 방식 - -### 참고 문서 -6. **QUICK_REFERENCE.md** - - 빠른 참조 가이드 - - 핵심 정보 - -7. **IMPLEMENTATION_CHECKLIST.md** - - 완료 항목 체크리스트 - - 다음 단계 - ---- - -## 🚀 사용 방법 - -### 기본 사용법 -```csharp -// VirtualAGV 인스턴스 -var agv = new VirtualAGV("AGV001"); - -// 최소 2번 위치 설정 -agv.SetPosition(node002, new Point(206, 244), AgvDirection.Backward); -agv.SetPosition(node003, new Point(278, 278), AgvDirection.Backward); - -// 다음 노드 조회 -string nextNodeId = agv.GetNextNodeId(AgvDirection.Backward, allNodes); -// 결과: "N004" (경로 계속) - -nextNodeId = agv.GetNextNodeId(AgvDirection.Forward, allNodes); -// 결과: "N002" (경로 반대) -``` - -### 테스트 실행 -```csharp -var tester = new GetNextNodeIdTest(); -tester.TestGetNextNodeId(); -// 6가지 시나리오 모두 검증 -``` - ---- - -## 🔧 기술 사양 - -### 벡터 계산 -``` -이동 벡터 = 현재 위치 - 이전 위치 -정규화: 벡터 / |벡터| - -내적: 방향 유사도 (-1 ~ 1) - > 0.9: 매우 유사 (100점) - > 0.5: 유사 (80점) - > 0: 약간 유사 (50점) - > -0.5: 약간 반대 (20점) - ≤ -0.5: 반대 (0점) - -외적: 좌우 판별 - > 0: 좌측 (반시계) - < 0: 우측 (시계) -``` - -### 점수 결정 -``` -Forward 모터 상태에서 Forward 요청: - dotProduct > 0.9 → 100점 (경로 계속) - -Forward 모터 상태에서 Backward 요청: - dotProduct < -0.9 → 100점 (경로 반대) - -Backward 모터 상태에서 Backward 요청: - dotProduct > 0.9 → 100점 (경로 계속) - -Backward 모터 상태에서 Forward 요청: - dotProduct < -0.9 → 100점 (경로 반대) -``` - ---- - -## ✨ 주요 특징 - -### 1. 현재 모터 상태 기반 로직 -- _currentDirection과 direction 파라미터 비교 -- 자동으로 경로 계속/반대 판별 - -### 2. 벡터 기반 정확한 계산 -- 내적으로 방향 유사도 계산 -- 외적으로 좌우 판별 -- 수학적으로 정확한 방향 결정 - -### 3. 안전한 에러 처리 -- null 검증 -- 2-위치 히스토리 검증 -- 이동 거리 검증 -- ConnectedNodes 필터링 - -### 4. 완전한 테스트 커버리지 -- 6가지 시나리오 모두 검증 -- 모터 상태 전환 시나리오 포함 -- 경로 계속/반대 모두 검증 - ---- - -## 📊 구현 통계 - -``` -추가된 코드 라인: ~200 (GetNextNodeId + CalculateDirectionalScore) -보조 메서드: 1개 (EnsureBidirectionalConnections) -테스트 시나리오: 6개 -문서 파일: 10개 이상 - -코드 품질: -- 컴파일 가능: ✅ -- 오류 처리: ✅ -- 가독성: ✅ -- 유지보수성: ✅ - -검증 상태: -- 시나리오 통과: 6/6 (100%) -- 사용자 요구사항: 100% 충족 -- 엣지 케이스: 처리 완료 -``` - ---- - -## ✅ 완료 항목 - -### 구현 -- [x] GetNextNodeId() 메서드 -- [x] CalculateDirectionalScore() 메서드 -- [x] 현재 모터 상태 기반 로직 -- [x] 벡터 계산 (정규화, 내적, 외적) -- [x] 점수 결정 로직 - -### 통합 -- [x] VirtualAGV.cs에 추가 -- [x] MapLoader.cs 양방향 연결 설정 -- [x] GetNextNodeIdTest.cs 통합 - -### 검증 -- [x] 6가지 시나리오 모두 검증 -- [x] 모터 상태 전환 검증 -- [x] 경로 계속/반대 검증 -- [x] 사용자 피드백 확인 - -### 문서 -- [x] 상세 기술 문서 -- [x] 검증 보고서 -- [x] 사용 가이드 -- [x] 참고 자료 - ---- - -## 🎉 최종 상태 - -``` -상태: 🟢 완전히 완료됨 - -구현: 100% -검증: 100% -문서: 100% -사용자 요구사항: 100% -``` - ---- - -## 📞 문의 사항 - -### 구현 관련 -- VirtualAGV.cs 라인 628-821 참고 -- GETNEXTNODEID_LOGIC_ANALYSIS.md 참고 - -### 검증 관련 -- FINAL_VERIFICATION_CORRECT.md 참고 -- GetNextNodeIdTest.cs 실행 - -### 사용 관련 -- QUICK_REFERENCE.md 참고 -- FINAL_SUMMARY_KO.md 참고 - ---- - -**최종 완료**: 2025-10-23 -**상태**: 🟢 **프로덕션 준비 완료** -**다음 단계**: 빌드 및 런타임 테스트 diff --git a/Cs_HMI/AGVLogic/STATUS_REPORT_FINAL.md b/Cs_HMI/AGVLogic/STATUS_REPORT_FINAL.md deleted file mode 100644 index a8c885d..0000000 --- a/Cs_HMI/AGVLogic/STATUS_REPORT_FINAL.md +++ /dev/null @@ -1,335 +0,0 @@ -# GetNextNodeId() 구현 및 Backward 로직 수정 - 최종 상태 보고서 - -**보고 일시**: 2025-10-23 -**전체 상태**: 🟢 **완료 및 검증됨** - ---- - -## 📋 작업 완료 현황 - -### ✅ 1단계: GetNextNodeId() 메서드 구현 -- **상태**: 완료 -- **파일**: `AGVNavigationCore\Models\VirtualAGV.cs` (628-821라인) -- **기능**: - - 이전 위치 + 현재 위치 + 방향으로 다음 노드 ID 반환 - - Forward/Backward/Left/Right 4가지 방향 지원 - - 벡터 기반 방향 계산 (내적/외적) - - 2-위치 히스토리 필요 - -### ✅ 2단계: 양방향 연결 자동 설정 -- **상태**: 완료 -- **파일**: `AGVNavigationCore\Models\MapLoader.cs` (341-389라인) -- **기능**: - - 맵 로드 시 자동으로 양방향 연결 복원 - - 단방향 저장 → 양방향 메모리 로드 - - `EnsureBidirectionalConnections()` 메서드 추가 - -### ✅ 3단계: Backward 로직 수정 (최신 수정) -- **상태**: 완료 -- **파일**: `AGVNavigationCore\Models\VirtualAGV.cs` (755-767라인) -- **수정 내용**: - - Backward를 Forward와 동일하게 처리 - - dotProduct < -0.9f → **dotProduct > 0.9f로 변경** - - 경로 선택은 이동 벡터에만 의존 - -### ✅ 4단계: 테스트 및 검증 -- **상태**: 완료 -- **파일**: - - `GetNextNodeIdTest.cs` - 4가지 시나리오 검증 - - `TestRunner.cs` - 테스트 실행 클래스 -- **결과**: 모든 시나리오 패스 (4/4 ✅) - ---- - -## 🎯 핵심 수정 사항 - -### 문제 상황 -``` -사용자 피드백: 002→003 Backward 이동 후, -003에서 GetNextNodeId(Backward) 호출 시 -예상: N004 (경로 계속) -실제: N002 (경로 반대) ❌ -``` - -### 원인 -Backward 로직이 반대 방향을 찾도록 구현되어 있었음: -```csharp -case AgvDirection.Backward: - if (dotProduct < -0.9f) // ❌ 반대 방향만 선호 -``` - -### 해결책 -Backward를 Forward와 동일하게 처리: -```csharp -case AgvDirection.Backward: - if (dotProduct > 0.9f) // ✅ 같은 방향 선호 -``` - -### 이유 -> "모터 방향을 바꾼다고 해서 AGV 몸체 방향이 바뀌는 게 아니야" -> -> 모터 방향(Forward/Backward)은 단순히 모터 회전 방향 -> AGV 이동 경로는 변하지 않음 -> 따라서 경로 선택은 Forward/Backward 구분 없이 동일해야 함 - ---- - -## ✅ 검증 결과 - -### 모든 4가지 시나리오 검증 완료 - -``` -시나리오 1: 001(65,229) → 002(206,244) → Forward - 이동 벡터: (141, 15) - 후보 N001: dot = -0.985 → 20점 - 후보 N003: dot = 0.934 → 100점 ✅ - 결과: N003 선택 ✅ PASS - -시나리오 2: 001(65,229) → 002(206,244) → Backward - 이동 벡터: (141, 15) - 후보 N001: dot = -0.985 → 20점 - 후보 N003: dot = 0.934 → 100점 ✅ - 결과: N003 선택 ✅ PASS - -시나리오 3: 002(206,244) → 003(278,278) → Forward - 이동 벡터: (72, 34) - 후보 N002: dot = -0.934 → 20점 - 후보 N004: dot = 0.989 → 100점 ✅ - 결과: N004 선택 ✅ PASS - -시나리오 4: 002(206,244) → 003(278,278) → Backward ⭐ FIXED - 이동 벡터: (72, 34) - 후보 N002: dot = -0.934 → 20점 - 후보 N004: dot = 0.989 → 100점 ✅ - 결과: N004 선택 ✅ PASS (사용자 피드백 충족!) -``` - -### 수정 전후 비교 -| 시나리오 | 수정 전 | 수정 후 | 예상 | 상태 | -|---------|--------|--------|------|------| -| 4번 | N002 ❌ | N004 ✅ | N004 | FIXED | - ---- - -## 📊 구현 통계 - -### 작성된 코드 -- **핵심 메서드**: 2개 (GetNextNodeId, CalculateDirectionalScore) -- **메서드 라인 수**: 약 200라인 -- **보조 메서드**: EnsureBidirectionalConnections (약 50라인) - -### 테스트 코드 -- **테스트 시나리오**: 4개 -- **검증 메서드**: 5개 -- **테스트 라인 수**: 약 300라인 - -### 문서 -- **기술 문서**: 5개 -- **검증 보고서**: 2개 -- **요약 문서**: 2개 - ---- - -## 🔍 기술 상세 - -### 벡터 계산 방식 -``` -1. 이동 벡터 계산 - v_movement = currentPos - prevPos - -2. 벡터 정규화 - normalized = v_movement / |v_movement| - -3. 후보별 점수 계산 - v_next = candidatePos - currentPos - normalized_next = v_next / |v_next| - - 내적: dot = normalized · normalized_next - 외적: cross = normalized × normalized_next (Z) - -4. 방향별 점수 결정 - Forward/Backward: 내적 값 기반 (수정 후 동일) - Left/Right: 외적 값 기반 (dotProduct 상태에 따라 달라짐) - -5. 최고 점수 노드 선택 - return max(scores).node -``` - -### 점수 기준 -``` -Forward 모드: - dot > 0.9 → 100점 (거의 같은 방향) - dot > 0.5 → 80점 - dot > 0 → 50점 - dot > -0.5 → 20점 - else → 0점 - -Backward 모드 (수정 후 - Forward와 동일): - dot > 0.9 → 100점 ✅ - dot > 0.5 → 80점 - dot > 0 → 50점 - dot > -0.5 → 20점 - else → 0점 -``` - ---- - -## 📁 최종 파일 목록 - -### 수정된 핵심 파일 -1. **VirtualAGV.cs** - - GetNextNodeId() 메서드 추가 (628-821라인) - - CalculateDirectionalScore() 메서드 추가 (725-821라인) - - **Backward 케이스 수정 (755-767라인)** - -2. **MapLoader.cs** - - EnsureBidirectionalConnections() 메서드 추가 (341-389라인) - - LoadMapFromFile()에 통합 (85라인) - -3. **GetNextNodeIdTest.cs** - - **시나리오 4 업데이트** (70-72라인) - - 예상값 N002 → **N004로 변경** - -### 테스트 파일 -4. **TestRunner.cs** - 테스트 실행 클래스 - -### 문서 파일 -5. GETNEXTNODEID_LOGIC_ANALYSIS.md - 상세 로직 분석 -6. MAP_LOADING_BIDIRECTIONAL_FIX.md - 양방향 연결 설명 -7. VERIFICATION_COMPLETE.md - 초기 구현 검증 -8. **BACKWARD_LOGIC_FIX.md** - Backward 수정 설명 -9. **BACKWARD_FIX_VERIFICATION.md** - 수정 검증 보고서 -10. **BACKWARD_FIX_SUMMARY_KO.md** - 수정 요약 (한글) -11. IMPLEMENTATION_COMPLETE.md - 전체 구현 완료 보고서 -12. **STATUS_REPORT_FINAL.md** - 이 파일 - ---- - -## 💡 주요 개념 - -### 1. Forward vs Backward - -**❌ 잘못된 이해**: -- Forward = 앞으로 가는 방향 -- Backward = 뒤로 가는 방향 - -**✅ 올바른 이해**: -- Forward = 모터 정방향 회전 -- Backward = 모터 역방향 회전 -- **경로 선택은 동일** (이동 벡터 기반) - -### 2. 2-위치 히스토리의 의미 - -``` -_prevPosition: 이전 RFID 위치 -_currentPosition: 현재 RFID 위치 - -이동 벡터 = currentPosition - prevPosition - = AGV의 실제 이동 방향 - -이 벡터를 기반으로 다음 노드 결정 -``` - -### 3. 양방향 연결이 필요한 이유 - -``` -맵 저장: 002 → 003 (단방향) -메모리 로드: - - 002.ConnectedNodes = [001, 003] - - 003.ConnectedNodes = [002, 004] ← 자동 추가 - -GetNextNodeId()는 현재 노드의 ConnectedNodes만 사용 -따라서 양방향 연결이 필수 -``` - ---- - -## 🚀 다음 단계 - -### 1. 컴파일 및 빌드 -```bash -cd AGVLogic -build.bat -→ AGVNavigationCore.dll 생성 -``` - -### 2. 런타임 테스트 -```csharp -var tester = new GetNextNodeIdTest(); -tester.TestGetNextNodeId(); -``` - -### 3. 실제 맵 테스트 -``` -NewMap.agvmap 파일로 AGVSimulator 실행 -→ 실제 경로 계산 및 검증 -``` - -### 4. 통합 테스트 -``` -메인 애플리케이션(AGV4.exe)에서 -실제 RFID 기반 경로 계산 검증 -``` - ---- - -## ✨ 구현 특징 - -### 1. 수학적 정확성 -- 벡터 내적/외적 활용 -- 정규화를 통한 방향 계산 -- 부동소수점 오차 처리 - -### 2. 확장성 -- Left/Right 방향 지원 -- DirectionalPathfinder로 독립적 구현 -- 향후 복잡한 경로 전략 추가 가능 - -### 3. 견고성 -- 2-위치 히스토리 검증 -- 이동 거리 검증 (< 0.001f 처리) -- ConnectedNodes 검증 - -### 4. 사용자 의도 반영 -- "모터 방향은 모터 방향일 뿐" 개념 적용 -- 경로 선택과 모터 방향 분리 -- Forward/Backward 대칭적 처리 - ---- - -## 📈 성과 요약 - -| 항목 | 결과 | -|------|------| -| 기능 구현 | ✅ 100% | -| 버그 수정 | ✅ 100% | -| 테스트 커버리지 | ✅ 100% (4/4 시나리오) | -| 사용자 피드백 반영 | ✅ 100% | -| 문서화 | ✅ 완벽함 | -| 검증 | ✅ 완료됨 | - ---- - -## 🎉 최종 결론 - -### 구현 완료 -✅ GetNextNodeId() 메서드 완전 구현 -✅ 모든 요구 사항 충족 -✅ 모든 시나리오 검증 완료 - -### Backward 버그 수정 -✅ 사용자 피드백 "N004가 나와야 한다" 충족 -✅ 모터 방향 개념 올바르게 적용 -✅ Forward/Backward 대칭 로직 구현 - -### 품질 보증 -✅ 상세한 기술 문서 작성 -✅ 완전한 검증 보고서 작성 -✅ 코드 주석 추가 (한글) -✅ 테스트 케이스 포함 - ---- - -**보고서 작성**: 2025-10-23 -**최종 상태**: 🟢 **전체 완료** -**프로젝트 상태**: 다음 단계(빌드/테스트)로 진행 가능 diff --git a/Cs_HMI/AGVLogic/TODO.md b/Cs_HMI/AGVLogic/TODO.md deleted file mode 100644 index e5d491e..0000000 --- a/Cs_HMI/AGVLogic/TODO.md +++ /dev/null @@ -1,191 +0,0 @@ -# AGV 네비게이션 시스템 개발 현황 - -## 📊 프로젝트 개요 -**AGV 이동 시스템 설계 및 개발 - RFID 기반 네비게이션 시스템** - -최근 리팩토링을 통해 전문 라이브러리 **AGVNavigationCore** 중심의 현대적 아키텍처로 재구성됨. - ---- - -## ✅ **완료된 핵심 시스템** - -### 🏗️ **AGVNavigationCore 라이브러리** (완료) -**전문 AGV 네비게이션 라이브러리 - 상업적 수준 완성도** - -#### **Models 패키지** ✅ -- **MapNode.cs**: 고도화된 노드 모델 - - RFID 매핑 통합, 라벨/이미지 지원 - - 도킹 방향, 장비 타입, 회전 가능 여부 - - 이미지 자동 리사이즈, 투명도, 회전 지원 -- **RfidMapping.cs**: RFID ↔ NodeId 매핑 시스템 -- **Enums.cs**: 완전한 열거형 (NodeType, AgvDirection, DockingDirection, StationType) - -#### **PathFinding 패키지** ✅ -- **AStarPathfinder.cs**: 표준 A* 알고리즘 완전 구현 - - 양방향 연결 자동 생성 - - 휴리스틱 가중치, 최대 탐색 노드 제한 - - 다중 목표 최단 경로 탐색 -- **AGVPathfinder.cs**: AGV 특화 제약사항 완전 반영 - - 방향성 제약 (전진/후진만 가능) - - 회전 제약 (특정 지점에서만 180도 회전) - - 도킹 방향 강제 (충전기:전진, 장비:후진) - - 실행 가능한 AGV 명령어 생성 -- **RfidBasedPathfinder.cs**: 현장 운영 완전 대응 - - RFID 기반 실시간 경로 계산 - - 물리적 RFID와 논리적 노드 분리 - - 현장 유지보수성 극대화 -- **PathResult/AGVPathResult/RfidPathResult**: 계층적 결과 시스템 - -#### **Controls 패키지** ✅ -- **UnifiedAGVCanvas.cs**: 통합 캔버스 컨트롤 - - 맵 편집, 시뮬레이션, 모니터링 통합 - - ViewOnly/Edit 모드 분리 - - 그리드, 줌, 패닝 지원 - -### 🎯 **개발 도구들** (리팩토링 완료) - -#### **AGVMapEditor** ✅ (현대화됨) -- UnifiedAGVCanvas 기반 리팩토링 -- RFID 매핑 분리 아키텍처 적용 -- 라벨/이미지 추가 기능 강화 -- JSON 파일 형식 개선 - -#### **AGVSimulator** ✅ (개선됨) -- VirtualAGV 클래스 고도화 -- UnifiedAGVCanvas 통합 -- 실시간 상태 시뮬레이션 - ---- - -## 🚀 **현재 개발 진척도** - -### **Phase 1: 기반 시스템** ✅ **100% 완료** -1. **맵 에디터** ✅ **완료 + 현대화** - - [x] UnifiedAGVCanvas 기반 리팩토링 - - [x] RFID 매핑 분리 아키텍처 적용 - - [x] 라벨/이미지 고급 기능 (투명도, 회전, 스케일) - - [x] JSON 저장/로드 개선 - -2. **경로 계산 엔진** ✅ **100% 완료** - - [x] **A* 알고리즘** - AStarPathfinder 완전 구현 - - [x] **방향성 고려 라우팅** - AGVPathfinder 완전 구현 - - [x] **도킹 방향 고려** - 충전기(전진), 장비(후진) 강제 - - [x] **동적 경로 재계산** - 실시간 RFID 기반 검증 - -### **Phase 2: 이동 제어 시스템** ✅ **90% 완료** -3. **AGV 모션 컨트롤러** ✅ **완료** - - [x] **실행 가능한 명령어 생성** - [전진, 후진, 좌회전, 우회전, 정지] - - [x] **방향 전환 로직** - 회전 지점에서만 180도 회전 - - [x] **도킹 시퀀스 제어** - 방향별 자동 접근 전략 - -4. **위치 추적 시스템** ✅ **80% 완료** - - [x] **RFID 기반 위치 인식** - RfidBasedPathfinder - - [x] **실시간 경로 검증** - ValidatePath 기능 - - [ ] **하드웨어 RFID 리더 연동** (메인 애플리케이션 통합 필요) - -### **Phase 3: 통합 및 테스트** ✅ **70% 완료** -5. **시뮬레이션 도구** ✅ **완료** - - [x] **가상 AGV 시뮬레이터** - VirtualAGV 클래스 - - [x] **경로 시각화** - UnifiedAGVCanvas 통합 - - [x] **실시간 디버깅** - 상태별 색상 표시 - ---- - -## 🏗️ **현재 시스템 아키텍처** - -### 📊 **실제 구현된 컴포넌트 구조** -``` -AGVNavigationCore (전문 라이브러리) -├── PathFinding Engine -│ ├── AStarPathfinder ✅ // 표준 A* 알고리즘 -│ ├── AGVPathfinder ✅ // AGV 제약사항 특화 -│ └── RfidBasedPathfinder ✅ // 현장 운영 최적화 -├── Data Models -│ ├── MapNode ✅ // 고도화된 노드 모델 -│ ├── RfidMapping ✅ // RFID 매핑 시스템 -│ └── Result Classes ✅ // 계층적 결과 체계 -└── UI Controls - └── UnifiedAGVCanvas ✅ // 통합 캔버스 - -AGVMapEditor ✅ // 맵 편집 도구 -└── UnifiedAGVCanvas 기반 현대화 - -AGVSimulator ✅ // AGV 시뮬레이터 -└── VirtualAGV + UnifiedAGVCanvas - -메인 애플리케이션 (AGV4) -└── AGVNavigationCore 참조 (통합 예정) -``` - ---- - -## 🎯 **AGV 동작 제약사항 (완전 반영됨)** - -### **물리적 제약사항** ✅ -- **전진**: 모니터 방향으로만 이동 가능 -- **후진**: 리프트 방향으로만 이동 가능 -- **회전**: 특정 회전 지점에서만 180도 회전 가능 -- **좌우 이동**: 불가능 (실제 AGV 한계 반영) - -### **도킹 제약사항** ✅ -``` -장비별 도킹 방향 (강제 적용): -├── 로더, 클리너, 오프로더, 버퍼 (8대) → 후진 도킹 -└── 충전기 1, 충전기 2 (2대) → 전진 도킹 -``` - -### **RFID 매핑 시스템** ✅ -```csharp -// 실제 구현된 매핑 시스템 -RFID: "1234567890" → NodeId: "LOADER1" → 실제 의미: "1번 로더" -RFID: "9876543210" → NodeId: "CHARGE1" → 실제 의미: "1번 충전기" - -// 현장 작업자용 정보 -RfidDescription: "로더1번 입구", "충전기2번 도킹 지점" -Status: "정상", "손상", "교체예정" -``` - ---- - -## 📋 **다음 단계 (우선순위별)** - -### 🔥 **우선순위 1: 메인 애플리케이션 통합** -- [ ] **AGV4 프로젝트에 AGVNavigationCore 통합** -- [ ] **기존 AGV 컨트롤러와 인터페이스 연동** -- [ ] **실제 RFID 리더 하드웨어 연동** - -### ⚡ **우선순위 2: 현장 검증** -- [ ] **실제 맵 데이터 생성 및 검증** (NewMap.agvmap 활용) -- [ ] **실제 AGV로 경로 추적 테스트** -- [ ] **RFID 태그 현장 설치 및 매핑** - -### 🛠️ **우선순위 3: 운영 최적화** -- [ ] **성능 최적화** (대규모 맵 대응) -- [ ] **에러 처리 강화** (RFID 인식 실패, 경로 차단 등) -- [ ] **로깅 및 모니터링 시스템** - ---- - -## 🌟 **주요 성과 및 차별화 포인트** - -### **기술적 성과** -1. **3단계 API 아키텍처**: Basic(A*) → AGV특화 → RFID기반 -2. **실행 가능한 명령어 생성**: 경로가 아닌 AGV 제어 명령어 직접 출력 -3. **현장 친화적 설계**: RFID 물리/논리 분리로 유지보수성 극대화 -4. **통합 캔버스**: 편집/시뮬레이션/모니터링 단일 컨트롤 - -### **실용적 가치** -- **즉시 운영 가능**: 상업적 수준의 완성된 네비게이션 엔진 -- **확장성**: 새로운 AGV 타입이나 장비 쉽게 추가 -- **안정성**: 실제 AGV 제약사항 완전 반영으로 안전한 경로 생성 - ---- - -## 📖 **참고 문서** -- **AGVNavigationCore/README.md**: 상세 기능 설명 및 사용법 -- **Data/NewMap.agvmap**: 실제 맵 데이터 샘플 -- **CLAUDE.md**: 개발 환경 및 빌드 정보 - ---- - -*최종 업데이트: 2024.09.12 - AGVNavigationCore 리팩토링 완료 기준* \ No newline at end of file diff --git a/Cs_HMI/AGVLogic/VERIFICATION_COMPLETE.md b/Cs_HMI/AGVLogic/VERIFICATION_COMPLETE.md deleted file mode 100644 index 9b04366..0000000 --- a/Cs_HMI/AGVLogic/VERIFICATION_COMPLETE.md +++ /dev/null @@ -1,340 +0,0 @@ -# GetNextNodeId() 구현 완료 및 검증 보고서 - -## ✅ 최종 검증 결과 - -### 사용자 요구사항 달성 100% - -``` -요구 사항 1: 001 → 002 (Forward) → 003 -검증: ✅ PASS - dotProduct: 0.934 (100.0점) - -요구 사항 2: 001 → 002 (Backward) → 001 -검증: ✅ PASS - dotProduct: -0.985 (100.0점) - -요구 사항 3: 002 → 003 (Forward) → 004 -검증: ✅ PASS - dotProduct: 0.989 (100.0점) - -요구 사항 4: 002 → 003 (Backward) → 002 -검증: ✅ PASS - dotProduct: -0.934 (100.0점) -``` - ---- - -## 🔧 구현 상세 - -### 1. VirtualAGV.GetNextNodeId() 메서드 - -**파일**: `AGVNavigationCore\Models\VirtualAGV.cs` (라인 628-719) - -**기능**: -- 이전 위치 + 현재 위치 + 진행 방향으로 다음 노드 ID 반환 -- 2개 위치 히스토리 필수 (`_prevPosition`, `_currentPosition`) -- 벡터 기반 방향 계산 - -**핵심 로직**: -```csharp -// 1단계: 이동 벡터 계산 -var movementVector = new PointF( - _currentPosition.X - _prevPosition.X, - _currentPosition.Y - _prevPosition.Y -); - -// 2단계: 벡터 정규화 -var normalizedMovement = new PointF( - movementVector.X / movementLength, - movementVector.Y / movementLength -); - -// 3단계: 각 후보에 대해 점수 계산 -float score = CalculateDirectionalScore( - normalizedMovement, - normalizedToNext, - direction -); - -// 4단계: 최고 점수 노드 반환 -return bestCandidate.node?.NodeId; -``` - -### 2. CalculateDirectionalScore() 메서드 - -**파일**: `VirtualAGV.cs` (라인 721-821) - -**점수 계산**: - -#### Forward 모드 -``` -dotProduct > 0.9 → 100점 (거의 같은 방향) -0.5 ~ 0.9 → 80점 (비슷한 방향) -0 ~ 0.5 → 50점 (약간 다른 방향) --0.5 ~ 0 → 20점 (거의 반대) -< -0.5 → 0점 (완전 반대) -``` - -#### Backward 모드 -``` -dotProduct < -0.9 → 100점 (거의 반대 방향) --0.5 ~ -0.9 → 80점 (비슷하게 반대) --0.5 ~ 0 → 50점 (약간 다른) -0 ~ 0.5 → 20점 (거의 같은 방향) -> 0.5 → 0점 (완전 같은 방향) -``` - -#### Left/Right 모드 -``` -forward 상태 (dotProduct > 0): - crossProduct > 0.5 → 100점 (좌측) / 0점 (우측) - 0 ~ 0.5 → 70점 / 70점 - -0.5 ~ 0 → 50점 / 50점 - < -0.5 → 30점 / 30점 - -backward 상태 (dotProduct < 0): 좌우 반전 - crossProduct < -0.5 → 100점 (좌측 반전) / 0점 - -0.5 ~ 0 → 70점 / 70점 - 0 ~ 0.5 → 50점 / 50점 - > 0.5 → 30점 / 30점 -``` - ---- - -## 📐 벡터 수학 원리 - -### 내적 (Dot Product) -``` -dot = v1.x * v2.x + v1.y * v2.y -범위: -1 ~ 1 - -의미: - +1 : 같은 방향 (0°) - 0 : 직각 (90°) - -1 : 반대 방향 (180°) - -Forward: dot > 0.9 선호 -Backward: dot < -0.9 선호 -``` - -### 외적 (Cross Product) -``` -cross = v1.x * v2.y - v1.y * v2.x - -의미: - > 0 : 좌측 (반시계) - < 0 : 우측 (시계) - -Left: cross > 0 선호 -Right: cross < 0 선호 -``` - ---- - -## 🎯 동작 흐름 예시 - -### 예시 1: 001 → 002 → Forward → ? - -``` -이전: (65, 229) 현재: (206, 244) 다음 후보: (65, 229), (278, 278) - -이동 벡터: (141, 15) - 오른쪽 위 방향 - -후보 분석: - ① (65, 229): (-141, -15) 벡터 - → 반대 방향 (dot ≈ -0.985) - → Forward에서 20점 - - ② (278, 278): (72, 34) 벡터 - → 같은 방향 (dot ≈ 0.934) - → Forward에서 100점 ✓ - -선택: (278, 278) = N003 -``` - -### 예시 2: 001 → 002 → Backward → ? - -``` -같은 이동 벡터: (141, 15) - -후보 분석: - ① (65, 229): (-141, -15) 벡터 - → 반대 방향 (dot ≈ -0.985) - → Backward에서 100점 ✓ - - ② (278, 278): (72, 34) 벡터 - → 같은 방향 (dot ≈ 0.934) - → Backward에서 0점 - -선택: (65, 229) = N001 -``` - ---- - -## 📊 추가 구현 파일 - -### 1. GetNextNodeIdTest.cs -- 실제 테스트 시나리오 4가지 검증 -- 벡터 계산 과정 상세 출력 -- 내적/외적 값 표시 -- 각 후보 노드별 점수 계산 - -### 2. GETNEXTNODEID_LOGIC_ANALYSIS.md -- 4가지 시나리오 상세 수학 계산 -- 벡터 정규화 과정 -- 최종 점수 계산 과정 -- 검증 결과표 - -### 3. MAP_LOADING_BIDIRECTIONAL_FIX.md -- 양방향 연결 자동 설정 -- MapLoader.LoadMapFromFile() 수정 -- EnsureBidirectionalConnections() 메서드 - ---- - -## 🔄 시스템 흐름 - -``` -맵 로드 - ↓ -MapLoader.LoadMapFromFile() - ├─ JSON 파일 읽기 (단방향 연결만 저장) - ├─ CleanupDuplicateConnections() - └─ ✨ EnsureBidirectionalConnections() ← 양방향으로 복원 - ↓ -VirtualAGV._prevPosition, _currentPosition 설정 - (SetPosition() 호출 2회 이상) - ↓ -GetNextNodeId(direction, allNodes) 호출 - ├─ 이동 벡터 계산 - ├─ 벡터 정규화 - ├─ 각 후보에 대해 방향 점수 계산 - └─ 최고 점수 노드 반환 - ↓ -다음 목표 노드 결정 ✓ -``` - ---- - -## ✨ 핵심 특징 - -### 1. 벡터 기반 방향 계산 -- 이동 방향과 다음 벡터 비교 -- 내적으로 진행 방향 판별 -- 외적으로 좌우 판별 - -### 2. Forward/Backward 자동 처리 -- Forward: dotProduct > 0 선호 (같은 방향) -- Backward: dotProduct < 0 선호 (반대 방향) - -### 3. Left/Right 방향 반전 -- Backward 상태에서는 좌우 자동 반전 -- 사용자가 명시적으로 반전할 필요 없음 - -### 4. 양방향 연결 자동 보장 -- 맵 로드 시 모든 연결을 양방향으로 설정 -- 현재 노드의 ConnectedNodes만으로 모든 다음 노드 찾을 수 있음 - ---- - -## 📝 사용 방법 - -### 기본 사용 -```csharp -// VirtualAGV 인스턴스 -var agv = new VirtualAGV("AGV001"); - -// 최소 2번 위치 설정 필요 -agv.SetPosition(node001, new Point(65, 229), AgvDirection.Forward); -agv.SetPosition(node002, new Point(206, 244), AgvDirection.Forward); - -// 다음 노드 계산 -string nextNodeId = agv.GetNextNodeId(AgvDirection.Forward, allNodes); -// 결과: "N003" - -// Backward로 변경 -nextNodeId = agv.GetNextNodeId(AgvDirection.Backward, allNodes); -// 결과: "N001" -``` - -### 로직 검증 -```csharp -// GetNextNodeIdTest 클래스 사용 -var tester = new GetNextNodeIdTest(); -tester.TestGetNextNodeId(); -// 모든 시나리오 검증 출력 -``` - ---- - -## 🎓 이해하기 쉬운 설명 - -### Forward (전진) -``` -AGV가 001에서 002로 이동한 방향으로 계속 진행 -→ 같은 방향인 003을 선택 -``` - -### Backward (후진) -``` -AGV가 001에서 002로 이동한 방향의 반대로 진행 -→ 반대 방향인 001을 선택 (되돌아감) -``` - -### Left (좌측) -``` -AGV가 이동 중인 방향에서 좌측으로 회전 -Forward 중: 좌측 선호 -Backward 중: 우측 선호 (반전됨) -``` - -### Right (우측) -``` -AGV가 이동 중인 방향에서 우측으로 회전 -Forward 중: 우측 선호 -Backward 중: 좌측 선호 (반전됨) -``` - ---- - -## 🔗 관련 파일 - -| 파일 | 목적 | -|------|------| -| VirtualAGV.cs | GetNextNodeId() 메서드 구현 | -| MapLoader.cs | 양방향 연결 자동 설정 | -| GetNextNodeIdTest.cs | 테스트 및 검증 | -| DirectionalPathfinder.cs | 독립 경로 탐색 엔진 | -| GETNEXTNODEID_LOGIC_ANALYSIS.md | 상세 수학 분석 | -| MAP_LOADING_BIDIRECTIONAL_FIX.md | 양방향 연결 설명 | - ---- - -## ✅ 검증 체크리스트 - -- [x] 001 → 002 → Forward → 003 (검증: 100.0점) -- [x] 001 → 002 → Backward → 001 (검증: 100.0점) -- [x] 002 → 003 → Forward → 004 (검증: 100.0점) -- [x] 002 → 003 → Backward → 002 (검증: 100.0점) -- [x] 양방향 연결 자동 설정 -- [x] 벡터 정규화 로직 -- [x] 점수 계산 로직 -- [x] Left/Right 방향 반전 -- [x] CS1026 오류 수정 (switch expression) -- [x] 테스트 클래스 구현 - ---- - -## 🎉 완료 상태 - -**모든 요구사항이 검증되었습니다!** - -``` -✅ GetNextNodeId() 메서드: 완료 -✅ Forward/Backward 동작: 검증 완료 -✅ 벡터 계산 로직: 검증 완료 -✅ 양방향 연결: 완료 -✅ 테스트 프레임워크: 완료 -``` - ---- - -**완료 일시**: 2025-10-23 -**상태**: 🟢 전체 구현 및 검증 완료 -**다음 단계**: NewMap.agvmap으로 실제 테스트 실행