refactor: PathFinding 폴더 구조 개선 및 도킹 에러 기능 추가
- PathFinding 폴더를 Core, Validation, Planning, Analysis로 세분화 - 네임스페이스 정리 및 using 문 업데이트 - UnifiedAGVCanvas에 SetDockingError 메서드 추가 - 도킹 검증 시스템 인프라 구축 - DockingValidator 유틸리티 클래스 추가 - 빌드 오류 수정 및 안정성 개선 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
174
Cs_HMI/AGVNavigationCore/Utils/DockingValidator.cs
Normal file
174
Cs_HMI/AGVNavigationCore/Utils/DockingValidator.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AGVNavigationCore.Models;
|
||||
using AGVNavigationCore.PathFinding.Core;
|
||||
using AGVNavigationCore.PathFinding.Validation;
|
||||
|
||||
namespace AGVNavigationCore.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// AGV 도킹 방향 검증 유틸리티
|
||||
/// 경로 계산 후 마지막 도킹 방향이 올바른지 검증
|
||||
/// </summary>
|
||||
public static class DockingValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// 경로의 도킹 방향 검증
|
||||
/// </summary>
|
||||
/// <param name="pathResult">경로 계산 결과</param>
|
||||
/// <param name="mapNodes">맵 노드 목록</param>
|
||||
/// <param name="currentDirection">AGV 현재 방향</param>
|
||||
/// <returns>도킹 검증 결과</returns>
|
||||
public static DockingValidationResult ValidateDockingDirection(AGVPathResult pathResult, List<MapNode> mapNodes, AgvDirection currentDirection)
|
||||
{
|
||||
// 경로가 없거나 실패한 경우
|
||||
if (pathResult == null || !pathResult.Success || pathResult.Path == null || pathResult.Path.Count == 0)
|
||||
{
|
||||
return DockingValidationResult.CreateNotRequired();
|
||||
}
|
||||
|
||||
// 목적지 노드 찾기
|
||||
string targetNodeId = pathResult.Path[pathResult.Path.Count - 1];
|
||||
var targetNode = mapNodes?.FirstOrDefault(n => n.NodeId == targetNodeId);
|
||||
|
||||
if (targetNode == null)
|
||||
{
|
||||
return DockingValidationResult.CreateNotRequired();
|
||||
}
|
||||
|
||||
// 도킹이 필요한 노드 타입인지 확인
|
||||
if (!IsDockingRequired(targetNode.Type))
|
||||
{
|
||||
return DockingValidationResult.CreateNotRequired();
|
||||
}
|
||||
|
||||
// 필요한 도킹 방향 확인
|
||||
var requiredDirection = GetRequiredDockingDirection(targetNode.Type);
|
||||
|
||||
// 경로 기반 최종 방향 계산
|
||||
var calculatedDirection = CalculateFinalDirection(pathResult.Path, mapNodes, currentDirection);
|
||||
|
||||
// 검증 수행
|
||||
if (calculatedDirection == requiredDirection)
|
||||
{
|
||||
return DockingValidationResult.CreateValid(
|
||||
targetNodeId,
|
||||
targetNode.Type,
|
||||
requiredDirection,
|
||||
calculatedDirection);
|
||||
}
|
||||
else
|
||||
{
|
||||
string error = $"도킹 방향 불일치: 필요={GetDirectionText(requiredDirection)}, 계산됨={GetDirectionText(calculatedDirection)}";
|
||||
return DockingValidationResult.CreateInvalid(
|
||||
targetNodeId,
|
||||
targetNode.Type,
|
||||
requiredDirection,
|
||||
calculatedDirection,
|
||||
error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 도킹이 필요한 노드 타입인지 확인
|
||||
/// </summary>
|
||||
private static bool IsDockingRequired(NodeType nodeType)
|
||||
{
|
||||
return nodeType == NodeType.Charging || nodeType == NodeType.Docking;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 노드 타입에 따른 필요한 도킹 방향 반환
|
||||
/// </summary>
|
||||
private static AgvDirection GetRequiredDockingDirection(NodeType nodeType)
|
||||
{
|
||||
switch (nodeType)
|
||||
{
|
||||
case NodeType.Charging:
|
||||
return AgvDirection.Forward; // 충전기는 전진 도킹
|
||||
case NodeType.Docking:
|
||||
return AgvDirection.Backward; // 일반 도킹은 후진 도킹
|
||||
default:
|
||||
return AgvDirection.Forward; // 기본값
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 경로 기반 최종 방향 계산
|
||||
/// 현재 구현: 간단한 추정 (향후 고도화 가능)
|
||||
/// </summary>
|
||||
private static AgvDirection CalculateFinalDirection(List<string> path, List<MapNode> mapNodes, AgvDirection currentDirection)
|
||||
{
|
||||
// 경로가 2개 이상일 때만 방향 변화 추정
|
||||
if (path.Count < 2)
|
||||
{
|
||||
return currentDirection;
|
||||
}
|
||||
|
||||
// 마지막 구간의 노드들 찾기
|
||||
var secondLastNodeId = path[path.Count - 2];
|
||||
var lastNodeId = path[path.Count - 1];
|
||||
|
||||
var secondLastNode = mapNodes?.FirstOrDefault(n => n.NodeId == secondLastNodeId);
|
||||
var lastNode = mapNodes?.FirstOrDefault(n => n.NodeId == lastNodeId);
|
||||
|
||||
if (secondLastNode == null || lastNode == null)
|
||||
{
|
||||
return currentDirection;
|
||||
}
|
||||
|
||||
// 마지막 구간의 이동 방향 분석
|
||||
var deltaX = lastNode.Position.X - secondLastNode.Position.X;
|
||||
var deltaY = lastNode.Position.Y - secondLastNode.Position.Y;
|
||||
|
||||
// 이동 거리가 매우 작으면 현재 방향 유지
|
||||
var distance = Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
if (distance < 1.0)
|
||||
{
|
||||
return currentDirection;
|
||||
}
|
||||
|
||||
// 간단한 방향 추정 (향후 더 정교한 로직으로 개선 가능)
|
||||
// 현재는 현재 방향을 유지한다고 가정
|
||||
return currentDirection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 방향을 텍스트로 변환
|
||||
/// </summary>
|
||||
private static string GetDirectionText(AgvDirection direction)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case AgvDirection.Forward:
|
||||
return "전진";
|
||||
case AgvDirection.Backward:
|
||||
return "후진";
|
||||
default:
|
||||
return direction.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 도킹 검증 결과를 문자열로 변환 (디버깅용)
|
||||
/// </summary>
|
||||
public static string GetValidationSummary(DockingValidationResult validation)
|
||||
{
|
||||
if (validation == null)
|
||||
return "검증 결과 없음";
|
||||
|
||||
if (!validation.IsValidationRequired)
|
||||
return "도킹 검증 불필요";
|
||||
|
||||
if (validation.IsValid)
|
||||
{
|
||||
return $"도킹 검증 통과: {validation.TargetNodeId}({validation.TargetNodeType}) - {GetDirectionText(validation.RequiredDockingDirection)} 도킹";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"도킹 검증 실패: {validation.TargetNodeId}({validation.TargetNodeType}) - {validation.ValidationError}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,10 @@ namespace AGVNavigationCore.Utils
|
||||
}
|
||||
else if (motorDirection == AgvDirection.Backward)
|
||||
{
|
||||
// 후진 모터: AGV가 리프트 쪽으로 이동 (현재 → 타겟 방향이 리프트 방향)
|
||||
var dx = targetPos.X - currentPos.X;
|
||||
var dy = targetPos.Y - currentPos.Y;
|
||||
// 후진 모터: AGV가 리프트 쪽으로 이동하므로 리프트는 AGV 이동 방향에 위치
|
||||
// 007→006 후진시: 리프트는 006방향(이동방향)을 향해야 함 (타겟→현재 반대방향)
|
||||
var dx = currentPos.X - targetPos.X;
|
||||
var dy = currentPos.Y - targetPos.Y;
|
||||
return Math.Atan2(dy, dx);
|
||||
}
|
||||
else
|
||||
@@ -131,7 +132,7 @@ namespace AGVNavigationCore.Utils
|
||||
if (motorDirection == AgvDirection.Forward)
|
||||
calculationMethod = "이동방향 + 180도 (전진모터)";
|
||||
else if (motorDirection == AgvDirection.Backward)
|
||||
calculationMethod = "이동방향과 동일 (후진모터)";
|
||||
calculationMethod = "이동방향과 동일 (후진모터 - 리프트는 이동방향에 위치)";
|
||||
else
|
||||
calculationMethod = "기본값 (전진모터)";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user