- agv_path_planner.py: JSON 형식 맵 파일 지원 추가 * parse_map_json() 함수로 MapData.json/NewMap.agvmap 파싱 * RFID 정규화 및 별칭 지원 (007/7 등) * TP(터닝포인트) 기반 방향전환 알고리즘 검증 완료 - universal_pathfinder.py: 범용 패턴 기반 경로 탐색 * Q1-1, Q1-2, Q2-1, Q2-2 모든 시나리오 패턴 구현 * UniversalPathFormatter 방향전환 표기 수정 * 28개 테스트 케이스 중 20개 성공 (71.4%) - test_all_scenarios.py: 전체 테스트 케이스 검증 스크립트 * 4개 시나리오 × 7개 목표 = 28개 케이스 자동 검증 * 사용자 제공 정답과 비교 분석 - show_map_info.py: 맵 구조 분석 도구 * RFID 목록, 연결 정보, 갈림길 분석 * 노드 타입별 분류 및 통계 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
125 lines
5.7 KiB
Python
125 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from universal_pathfinder import UniversalAGVPathfinder, UniversalPathFormatter
|
|
from agv_pathfinder import AGVMap, AgvDirection
|
|
|
|
def test_all_scenarios():
|
|
"""전체 28개 테스트 케이스 검증"""
|
|
print("="*80)
|
|
print("전체 28개 테스트 케이스 검증")
|
|
print("="*80)
|
|
|
|
# 맵 로드
|
|
agv_map = AGVMap()
|
|
agv_map.load_from_file(r"C:\Data\Source\(5613#) ENIG AGV\Source\Cs_HMI\Data\NewMap.agvmap")
|
|
|
|
pathfinder = UniversalAGVPathfinder(agv_map)
|
|
|
|
# 모든 테스트 케이스 정의 (사용자 제공 정답)
|
|
test_scenarios = [
|
|
{
|
|
"name": "Q1-1: 033→032(전진)",
|
|
"start": "032", "came_from": "033", "direction": AgvDirection.FORWARD,
|
|
"targets": {
|
|
"040": "032 ->(F) 031 ->(R) 032 -> 040",
|
|
"041": "032 ->(F) 040 ->(R) 032 -> 031 -> 041",
|
|
"008": "032 ->(B) 033 -> 034 -> 035 -> 036 -> 037 -> 005 -> 006 -> 007 -> 008",
|
|
"001": "032 ->(B) 033 -> 034 -> 035 -> 036 -> 037 -> 005 -> 004 -> 003 -> 002 -> 001",
|
|
"011": "032 ->(B) 033 -> 034 -> 035 -> 036 -> 037 -> 005 -> 004 -> 030 -> 009 -> 010 -> 011",
|
|
"019": "032 ->(B) 033 -> 034 -> 035 -> 036 -> 037 -> 005 -> 004 -> 012 -> 013 ->(F) -> 012 -> 016 -> 017 -> 018 -> 019",
|
|
"015": "032 ->(B) 033 -> 034 -> 035 -> 036 -> 037 -> 005 -> 004 -> 012 -> 016 ->(F) -> 012 -> 013 -> 014 -> 015"
|
|
}
|
|
},
|
|
{
|
|
"name": "Q1-2: 033→032(후진)",
|
|
"start": "032", "came_from": "033", "direction": AgvDirection.BACKWARD,
|
|
"targets": {
|
|
"040": "032 ->(F) 033 ->(R) 032 -> 040",
|
|
"041": "032 ->(R) 031 -> 041",
|
|
"008": "032 ->(F) 033 -> 034 -> 035 -> 036 -> 037 -> 005 -> 004 ->(B) -> 005 -> 006 -> 007 -> 008",
|
|
"001": "032 ->(F) 033 -> 034 -> 035 -> 036 -> 037 -> 005 -> 006 ->(B) -> 004 -> 003 -> 002 -> 001",
|
|
"011": "032 ->(F) 033 -> 034 -> 035 -> 036 -> 037 -> 005 -> 004 -> 003 ->(B) -> 004 -> 030 -> 009 -> 010 -> 011",
|
|
"019": "032 ->(F) 033 -> 034 -> 035 -> 036 -> 037 -> 005 -> 004 -> 012 -> 016 -> 017 -> 018 -> 019",
|
|
"015": "032 ->(F) 033 -> 034 -> 035 -> 036 -> 037 -> 005 -> 004 -> 012 -> 013 -> 014 -> 015"
|
|
}
|
|
},
|
|
{
|
|
"name": "Q2-1: 006→007(전진)",
|
|
"start": "007", "came_from": "006", "direction": AgvDirection.FORWARD,
|
|
"targets": {
|
|
"040": "007 ->(B) 006 -> 005 -> 037 -> 036 -> 035 -> 034 -> 033 -> 032 -> 040",
|
|
"041": "007 ->(B) 006 -> 005 -> 037 -> 036 -> 035 -> 034 -> 033 -> 032 -> 031 -> 041",
|
|
"008": "007 ->(F) 006 -> 005 -> 037 ->(B) 005 -> 006 -> 007 -> 008",
|
|
"001": "007 ->(B) 006 -> 005 -> 004 -> 003 -> 002 -> 001",
|
|
"011": "007 ->(B) 006 -> 005 -> 004 -> 030 -> 009 -> 010 -> 011",
|
|
"019": "007 ->(B) 006 -> 005 -> 004 -> 012 -> 013 ->(F) 012 -> 016 -> 017 -> 018 -> 019",
|
|
"015": "007 ->(B) 006 -> 005 -> 004 -> 012 -> 016 ->(F) 012 -> 013 -> 014 -> 015"
|
|
}
|
|
},
|
|
{
|
|
"name": "Q2-2: 006→007(후진)",
|
|
"start": "007", "came_from": "006", "direction": AgvDirection.BACKWARD,
|
|
"targets": {
|
|
"040": "007 ->(F) 006 -> 005 -> 004 ->(B) 005 -> 037 -> 036 -> 035 -> 034 -> 033 -> 032 -> 040",
|
|
"041": "007 ->(F) 006 -> 005 -> 004 ->(B) 005 -> 037 -> 036 -> 035 -> 034 -> 033 -> 032 -> 031 -> 041",
|
|
"008": "007 ->(B) 008",
|
|
"001": "007 ->(F) 006 -> 005 -> 004 -> 030 ->(B) 004 -> 003 -> 002 -> 001",
|
|
"011": "007 ->(F) 006 -> 005 -> 004 -> 003 ->(B) 004 -> 030 -> 009 -> 010 -> 011",
|
|
"019": "007 ->(F) 006 -> 005 -> 004 -> 012 -> 016 -> 017 -> 018 -> 019",
|
|
"015": "007 ->(F) 006 -> 005 -> 004 -> 012 -> 013 -> 014 -> 015"
|
|
}
|
|
}
|
|
]
|
|
|
|
total_tests = 0
|
|
total_success = 0
|
|
|
|
for scenario in test_scenarios:
|
|
print(f"\n{scenario['name']}")
|
|
print("-" * 60)
|
|
|
|
scenario_success = 0
|
|
scenario_total = len(scenario['targets'])
|
|
|
|
for target_rfid, expected_path in scenario['targets'].items():
|
|
total_tests += 1
|
|
|
|
print(f"\n목표 {target_rfid}:")
|
|
print(f" 정답: {expected_path}")
|
|
|
|
result = pathfinder.find_path(
|
|
start_rfid=scenario['start'],
|
|
target_rfid=target_rfid,
|
|
current_direction=scenario['direction'],
|
|
came_from_rfid=scenario['came_from']
|
|
)
|
|
|
|
if result.success:
|
|
actual_path = UniversalPathFormatter.format_path(result, agv_map)
|
|
print(f" 실제: {actual_path}")
|
|
|
|
if actual_path == expected_path:
|
|
print(" [SUCCESS]")
|
|
scenario_success += 1
|
|
total_success += 1
|
|
else:
|
|
print(" [FAILED] - Path mismatch")
|
|
else:
|
|
print(f" [FAILED]: {result.error_message}")
|
|
|
|
print(f"\n{scenario['name']} 결과: {scenario_success}/{scenario_total} 성공")
|
|
|
|
print(f"\n{'='*80}")
|
|
print(f"전체 결과: {total_success}/{total_tests} 성공 ({total_success/total_tests*100:.1f}%)")
|
|
print(f"{'='*80}")
|
|
|
|
if total_success == total_tests:
|
|
print("*** 모든 테스트 케이스 성공! 범용 알고리즘 완성! ***")
|
|
else:
|
|
print(f"*** {total_tests - total_success}개 케이스 실패. 추가 수정 필요. ***")
|
|
|
|
return total_success == total_tests
|
|
|
|
if __name__ == "__main__":
|
|
test_all_scenarios() |