fix: 맵 에디터 연결 버그 수정 및 기능 개선

주요 변경사항:
- ConnectedMapNodes 속성 추가로 런타임 객체 참조 지원
- 이미지 에디터 UI 개선 (ImageEditorCanvas 추가)
- 연결 생성 버그 수정: 양방향 연결 생성
- 연결 삭제 버그 수정: 양방향 모두 제거
- CleanupDuplicateConnections 비활성화 (단방향 변환 버그)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
backuppc
2025-10-30 09:34:10 +09:00
parent 0b59479d34
commit 3f6db7113f
18 changed files with 838 additions and 1303 deletions

View File

@@ -92,15 +92,18 @@ namespace AGVNavigationCore.PathFinding.Analysis
var connected = new HashSet<string>();
// 직접 연결된 노드들
foreach (var connectedId in node.ConnectedNodes)
foreach (var connectedNode in node.ConnectedMapNodes)
{
connected.Add(connectedId);
if (connectedNode != null)
{
connected.Add(connectedNode.NodeId);
}
}
// 역방향 연결된 노드들 (다른 노드에서 이 노드로 연결)
foreach (var otherNode in _mapNodes)
{
if (otherNode.NodeId != node.NodeId && otherNode.ConnectedNodes.Contains(node.NodeId))
if (otherNode.NodeId != node.NodeId && otherNode.ConnectedMapNodes.Any(n => n.NodeId == node.NodeId))
{
connected.Add(otherNode.NodeId);
}

View File

@@ -66,17 +66,17 @@ namespace AGVNavigationCore.PathFinding.Core
{
var pathNode = _nodeMap[mapNode.NodeId];
foreach (var connectedNode in mapNode.ConnectedNodes)
foreach (var connectedNode in mapNode.ConnectedMapNodes)
{
if (_nodeMap.ContainsKey(connectedNode))
if (connectedNode != null && _nodeMap.ContainsKey(connectedNode.NodeId))
{
// 양방향 연결 생성 (단일 연결이 양방향을 의미)
if (!pathNode.ConnectedNodes.Contains(connectedNode))
if (!pathNode.ConnectedNodes.Contains(connectedNode.NodeId))
{
pathNode.ConnectedNodes.Add(connectedNode);
pathNode.ConnectedNodes.Add(connectedNode.NodeId);
}
var connectedPathNode = _nodeMap[connectedNode];
var connectedPathNode = _nodeMap[connectedNode.NodeId];
if (!connectedPathNode.ConnectedNodes.Contains(mapNode.NodeId))
{
connectedPathNode.ConnectedNodes.Add(mapNode.NodeId);

View File

@@ -22,7 +22,7 @@ namespace AGVNavigationCore.PathFinding.Planning
private readonly JunctionAnalyzer _junctionAnalyzer;
private readonly DirectionChangePlanner _directionChangePlanner;
public AGVPathfinder(List<MapNode> mapNodes)
{
@@ -50,9 +50,13 @@ namespace AGVNavigationCore.PathFinding.Planning
n.IsNavigationNode() &&
n.ConnectedNodes != null &&
n.ConnectedNodes.Count >= 3 &&
n.ConnectedMapNodes.Where(t => t.Type == NodeType.Docking || t.Type == NodeType.Charging).Any() == false &&
n.NodeId != startNode.NodeId
).ToList();
// docking 포인트가 연결된 노드는 제거한다.
if (junctions.Count == 0)
return null;
@@ -96,7 +100,8 @@ namespace AGVNavigationCore.PathFinding.Planning
pathNode.IsActive &&
pathNode.IsNavigationNode() &&
pathNode.ConnectedNodes != null &&
pathNode.ConnectedNodes.Count >= 3)
pathNode.ConnectedNodes.Count >= 3 &&
pathNode.ConnectedMapNodes.Where(t => t.Type == NodeType.Docking || t.Type == NodeType.Charging).Any() == false)
{
if (pathNode.NodeId.Equals(StartNode.NodeId) == false)
return pathNode;
@@ -107,7 +112,7 @@ namespace AGVNavigationCore.PathFinding.Planning
}
public AGVPathResult FindPath_test(MapNode startNode, MapNode targetNode,
MapNode prevNode, AgvDirection prevDirection, AgvDirection currentDirection)
MapNode prevNode, AgvDirection prevDirection, AgvDirection currentDirection, bool crossignore = false)
{
// 입력 검증
if (startNode == null)
@@ -117,7 +122,7 @@ namespace AGVNavigationCore.PathFinding.Planning
if (prevNode == null)
return AGVPathResult.CreateFailure("이전위치 노드가 null입니다.", 0, 0);
if (startNode.NodeId == targetNode.NodeId && targetNode.DockDirection.MatchAGVDirection(prevDirection))
return AGVPathResult.CreateFailure("목적지와 현재위치가 동일합니다.", 0, 0);
return AGVPathResult.CreateSuccess(new List<MapNode> { startNode,startNode }, new List<AgvDirection>(), 0, 0);
var ReverseDirection = (currentDirection == AgvDirection.Forward ? AgvDirection.Backward : AgvDirection.Forward);
@@ -203,6 +208,39 @@ namespace AGVNavigationCore.PathFinding.Planning
// return pathResult;
//}
//현재 내 포인트가 교차로라면.. 무조건 왓던 방향 혹은 그 반대방향으로 이동해서 경로를 계산해야한다.
//교차로에 멈춰있을때에는 바로 방향전환을 할 수없으니. 정/역(straight)로 이동해서 다시 계산을 해야한다
if (crossignore == false && startNode.ConnectedNodes.Count > 2)
{
//진행방향으로 이동했을때 나오는 노드를 사용한다.
if (nextNodeForward != null)
{
var Path0 = _basicPathfinder.FindPath(startNode.NodeId, nextNodeForward.NodeId);
Path0.PrevNode = prevNode;
Path0.PrevDirection = prevDirection;
MakeDetailData(Path0, prevDirection);
var Path1 = FindPath_test(nextNodeForward, targetNode, startNode, prevDirection, currentDirection, true);
Path1.PrevNode = startNode;
Path1.PrevDirection = prevDirection;
//MakeDetailData(Path1, ReverseDirection);
var combinedResult0 = Path0;
combinedResult0 = _basicPathfinder.CombineResults(combinedResult0, Path1);
MakeMagnetDirection(combinedResult0);
return combinedResult0;
}
else if (nextNodeBackward != null)
{
return AGVPathResult.CreateFailure("backward 처리코드가 없습니다 오류", 0, 0);
}
else
{
return AGVPathResult.CreateFailure("교차로에서 시작하는 조건중 forward/backrad 노드 검색 실패", 0, 0);
}
}
//3. 도킹방향이 일치하지 않으니 교차로에서 방향을 회전시켜야 한다
//최단거리(=minpath)경로에 속하는 교차로가 있다면 그것을 사용하고 없다면 가장 가까운 교차로를 찾는다.
var JunctionInPath = FindNearestJunctionOnPath(pathResult);
@@ -224,19 +262,23 @@ namespace AGVNavigationCore.PathFinding.Planning
path1.PrevNode = prevNode;
path1.PrevDirection = prevDirection;
//다음좌표를 보고 교차로가 진행방향인지 반대방향인지 체크한다.(!모터의 정/역방향을 말하는것이 아님)
//다음좌표를 보고 교차로가 진행방향인지 반대방향인지 체크한다.
bool ReverseCheck = false;
if (path1.Path.Count > 1 && nextNodeForward != null && nextNodeForward.NodeId.Equals(path1.Path[1].NodeId))
{
ReverseCheck = false; //현재 진행 방향으로 이동해야한다
MakeDetailData(path1, currentDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정)
}
else if (path1.Path.Count > 1 && nextNodeBackward != null && nextNodeBackward.NodeId.Equals(path1.Path[1].NodeId))
//if (path1.Path.Count > 1 && nextNodeForward != null && nextNodeForward.NodeId.Equals(path1.Path[1].NodeId))
//{
// ReverseCheck = false; //현재 진행 방향으로 이동해야한다
// MakeDetailData(path1, currentDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정)
//}
if (path1.Path.Count > 1 && nextNodeBackward != null && nextNodeBackward.NodeId.Equals(path1.Path[1].NodeId))
{
ReverseCheck = true; //현재 방향의 반대방향으로 이동해야한다
MakeDetailData(path1, ReverseDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정)
}
else return AGVPathResult.CreateFailure("교차로까지 계산된 경로에 현재 위치정보로 추측을 할 수 없습니다", 0, 0);
else
{
ReverseCheck = false; //현재 진행 방향으로 이동해야한다
MakeDetailData(path1, currentDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정)
}

View File

@@ -232,15 +232,18 @@ namespace AGVNavigationCore.PathFinding.Planning
var connected = new HashSet<string>();
// 직접 연결
foreach (var connectedId in node.ConnectedNodes)
foreach (var connectedNode in node.ConnectedMapNodes)
{
connected.Add(connectedId);
if (connectedNode != null)
{
connected.Add(connectedNode.NodeId);
}
}
// 역방향 연결
foreach (var otherNode in _mapNodes)
{
if (otherNode.NodeId != nodeId && otherNode.ConnectedNodes.Contains(nodeId))
if (otherNode.NodeId != nodeId && otherNode.ConnectedMapNodes.Any(n => n.NodeId == nodeId))
{
connected.Add(otherNode.NodeId);
}
@@ -728,9 +731,9 @@ namespace AGVNavigationCore.PathFinding.Planning
string currentNode = path[i].NodeId;
string nextNode = path[i + 1].NodeId;
// 두 노드간 직접 연결성 확인 (맵 노드의 ConnectedNodes 리스트 사용)
// 두 노드간 직접 연결성 확인 (맵 노드의 ConnectedMapNodes 리스트 사용)
var currentMapNode = _mapNodes.FirstOrDefault(n => n.NodeId == currentNode);
if (currentMapNode == null || !currentMapNode.ConnectedNodes.Contains(nextNode))
if (currentMapNode == null || !currentMapNode.ConnectedMapNodes.Any(n => n.NodeId == nextNode))
{
return PathValidationResult.CreateInvalid(currentNode, nextNode, $"노드 {currentNode}와 {nextNode} 사이에 연결이 없음");
}