feat: Add comprehensive path prediction test with ProgressLogForm
- Add ProgressLogForm.cs for test result logging with ListView - Implement real UI workflow simulation in path prediction test - Test all connected node pairs to all docking targets - Support CSV export for test results - Keep List<string> ConnectedNodes structure (reverted List<MapNode> changes) - Display RFID values in log for better readability 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -66,17 +66,17 @@ namespace AGVNavigationCore.PathFinding.Core
|
||||
{
|
||||
var pathNode = _nodeMap[mapNode.NodeId];
|
||||
|
||||
foreach (var connectedNodeId in mapNode.ConnectedNodes)
|
||||
foreach (var connectedNode in mapNode.ConnectedNodes)
|
||||
{
|
||||
if (_nodeMap.ContainsKey(connectedNodeId))
|
||||
if (_nodeMap.ContainsKey(connectedNode))
|
||||
{
|
||||
// 양방향 연결 생성 (단일 연결이 양방향을 의미)
|
||||
if (!pathNode.ConnectedNodes.Contains(connectedNodeId))
|
||||
if (!pathNode.ConnectedNodes.Contains(connectedNode))
|
||||
{
|
||||
pathNode.ConnectedNodes.Add(connectedNodeId);
|
||||
pathNode.ConnectedNodes.Add(connectedNode);
|
||||
}
|
||||
|
||||
var connectedPathNode = _nodeMap[connectedNodeId];
|
||||
var connectedPathNode = _nodeMap[connectedNode];
|
||||
if (!connectedPathNode.ConnectedNodes.Contains(mapNode.NodeId))
|
||||
{
|
||||
connectedPathNode.ConnectedNodes.Add(mapNode.NodeId);
|
||||
@@ -594,13 +594,13 @@ namespace AGVNavigationCore.PathFinding.Core
|
||||
|
||||
foreach (var connectedNodeId in junctionNode.ConnectedNodes)
|
||||
{
|
||||
if (connectedNodeId == null) continue;
|
||||
|
||||
// 조건 1: 왔던 길이 아님
|
||||
if (connectedNodeId == previousNodeId)
|
||||
continue;
|
||||
if (connectedNodeId == previousNodeId) continue;
|
||||
|
||||
// 조건 2: 갈 길이 아님
|
||||
if (connectedNodeId == targetNodeId)
|
||||
continue;
|
||||
if (connectedNodeId == targetNodeId) continue;
|
||||
|
||||
// 조건 3, 4, 5: 존재하고, 활성 상태이고, 네비게이션 가능
|
||||
var connectedNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectedNodeId);
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
private readonly JunctionAnalyzer _junctionAnalyzer;
|
||||
private readonly DirectionChangePlanner _directionChangePlanner;
|
||||
|
||||
|
||||
|
||||
public AGVPathfinder(List<MapNode> mapNodes)
|
||||
{
|
||||
_mapNodes = mapNodes ?? new List<MapNode>();
|
||||
@@ -114,7 +116,7 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
return AGVPathResult.CreateFailure("목적지 노드가 null입니다.", 0, 0);
|
||||
if (prevNode == null)
|
||||
return AGVPathResult.CreateFailure("이전위치 노드가 null입니다.", 0, 0);
|
||||
if (startNode == targetNode)
|
||||
if (startNode.NodeId == targetNode.NodeId && targetNode.DockDirection.MatchAGVDirection(prevDirection))
|
||||
return AGVPathResult.CreateFailure("목적지와 현재위치가 동일합니다.", 0, 0);
|
||||
|
||||
var ReverseDirection = (currentDirection == AgvDirection.Forward ? AgvDirection.Backward : AgvDirection.Forward);
|
||||
@@ -258,77 +260,101 @@ namespace AGVNavigationCore.PathFinding.Planning
|
||||
MakeDetailData(path2, currentDirection);
|
||||
}
|
||||
|
||||
MapNode tempNode = null;
|
||||
|
||||
|
||||
//3.방향전환을 위환 대체 노드찾기
|
||||
var tempNode = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.NodeId,
|
||||
path1.Path[path1.Path.Count - 2].NodeId,
|
||||
path2.Path[1].NodeId);
|
||||
tempNode = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.NodeId,
|
||||
path1.Path[path1.Path.Count - 2].NodeId,
|
||||
path2.Path[1].NodeId);
|
||||
|
||||
//4. path1 + tempnode + path2 가 최종 위치가 된다.
|
||||
if (tempNode == null)
|
||||
return AGVPathResult.CreateFailure("방향 전환을 위한 대체 노드를 찾을 수 없습니다.", 0, 0);
|
||||
|
||||
|
||||
|
||||
// path1 (시작 → 교차로)
|
||||
var combinedResult = path1;
|
||||
|
||||
// 교차로 → 대체노드 경로 계산
|
||||
var pathToTemp = _basicPathfinder.FindPath(JunctionInPath.NodeId, tempNode.NodeId);
|
||||
pathToTemp.PrevNode = JunctionInPath;
|
||||
pathToTemp.PrevDirection = (ReverseCheck ? ReverseDirection : currentDirection);
|
||||
if (!pathToTemp.Success)
|
||||
return AGVPathResult.CreateFailure("교차로에서 대체 노드까지의 경로를 찾을 수 없습니다.", 0, 0);
|
||||
if (ReverseCheck) MakeDetailData(pathToTemp, ReverseDirection);
|
||||
else MakeDetailData(pathToTemp, currentDirection);
|
||||
|
||||
//교차로찍고 원래방향으로 돌어가야한다.
|
||||
if (pathToTemp.DetailedPath.Count > 1)
|
||||
pathToTemp.DetailedPath[pathToTemp.DetailedPath.Count - 1].MotorDirection = currentDirection;
|
||||
|
||||
// path1 + pathToTemp 합치기
|
||||
combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp);
|
||||
|
||||
// 대체노드 → 교차로 경로 계산 (역방향)
|
||||
var pathFromTemp = _basicPathfinder.FindPath(tempNode.NodeId, JunctionInPath.NodeId);
|
||||
pathFromTemp.PrevNode = JunctionInPath;
|
||||
pathFromTemp.PrevDirection = (ReverseCheck ? ReverseDirection : currentDirection);
|
||||
if (!pathFromTemp.Success)
|
||||
return AGVPathResult.CreateFailure("대체 노드에서 교차로까지의 경로를 찾을 수 없습니다.", 0, 0);
|
||||
|
||||
if (ReverseCheck) MakeDetailData(pathFromTemp, currentDirection);
|
||||
else MakeDetailData(pathFromTemp, ReverseDirection);
|
||||
|
||||
// (path1 + pathToTemp) + pathFromTemp 합치기
|
||||
combinedResult = _basicPathfinder.CombineResults(combinedResult, pathFromTemp);
|
||||
|
||||
//대체노드에서 최종 목적지를 다시 확인한다.
|
||||
if ((currentDirection == AgvDirection.Forward && targetNode.DockDirection != DockingDirection.Forward) ||
|
||||
(currentDirection == AgvDirection.Backward && targetNode.DockDirection != DockingDirection.Backward))
|
||||
//교차로 대체노드를 사용한 경우
|
||||
//if (tempNode != null)
|
||||
{
|
||||
//목적지와 방향이 맞지 않다. 그러므로 대체노드를 추가로 더 찾아야한다.
|
||||
var tempNode2 = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.NodeId,
|
||||
combinedResult.Path[combinedResult.Path.Count - 2].NodeId,
|
||||
path2.Path[1].NodeId);
|
||||
|
||||
var pathToTemp2 = _basicPathfinder.FindPath(JunctionInPath.NodeId, tempNode2.NodeId);
|
||||
if (ReverseCheck) MakeDetailData(pathToTemp2, currentDirection);
|
||||
else MakeDetailData(pathToTemp2, ReverseDirection);
|
||||
|
||||
combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp2);
|
||||
// 교차로 → 대체노드 경로 계산
|
||||
var pathToTemp = _basicPathfinder.FindPath(JunctionInPath.NodeId, tempNode.NodeId);
|
||||
pathToTemp.PrevNode = JunctionInPath;
|
||||
pathToTemp.PrevDirection = (ReverseCheck ? ReverseDirection : currentDirection);
|
||||
if (!pathToTemp.Success)
|
||||
return AGVPathResult.CreateFailure("교차로에서 대체 노드까지의 경로를 찾을 수 없습니다.", 0, 0);
|
||||
if (ReverseCheck) MakeDetailData(pathToTemp, ReverseDirection);
|
||||
else MakeDetailData(pathToTemp, currentDirection);
|
||||
|
||||
//교차로찍고 원래방향으로 돌어가야한다.
|
||||
if (combinedResult.DetailedPath.Count > 1)
|
||||
if (pathToTemp.DetailedPath.Count > 1)
|
||||
pathToTemp.DetailedPath[pathToTemp.DetailedPath.Count - 1].MotorDirection = currentDirection;
|
||||
|
||||
// path1 + pathToTemp 합치기
|
||||
combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp);
|
||||
|
||||
// 대체노드 → 교차로 경로 계산 (역방향)
|
||||
var pathFromTemp = _basicPathfinder.FindPath(tempNode.NodeId, JunctionInPath.NodeId);
|
||||
pathFromTemp.PrevNode = JunctionInPath;
|
||||
pathFromTemp.PrevDirection = (ReverseCheck ? ReverseDirection : currentDirection);
|
||||
if (!pathFromTemp.Success)
|
||||
return AGVPathResult.CreateFailure("대체 노드에서 교차로까지의 경로를 찾을 수 없습니다.", 0, 0);
|
||||
|
||||
if (ReverseCheck) MakeDetailData(pathFromTemp, currentDirection);
|
||||
else MakeDetailData(pathFromTemp, ReverseDirection);
|
||||
|
||||
// (path1 + pathToTemp) + pathFromTemp 합치기
|
||||
combinedResult = _basicPathfinder.CombineResults(combinedResult, pathFromTemp);
|
||||
|
||||
//현재까지 노드에서 목적지까지의 방향이 일치하면 그대로 사용한다.
|
||||
bool temp3ok = false;
|
||||
var TempCheck3 = _basicPathfinder.FindPath(combinedResult.Path.Last().NodeId, targetNode.NodeId);
|
||||
if (TempCheck3.Path.First().NodeId.Equals(combinedResult.Path.Last().NodeId))
|
||||
{
|
||||
if (ReverseCheck)
|
||||
combinedResult.DetailedPath[combinedResult.DetailedPath.Count - 1].MotorDirection = ReverseDirection;
|
||||
else
|
||||
combinedResult.DetailedPath[combinedResult.DetailedPath.Count - 1].MotorDirection = currentDirection;
|
||||
if (targetNode.DockDirection == DockingDirection.Forward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Forward)
|
||||
{
|
||||
temp3ok = true;
|
||||
}
|
||||
else if (targetNode.DockDirection == DockingDirection.Backward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Backward)
|
||||
{
|
||||
temp3ok = true;
|
||||
}
|
||||
}
|
||||
|
||||
var pathToTemp3 = _basicPathfinder.FindPath(tempNode2.NodeId, JunctionInPath.NodeId);
|
||||
if (ReverseCheck) MakeDetailData(pathToTemp3, ReverseDirection);
|
||||
else MakeDetailData(pathToTemp3, currentDirection);
|
||||
|
||||
combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp3);
|
||||
|
||||
//대체노드에서 최종 목적지를 다시 확인한다.
|
||||
if (temp3ok == false)
|
||||
{
|
||||
//목적지와 방향이 맞지 않다. 그러므로 대체노드를 추가로 더 찾아야한다.
|
||||
var tempNode2 = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.NodeId,
|
||||
combinedResult.Path[combinedResult.Path.Count - 2].NodeId,
|
||||
path2.Path[1].NodeId);
|
||||
|
||||
var pathToTemp2 = _basicPathfinder.FindPath(JunctionInPath.NodeId, tempNode2.NodeId);
|
||||
if (ReverseCheck) MakeDetailData(pathToTemp2, currentDirection);
|
||||
else MakeDetailData(pathToTemp2, ReverseDirection);
|
||||
|
||||
combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp2);
|
||||
|
||||
//교차로찍고 원래방향으로 돌어가야한다.
|
||||
if (combinedResult.DetailedPath.Count > 1)
|
||||
{
|
||||
if (ReverseCheck)
|
||||
combinedResult.DetailedPath[combinedResult.DetailedPath.Count - 1].MotorDirection = ReverseDirection;
|
||||
else
|
||||
combinedResult.DetailedPath[combinedResult.DetailedPath.Count - 1].MotorDirection = currentDirection;
|
||||
}
|
||||
|
||||
var pathToTemp3 = _basicPathfinder.FindPath(tempNode2.NodeId, JunctionInPath.NodeId);
|
||||
if (ReverseCheck) MakeDetailData(pathToTemp3, ReverseDirection);
|
||||
else MakeDetailData(pathToTemp3, currentDirection);
|
||||
|
||||
combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp3);
|
||||
}
|
||||
}
|
||||
|
||||
// (path1 + pathToTemp + pathFromTemp) + path2 합치기
|
||||
|
||||
Reference in New Issue
Block a user