"feat:Enable-hover-highlight-and-refactor"

This commit is contained in:
2025-12-14 17:20:50 +09:00
parent 34b038c4be
commit 764fbbd204
48 changed files with 3980 additions and 2750 deletions

View File

@@ -59,7 +59,7 @@ namespace AGVNavigationCore.PathFinding.Analysis
if (node.IsNavigationNode())
{
var junctionInfo = AnalyzeNode(node);
_junctions[node.NodeId] = junctionInfo;
_junctions[node.Id] = junctionInfo;
}
}
}
@@ -69,7 +69,7 @@ namespace AGVNavigationCore.PathFinding.Analysis
/// </summary>
private JunctionInfo AnalyzeNode(MapNode node)
{
var junction = new JunctionInfo(node.NodeId);
var junction = new JunctionInfo(node.Id);
// 양방향 연결을 고려하여 모든 연결된 노드 찾기
var connectedNodes = GetAllConnectedNodes(node);
@@ -96,16 +96,16 @@ namespace AGVNavigationCore.PathFinding.Analysis
{
if (connectedNode != null)
{
connected.Add(connectedNode.NodeId);
connected.Add(connectedNode.Id);
}
}
// 역방향 연결된 노드들 (다른 노드에서 이 노드로 연결)
foreach (var otherNode in _mapNodes)
{
if (otherNode.NodeId != node.NodeId && otherNode.ConnectedMapNodes.Any(n => n.NodeId == node.NodeId))
if (otherNode.Id != node.Id && otherNode.ConnectedMapNodes.Any(n => n.Id == node.Id))
{
connected.Add(otherNode.NodeId);
connected.Add(otherNode.Id);
}
}
@@ -124,7 +124,7 @@ namespace AGVNavigationCore.PathFinding.Analysis
foreach (var connectedId in connectedNodes)
{
var connectedNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectedId);
var connectedNode = _mapNodes.FirstOrDefault(n => n.Id == connectedId);
if (connectedNode != null)
{
double angle = CalculateAngle(junctionNode.Position, connectedNode.Position);
@@ -226,9 +226,9 @@ namespace AGVNavigationCore.PathFinding.Analysis
return MagnetDirection.Straight;
// 실제 각도 기반으로 마그넷 방향 계산
var fromNode = _mapNodes.FirstOrDefault(n => n.NodeId == fromNodeId);
var currentNode = _mapNodes.FirstOrDefault(n => n.NodeId == currentNodeId);
var toNode = _mapNodes.FirstOrDefault(n => n.NodeId == toNodeId);
var fromNode = _mapNodes.FirstOrDefault(n => n.Id == fromNodeId);
var currentNode = _mapNodes.FirstOrDefault(n => n.Id == currentNodeId);
var toNode = _mapNodes.FirstOrDefault(n => n.Id == toNodeId);
if (fromNode == null || currentNode == null || toNode == null)
return MagnetDirection.Straight;

View File

@@ -320,7 +320,7 @@ namespace AGVNavigationCore.PathFinding.Core
{
return DetailedPath.Select(n => n.NodeId).ToList();
}
return Path?.Select(n => n.NodeId).ToList() ?? new List<string>();
return Path?.Select(n => n.Id).ToList() ?? new List<string>();
}
/// <summary>

View File

@@ -50,36 +50,36 @@ namespace AGVNavigationCore.PathFinding.Core
// 모든 네비게이션 노드를 PathNode로 변환하고 양방향 연결 생성
foreach (var mapNode in _mapNodes)
{
_mapNodeLookup[mapNode.NodeId] = mapNode; // Add to lookup table
_mapNodeLookup[mapNode.Id] = mapNode; // Add to lookup table
if (mapNode.IsNavigationNode())
{
var pathNode = new PathNode(mapNode.NodeId, mapNode.Position);
_nodeMap[mapNode.NodeId] = pathNode;
var pathNode = new PathNode(mapNode.Id, mapNode.Position);
_nodeMap[mapNode.Id] = pathNode;
}
}
// 단일 연결을 양방향으로 확장
foreach (var mapNode in _mapNodes)
{
if (mapNode.IsNavigationNode() && _nodeMap.ContainsKey(mapNode.NodeId))
if (mapNode.IsNavigationNode() && _nodeMap.ContainsKey(mapNode.Id))
{
var pathNode = _nodeMap[mapNode.NodeId];
var pathNode = _nodeMap[mapNode.Id];
foreach (var connectedNode in mapNode.ConnectedMapNodes)
{
if (connectedNode != null && _nodeMap.ContainsKey(connectedNode.NodeId))
if (connectedNode != null && _nodeMap.ContainsKey(connectedNode.Id))
{
// 양방향 연결 생성 (단일 연결이 양방향을 의미)
if (!pathNode.ConnectedNodes.Contains(connectedNode.NodeId))
if (!pathNode.ConnectedNodes.Contains(connectedNode.Id))
{
pathNode.ConnectedNodes.Add(connectedNode.NodeId);
pathNode.ConnectedNodes.Add(connectedNode.Id);
}
var connectedPathNode = _nodeMap[connectedNode.NodeId];
if (!connectedPathNode.ConnectedNodes.Contains(mapNode.NodeId))
var connectedPathNode = _nodeMap[connectedNode.Id];
if (!connectedPathNode.ConnectedNodes.Contains(mapNode.Id))
{
connectedPathNode.ConnectedNodes.Add(mapNode.NodeId);
connectedPathNode.ConnectedNodes.Add(mapNode.Id);
}
}
}
@@ -371,8 +371,8 @@ namespace AGVNavigationCore.PathFinding.Core
var combinedDetailedPath = new List<NodeMotorInfo>(previousResult.DetailedPath ?? new List<NodeMotorInfo>());
// 이전 경로의 마지막 노드와 현재 경로의 시작 노드 비교
string lastNodeOfPrevious = previousResult.Path[previousResult.Path.Count - 1].NodeId;
string firstNodeOfCurrent = currentResult.Path[0].NodeId;
string lastNodeOfPrevious = previousResult.Path[previousResult.Path.Count - 1].Id;
string firstNodeOfCurrent = currentResult.Path[0].Id;
if (lastNodeOfPrevious == firstNodeOfCurrent)
{
@@ -508,8 +508,8 @@ namespace AGVNavigationCore.PathFinding.Core
float totalDistance = 0;
for (int i = 0; i < path.Count - 1; i++)
{
var nodeId1 = path[i].NodeId;
var nodeId2 = path[i + 1].NodeId;
var nodeId1 = path[i].Id;
var nodeId2 = path[i + 1].Id;
if (_nodeMap.ContainsKey(nodeId1) && _nodeMap.ContainsKey(nodeId2))
{
@@ -577,7 +577,7 @@ namespace AGVNavigationCore.PathFinding.Core
return null;
// 교차로 노드 찾기
var junctionNode = _mapNodes.FirstOrDefault(n => n.NodeId == junctionNodeId);
var junctionNode = _mapNodes.FirstOrDefault(n => n.Id == junctionNodeId);
if (junctionNode == null || junctionNode.ConnectedNodes == null || junctionNode.ConnectedNodes.Count == 0)
return null;
@@ -602,7 +602,7 @@ namespace AGVNavigationCore.PathFinding.Core
if (connectedNodeId == targetNodeId) continue;
// 조건 3, 4, 5: 존재하고, 활성 상태이고, 네비게이션 가능
var connectedNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectedNodeId);
var connectedNode = _mapNodes.FirstOrDefault(n => n.Id == connectedNodeId);
if (connectedNode != null && connectedNode.IsActive && connectedNode.IsNavigationNode())
{
alternateNodes.Add(connectedNode);

View File

@@ -50,7 +50,7 @@ namespace AGVNavigationCore.PathFinding.Planning
n.DisableCross == false &&
n.ConnectedNodes.Count >= 3 &&
n.ConnectedMapNodes.Where(t => t.CanDocking).Any() == false &&
n.NodeId != startNode.NodeId
n.Id != startNode.Id
).ToList();
// docking 포인트가 연결된 노드는 제거한다.
@@ -103,7 +103,7 @@ namespace AGVNavigationCore.PathFinding.Planning
pathNode.ConnectedNodes.Count >= 3 &&
pathNode.ConnectedMapNodes.Where(t => t.CanDocking).Any() == false)
{
if (pathNode.NodeId.Equals(StartNode.NodeId) == false)
if (pathNode.Id.Equals(StartNode.Id) == false)
return pathNode;
}
}
@@ -111,6 +111,12 @@ namespace AGVNavigationCore.PathFinding.Planning
return null;
}
public AGVPathResult FindPath(MapNode startNode, MapNode targetNode)
{
// 기본값으로 경로 탐색 (이전 위치 = 현재 위치, 방향 = 전진)
return FindPath(startNode, targetNode, startNode, AgvDirection.Forward, AgvDirection.Forward, false);
}
public AGVPathResult FindPath(MapNode startNode, MapNode targetNode,
MapNode prevNode, AgvDirection prevDirection, AgvDirection currentDirection, bool crossignore = false)
{
@@ -121,13 +127,17 @@ namespace AGVNavigationCore.PathFinding.Planning
return AGVPathResult.CreateFailure("목적지 노드가 null입니다.", 0, 0);
if (prevNode == null)
return AGVPathResult.CreateFailure("이전위치 노드가 null입니다.", 0, 0);
if (startNode.NodeId == targetNode.NodeId && targetNode.DockDirection.MatchAGVDirection(prevDirection))
if (targetNode.isDockingNode == false && targetNode.Type != NodeType.Normal)
return AGVPathResult.CreateFailure("이동 가능한 노드가 아닙니다", 0, 0);
var tnode = targetNode as MapNode;
if (startNode.Id == targetNode.Id && tnode.DockDirection.MatchAGVDirection(prevDirection))
return AGVPathResult.CreateSuccess(new List<MapNode> { startNode, startNode }, new List<AgvDirection>(), 0, 0);
var ReverseDirection = (currentDirection == AgvDirection.Forward ? AgvDirection.Backward : AgvDirection.Forward);
//1.목적지까지의 최단거리 경로를 찾는다.
var pathResult = _basicPathfinder.FindPathAStar(startNode.NodeId, targetNode.NodeId);
var pathResult = _basicPathfinder.FindPathAStar(startNode.Id, targetNode.Id);
pathResult.PrevNode = prevNode;
pathResult.PrevDirection = prevDirection;
if (!pathResult.Success || pathResult.Path == null || pathResult.Path.Count == 0)
@@ -146,11 +156,11 @@ namespace AGVNavigationCore.PathFinding.Planning
//2.AGV방향과 목적지에 설정된 방향이 일치하면 그대로 진행하면된다.(목적지에 방향이 없는 경우에도 그대로 진행)
if (targetNode.DockDirection == DockingDirection.DontCare ||
(targetNode.DockDirection == DockingDirection.Forward && currentDirection == AgvDirection.Forward) ||
(targetNode.DockDirection == DockingDirection.Backward && currentDirection == AgvDirection.Backward))
if (tnode.DockDirection == DockingDirection.DontCare ||
(tnode.DockDirection == DockingDirection.Forward && currentDirection == AgvDirection.Forward) ||
(tnode.DockDirection == DockingDirection.Backward && currentDirection == AgvDirection.Backward))
{
if ((nextNodeForward?.NodeId ?? string.Empty) == pathResult.Path[1].NodeId) //예측경로와 다음진행방향 경로가 일치하면 해당 방향이 맞다
if ((nextNodeForward?.Id ?? string.Empty) == pathResult.Path[1].Id) //예측경로와 다음진행방향 경로가 일치하면 해당 방향이 맞다
{
MakeDetailData(pathResult, currentDirection);
MakeMagnetDirection(pathResult);
@@ -177,10 +187,10 @@ namespace AGVNavigationCore.PathFinding.Planning
//뒤로 이동시 경로상의 처음 만나는 노드가 같다면 그 방향으로 이동하면 된다.
// ⚠️ 단, 현재 방향과 목적지 도킹 방향이 일치해야 함!
if (nextNodeBackward != null && pathResult.Path.Count > 1 &&
nextNodeBackward.NodeId == pathResult.Path[1].NodeId) // ✅ 추가: 현재도 Backward여야 함
nextNodeBackward.Id == pathResult.Path[1].Id) // ✅ 추가: 현재도 Backward여야 함
{
if (targetNode.DockDirection == DockingDirection.Forward && ReverseDirection == AgvDirection.Forward ||
targetNode.DockDirection == DockingDirection.Backward && ReverseDirection == AgvDirection.Backward)
if (tnode.DockDirection == DockingDirection.Forward && ReverseDirection == AgvDirection.Forward ||
tnode.DockDirection == DockingDirection.Backward && ReverseDirection == AgvDirection.Backward)
{
MakeDetailData(pathResult, ReverseDirection);
MakeMagnetDirection(pathResult);
@@ -191,12 +201,12 @@ namespace AGVNavigationCore.PathFinding.Planning
}
if (nextNodeForward != null && pathResult.Path.Count > 1 &&
nextNodeForward.NodeId == pathResult.Path[1].NodeId &&
targetNode.DockDirection == DockingDirection.Forward &&
nextNodeForward.Id == pathResult.Path[1].Id &&
tnode.DockDirection == DockingDirection.Forward &&
currentDirection == AgvDirection.Forward) // ✅ 추가: 현재도 Forward여야 함
{
if (targetNode.DockDirection == DockingDirection.Forward && currentDirection == AgvDirection.Forward ||
targetNode.DockDirection == DockingDirection.Backward && currentDirection == AgvDirection.Backward)
if (tnode.DockDirection == DockingDirection.Forward && currentDirection == AgvDirection.Forward ||
tnode.DockDirection == DockingDirection.Backward && currentDirection == AgvDirection.Backward)
{
MakeDetailData(pathResult, currentDirection);
MakeMagnetDirection(pathResult);
@@ -221,7 +231,7 @@ namespace AGVNavigationCore.PathFinding.Planning
//진행방향으로 이동했을때 나오는 노드를 사용한다.
if (nextNodeForward != null)
{
var Path0 = _basicPathfinder.FindPathAStar(startNode.NodeId, nextNodeForward.NodeId);
var Path0 = _basicPathfinder.FindPathAStar(startNode.Id, nextNodeForward.Id);
Path0.PrevNode = prevNode;
Path0.PrevDirection = prevDirection;
MakeDetailData(Path0, prevDirection);
@@ -259,7 +269,7 @@ namespace AGVNavigationCore.PathFinding.Planning
JunctionInPath = FindNearestJunction(startNode);
//종료노드로부터 가까운 교차로 검색
if (JunctionInPath == null) JunctionInPath = FindNearestJunction(targetNode);
if (JunctionInPath == null) JunctionInPath = FindNearestJunction(tnode);
}
if (JunctionInPath == null)
return AGVPathResult.CreateFailure("교차로가 없어 경로계산을 할 수 없습니다", 0, 0);
@@ -267,7 +277,7 @@ namespace AGVNavigationCore.PathFinding.Planning
//경유지를 포함하여 경로를 다시 계산한다.
//1.시작위치 - 교차로(여기까지는 현재 방향으로 그대로 이동을 한다)
var path1 = _basicPathfinder.FindPathAStar(startNode.NodeId, JunctionInPath.NodeId);
var path1 = _basicPathfinder.FindPathAStar(startNode.Id, JunctionInPath.Id);
path1.PrevNode = prevNode;
path1.PrevDirection = prevDirection;
@@ -278,7 +288,7 @@ namespace AGVNavigationCore.PathFinding.Planning
// ReverseCheck = false; //현재 진행 방향으로 이동해야한다
// MakeDetailData(path1, currentDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정)
//}
if (path1.Path.Count > 1 && nextNodeBackward != null && nextNodeBackward.NodeId.Equals(path1.Path[1].NodeId))
if (path1.Path.Count > 1 && nextNodeBackward != null && nextNodeBackward.Id.Equals(path1.Path[1].Id))
{
ReverseCheck = true; //현재 방향의 반대방향으로 이동해야한다
MakeDetailData(path1, ReverseDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정)
@@ -295,7 +305,7 @@ namespace AGVNavigationCore.PathFinding.Planning
//2.교차로 - 종료위치
var path2 = _basicPathfinder.FindPathAStar(JunctionInPath.NodeId, targetNode.NodeId);
var path2 = _basicPathfinder.FindPathAStar(JunctionInPath.Id, targetNode.Id);
path2.PrevNode = prevNode;
path2.PrevDirection = prevDirection;
@@ -315,9 +325,9 @@ namespace AGVNavigationCore.PathFinding.Planning
//3.방향전환을 위환 대체 노드찾기
tempNode = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.NodeId,
path1.Path[path1.Path.Count - 2].NodeId,
path2.Path[1].NodeId);
tempNode = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.Id,
path1.Path[path1.Path.Count - 2].Id,
path2.Path[1].Id);
//4. path1 + tempnode + path2 가 최종 위치가 된다.
if (tempNode == null)
@@ -332,7 +342,7 @@ namespace AGVNavigationCore.PathFinding.Planning
//if (tempNode != null)
{
// 교차로 → 대체노드 경로 계산
var pathToTemp = _basicPathfinder.FindPathAStar(JunctionInPath.NodeId, tempNode.NodeId);
var pathToTemp = _basicPathfinder.FindPathAStar(JunctionInPath.Id, tempNode.Id);
pathToTemp.PrevNode = JunctionInPath;
pathToTemp.PrevDirection = (ReverseCheck ? ReverseDirection : currentDirection);
if (!pathToTemp.Success)
@@ -348,7 +358,7 @@ namespace AGVNavigationCore.PathFinding.Planning
combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp);
// 대체노드 → 교차로 경로 계산 (역방향)
var pathFromTemp = _basicPathfinder.FindPathAStar(tempNode.NodeId, JunctionInPath.NodeId);
var pathFromTemp = _basicPathfinder.FindPathAStar(tempNode.Id, JunctionInPath.Id);
pathFromTemp.PrevNode = JunctionInPath;
pathFromTemp.PrevDirection = (ReverseCheck ? ReverseDirection : currentDirection);
if (!pathFromTemp.Success)
@@ -362,14 +372,14 @@ namespace AGVNavigationCore.PathFinding.Planning
//현재까지 노드에서 목적지까지의 방향이 일치하면 그대로 사용한다.
bool temp3ok = false;
var TempCheck3 = _basicPathfinder.FindPathAStar(combinedResult.Path.Last().NodeId, targetNode.NodeId);
if (TempCheck3.Path.First().NodeId.Equals(combinedResult.Path.Last().NodeId))
var TempCheck3 = _basicPathfinder.FindPathAStar(combinedResult.Path.Last().Id, targetNode.Id);
if (TempCheck3.Path.First().Id.Equals(combinedResult.Path.Last().Id))
{
if (targetNode.DockDirection == DockingDirection.Forward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Forward)
if (tnode.DockDirection == DockingDirection.Forward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Forward)
{
temp3ok = true;
}
else if (targetNode.DockDirection == DockingDirection.Backward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Backward)
else if (tnode.DockDirection == DockingDirection.Backward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Backward)
{
temp3ok = true;
}
@@ -381,11 +391,11 @@ namespace AGVNavigationCore.PathFinding.Planning
if (temp3ok == false)
{
//목적지와 방향이 맞지 않다. 그러므로 대체노드를 추가로 더 찾아야한다.
var tempNode2 = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.NodeId,
combinedResult.Path[combinedResult.Path.Count - 2].NodeId,
path2.Path[1].NodeId);
var tempNode2 = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.Id,
combinedResult.Path[combinedResult.Path.Count - 2].Id,
path2.Path[1].Id);
var pathToTemp2 = _basicPathfinder.FindPathAStar(JunctionInPath.NodeId, tempNode2.NodeId);
var pathToTemp2 = _basicPathfinder.FindPathAStar(JunctionInPath.Id, tempNode2.Id);
if (ReverseCheck) MakeDetailData(pathToTemp2, currentDirection);
else MakeDetailData(pathToTemp2, ReverseDirection);
@@ -400,7 +410,7 @@ namespace AGVNavigationCore.PathFinding.Planning
combinedResult.DetailedPath[combinedResult.DetailedPath.Count - 1].MotorDirection = currentDirection;
}
var pathToTemp3 = _basicPathfinder.FindPathAStar(tempNode2.NodeId, JunctionInPath.NodeId);
var pathToTemp3 = _basicPathfinder.FindPathAStar(tempNode2.Id, JunctionInPath.Id);
if (ReverseCheck) MakeDetailData(pathToTemp3, ReverseDirection);
else MakeDetailData(pathToTemp3, currentDirection);
@@ -420,12 +430,15 @@ namespace AGVNavigationCore.PathFinding.Planning
}
/// <summary>
/// 이 작업후에 MakeMagnetDirection 를 추가로 실행 하세요
/// </summary>
/// <summary>
/// 이 작업후에 MakeMagnetDirection 를 추가로 실행 하세요
/// </summary>
/// <param name="path1"></param>
/// <param name="currentDirection"></param>
private void MakeDetailData(AGVPathResult path1, AgvDirection currentDirection)
public void MakeDetailData(AGVPathResult path1, AgvDirection currentDirection)
{
if (path1.Success && path1.Path != null && path1.Path.Count > 0)
{
@@ -433,9 +446,9 @@ namespace AGVNavigationCore.PathFinding.Planning
for (int i = 0; i < path1.Path.Count; i++)
{
var node = path1.Path[i];
string nodeId = node.NodeId;
string nodeId = node.Id;
string RfidId = node.RfidId;
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].NodeId : null;
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].Id : null;
// 노드 정보 생성 (현재 방향 유지)
var nodeInfo = new NodeMotorInfo(i + 1,
@@ -446,7 +459,7 @@ namespace AGVNavigationCore.PathFinding.Planning
);
// [Speed Control] MapNode의 속도 설정 적용
var mapNode = _mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
var mapNode = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
if (mapNode != null)
{
nodeInfo.Speed = mapNode.SpeedLimit;
@@ -470,13 +483,13 @@ namespace AGVNavigationCore.PathFinding.Planning
for (int i = 0; i < path1.DetailedPath.Count; i++)
{
var detailPath = path1.DetailedPath[i];
string nodeId = path1.Path[i].NodeId;
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].NodeId : null;
string nodeId = path1.Path[i].Id;
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].Id : null;
// 마그넷 방향 계산 (3개 이상 연결된 교차로에서만 좌/우 가중치 적용)
if (i > 0 && nextNodeId != null)
{
string prevNodeId = path1.Path[i - 1].NodeId;
string prevNodeId = path1.Path[i - 1].Id;
if (path1.DetailedPath[i - 1].MotorDirection != detailPath.MotorDirection)
detailPath.MagnetDirection = MagnetDirection.Straight;
else

View File

@@ -91,14 +91,14 @@ namespace AGVNavigationCore.PathFinding.Planning
// 직접 경로에 갈림길이 포함된 경우 그 갈림길에서 방향 전환
foreach (var node in directPath2.Path.Skip(1).Take(directPath2.Path.Count - 2)) // 시작과 끝 제외
{
var junctionInfo = _junctionAnalyzer.GetJunctionInfo(node.NodeId);
var junctionInfo = _junctionAnalyzer.GetJunctionInfo(node.Id);
if (junctionInfo != null && junctionInfo.IsJunction)
{
// 간단한 방향 전환: 직접 경로 사용하되 방향 전환 노드 표시
return DirectionChangePlan.CreateSuccess(
directPath2.Path,
node.NodeId,
$"갈림길 {node.NodeId}에서 방향 전환: {currentDirection} → {requiredDirection}"
node.Id,
$"갈림길 {node.Id}에서 방향 전환: {currentDirection} → {requiredDirection}"
);
}
}
@@ -165,14 +165,14 @@ namespace AGVNavigationCore.PathFinding.Planning
{
foreach (var node in directPath.Path.Skip(2)) // 시작점과 다음 노드는 제외
{
var junctionInfo = _junctionAnalyzer.GetJunctionInfo(node.NodeId);
var junctionInfo = _junctionAnalyzer.GetJunctionInfo(node.Id);
if (junctionInfo != null && junctionInfo.IsJunction)
{
// 직진 경로상에서는 더 엄격한 조건 적용
if (!suitableJunctions.Contains(node.NodeId) &&
HasMultipleExitOptions(node.NodeId))
if (!suitableJunctions.Contains(node.Id) &&
HasMultipleExitOptions(node.Id))
{
suitableJunctions.Add(node.NodeId);
suitableJunctions.Add(node.Id);
}
}
}
@@ -226,7 +226,7 @@ namespace AGVNavigationCore.PathFinding.Planning
/// </summary>
private List<string> GetAllConnectedNodes(string nodeId)
{
var node = _mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
var node = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
if (node == null) return new List<string>();
var connected = new HashSet<string>();
@@ -236,16 +236,16 @@ namespace AGVNavigationCore.PathFinding.Planning
{
if (connectedNode != null)
{
connected.Add(connectedNode.NodeId);
connected.Add(connectedNode.Id);
}
}
// 역방향 연결
foreach (var otherNode in _mapNodes)
{
if (otherNode.NodeId != nodeId && otherNode.ConnectedMapNodes.Any(n => n.NodeId == nodeId))
if (otherNode.Id != nodeId && otherNode.ConnectedMapNodes.Any(n => n.Id == nodeId))
{
connected.Add(otherNode.NodeId);
connected.Add(otherNode.Id);
}
}
@@ -293,7 +293,7 @@ namespace AGVNavigationCore.PathFinding.Planning
string actualDirectionChangeNode = FindActualDirectionChangeNode(changePath, junctionNodeId);
string description = $"갈림길 {GetDisplayName(junctionNodeId)}를 통해 {GetDisplayName(actualDirectionChangeNode)}에서 방향 전환: {currentDirection} → {requiredDirection}";
System.Diagnostics.Debug.WriteLine($"[DirectionChangePlanner] ✅ 유효한 방향전환 경로: {string.Join(" ", changePath.Select(n => n.NodeId))}");
System.Diagnostics.Debug.WriteLine($"[DirectionChangePlanner] ✅ 유효한 방향전환 경로: {string.Join(" ", changePath.Select(n => n.Id))}");
return DirectionChangePlan.CreateSuccess(changePath, actualDirectionChangeNode, description);
}
@@ -319,7 +319,7 @@ namespace AGVNavigationCore.PathFinding.Planning
// 2. 인근 갈림길을 통한 우회인지, 직진 경로상 갈림길인지 판단
var directPath = _pathfinder.FindPathAStar(startNodeId, targetNodeId);
bool isNearbyDetour = !directPath.Success || !directPath.Path.Any(n => n.NodeId == junctionNodeId);
bool isNearbyDetour = !directPath.Success || !directPath.Path.Any(n => n.Id == junctionNodeId);
if (isNearbyDetour)
{
@@ -376,17 +376,17 @@ namespace AGVNavigationCore.PathFinding.Planning
if (currentDirection != requiredDirection)
{
string fromNodeId = toJunctionPath.Path.Count >= 2 ?
toJunctionPath.Path[toJunctionPath.Path.Count - 2].NodeId : startNodeId;
toJunctionPath.Path[toJunctionPath.Path.Count - 2].Id : startNodeId;
var changeSequence = GenerateDirectionChangeSequence(junctionNodeId, fromNodeId, currentDirection, requiredDirection);
if (changeSequence.Count > 1)
{
fullPath.AddRange(changeSequence.Skip(1).Select(nodeId => _mapNodes.FirstOrDefault(n => n.NodeId == nodeId)).Where(n => n != null));
fullPath.AddRange(changeSequence.Skip(1).Select(nodeId => _mapNodes.FirstOrDefault(n => n.Id == nodeId)).Where(n => n != null));
}
}
// 3. 갈림길에서 목표점까지의 경로
string lastNode = fullPath.LastOrDefault()?.NodeId ?? junctionNodeId;
string lastNode = fullPath.LastOrDefault()?.Id ?? junctionNodeId;
var fromJunctionPath = _pathfinder.FindPathAStar(lastNode, targetNodeId);
if (fromJunctionPath.Success && fromJunctionPath.Path.Count > 1)
{
@@ -461,8 +461,8 @@ namespace AGVNavigationCore.PathFinding.Planning
// 왔던 길(excludeNodeId)를 제외한 노드 중에서 최적의 우회 노드 선택
// 우선순위: 1) 막다른 길이 아닌 노드 (우회 후 복귀 가능) 2) 직진방향 3) 목적지 방향
var junctionNode = _mapNodes.FirstOrDefault(n => n.NodeId == junctionNodeId);
var fromNode = _mapNodes.FirstOrDefault(n => n.NodeId == excludeNodeId);
var junctionNode = _mapNodes.FirstOrDefault(n => n.Id == junctionNodeId);
var fromNode = _mapNodes.FirstOrDefault(n => n.Id == excludeNodeId);
if (junctionNode == null || fromNode == null)
return availableNodes.FirstOrDefault();
@@ -478,7 +478,7 @@ namespace AGVNavigationCore.PathFinding.Planning
{
if (nodeId == excludeNodeId) continue; // 왔던 길 제외
var candidateNode = _mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
var candidateNode = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
if (candidateNode == null) continue;
// 갈림길에서 후보 노드로의 방향 벡터 계산 (junctionNode → candidateNode)
@@ -561,11 +561,11 @@ namespace AGVNavigationCore.PathFinding.Planning
return junctionNodeId; // 기본값으로 갈림길 반환
// 갈림길이 두 번 나타나는 위치 찾기
int firstJunctionIndex = changePath.FindIndex(n => n.NodeId == junctionNodeId);
int firstJunctionIndex = changePath.FindIndex(n => n.Id == junctionNodeId);
int lastJunctionIndex = -1;
for (int i = changePath.Count - 1; i >= 0; i--)
{
if (changePath[i].NodeId == junctionNodeId)
if (changePath[i].Id == junctionNodeId)
{
lastJunctionIndex = i;
break;
@@ -577,7 +577,7 @@ namespace AGVNavigationCore.PathFinding.Planning
firstJunctionIndex != lastJunctionIndex && lastJunctionIndex - firstJunctionIndex == 2)
{
// 첫 번째와 두 번째 갈림길 사이에 있는 노드가 실제 방향전환 노드
string detourNode = changePath[firstJunctionIndex + 1].NodeId;
string detourNode = changePath[firstJunctionIndex + 1].Id;
return detourNode;
}
@@ -647,11 +647,11 @@ namespace AGVNavigationCore.PathFinding.Planning
}
string errorMessage = $"되돌아가기 패턴 검출 ({backtrackingPatterns.Count}개): {string.Join(", ", issues)}";
System.Diagnostics.Debug.WriteLine($"[PathValidation] ❌ 경로: {string.Join(" ", path.Select(n => n.NodeId))}");
System.Diagnostics.Debug.WriteLine($"[PathValidation] ❌ 경로: {string.Join(" ", path.Select(n => n.Id))}");
System.Diagnostics.Debug.WriteLine($"[PathValidation] ❌ 되돌아가기 패턴: {errorMessage}");
return PathValidationResult.CreateInvalidWithBacktracking(
path.Select(n => n.NodeId).ToList(), backtrackingPatterns, startNodeId, "", junctionNodeId, errorMessage);
path.Select(n => n.Id).ToList(), backtrackingPatterns, startNodeId, "", junctionNodeId, errorMessage);
}
// 2. 연속된 중복 노드 검증
@@ -670,13 +670,13 @@ namespace AGVNavigationCore.PathFinding.Planning
}
// 4. 갈림길 포함 여부 검증
if (!path.Any(n => n.NodeId == junctionNodeId))
if (!path.Any(n => n.Id == junctionNodeId))
{
return PathValidationResult.CreateInvalid(startNodeId, "", $"갈림길 {junctionNodeId}이 경로에 포함되지 않음");
}
System.Diagnostics.Debug.WriteLine($"[PathValidation] ✅ 유효한 경로: {string.Join(" ", path.Select(n => n.NodeId))}");
return PathValidationResult.CreateValid(path.Select(n => n.NodeId).ToList(), startNodeId, "", junctionNodeId);
System.Diagnostics.Debug.WriteLine($"[PathValidation] ✅ 유효한 경로: {string.Join(" ", path.Select(n => n.Id))}");
return PathValidationResult.CreateValid(path.Select(n => n.Id).ToList(), startNodeId, "", junctionNodeId);
}
/// <summary>
@@ -688,9 +688,9 @@ namespace AGVNavigationCore.PathFinding.Planning
for (int i = 0; i < path.Count - 2; i++)
{
string nodeA = path[i].NodeId;
string nodeB = path[i + 1].NodeId;
string nodeC = path[i + 2].NodeId;
string nodeA = path[i].Id;
string nodeB = path[i + 1].Id;
string nodeC = path[i + 2].Id;
// A → B → A 패턴 검출
if (nodeA == nodeC && nodeA != nodeB)
@@ -712,9 +712,9 @@ namespace AGVNavigationCore.PathFinding.Planning
for (int i = 0; i < path.Count - 1; i++)
{
if (path[i].NodeId == path[i + 1].NodeId)
if (path[i].Id == path[i + 1].Id)
{
duplicates.Add(path[i].NodeId);
duplicates.Add(path[i].Id);
}
}
@@ -728,12 +728,12 @@ namespace AGVNavigationCore.PathFinding.Planning
{
for (int i = 0; i < path.Count - 1; i++)
{
string currentNode = path[i].NodeId;
string nextNode = path[i + 1].NodeId;
string currentNode = path[i].Id;
string nextNode = path[i + 1].Id;
// 두 노드간 직접 연결성 확인 (맵 노드의 ConnectedMapNodes 리스트 사용)
var currentMapNode = _mapNodes.FirstOrDefault(n => n.NodeId == currentNode);
if (currentMapNode == null || !currentMapNode.ConnectedMapNodes.Any(n => n.NodeId == nextNode))
var currentMapNode = _mapNodes.FirstOrDefault(n => n.Id == currentNode);
if (currentMapNode == null || !currentMapNode.ConnectedMapNodes.Any(n => n.Id == nextNode))
{
return PathValidationResult.CreateInvalid(currentNode, nextNode, $"노드 {currentNode}와 {nextNode} 사이에 연결이 없음");
}
@@ -769,7 +769,7 @@ namespace AGVNavigationCore.PathFinding.Planning
/// <returns>표시할 이름</returns>
private string GetDisplayName(string nodeId)
{
var node = _mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
var node = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
if (node != null && !string.IsNullOrEmpty(node.RfidId))
{
return node.RfidId;

View File

@@ -66,7 +66,7 @@ namespace AGVNavigationCore.PathFinding.Planning
// 연결된 노드 중 현재 노드가 아닌 것들만 필터링
var candidateNodes = allNodes.Where(n =>
connectedNodeIds.Contains(n.NodeId) && n.NodeId != currentNode.NodeId
connectedNodeIds.Contains(n.Id) && n.Id != currentNode.Id
).ToList();
if (candidateNodes.Count == 0)
@@ -88,7 +88,7 @@ namespace AGVNavigationCore.PathFinding.Planning
if (movementLength < 0.001f) // 거의 이동하지 않음
{
return candidateNodes[0].NodeId; // 첫 번째 연결 노드 반환
return candidateNodes[0].Id; // 첫 번째 연결 노드 반환
}
var normalizedMovement = new PointF(
@@ -138,7 +138,7 @@ namespace AGVNavigationCore.PathFinding.Planning
// 가장 높은 점수를 가진 노드 반환
var bestCandidate = scoredCandidates.OrderByDescending(x => x.score).First();
return bestCandidate.node.NodeId;
return bestCandidate.node.Id;
}
/// <summary>