Files
ENIG/Cs_HMI/AGVMapEditor/Models/PathResult.cs
ChiKyun Kim 7567602479 feat: Add AGV Map Editor and Simulator tools
- Add AGVMapEditor: Visual map editing with drag-and-drop node placement
  * RFID mapping separation (physical ID ↔ logical node mapping)
  * A* pathfinding algorithm with AGV directional constraints
  * JSON map data persistence with structured format
  * Interactive map canvas with zoom/pan functionality

- Add AGVSimulator: Real-time AGV movement simulation
  * Virtual AGV with state machine (Idle, Moving, Rotating, Docking, Charging, Error)
  * Path execution and visualization from calculated routes
  * Real-time position tracking and battery simulation
  * Integration with map editor data format

- Update solution structure and build configuration
- Add comprehensive documentation in CLAUDE.md
- Implement AGV-specific constraints (forward/backward docking, rotation limits)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-10 17:39:23 +09:00

277 lines
8.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
namespace AGVMapEditor.Models
{
/// <summary>
/// 경로 계산 결과
/// </summary>
public class PathResult
{
/// <summary>
/// 경로 계산 성공 여부
/// </summary>
public bool Success { get; set; } = false;
/// <summary>
/// 경로상의 노드 ID 시퀀스
/// </summary>
public List<string> NodeSequence { get; set; } = new List<string>();
/// <summary>
/// AGV 이동 명령 시퀀스
/// </summary>
public List<AgvDirection> MovementSequence { get; set; } = new List<AgvDirection>();
/// <summary>
/// 총 이동 거리 (비용)
/// </summary>
public float TotalDistance { get; set; } = 0;
/// <summary>
/// 총 회전 횟수
/// </summary>
public int TotalRotations { get; set; } = 0;
/// <summary>
/// 예상 소요 시간 (초)
/// </summary>
public float EstimatedTime { get; set; } = 0;
/// <summary>
/// 시작 노드 ID
/// </summary>
public string StartNodeId { get; set; } = string.Empty;
/// <summary>
/// 목표 노드 ID
/// </summary>
public string TargetNodeId { get; set; } = string.Empty;
/// <summary>
/// 시작시 AGV 방향
/// </summary>
public AgvDirection StartDirection { get; set; } = AgvDirection.Forward;
/// <summary>
/// 도착시 AGV 방향
/// </summary>
public AgvDirection EndDirection { get; set; } = AgvDirection.Forward;
/// <summary>
/// 경로 계산에 걸린 시간 (밀리초)
/// </summary>
public long CalculationTime { get; set; } = 0;
/// <summary>
/// 오류 메시지 (실패시)
/// </summary>
public string ErrorMessage { get; set; } = string.Empty;
/// <summary>
/// 경로상의 상세 정보 (디버깅용)
/// </summary>
public List<PathNode> DetailedPath { get; set; } = new List<PathNode>();
/// <summary>
/// 회전이 발생하는 노드들
/// </summary>
public List<string> RotationNodes { get; set; } = new List<string>();
/// <summary>
/// 기본 생성자
/// </summary>
public PathResult()
{
}
/// <summary>
/// 성공 결과 생성자
/// </summary>
public PathResult(List<PathNode> path, string startNodeId, string targetNodeId, AgvDirection startDirection)
{
if (path == null || path.Count == 0)
{
Success = false;
ErrorMessage = "빈 경로입니다.";
return;
}
Success = true;
StartNodeId = startNodeId;
TargetNodeId = targetNodeId;
StartDirection = startDirection;
DetailedPath = new List<PathNode>(path);
// 노드 시퀀스 구성
NodeSequence = path.Select(p => p.NodeId).ToList();
// 이동 명령 시퀀스 구성
MovementSequence = new List<AgvDirection>();
for (int i = 0; i < path.Count; i++)
{
MovementSequence.AddRange(path[i].MovementSequence);
}
// 통계 계산
if (path.Count > 0)
{
TotalDistance = path[path.Count - 1].GCost;
EndDirection = path[path.Count - 1].Direction;
}
TotalRotations = MovementSequence.Count(cmd =>
cmd == AgvDirection.Left || cmd == AgvDirection.Right);
// 회전 노드 추출
var previousDirection = startDirection;
for (int i = 0; i < path.Count; i++)
{
if (path[i].Direction != previousDirection)
{
RotationNodes.Add(path[i].NodeId);
}
previousDirection = path[i].Direction;
}
// 예상 소요 시간 계산 (단순 추정)
EstimatedTime = CalculateEstimatedTime();
}
/// <summary>
/// 실패 결과 생성자
/// </summary>
public PathResult(string errorMessage)
{
Success = false;
ErrorMessage = errorMessage;
}
/// <summary>
/// 예상 소요 시간 계산
/// </summary>
private float CalculateEstimatedTime()
{
// 기본 이동 속도 및 회전 시간 가정
const float MOVE_SPEED = 1.0f; // 단위/초
const float ROTATION_TIME = 2.0f; // 초/회전
float moveTime = TotalDistance / MOVE_SPEED;
float rotationTime = TotalRotations * ROTATION_TIME;
return moveTime + rotationTime;
}
/// <summary>
/// 경로 요약 정보
/// </summary>
public string GetSummary()
{
if (!Success)
{
return $"경로 계산 실패: {ErrorMessage}";
}
return $"경로: {NodeSequence.Count}개 노드, " +
$"거리: {TotalDistance:F1}, " +
$"회전: {TotalRotations}회, " +
$"예상시간: {EstimatedTime:F1}초";
}
/// <summary>
/// 상세 경로 정보
/// </summary>
public List<string> GetDetailedSteps()
{
var steps = new List<string>();
if (!Success)
{
steps.Add($"경로 계산 실패: {ErrorMessage}");
return steps;
}
steps.Add($"시작: {StartNodeId} (방향: {StartDirection})");
for (int i = 0; i < DetailedPath.Count; i++)
{
var node = DetailedPath[i];
var step = $"{i + 1}. {node.NodeId}";
if (node.MovementSequence.Count > 0)
{
step += $" [명령: {string.Join(",", node.MovementSequence)}]";
}
step += $" (F:{node.FCost:F1}, 방향:{node.Direction})";
steps.Add(step);
}
steps.Add($"도착: {TargetNodeId} (최종 방향: {EndDirection})");
return steps;
}
/// <summary>
/// RFID 시퀀스 추출 (실제 AGV 제어용)
/// </summary>
public List<string> GetRfidSequence(NodeResolver nodeResolver)
{
var rfidSequence = new List<string>();
foreach (var nodeId in NodeSequence)
{
var rfidId = nodeResolver.GetRfidByNodeId(nodeId);
if (!string.IsNullOrEmpty(rfidId))
{
rfidSequence.Add(rfidId);
}
}
return rfidSequence;
}
/// <summary>
/// 경로 유효성 검증
/// </summary>
public bool ValidatePath(List<MapNode> mapNodes)
{
if (!Success || NodeSequence.Count == 0)
return false;
// 모든 노드가 존재하는지 확인
foreach (var nodeId in NodeSequence)
{
if (!mapNodes.Any(n => n.NodeId == nodeId))
{
ErrorMessage = $"존재하지 않는 노드: {nodeId}";
return false;
}
}
// 연결성 확인
for (int i = 0; i < NodeSequence.Count - 1; i++)
{
var currentNode = mapNodes.FirstOrDefault(n => n.NodeId == NodeSequence[i]);
var nextNodeId = NodeSequence[i + 1];
if (currentNode != null && !currentNode.ConnectedNodes.Contains(nextNodeId))
{
ErrorMessage = $"연결되지 않은 노드: {currentNode.NodeId} → {nextNodeId}";
return false;
}
}
return true;
}
/// <summary>
/// JSON 직렬화를 위한 문자열 변환
/// </summary>
public override string ToString()
{
return GetSummary();
}
}
}