From 0b59479d349b1068885a5321b703db962e678ede Mon Sep 17 00:00:00 2001 From: backuppc Date: Wed, 29 Oct 2025 14:14:33 +0900 Subject: [PATCH] feat: Add comprehensive path prediction test with ProgressLogForm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add ProgressLogForm.cs for test result logging with ListView - Implement real UI workflow simulation in path prediction test - Test all connected node pairs to all docking targets - Support CSV export for test results - Keep List ConnectedNodes structure (reverted List changes) - Display RFID values in log for better readability ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../AGVNavigationCore/Models/VirtualAGV.cs | 248 +---------- .../PathFinding/Core/AStarPathfinder.cs | 18 +- .../PathFinding/Planning/AGVPathfinder.cs | 136 +++--- .../Utils/DirectionalHelper.cs | 15 + .../AGVLogic/AGVSimulator/AGVSimulator.csproj | 1 + .../AGVSimulator/Forms/ProgressLogForm.cs | 356 ++++++++++++++++ .../Forms/SimulatorForm.Designer.cs | 72 ++-- .../AGVSimulator/Forms/SimulatorForm.cs | 388 ++++++++++++++++-- .../AGVSimulator/Forms/SimulatorForm.resx | 16 + 9 files changed, 884 insertions(+), 366 deletions(-) create mode 100644 Cs_HMI/AGVLogic/AGVSimulator/Forms/ProgressLogForm.cs diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs index 4f518ba..f200ce6 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/VirtualAGV.cs @@ -559,253 +559,7 @@ namespace AGVNavigationCore.Models #endregion - #region Directional Navigation - - /// - /// ํ˜„์žฌ ์ด์ „/ํ˜„์žฌ ์œ„์น˜์™€ ์ด๋™ ๋ฐฉํ–ฅ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ค์Œ ๋…ธ๋“œ ID๋ฅผ ๋ฐ˜ํ™˜ - /// - /// ์‚ฌ์šฉ ์˜ˆ์‹œ: - /// - 001์—์„œ 002๋กœ ์ด๋™ ํ›„, Forward ์„ ํƒ โ†’ 003 ๋ฐ˜ํ™˜ - /// - 003์—์„œ 004๋กœ ์ด๋™ ํ›„, Right ์„ ํƒ โ†’ 030 ๋ฐ˜ํ™˜ - /// - 004์—์„œ 003์œผ๋กœ Backward ์ด๋™ ์ค‘, GetNextNodeId(Backward) โ†’ 002 ๋ฐ˜ํ™˜ - /// - /// ์ „์ œ์กฐ๊ฑด: SetPosition์ด ์ตœ์†Œ 2๋ฒˆ ์ด์ƒ ํ˜ธ์ถœ๋˜์–ด _prevPosition์ด ์„ค์ •๋˜์–ด์•ผ ํ•จ - /// - /// ์ด๋™ ๋ฐฉํ–ฅ (Forward/Backward/Left/Right) - /// ๋งต์˜ ๋ชจ๋“  ๋…ธ๋“œ - /// ๋‹ค์Œ ๋…ธ๋“œ ID (๋˜๋Š” null) - public MapNode GetNextNodeId(AgvDirection direction, List allNodes) - { - // ์ „์ œ์กฐ๊ฑด ๊ฒ€์ฆ: 2๊ฐœ ์œ„์น˜ ํžˆ์Šคํ† ๋ฆฌ ํ•„์š” - if ( _prevNode == null) - { - return null; - } - - if (_currentNode == null || allNodes == null || allNodes.Count == 0) - { - return null; - } - - // ํ˜„์žฌ ๋…ธ๋“œ์— ์—ฐ๊ฒฐ๋œ ๋…ธ๋“œ๋“ค ๊ฐ€์ ธ์˜ค๊ธฐ - var connectedNodeIds = _currentNode.ConnectedNodes; - if (connectedNodeIds == null || connectedNodeIds.Count == 0) - { - return null; - } - - // ์—ฐ๊ฒฐ๋œ ๋…ธ๋“œ ์ค‘ ํ˜„์žฌ ๋…ธ๋“œ๊ฐ€ ์•„๋‹Œ ๊ฒƒ๋“ค๋งŒ ํ•„ํ„ฐ๋ง - var candidateNodes = allNodes.Where(n => - connectedNodeIds.Contains(n.NodeId) && n.NodeId != _currentNode.NodeId - ).ToList(); - - if (candidateNodes.Count == 0) - { - return null; - } - - // ์ด์ „โ†’ํ˜„์žฌ ๋ฒกํ„ฐ ๊ณ„์‚ฐ (์ง„ํ–‰ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ) - var movementVector = new PointF( - _currentPosition.X - _prevPosition.X, - _currentPosition.Y - _prevPosition.Y - ); - - // ๋ฒกํ„ฐ ์ •๊ทœํ™” - var movementLength = (float)Math.Sqrt( - movementVector.X * movementVector.X + - movementVector.Y * movementVector.Y - ); - - if (movementLength < 0.001f) // ๊ฑฐ์˜ ์ด๋™ํ•˜์ง€ ์•Š์Œ - { - return candidateNodes[0]; // ์ฒซ ๋ฒˆ์งธ ์—ฐ๊ฒฐ ๋…ธ๋“œ ๋ฐ˜ํ™˜ - } - - var normalizedMovement = new PointF( - movementVector.X / movementLength, - movementVector.Y / movementLength - ); - - // ๊ฐ ํ›„๋ณด ๋…ธ๋“œ์— ๋Œ€ํ•ด ๋ฐฉํ–ฅ ์ ์ˆ˜ ๊ณ„์‚ฐ - var bestCandidate = (node: (MapNode)null, score: 0.0f); - - foreach (var candidate in candidateNodes) - { - var toNextVector = new PointF( - candidate.Position.X - _currentPosition.X, - candidate.Position.Y - _currentPosition.Y - ); - - var toNextLength = (float)Math.Sqrt( - toNextVector.X * toNextVector.X + - toNextVector.Y * toNextVector.Y - ); - - if (toNextLength < 0.001f) - { - continue; - } - - var normalizedToNext = new PointF( - toNextVector.X / toNextLength, - toNextVector.Y / toNextLength - ); - - // ์ง„ํ–‰ ๋ฐฉํ–ฅ ๊ธฐ๋ฐ˜ ์ ์ˆ˜ ๊ณ„์‚ฐ - float score = CalculateDirectionalScore( - normalizedMovement, - normalizedToNext, - direction - ); - - if (score > bestCandidate.score) - { - bestCandidate = (candidate, score); - } - } - - return bestCandidate.node; - } - - /// - /// ์ด๋™ ๋ฐฉํ–ฅ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฐฉํ–ฅ ์ ์ˆ˜๋ฅผ ๊ณ„์‚ฐ - /// ๋†’์€ ์ ์ˆ˜ = ๋” ๋‚˜์€ ์„ ํƒ์ง€ - /// - private float CalculateDirectionalScore( - PointF movementDirection, // ์ •๊ทœํ™”๋œ ์ด์ „โ†’ํ˜„์žฌ ๋ฒกํ„ฐ - PointF nextDirection, // ์ •๊ทœํ™”๋œ ํ˜„์žฌโ†’๋‹ค์Œ ๋ฒกํ„ฐ - AgvDirection requestedDir) // ์š”์ฒญ๋œ ์ด๋™ ๋ฐฉํ–ฅ - { - // ๋ฒกํ„ฐ ๊ฐ„ ๋‚ด์  ๊ณ„์‚ฐ (์œ ์‚ฌ๋„: -1 ~ 1) - float dotProduct = (movementDirection.X * nextDirection.X) + - (movementDirection.Y * nextDirection.Y); - - // ๋ฒกํ„ฐ ๊ฐ„ ์™ธ์  ๊ณ„์‚ฐ (์ขŒ์šฐ ํŒ๋ณ„: Z ์„ฑ๋ถ„) - // ์–‘์ˆ˜ = ์ขŒ์ธก, ์Œ์ˆ˜ = ์šฐ์ธก - float crossProduct = (movementDirection.X * nextDirection.Y) - - (movementDirection.Y * nextDirection.X); - - float baseScore = 0; - - switch (requestedDir) - { - case AgvDirection.Forward: - // Forward: ํ˜„์žฌ ๋ชจํ„ฐ ์ƒํƒœ์— ๋”ฐ๋ผ ๋‹ค๋ฆ„ - // 1) ํ˜„์žฌ Forward ๋ชจํ„ฐ ์ƒํƒœ๋ผ๋ฉด โ†’ ๊ฐ™์€ ๊ฒฝ๋กœ (๊ณ„์† ์ „์ง„) - // 2) ํ˜„์žฌ Backward ๋ชจํ„ฐ ์ƒํƒœ๋ผ๋ฉด โ†’ ๋ฐ˜๋Œ€ ๊ฒฝ๋กœ (๋ชจํ„ฐ ๋ฐฉํ–ฅ ์ „ํ™˜) - if (_currentDirection == AgvDirection.Forward) - { - // ์ด๋ฏธ Forward ์ƒํƒœ, ๊ณ„์† Forward โ†’ ๊ฐ™์€ ๊ฒฝ๋กœ - if (dotProduct > 0.9f) - baseScore = 100.0f; - else if (dotProduct > 0.5f) - baseScore = 80.0f; - else if (dotProduct > 0.0f) - baseScore = 50.0f; - else if (dotProduct > -0.5f) - baseScore = 20.0f; - } - else - { - // Backward ์ƒํƒœ์—์„œ Forward๋กœ โ†’ ๋ฐ˜๋Œ€ ๊ฒฝ๋กœ - if (dotProduct < -0.9f) - baseScore = 100.0f; - else if (dotProduct < -0.5f) - baseScore = 80.0f; - else if (dotProduct < 0.0f) - baseScore = 50.0f; - else if (dotProduct < 0.5f) - baseScore = 20.0f; - } - break; - - case AgvDirection.Backward: - // Backward: ํ˜„์žฌ ๋ชจํ„ฐ ์ƒํƒœ์— ๋”ฐ๋ผ ๋‹ค๋ฆ„ - // 1) ํ˜„์žฌ Backward ๋ชจํ„ฐ ์ƒํƒœ๋ผ๋ฉด โ†’ ๊ฐ™์€ ๊ฒฝ๋กœ (Forward์™€ ๋™์ผ) - // 2) ํ˜„์žฌ Forward ๋ชจํ„ฐ ์ƒํƒœ๋ผ๋ฉด โ†’ ๋ฐ˜๋Œ€ ๊ฒฝ๋กœ (ํ˜„์žฌ์˜ Backward์™€ ๋ฐ˜๋Œ€) - if (_currentDirection == AgvDirection.Backward) - { - // ์ด๋ฏธ Backward ์ƒํƒœ, ๊ณ„์† Backward โ†’ ๊ฐ™์€ ๊ฒฝ๋กœ - if (dotProduct > 0.9f) - baseScore = 100.0f; - else if (dotProduct > 0.5f) - baseScore = 80.0f; - else if (dotProduct > 0.0f) - baseScore = 50.0f; - else if (dotProduct > -0.5f) - baseScore = 20.0f; - } - else - { - // Forward ์ƒํƒœ์—์„œ Backward๋กœ โ†’ ๋ฐ˜๋Œ€ ๊ฒฝ๋กœ - if (dotProduct < -0.9f) - baseScore = 100.0f; - else if (dotProduct < -0.5f) - baseScore = 80.0f; - else if (dotProduct < 0.0f) - baseScore = 50.0f; - else if (dotProduct < 0.5f) - baseScore = 20.0f; - } - break; - - case AgvDirection.Left: - // Left: ์ขŒ์ธก ๋ฐฉํ–ฅ ์„ ํ˜ธ - if (dotProduct > 0.0f) // Forward ์ƒํƒœ - { - if (crossProduct > 0.5f) - baseScore = 100.0f; - else if (crossProduct > 0.0f) - baseScore = 70.0f; - else if (crossProduct > -0.5f) - baseScore = 50.0f; - else - baseScore = 30.0f; - } - else // Backward ์ƒํƒœ - ์ขŒ์šฐ ๋ฐ˜์ „ - { - if (crossProduct < -0.5f) - baseScore = 100.0f; - else if (crossProduct < 0.0f) - baseScore = 70.0f; - else if (crossProduct < 0.5f) - baseScore = 50.0f; - else - baseScore = 30.0f; - } - break; - - case AgvDirection.Right: - // Right: ์šฐ์ธก ๋ฐฉํ–ฅ ์„ ํ˜ธ - if (dotProduct > 0.0f) // Forward ์ƒํƒœ - { - if (crossProduct < -0.5f) - baseScore = 100.0f; - else if (crossProduct < 0.0f) - baseScore = 70.0f; - else if (crossProduct < 0.5f) - baseScore = 50.0f; - else - baseScore = 30.0f; - } - else // Backward ์ƒํƒœ - ์ขŒ์šฐ ๋ฐ˜์ „ - { - if (crossProduct > 0.5f) - baseScore = 100.0f; - else if (crossProduct > 0.0f) - baseScore = 70.0f; - else if (crossProduct > -0.5f) - baseScore = 50.0f; - else - baseScore = 30.0f; - } - break; - } - - return baseScore; - } - - #endregion + #region Cleanup diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs index e7adebf..ed8105e 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs @@ -66,17 +66,17 @@ namespace AGVNavigationCore.PathFinding.Core { var pathNode = _nodeMap[mapNode.NodeId]; - foreach (var connectedNodeId in mapNode.ConnectedNodes) + foreach (var connectedNode in mapNode.ConnectedNodes) { - if (_nodeMap.ContainsKey(connectedNodeId)) + if (_nodeMap.ContainsKey(connectedNode)) { // ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ ์ƒ์„ฑ (๋‹จ์ผ ์—ฐ๊ฒฐ์ด ์–‘๋ฐฉํ–ฅ์„ ์˜๋ฏธ) - if (!pathNode.ConnectedNodes.Contains(connectedNodeId)) + if (!pathNode.ConnectedNodes.Contains(connectedNode)) { - pathNode.ConnectedNodes.Add(connectedNodeId); + pathNode.ConnectedNodes.Add(connectedNode); } - var connectedPathNode = _nodeMap[connectedNodeId]; + var connectedPathNode = _nodeMap[connectedNode]; if (!connectedPathNode.ConnectedNodes.Contains(mapNode.NodeId)) { connectedPathNode.ConnectedNodes.Add(mapNode.NodeId); @@ -594,13 +594,13 @@ namespace AGVNavigationCore.PathFinding.Core foreach (var connectedNodeId in junctionNode.ConnectedNodes) { + if (connectedNodeId == null) continue; + // ์กฐ๊ฑด 1: ์™”๋˜ ๊ธธ์ด ์•„๋‹˜ - if (connectedNodeId == previousNodeId) - continue; + if (connectedNodeId == previousNodeId) continue; // ์กฐ๊ฑด 2: ๊ฐˆ ๊ธธ์ด ์•„๋‹˜ - if (connectedNodeId == targetNodeId) - continue; + if (connectedNodeId == targetNodeId) continue; // ์กฐ๊ฑด 3, 4, 5: ์กด์žฌํ•˜๊ณ , ํ™œ์„ฑ ์ƒํƒœ์ด๊ณ , ๋„ค๋น„๊ฒŒ์ด์…˜ ๊ฐ€๋Šฅ var connectedNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectedNodeId); diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs index afb699c..aebb94c 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs @@ -22,6 +22,8 @@ namespace AGVNavigationCore.PathFinding.Planning private readonly JunctionAnalyzer _junctionAnalyzer; private readonly DirectionChangePlanner _directionChangePlanner; + + public AGVPathfinder(List mapNodes) { _mapNodes = mapNodes ?? new List(); @@ -114,7 +116,7 @@ namespace AGVNavigationCore.PathFinding.Planning return AGVPathResult.CreateFailure("๋ชฉ์ ์ง€ ๋…ธ๋“œ๊ฐ€ null์ž…๋‹ˆ๋‹ค.", 0, 0); if (prevNode == null) return AGVPathResult.CreateFailure("์ด์ „์œ„์น˜ ๋…ธ๋“œ๊ฐ€ null์ž…๋‹ˆ๋‹ค.", 0, 0); - if (startNode == targetNode) + if (startNode.NodeId == targetNode.NodeId && targetNode.DockDirection.MatchAGVDirection(prevDirection)) return AGVPathResult.CreateFailure("๋ชฉ์ ์ง€์™€ ํ˜„์žฌ์œ„์น˜๊ฐ€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.", 0, 0); var ReverseDirection = (currentDirection == AgvDirection.Forward ? AgvDirection.Backward : AgvDirection.Forward); @@ -258,77 +260,101 @@ namespace AGVNavigationCore.PathFinding.Planning MakeDetailData(path2, currentDirection); } + MapNode tempNode = null; + + //3.๋ฐฉํ–ฅ์ „ํ™˜์„ ์œ„ํ™˜ ๋Œ€์ฒด ๋…ธ๋“œ์ฐพ๊ธฐ - var tempNode = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.NodeId, - path1.Path[path1.Path.Count - 2].NodeId, - path2.Path[1].NodeId); + tempNode = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.NodeId, + path1.Path[path1.Path.Count - 2].NodeId, + path2.Path[1].NodeId); //4. path1 + tempnode + path2 ๊ฐ€ ์ตœ์ข… ์œ„์น˜๊ฐ€ ๋œ๋‹ค. if (tempNode == null) return AGVPathResult.CreateFailure("๋ฐฉํ–ฅ ์ „ํ™˜์„ ์œ„ํ•œ ๋Œ€์ฒด ๋…ธ๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", 0, 0); + // path1 (์‹œ์ž‘ โ†’ ๊ต์ฐจ๋กœ) var combinedResult = path1; - // ๊ต์ฐจ๋กœ โ†’ ๋Œ€์ฒด๋…ธ๋“œ ๊ฒฝ๋กœ ๊ณ„์‚ฐ - var pathToTemp = _basicPathfinder.FindPath(JunctionInPath.NodeId, tempNode.NodeId); - pathToTemp.PrevNode = JunctionInPath; - pathToTemp.PrevDirection = (ReverseCheck ? ReverseDirection : currentDirection); - if (!pathToTemp.Success) - return AGVPathResult.CreateFailure("๊ต์ฐจ๋กœ์—์„œ ๋Œ€์ฒด ๋…ธ๋“œ๊นŒ์ง€์˜ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", 0, 0); - if (ReverseCheck) MakeDetailData(pathToTemp, ReverseDirection); - else MakeDetailData(pathToTemp, currentDirection); - - //๊ต์ฐจ๋กœ์ฐ๊ณ  ์›๋ž˜๋ฐฉํ–ฅ์œผ๋กœ ๋Œ์–ด๊ฐ€์•ผํ•œ๋‹ค. - if (pathToTemp.DetailedPath.Count > 1) - pathToTemp.DetailedPath[pathToTemp.DetailedPath.Count - 1].MotorDirection = currentDirection; - - // path1 + pathToTemp ํ•ฉ์น˜๊ธฐ - combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp); - - // ๋Œ€์ฒด๋…ธ๋“œ โ†’ ๊ต์ฐจ๋กœ ๊ฒฝ๋กœ ๊ณ„์‚ฐ (์—ญ๋ฐฉํ–ฅ) - var pathFromTemp = _basicPathfinder.FindPath(tempNode.NodeId, JunctionInPath.NodeId); - pathFromTemp.PrevNode = JunctionInPath; - pathFromTemp.PrevDirection = (ReverseCheck ? ReverseDirection : currentDirection); - if (!pathFromTemp.Success) - return AGVPathResult.CreateFailure("๋Œ€์ฒด ๋…ธ๋“œ์—์„œ ๊ต์ฐจ๋กœ๊นŒ์ง€์˜ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", 0, 0); - - if (ReverseCheck) MakeDetailData(pathFromTemp, currentDirection); - else MakeDetailData(pathFromTemp, ReverseDirection); - - // (path1 + pathToTemp) + pathFromTemp ํ•ฉ์น˜๊ธฐ - combinedResult = _basicPathfinder.CombineResults(combinedResult, pathFromTemp); - - //๋Œ€์ฒด๋…ธ๋“œ์—์„œ ์ตœ์ข… ๋ชฉ์ ์ง€๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•œ๋‹ค. - if ((currentDirection == AgvDirection.Forward && targetNode.DockDirection != DockingDirection.Forward) || - (currentDirection == AgvDirection.Backward && targetNode.DockDirection != DockingDirection.Backward)) + //๊ต์ฐจ๋กœ ๋Œ€์ฒด๋…ธ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒฝ์šฐ + //if (tempNode != null) { - //๋ชฉ์ ์ง€์™€ ๋ฐฉํ–ฅ์ด ๋งž์ง€ ์•Š๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ๋Œ€์ฒด๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€๋กœ ๋” ์ฐพ์•„์•ผํ•œ๋‹ค. - var tempNode2 = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.NodeId, - combinedResult.Path[combinedResult.Path.Count - 2].NodeId, - path2.Path[1].NodeId); - - var pathToTemp2 = _basicPathfinder.FindPath(JunctionInPath.NodeId, tempNode2.NodeId); - if (ReverseCheck) MakeDetailData(pathToTemp2, currentDirection); - else MakeDetailData(pathToTemp2, ReverseDirection); - - combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp2); + // ๊ต์ฐจ๋กœ โ†’ ๋Œ€์ฒด๋…ธ๋“œ ๊ฒฝ๋กœ ๊ณ„์‚ฐ + var pathToTemp = _basicPathfinder.FindPath(JunctionInPath.NodeId, tempNode.NodeId); + pathToTemp.PrevNode = JunctionInPath; + pathToTemp.PrevDirection = (ReverseCheck ? ReverseDirection : currentDirection); + if (!pathToTemp.Success) + return AGVPathResult.CreateFailure("๊ต์ฐจ๋กœ์—์„œ ๋Œ€์ฒด ๋…ธ๋“œ๊นŒ์ง€์˜ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", 0, 0); + if (ReverseCheck) MakeDetailData(pathToTemp, ReverseDirection); + else MakeDetailData(pathToTemp, currentDirection); //๊ต์ฐจ๋กœ์ฐ๊ณ  ์›๋ž˜๋ฐฉํ–ฅ์œผ๋กœ ๋Œ์–ด๊ฐ€์•ผํ•œ๋‹ค. - if (combinedResult.DetailedPath.Count > 1) + if (pathToTemp.DetailedPath.Count > 1) + pathToTemp.DetailedPath[pathToTemp.DetailedPath.Count - 1].MotorDirection = currentDirection; + + // path1 + pathToTemp ํ•ฉ์น˜๊ธฐ + combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp); + + // ๋Œ€์ฒด๋…ธ๋“œ โ†’ ๊ต์ฐจ๋กœ ๊ฒฝ๋กœ ๊ณ„์‚ฐ (์—ญ๋ฐฉํ–ฅ) + var pathFromTemp = _basicPathfinder.FindPath(tempNode.NodeId, JunctionInPath.NodeId); + pathFromTemp.PrevNode = JunctionInPath; + pathFromTemp.PrevDirection = (ReverseCheck ? ReverseDirection : currentDirection); + if (!pathFromTemp.Success) + return AGVPathResult.CreateFailure("๋Œ€์ฒด ๋…ธ๋“œ์—์„œ ๊ต์ฐจ๋กœ๊นŒ์ง€์˜ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", 0, 0); + + if (ReverseCheck) MakeDetailData(pathFromTemp, currentDirection); + else MakeDetailData(pathFromTemp, ReverseDirection); + + // (path1 + pathToTemp) + pathFromTemp ํ•ฉ์น˜๊ธฐ + combinedResult = _basicPathfinder.CombineResults(combinedResult, pathFromTemp); + + //ํ˜„์žฌ๊นŒ์ง€ ๋…ธ๋“œ์—์„œ ๋ชฉ์ ์ง€๊นŒ์ง€์˜ ๋ฐฉํ–ฅ์ด ์ผ์น˜ํ•˜๋ฉด ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•œ๋‹ค. + bool temp3ok = false; + var TempCheck3 = _basicPathfinder.FindPath(combinedResult.Path.Last().NodeId, targetNode.NodeId); + if (TempCheck3.Path.First().NodeId.Equals(combinedResult.Path.Last().NodeId)) { - if (ReverseCheck) - combinedResult.DetailedPath[combinedResult.DetailedPath.Count - 1].MotorDirection = ReverseDirection; - else - combinedResult.DetailedPath[combinedResult.DetailedPath.Count - 1].MotorDirection = currentDirection; + if (targetNode.DockDirection == DockingDirection.Forward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Forward) + { + temp3ok = true; + } + else if (targetNode.DockDirection == DockingDirection.Backward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Backward) + { + temp3ok = true; + } } - var pathToTemp3 = _basicPathfinder.FindPath(tempNode2.NodeId, JunctionInPath.NodeId); - if (ReverseCheck) MakeDetailData(pathToTemp3, ReverseDirection); - else MakeDetailData(pathToTemp3, currentDirection); - combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp3); + + //๋Œ€์ฒด๋…ธ๋“œ์—์„œ ์ตœ์ข… ๋ชฉ์ ์ง€๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•œ๋‹ค. + if (temp3ok == false) + { + //๋ชฉ์ ์ง€์™€ ๋ฐฉํ–ฅ์ด ๋งž์ง€ ์•Š๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ๋Œ€์ฒด๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€๋กœ ๋” ์ฐพ์•„์•ผํ•œ๋‹ค. + var tempNode2 = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.NodeId, + combinedResult.Path[combinedResult.Path.Count - 2].NodeId, + path2.Path[1].NodeId); + + var pathToTemp2 = _basicPathfinder.FindPath(JunctionInPath.NodeId, tempNode2.NodeId); + if (ReverseCheck) MakeDetailData(pathToTemp2, currentDirection); + else MakeDetailData(pathToTemp2, ReverseDirection); + + combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp2); + + //๊ต์ฐจ๋กœ์ฐ๊ณ  ์›๋ž˜๋ฐฉํ–ฅ์œผ๋กœ ๋Œ์–ด๊ฐ€์•ผํ•œ๋‹ค. + if (combinedResult.DetailedPath.Count > 1) + { + if (ReverseCheck) + combinedResult.DetailedPath[combinedResult.DetailedPath.Count - 1].MotorDirection = ReverseDirection; + else + combinedResult.DetailedPath[combinedResult.DetailedPath.Count - 1].MotorDirection = currentDirection; + } + + var pathToTemp3 = _basicPathfinder.FindPath(tempNode2.NodeId, JunctionInPath.NodeId); + if (ReverseCheck) MakeDetailData(pathToTemp3, ReverseDirection); + else MakeDetailData(pathToTemp3, currentDirection); + + combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp3); + } } // (path1 + pathToTemp + pathFromTemp) + path2 ํ•ฉ์น˜๊ธฐ diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs index 7ee251e..389b94e 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs @@ -15,6 +15,21 @@ namespace AGVNavigationCore.Utils /// public static class DirectionalHelper { + + /// + /// AGV๋ฐฉํ–ฅ๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. ๋‹จ ์›๋ณธ์œ„์น˜์—์„œ dock ์œ„์น˜๊ฐ€ Don't Care ๋ผ๋ฉด true๊ฐ€ ๋ฐ˜ํ™˜ ๋ฉ๋‹ˆ๋‹ค. + /// + /// + /// + /// + public static bool MatchAGVDirection(this DockingDirection dock, AgvDirection agvdirection) + { + if (dock == DockingDirection.DontCare) return true; + if (dock == DockingDirection.Forward && agvdirection == AgvDirection.Forward) return true; + if (dock == DockingDirection.Backward && agvdirection == AgvDirection.Backward) return true; + return false; + } + private static JunctionAnalyzer _junctionAnalyzer; /// diff --git a/Cs_HMI/AGVLogic/AGVSimulator/AGVSimulator.csproj b/Cs_HMI/AGVLogic/AGVSimulator/AGVSimulator.csproj index 2b0e79b..9088fe2 100644 --- a/Cs_HMI/AGVLogic/AGVSimulator/AGVSimulator.csproj +++ b/Cs_HMI/AGVLogic/AGVSimulator/AGVSimulator.csproj @@ -45,6 +45,7 @@ + diff --git a/Cs_HMI/AGVLogic/AGVSimulator/Forms/ProgressLogForm.cs b/Cs_HMI/AGVLogic/AGVSimulator/Forms/ProgressLogForm.cs new file mode 100644 index 0000000..c06eac7 --- /dev/null +++ b/Cs_HMI/AGVLogic/AGVSimulator/Forms/ProgressLogForm.cs @@ -0,0 +1,356 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Text; +using System.Windows.Forms; + +namespace AGVSimulator.Forms +{ + /// + /// ๊ฒฝ๋กœ ์˜ˆ์ธก ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ๋กœ๊ทธ ํ•ญ๋ชฉ + /// + public class PathTestLogItem + { + public string PreviousPosition { get; set; } + public string MotorDirection { get; set; } // ์ •๋ฐฉํ–ฅ or ์—ญ๋ฐฉํ–ฅ + public string CurrentPosition { get; set; } + public string TargetPosition { get; set; } + public string DockingPosition { get; set; } // ๋„ํ‚น์œ„์น˜ + public bool Success { get; set; } + public string Message { get; set; } + public string DetailedPath { get; set; } + public DateTime Timestamp { get; set; } + + public PathTestLogItem() + { + Timestamp = DateTime.Now; + } + } + + /// + /// ๊ฒฝ๋กœ ์˜ˆ์ธก ํ…Œ์ŠคํŠธ ์ง„ํ–‰ ์ƒํ™ฉ ๋กœ๊ทธ ํ‘œ์‹œ ํผ + /// + public partial class ProgressLogForm : Form + { + private ListView _logListView; + private Button _closeButton; + private Button _cancelButton; + private Button _saveCSVButton; + private ProgressBar _progressBar; + private Label _statusLabel; + private List _logItems; + + /// + /// ์ทจ์†Œ ์š”์ฒญ ์—ฌ๋ถ€ + /// + public bool CancelRequested { get; private set; } + + public ProgressLogForm() + { + InitializeComponent(); + CancelRequested = false; + _logItems = new List(); + } + + private void InitializeComponent() + { + this.Text = "๊ฒฝ๋กœ ์˜ˆ์ธก ํ…Œ์ŠคํŠธ ์ง„ํ–‰ ์ƒํ™ฉ"; + this.Size = new Size(1200, 600); + this.StartPosition = FormStartPosition.CenterParent; + this.FormBorderStyle = FormBorderStyle.Sizable; + this.MinimumSize = new Size(800, 400); + + // ์ƒํƒœ ๋ ˆ์ด๋ธ” + _statusLabel = new Label + { + Text = "์ค€๋น„ ์ค‘...", + Dock = DockStyle.Top, + Height = 30, + TextAlign = ContentAlignment.MiddleLeft, + Padding = new Padding(10, 5, 10, 5), + Font = new Font("๋ง‘์€ ๊ณ ๋”•", 10F, FontStyle.Bold) + }; + + // ํ”„๋กœ๊ทธ๋ ˆ์Šค๋ฐ” + _progressBar = new ProgressBar + { + Dock = DockStyle.Top, + Height = 25, + Style = ProgressBarStyle.Continuous, + Minimum = 0, + Maximum = 100 + }; + + // ๋กœ๊ทธ ๋ฆฌ์ŠคํŠธ๋ทฐ + _logListView = new ListView + { + Dock = DockStyle.Fill, + View = View.Details, + FullRowSelect = true, + GridLines = true, + Font = new Font("๋ง‘์€ ๊ณ ๋”•", 9F), + BackColor = Color.White + }; + + // ์ปฌ๋Ÿผ ์ถ”๊ฐ€ + _logListView.Columns.Add("์ด์ „์œ„์น˜", 80); + _logListView.Columns.Add("๋ชจํ„ฐ๋ฐฉํ–ฅ", 80); + _logListView.Columns.Add("ํ˜„์žฌ์œ„์น˜", 80); + _logListView.Columns.Add("๋Œ€์ƒ์œ„์น˜", 80); + _logListView.Columns.Add("๋„ํ‚น์œ„์น˜", 80); + _logListView.Columns.Add("์„ฑ๊ณต", 50); + _logListView.Columns.Add("๋ฉ”์„ธ์ง€", 180); + _logListView.Columns.Add("์ƒ์„ธ๊ฒฝ๋กœ", 350); + _logListView.Columns.Add("์‹œ๊ฐ„", 90); + + // ๋ฒ„ํŠผ ํŒจ๋„ + var buttonPanel = new Panel + { + Dock = DockStyle.Bottom, + Height = 50 + }; + + _cancelButton = new Button + { + Text = "์ทจ์†Œ", + Size = new Size(100, 30), + Location = new Point(10, 10), + Enabled = true + }; + _cancelButton.Click += OnCancel_Click; + + _closeButton = new Button + { + Text = "๋‹ซ๊ธฐ", + Size = new Size(100, 30), + Location = new Point(120, 10), + Enabled = false + }; + _closeButton.Click += OnClose_Click; + + _saveCSVButton = new Button + { + Text = "CSV ์ €์žฅ", + Size = new Size(100, 30), + Location = new Point(230, 10), + Enabled = true + }; + _saveCSVButton.Click += OnSaveCSV_Click; + + buttonPanel.Controls.Add(_cancelButton); + buttonPanel.Controls.Add(_closeButton); + buttonPanel.Controls.Add(_saveCSVButton); + + this.Controls.Add(_logListView); + this.Controls.Add(_progressBar); + this.Controls.Add(_statusLabel); + this.Controls.Add(buttonPanel); + } + + /// + /// ๋กœ๊ทธ ์ถ”๊ฐ€ (PathTestLogItem) + /// + public void AddLogItem(PathTestLogItem item) + { + if (InvokeRequired) + { + Invoke(new Action(AddLogItem), item); + return; + } + + _logItems.Add(item); + + var listItem = new ListViewItem(item.PreviousPosition ?? "-"); + listItem.SubItems.Add(item.MotorDirection ?? "-"); + listItem.SubItems.Add(item.CurrentPosition ?? "-"); + listItem.SubItems.Add(item.TargetPosition ?? "-"); + listItem.SubItems.Add(item.DockingPosition ?? "-"); + listItem.SubItems.Add(item.Success ? "O" : "X"); + listItem.SubItems.Add(item.Message ?? "-"); + listItem.SubItems.Add(item.DetailedPath ?? "-"); + listItem.SubItems.Add(item.Timestamp.ToString("HH:mm:ss")); + + // ์„ฑ๊ณต ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์ƒ‰์ƒ ์„ค์ • + if (!item.Success) + { + listItem.BackColor = Color.LightPink; + } + + _logListView.Items.Add(listItem); + _logListView.EnsureVisible(_logListView.Items.Count - 1); + } + + /// + /// ๊ฐ„๋‹จํ•œ ํ…์ŠคํŠธ ๋กœ๊ทธ ์ถ”๊ฐ€ (์ƒํƒœ ๋ฉ”์‹œ์ง€์šฉ) + /// + public void AppendLog(string message) + { + var item = new PathTestLogItem + { + Message = message, + Success = true + }; + AddLogItem(item); + } + + /// + /// ์ƒํƒœ ๋ฉ”์‹œ์ง€ ์—…๋ฐ์ดํŠธ + /// + public void UpdateStatus(string status) + { + if (InvokeRequired) + { + Invoke(new Action(UpdateStatus), status); + return; + } + + _statusLabel.Text = status; + } + + /// + /// ํ”„๋กœ๊ทธ๋ ˆ์Šค๋ฐ” ์—…๋ฐ์ดํŠธ + /// + public void UpdateProgress(int value, int maximum) + { + if (InvokeRequired) + { + Invoke(new Action(UpdateProgress), value, maximum); + return; + } + + _progressBar.Maximum = maximum; + _progressBar.Value = Math.Min(value, maximum); + } + + /// + /// ์ž‘์—… ์™„๋ฃŒ ์‹œ ํ˜ธ์ถœ + /// + public void SetCompleted() + { + if (InvokeRequired) + { + Invoke(new Action(SetCompleted)); + return; + } + + _cancelButton.Enabled = false; + _closeButton.Enabled = true; + UpdateStatus("์ž‘์—… ์™„๋ฃŒ"); + } + + /// + /// ์ž‘์—… ์ทจ์†Œ ์‹œ ํ˜ธ์ถœ + /// + public void SetCancelled() + { + if (InvokeRequired) + { + Invoke(new Action(SetCancelled)); + return; + } + + _cancelButton.Enabled = false; + _closeButton.Enabled = true; + UpdateStatus("์ž‘์—… ์ทจ์†Œ๋จ"); + } + + private void OnCancel_Click(object sender, EventArgs e) + { + var result = MessageBox.Show( + "์ง„ํ–‰ ์ค‘์ธ ์ž‘์—…์„ ์ทจ์†Œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "์ทจ์†Œ ํ™•์ธ", + MessageBoxButtons.YesNo, + MessageBoxIcon.Question); + + if (result == DialogResult.Yes) + { + CancelRequested = true; + _cancelButton.Enabled = false; + UpdateStatus("์ทจ์†Œ ์š”์ฒญ๋จ..."); + AppendLog("์‚ฌ์šฉ์ž๊ฐ€ ์ทจ์†Œ๋ฅผ ์š”์ฒญํ–ˆ์Šต๋‹ˆ๋‹ค."); + } + } + + private void OnSaveCSV_Click(object sender, EventArgs e) + { + if (_logItems.Count == 0) + { + MessageBox.Show("์ €์žฅํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", "์•Œ๋ฆผ", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + + using (var saveDialog = new SaveFileDialog()) + { + saveDialog.Filter = "CSV ํŒŒ์ผ (*.csv)|*.csv|๋ชจ๋“  ํŒŒ์ผ (*.*)|*.*"; + saveDialog.DefaultExt = "csv"; + saveDialog.FileName = $"๊ฒฝ๋กœ์˜ˆ์ธกํ…Œ์ŠคํŠธ_{DateTime.Now:yyyyMMdd_HHmmss}.csv"; + + if (saveDialog.ShowDialog() == DialogResult.OK) + { + try + { + SaveToCSV(saveDialog.FileName); + MessageBox.Show($"CSV ํŒŒ์ผ์ด ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.\n{saveDialog.FileName}", + "์ €์žฅ ์™„๋ฃŒ", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + catch (Exception ex) + { + MessageBox.Show($"CSV ์ €์žฅ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค:\n{ex.Message}", + "์˜ค๋ฅ˜", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + } + + /// + /// CSV ํŒŒ์ผ๋กœ ์ €์žฅ + /// + private void SaveToCSV(string filePath) + { + using (var writer = new StreamWriter(filePath, false, Encoding.UTF8)) + { + // ํ—ค๋” ์ž‘์„ฑ + writer.WriteLine("์ด์ „์œ„์น˜,๋ชจํ„ฐ๋ฐฉํ–ฅ,ํ˜„์žฌ์œ„์น˜,๋Œ€์ƒ์œ„์น˜,๋„ํ‚น์œ„์น˜,์„ฑ๊ณต,๋ฉ”์„ธ์ง€,์ƒ์„ธ๊ฒฝ๋กœ,์‹œ๊ฐ„"); + + // ๋ฐ์ดํ„ฐ ์ž‘์„ฑ + foreach (var item in _logItems) + { + var line = $"{EscapeCSV(item.PreviousPosition)}," + + $"{EscapeCSV(item.MotorDirection)}," + + $"{EscapeCSV(item.CurrentPosition)}," + + $"{EscapeCSV(item.TargetPosition)}," + + $"{EscapeCSV(item.DockingPosition)}," + + $"{(item.Success ? "O" : "X")}," + + $"{EscapeCSV(item.Message)}," + + $"{EscapeCSV(item.DetailedPath)}," + + $"{item.Timestamp:yyyy-MM-dd HH:mm:ss}"; + + writer.WriteLine(line); + } + } + } + + /// + /// CSV ์…€ ๋ฐ์ดํ„ฐ ์ด์Šค์ผ€์ดํ”„ ์ฒ˜๋ฆฌ + /// + private string EscapeCSV(string value) + { + if (string.IsNullOrEmpty(value)) + return ""; + + // ์‰ผํ‘œ, ํฐ๋”ฐ์˜ดํ‘œ, ์ค„๋ฐ”๊ฟˆ์ด ์žˆ์œผ๋ฉด ํฐ๋”ฐ์˜ดํ‘œ๋กœ ๊ฐ์‹ธ๊ณ  ๋‚ด๋ถ€ ํฐ๋”ฐ์˜ดํ‘œ๋Š” ๋‘ ๊ฐœ๋กœ + if (value.Contains(",") || value.Contains("\"") || value.Contains("\n") || value.Contains("\r")) + { + return "\"" + value.Replace("\"", "\"\"") + "\""; + } + + return value; + } + + private void OnClose_Click(object sender, EventArgs e) + { + this.Close(); + } + } +} diff --git a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.Designer.cs b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.Designer.cs index 20f5d69..0ebf626 100644 --- a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.Designer.cs +++ b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.Designer.cs @@ -45,6 +45,7 @@ namespace AGVSimulator.Forms /// private void InitializeComponent() { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SimulatorForm)); this._menuStrip = new System.Windows.Forms.MenuStrip(); this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.openMapToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -74,9 +75,12 @@ namespace AGVSimulator.Forms this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); this.fitToMapToolStripButton = new System.Windows.Forms.ToolStripButton(); this.resetZoomToolStripButton = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator(); + this.toolStripButton1 = new System.Windows.Forms.ToolStripButton(); this._statusStrip = new System.Windows.Forms.StatusStrip(); this._statusLabel = new System.Windows.Forms.ToolStripStatusLabel(); this._coordLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.prb1 = new System.Windows.Forms.ToolStripProgressBar(); this._controlPanel = new System.Windows.Forms.Panel(); this._statusGroup = new System.Windows.Forms.GroupBox(); this._pathLengthLabel = new System.Windows.Forms.Label(); @@ -92,7 +96,6 @@ namespace AGVSimulator.Forms this._startNodeCombo = new System.Windows.Forms.ComboBox(); this.startNodeLabel = new System.Windows.Forms.Label(); this._agvControlGroup = new System.Windows.Forms.GroupBox(); - this.btNextNode = new System.Windows.Forms.Button(); this._setPositionButton = new System.Windows.Forms.Button(); this._rfidTextBox = new System.Windows.Forms.TextBox(); this._rfidLabel = new System.Windows.Forms.Label(); @@ -105,10 +108,10 @@ namespace AGVSimulator.Forms this._agvListCombo = new System.Windows.Forms.ComboBox(); this._canvasPanel = new System.Windows.Forms.Panel(); this._agvInfoPanel = new System.Windows.Forms.Panel(); + this._pathDebugLabel = new System.Windows.Forms.TextBox(); this._agvInfoTitleLabel = new System.Windows.Forms.Label(); this._liftDirectionLabel = new System.Windows.Forms.Label(); this._motorDirectionLabel = new System.Windows.Forms.Label(); - this._pathDebugLabel = new System.Windows.Forms.TextBox(); this._menuStrip.SuspendLayout(); this._toolStrip.SuspendLayout(); this._statusStrip.SuspendLayout(); @@ -274,7 +277,9 @@ namespace AGVSimulator.Forms this.btAllReset, this.toolStripSeparator3, this.fitToMapToolStripButton, - this.resetZoomToolStripButton}); + this.resetZoomToolStripButton, + this.toolStripSeparator5, + this.toolStripButton1}); this._toolStrip.Location = new System.Drawing.Point(0, 24); this._toolStrip.Name = "_toolStrip"; this._toolStrip.Size = new System.Drawing.Size(1200, 25); @@ -372,11 +377,26 @@ namespace AGVSimulator.Forms this.resetZoomToolStripButton.ToolTipText = "์คŒ์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค"; this.resetZoomToolStripButton.Click += new System.EventHandler(this.OnResetZoom_Click); // + // toolStripSeparator5 + // + this.toolStripSeparator5.Name = "toolStripSeparator5"; + this.toolStripSeparator5.Size = new System.Drawing.Size(6, 25); + // + // toolStripButton1 + // + this.toolStripButton1.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton1.Image"))); + this.toolStripButton1.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolStripButton1.Name = "toolStripButton1"; + this.toolStripButton1.Size = new System.Drawing.Size(75, 22); + this.toolStripButton1.Text = "๊ฒฝ๋กœ์˜ˆ์ธก"; + this.toolStripButton1.Click += new System.EventHandler(this.toolStripButton1_Click); + // // _statusStrip // this._statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this._statusLabel, - this._coordLabel}); + this._coordLabel, + this.prb1}); this._statusStrip.Location = new System.Drawing.Point(0, 778); this._statusStrip.Name = "_statusStrip"; this._statusStrip.Size = new System.Drawing.Size(1200, 22); @@ -394,6 +414,11 @@ namespace AGVSimulator.Forms this._coordLabel.Name = "_coordLabel"; this._coordLabel.Size = new System.Drawing.Size(0, 17); // + // prb1 + // + this.prb1.Name = "prb1"; + this.prb1.Size = new System.Drawing.Size(200, 16); + // // _controlPanel // this._controlPanel.BackColor = System.Drawing.SystemColors.Control; @@ -540,7 +565,6 @@ namespace AGVSimulator.Forms // // _agvControlGroup // - this._agvControlGroup.Controls.Add(this.btNextNode); this._agvControlGroup.Controls.Add(this._setPositionButton); this._agvControlGroup.Controls.Add(this._rfidTextBox); this._agvControlGroup.Controls.Add(this._rfidLabel); @@ -559,16 +583,6 @@ namespace AGVSimulator.Forms this._agvControlGroup.TabStop = false; this._agvControlGroup.Text = "AGV ์ œ์–ด"; // - // btNextNode - // - this.btNextNode.Location = new System.Drawing.Point(160, 183); - this.btNextNode.Name = "btNextNode"; - this.btNextNode.Size = new System.Drawing.Size(60, 21); - this.btNextNode.TabIndex = 10; - this.btNextNode.Text = "๋‹ค์Œ"; - this.btNextNode.UseVisualStyleBackColor = true; - this.btNextNode.Click += new System.EventHandler(this.btNextNode_Click); - // // _setPositionButton // this._setPositionButton.Location = new System.Drawing.Point(160, 138); @@ -685,6 +699,18 @@ namespace AGVSimulator.Forms this._agvInfoPanel.Size = new System.Drawing.Size(967, 80); this._agvInfoPanel.TabIndex = 5; // + // _pathDebugLabel + // + this._pathDebugLabel.BackColor = System.Drawing.Color.LightBlue; + this._pathDebugLabel.BorderStyle = System.Windows.Forms.BorderStyle.None; + this._pathDebugLabel.Font = new System.Drawing.Font("๊ตด๋ฆผ", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129))); + this._pathDebugLabel.Location = new System.Drawing.Point(10, 30); + this._pathDebugLabel.Multiline = true; + this._pathDebugLabel.Name = "_pathDebugLabel"; + this._pathDebugLabel.Size = new System.Drawing.Size(947, 43); + this._pathDebugLabel.TabIndex = 4; + this._pathDebugLabel.Text = "๊ฒฝ๋กœ: ์„ค์ •๋˜์ง€ ์•Š์Œ"; + // // _agvInfoTitleLabel // this._agvInfoTitleLabel.AutoSize = true; @@ -715,18 +741,6 @@ namespace AGVSimulator.Forms this._motorDirectionLabel.TabIndex = 2; this._motorDirectionLabel.Text = "๋ชจํ„ฐ ๋ฐฉํ–ฅ: -"; // - // _pathDebugLabel - // - this._pathDebugLabel.BackColor = System.Drawing.Color.LightBlue; - this._pathDebugLabel.BorderStyle = System.Windows.Forms.BorderStyle.None; - this._pathDebugLabel.Font = new System.Drawing.Font("๊ตด๋ฆผ", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129))); - this._pathDebugLabel.Location = new System.Drawing.Point(10, 30); - this._pathDebugLabel.Multiline = true; - this._pathDebugLabel.Name = "_pathDebugLabel"; - this._pathDebugLabel.Size = new System.Drawing.Size(947, 43); - this._pathDebugLabel.TabIndex = 4; - this._pathDebugLabel.Text = "๊ฒฝ๋กœ: ์„ค์ •๋˜์ง€ ์•Š์Œ"; - // // SimulatorForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F); @@ -827,7 +841,9 @@ namespace AGVSimulator.Forms private System.Windows.Forms.Label _liftDirectionLabel; private System.Windows.Forms.Label _motorDirectionLabel; private System.Windows.Forms.Label _agvInfoTitleLabel; - private System.Windows.Forms.Button btNextNode; private System.Windows.Forms.TextBox _pathDebugLabel; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator5; + private System.Windows.Forms.ToolStripButton toolStripButton1; + private System.Windows.Forms.ToolStripProgressBar prb1; } } \ No newline at end of file diff --git a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs index 899c527..adb7be7 100644 --- a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs +++ b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs @@ -4,6 +4,8 @@ using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; using AGVNavigationCore.Models; using AGVNavigationCore.Controls; @@ -28,7 +30,7 @@ namespace AGVSimulator.Forms private AGVPathfinder _advancedPathfinder; private List _agvList; private SimulationState _simulationState; - private Timer _simulationTimer; + private System.Windows.Forms.Timer _simulationTimer; private SimulatorConfig _config; private string _currentMapFilePath; private bool _isTargetCalcMode; // ํƒ€๊ฒŸ๊ณ„์‚ฐ ๋ชจ๋“œ ์ƒํƒœ @@ -76,7 +78,7 @@ namespace AGVSimulator.Forms CreateSimulatorCanvas(); // ํƒ€์ด๋จธ ์ดˆ๊ธฐํ™” - _simulationTimer = new Timer(); + _simulationTimer = new System.Windows.Forms.Timer(); _simulationTimer.Interval = 100; // 100ms ๊ฐ„๊ฒฉ _simulationTimer.Tick += OnSimulationTimer_Tick; @@ -268,6 +270,12 @@ namespace AGVSimulator.Forms } private void OnCalculatePath_Click(object sender, EventArgs e) + { + var rlt = CalcPath(); + if (rlt.result == false) MessageBox.Show(rlt.message, "์•Œ๋ฆผ", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + + (bool result, string message) CalcPath() { // ์‹œ์ž‘ RFID๊ฐ€ ์—†์œผ๋ฉด AGV ํ˜„์žฌ ์œ„์น˜๋กœ ์„ค์ • if (_startNodeCombo.SelectedItem == null || _startNodeCombo.Text == "์„ ํƒํ•˜์„ธ์š”") @@ -277,19 +285,17 @@ namespace AGVSimulator.Forms if (_startNodeCombo.SelectedItem == null || _targetNodeCombo.SelectedItem == null) { - MessageBox.Show("์‹œ์ž‘ RFID์™€ ๋ชฉํ‘œ RFID๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”.", "์•Œ๋ฆผ", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; + return (false, "์‹œ์ž‘ RFID์™€ ๋ชฉํ‘œ RFID๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”."); } var startItem = _startNodeCombo.SelectedItem as ComboBoxItem; var targetItem = _targetNodeCombo.SelectedItem as ComboBoxItem; var startNode = startItem?.Value; var targetNode = targetItem?.Value; - + if (startNode == null || targetNode == null) { - MessageBox.Show("์„ ํƒํ•œ ๋…ธ๋“œ ์ •๋ณด๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", "์˜ค๋ฅ˜", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; + return (false, "์„ ํƒํ•œ ๋…ธ๋“œ ์ •๋ณด๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค."); } @@ -300,10 +306,9 @@ namespace AGVSimulator.Forms // ํ˜„์žฌ AGV ๋ฐฉํ–ฅ ๊ฐ€์ ธ์˜ค๊ธฐ var selectedAGV = _agvListCombo.SelectedItem as VirtualAGV; - if(selectedAGV == null) + if (selectedAGV == null) { - MessageBox.Show("Virtual AGV ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค", "์˜ค๋ฅ˜", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; + return (false, "Virtual AGV ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค"); } var currentDirection = selectedAGV.CurrentDirection; @@ -331,14 +336,14 @@ namespace AGVSimulator.Forms // ๊ณ ๊ธ‰ ๊ฒฝ๋กœ ๋””๋ฒ„๊น… ์ •๋ณด ํ‘œ์‹œ UpdateAdvancedPathDebugInfo(advancedResult); + return (true, string.Empty); } else { // ๊ฒฝ๋กœ ์‹คํŒจ์‹œ ๋””๋ฒ„๊น… ์ •๋ณด ์ดˆ๊ธฐํ™” _pathDebugLabel.Text = $"๊ฒฝ๋กœ: ์‹คํŒจ - {advancedResult.ErrorMessage}"; - MessageBox.Show($"๊ฒฝ๋กœ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค:\n{advancedResult.ErrorMessage}", "๊ฒฝ๋กœ ๊ณ„์‚ฐ ์‹คํŒจ", - MessageBoxButtons.OK, MessageBoxIcon.Warning); + return (false, $"๊ฒฝ๋กœ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค:\n{advancedResult.ErrorMessage}"); } } @@ -559,7 +564,7 @@ namespace AGVSimulator.Forms var selectedDirection = selectedDirectionItem?.Direction ?? AgvDirection.Forward; //์ด์ „์œ„์น˜์™€ ๋™์ผํ•œ์ง€ ์ฒดํฌํ•œ๋‹ค. - if(selectedAGV.CurrentNodeId == targetNode.NodeId && selectedAGV.CurrentDirection == selectedDirection) + if (selectedAGV.CurrentNodeId == targetNode.NodeId && selectedAGV.CurrentDirection == selectedDirection) { Program.WriteLine($"์ด์ „ ๋…ธ๋“œ์œ„์น˜์™€ ๋ชจํ„ฐ์˜ ๋ฐฉํ–ฅ์ด ๋™์ผํ•˜์—ฌ ํ˜„์žฌ ์œ„์น˜ ๋ณ€๊ฒฝ์ด ์ทจ์†Œ๋ฉ๋‹ˆ๋‹ค(NODE:{targetNode.NodeId},RFID:{targetNode.RfidId},DIR:{selectedDirection})"); return; @@ -741,7 +746,7 @@ namespace AGVSimulator.Forms var nodeNamePart = !string.IsNullOrEmpty(node.Name) ? $" {node.Name}" : ""; var displayText = $"{node.RfidId} - [{node.NodeId}]{nodeNamePart}"; var item = new ComboBoxItem(node, displayText); - + _startNodeCombo.Items.Add(item); _targetNodeCombo.Items.Add(item); } @@ -1106,7 +1111,7 @@ namespace AGVSimulator.Forms _pathDebugLabel.Text = $"๊ณ ๊ธ‰๊ฒฝ๋กœ: {pathString} (์ด {advancedResult.DetailedPath.Count}๊ฐœ ๋…ธ๋“œ, {advancedResult.TotalDistance:F1}px, {stats})"; } - + private void OnReloadMap_Click(object sender, EventArgs e) { @@ -1218,26 +1223,355 @@ namespace AGVSimulator.Forms _statusLabel.Text = "์ดˆ๊ธฐํ™” ์™„๋ฃŒ"; } - private void btNextNode_Click(object sender, EventArgs e) + + private async void toolStripButton1_Click(object sender, EventArgs e) { - //get next node - - // ์„ ํƒ๋œ AGV ํ™•์ธ - var selectedAGV = _agvListCombo.SelectedItem as VirtualAGV; - if (selectedAGV == null) + // ๋งต๊ณผ AGV ํ™•์ธ + if (_mapNodes == null || _mapNodes.Count == 0) { - MessageBox.Show("๋จผ์ € AGV๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”.", "์•Œ๋ฆผ", MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show("๋งต ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋จผ์ € ๋งต์„ ๋กœ๋“œํ•ด์ฃผ์„ธ์š”.", "์•Œ๋ฆผ", + MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } - // ์„ ํƒ๋œ ๋ฐฉํ–ฅ ํ™•์ธ - var selectedDirectionItem = _directionCombo.SelectedItem as DirectionItem; - var selectedDirection = selectedDirectionItem?.Direction ?? AgvDirection.Forward; + var selectedAGV = _agvListCombo.SelectedItem as VirtualAGV; + if (selectedAGV == null) + { + MessageBox.Show("ํ…Œ์ŠคํŠธํ•  AGV๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”.", "์•Œ๋ฆผ", + MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } - var nextNode = selectedAGV.GetNextNodeId(selectedDirection, this._mapNodes); - MessageBox.Show($"Node:{nextNode.NodeId},RFID:{nextNode.RfidId}"); + // ๋„ํ‚น ํƒ€๊ฒŸ ๋…ธ๋“œ ์ฐพ๊ธฐ + var dockingTargets = _mapNodes.Where(n => + n.Type == NodeType.Charging || n.Type == NodeType.Docking).ToList(); + if (dockingTargets.Count == 0) + { + MessageBox.Show("๋„ํ‚น ํƒ€๊ฒŸ(์ถฉ์ „๊ธฐ ๋˜๋Š” ์žฅ๋น„)์ด ์—†์Šต๋‹ˆ๋‹ค.", "์•Œ๋ฆผ", + MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + // ์—ฐ๊ฒฐ๋œ ๋…ธ๋“œ ์Œ ์ฐพ๊ธฐ (์‚ฌ์ „ ๊ณ„์‚ฐ) + var nodePairs = GetConnectedNodePairs(); + var testCount = nodePairs.Count * dockingTargets.Count * 2; // ๋…ธ๋“œ ์Œ x ๋„ํ‚น ํƒ€๊ฒŸ x ๋ฐฉํ–ฅ(2) + + // ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ํ™•์ธ + var result = MessageBox.Show( + $"๊ฒฝ๋กœ ์˜ˆ์ธก ํ…Œ์ŠคํŠธ๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.\n\n" + + $"โ€ป ์‹ค์ œ ์‚ฌ์šฉ์ž ์‹œ๋‚˜๋ฆฌ์˜ค ์žฌํ˜„ ๋ฐฉ์‹:\n" + + $" ์—ฐ๊ฒฐ๋œ ๋…ธ๋“œ ์Œ์„ ๋”ฐ๋ผ AGV๋ฅผ 2๋ฒˆ ์ด๋™์‹œ์ผœ\n" + + $" ๋ฐฉํ–ฅ์„ ํ™•์ •ํ•œ ํ›„ ๊ฐ ๋„ํ‚น ํƒ€๊ฒŸ์œผ๋กœ ๊ฒฝ๋กœ ๊ณ„์‚ฐ\n\n" + + $"โ€ข ์—ฐ๊ฒฐ๋œ ๋…ธ๋“œ ์Œ: {nodePairs.Count}๊ฐœ\n" + + $"โ€ข ๋„ํ‚น ํƒ€๊ฒŸ: {dockingTargets.Count}๊ฐœ (์ถฉ์ „๊ธฐ/์žฅ๋น„)\n" + + $"โ€ข AGV ๋ฐฉํ–ฅ: 2๊ฐ€์ง€ (์ •๋ฐฉํ–ฅ/์—ญ๋ฐฉํ–ฅ)\n" + + $"โ€ข ์ด ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค: {testCount}๊ฐœ\n\n" + + $"โ€ป UI๊ฐ€ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค.\n" + + $"๊ณ„์†ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", + "๊ฒฝ๋กœ ์˜ˆ์ธก ํ…Œ์ŠคํŠธ ํ™•์ธ", + MessageBoxButtons.YesNo, + MessageBoxIcon.Question); + + if (result != DialogResult.Yes) + return; + + // ๋กœ๊ทธ ํผ ์ƒ์„ฑ ๋ฐ ํ‘œ์‹œ + var logForm = new ProgressLogForm(); + logForm.Show(this); + logForm.UpdateProgress(0, testCount); + logForm.UpdateStatus("ํ…Œ์ŠคํŠธ ์ค€๋น„ ์ค‘..."); + + // ๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ ์‹œ์ž‘ + await Task.Run(() => RunPathPredictionTest(selectedAGV, dockingTargets, logForm)); + + // ์™„๋ฃŒ + if (logForm.CancelRequested) + { + logForm.SetCancelled(); + MessageBox.Show("ํ…Œ์ŠคํŠธ๊ฐ€ ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", "์•Œ๋ฆผ", + MessageBoxButtons.OK, MessageBoxIcon.Information); + } + else + { + logForm.SetCompleted(); + MessageBox.Show("๊ฒฝ๋กœ ์˜ˆ์ธก ํ…Œ์ŠคํŠธ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", "์™„๋ฃŒ", + MessageBoxButtons.OK, MessageBoxIcon.Information); + } } + + /// + /// ๋…ธ๋“œ์˜ ํ‘œ์‹œ ์ด๋ฆ„ ๊ฐ€์ ธ์˜ค๊ธฐ (RFID ์šฐ์„ , ์—†์œผ๋ฉด (NodeId)) + /// + private string GetNodeDisplayName(MapNode node) + { + if (node == null) + return "-"; + + if (!string.IsNullOrEmpty(node.RfidId)) + return node.RfidId; + + return $"({node.NodeId})"; + } + + /// + /// ๋ฐฉํ–ฅ ์ฝค๋ณด๋ฐ•์Šค ์„ ํƒ (ํ…Œ์ŠคํŠธ์šฉ) + /// + private void SetDirectionComboBox(AgvDirection direction) + { + for (int i = 0; i < _directionCombo.Items.Count; i++) + { + var item = _directionCombo.Items[i] as DirectionItem; + if (item != null && item.Direction == direction) + { + _directionCombo.SelectedIndex = i; + return; + } + } + } + + /// + /// ๋ชฉํ‘œ ๋…ธ๋“œ ์ฝค๋ณด๋ฐ•์Šค ์„ ํƒ (ํ…Œ์ŠคํŠธ์šฉ) + /// + private void SetTargetNodeComboBox(string nodeId) + { + for (int i = 0; i < _targetNodeCombo.Items.Count; i++) + { + var item = _targetNodeCombo.Items[i] as ComboBoxItem; + if (item?.Value?.NodeId == nodeId) + { + _targetNodeCombo.SelectedIndex = i; + return; + } + } + } + + /// + /// UI ์ƒํƒœ๋กœ๋ถ€ํ„ฐ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ์ƒ์„ฑ (ํ…Œ์ŠคํŠธ์šฉ) + /// + private PathTestLogItem CreateTestResultFromUI(MapNode prevNode, MapNode targetNode, + string directionName, (bool result, string message) calcResult) + { + var currentNode = _mapNodes.FirstOrDefault(n => n.NodeId == + (_agvListCombo.SelectedItem as VirtualAGV)?.CurrentNodeId); + + var logItem = new PathTestLogItem + { + PreviousPosition = GetNodeDisplayName(prevNode), + MotorDirection = directionName, + CurrentPosition = GetNodeDisplayName(currentNode), + TargetPosition = GetNodeDisplayName(targetNode), + DockingPosition = targetNode.Type == NodeType.Charging ? "์ถฉ์ „๊ธฐ" : "์žฅ๋น„" + }; + + if (calcResult.result) + { + // ๊ฒฝ๋กœ ๊ณ„์‚ฐ ์„ฑ๊ณต - ํ˜„์žฌ ํ™”๋ฉด์— ํ‘œ์‹œ๋œ ๊ฒฝ๋กœ ์ •๋ณด ์‚ฌ์šฉ + var currentPath = _simulatorCanvas.CurrentPath; + if (currentPath != null && currentPath.Success) + { + // ๋„ํ‚น ๊ฒ€์ฆ + var dockingValidation = DockingValidator.ValidateDockingDirection(currentPath, _mapNodes); + + if (dockingValidation.IsValid) + { + logItem.Success = true; + logItem.Message = "์„ฑ๊ณต"; + logItem.DetailedPath = string.Join(" โ†’ ", currentPath.GetDetailedInfo()); + } + else + { + logItem.Success = false; + logItem.Message = $"๋„ํ‚น ๊ฒ€์ฆ ์‹คํŒจ: {dockingValidation.ValidationError}"; + logItem.DetailedPath = string.Join(" โ†’ ", currentPath.GetDetailedInfo()); + } + } + else + { + logItem.Success = true; + logItem.Message = "๊ฒฝ๋กœ ๊ณ„์‚ฐ ์„ฑ๊ณต"; + logItem.DetailedPath = "-"; + } + } + else + { + // ๊ฒฝ๋กœ ๊ณ„์‚ฐ ์‹คํŒจ + logItem.Success = false; + logItem.Message = calcResult.message; + logItem.DetailedPath = "-"; + } + + return logItem; + } + + /// + /// ์—ฐ๊ฒฐ๋œ ๋…ธ๋“œ ์Œ ์ฐพ๊ธฐ (Aโ†’B ํ˜•ํƒœ) + /// + private List<(MapNode nodeA, MapNode nodeB)> GetConnectedNodePairs() + { + var pairs = new List<(MapNode, MapNode)>(); + var processedPairs = new HashSet(); + + foreach (var nodeA in _mapNodes) + { + if (nodeA.ConnectedNodes == null || nodeA.ConnectedNodes.Count == 0) + continue; + + // ์—ฐ๊ฒฐ๋œ ๋…ธ๋“œ ID๋ฅผ ์‹ค์ œ MapNode ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ + foreach (var connectedNodeId in nodeA.ConnectedNodes) + { + var nodeB = _mapNodes.FirstOrDefault(n => n.NodeId == connectedNodeId); + if (nodeB == null) + continue; + + // ์ค‘๋ณต ๋ฐฉ์ง€ (Aโ†’B์™€ Bโ†’A๋ฅผ ๊ฐ™์€ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ) + var pairKey1 = $"{nodeA.NodeId}โ†’{nodeB.NodeId}"; + var pairKey2 = $"{nodeB.NodeId}โ†’{nodeA.NodeId}"; + + if (!processedPairs.Contains(pairKey1) && !processedPairs.Contains(pairKey2)) + { + pairs.Add((nodeA, nodeB)); + processedPairs.Add(pairKey1); + processedPairs.Add(pairKey2); + } + } + } + + return pairs; + } + + /// + /// ๊ฒฝ๋กœ ์˜ˆ์ธก ํ…Œ์ŠคํŠธ ์‹คํ–‰ (์‹ค์ œ ์‚ฌ์šฉ์ž ์‹œ๋‚˜๋ฆฌ์˜ค ์žฌํ˜„) + /// + private void RunPathPredictionTest(VirtualAGV agv, List dockingTargets, ProgressLogForm logForm) + { + var directions = new[] + { + (AgvDirection.Forward, "์ •๋ฐฉํ–ฅ"), + (AgvDirection.Backward, "์—ญ๋ฐฉํ–ฅ") + }; + + // ์—ฐ๊ฒฐ๋œ ๋…ธ๋“œ ์Œ ์ฐพ๊ธฐ + var nodePairs = GetConnectedNodePairs(); + + int totalTests = nodePairs.Count * dockingTargets.Count * 2; + int currentTest = 0; + int successCount = 0; + int failCount = 0; + + logForm.UpdateStatus("๊ฒฝ๋กœ ์˜ˆ์ธก ํ…Œ์ŠคํŠธ ์ง„ํ–‰ ์ค‘..."); + logForm.AppendLog($"ํ…Œ์ŠคํŠธ ์‹œ์ž‘: ์ด {totalTests}๊ฐœ ์ผ€์ด์Šค"); + logForm.AppendLog($"์—ฐ๊ฒฐ๋œ ๋…ธ๋“œ ์Œ: {nodePairs.Count}๊ฐœ"); + logForm.AppendLog($"๋„ํ‚น ํƒ€๊ฒŸ: {dockingTargets.Count}๊ฐœ"); + logForm.AppendLog("---"); + + // ๊ฐ ์—ฐ๊ฒฐ๋œ ๋…ธ๋“œ ์Œ์— ๋Œ€ํ•ด ํ…Œ์ŠคํŠธ + foreach (var (nodeA, nodeB) in nodePairs) + { + foreach (var (direction, directionName) in directions) + { + // ์ทจ์†Œ ํ™•์ธ + if (logForm.CancelRequested) + { + logForm.AppendLog($"ํ…Œ์ŠคํŠธ ์ทจ์†Œ๋จ - {currentTest}/{totalTests} ์™„๋ฃŒ"); + return; + } + + // === ์‹ค์ œ ์‚ฌ์šฉ์ž ์›Œํฌํ”Œ๋กœ์šฐ ์žฌํ˜„ === + // 1๋‹จ๊ณ„: AGV๋ฅผ nodeA๋กœ ์ด๋™ (์‹ค์ œ UI ์กฐ์ž‘) + this.Invoke((MethodInvoker)delegate + { + // RFID ํ…์ŠคํŠธ๋ฐ•์Šค์— ๊ฐ’ ์ž…๋ ฅ + _rfidTextBox.Text = nodeA.RfidId; + + // ๋ฐฉํ–ฅ ์ฝค๋ณด๋ฐ•์Šค ์„ ํƒ + SetDirectionComboBox(direction); + + // ์œ„์น˜์„ค์ • ๋ฒ„ํŠผ ํด๋ฆญ (์‹ค์ œ ์‚ฌ์šฉ์ž ๋™์ž‘) + SetAGVPositionByRfid(); + + Application.DoEvents(); // UI ์—…๋ฐ์ดํŠธ + }); + Thread.Sleep(100); // ์‹œ๊ฐ์  ํšจ๊ณผ + + // 2๋‹จ๊ณ„: AGV๋ฅผ nodeB๋กœ ์ด๋™ (๋ฐฉํ–ฅ ํ™•์ •๋จ) + this.Invoke((MethodInvoker)delegate + { + // RFID ํ…์ŠคํŠธ๋ฐ•์Šค์— ๊ฐ’ ์ž…๋ ฅ + _rfidTextBox.Text = nodeB.RfidId; + + // ๋ฐฉํ–ฅ ์ฝค๋ณด๋ฐ•์Šค ์„ ํƒ + SetDirectionComboBox(direction); + + // ์œ„์น˜์„ค์ • ๋ฒ„ํŠผ ํด๋ฆญ (์‹ค์ œ ์‚ฌ์šฉ์ž ๋™์ž‘) + SetAGVPositionByRfid(); + + Application.DoEvents(); // UI ์—…๋ฐ์ดํŠธ + }); + Thread.Sleep(100); // ์‹œ๊ฐ์  ํšจ๊ณผ + + // 3๋‹จ๊ณ„: nodeB ์œ„์น˜์—์„œ ๋ชจ๋“  ๋„ํ‚น ํƒ€๊ฒŸ์œผ๋กœ ๊ฒฝ๋กœ ์˜ˆ์ธก + foreach (var dockingTarget in dockingTargets) + { + // ์ทจ์†Œ ํ™•์ธ + if (logForm.CancelRequested) + { + logForm.AppendLog($"ํ…Œ์ŠคํŠธ ์ทจ์†Œ๋จ - {currentTest}/{totalTests} ์™„๋ฃŒ"); + return; + } + + currentTest++; + + // UI ์Šค๋ ˆ๋“œ์—์„œ ๊ฒฝ๋กœ ๊ณ„์‚ฐ ๋ฐ ํ…Œ์ŠคํŠธ (์‹ค์ œ UI ์‚ฌ์šฉ) + PathTestLogItem testResult = null; + this.Invoke((MethodInvoker)delegate + { + // ์ง„ํ–‰์ƒํ™ฉ ์—…๋ฐ์ดํŠธ + logForm.UpdateProgress(currentTest, totalTests); + logForm.UpdateStatus($"ํ…Œ์ŠคํŠธ ์ง„ํ–‰ ์ค‘... ({currentTest}/{totalTests}) [{GetNodeDisplayName(nodeA)}โ†’{GetNodeDisplayName(nodeB)}โ†’{GetNodeDisplayName(dockingTarget)}]"); + prb1.Value = (int)((double)currentTest / totalTests * 100); + + // ๋ชฉํ‘œ ๋…ธ๋“œ ์ฝค๋ณด๋ฐ•์Šค ์„ ํƒ + SetTargetNodeComboBox(dockingTarget.NodeId); + + // ๊ฒฝ๋กœ ๊ณ„์‚ฐ ๋ฒ„ํŠผ ํด๋ฆญ (์‹ค์ œ ์‚ฌ์šฉ์ž ๋™์ž‘) + var calcResult = CalcPath(); + + // ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ์ƒ์„ฑ + testResult = CreateTestResultFromUI(nodeA, dockingTarget, directionName, calcResult); + + // ๋กœ๊ทธ ์ถ”๊ฐ€ + logForm.AddLogItem(testResult); + + // ์‹คํŒจํ•œ ๊ฒฝ์šฐ์—๋งŒ ๊ฒฝ๋กœ๋ฅผ ํ™”๋ฉด์— ํ‘œ์‹œ (์‹œ๊ฐ์  ํ™•์ธ) + if (!testResult.Success && _simulatorCanvas.CurrentPath != null) + { + _simulatorCanvas.Invalidate(); + } + + Application.DoEvents(); + }); + + if (testResult.Success) + successCount++; + else + failCount++; + + // UI ๋ฐ˜์‘์„ฑ์„ ์œ„ํ•œ ์งง์€ ๋Œ€๊ธฐ + Thread.Sleep(50); + } + } + } + + // ์ตœ์ข… ๊ฒฐ๊ณผ + logForm.AppendLog($""); + logForm.AppendLog($"=== ํ…Œ์ŠคํŠธ ์™„๋ฃŒ ==="); + logForm.AppendLog($"์ด ํ…Œ์ŠคํŠธ: {totalTests}"); + logForm.AppendLog($"์„ฑ๊ณต: {successCount}"); + logForm.AppendLog($"์‹คํŒจ: {failCount}"); + logForm.AppendLog($"์„ฑ๊ณต๋ฅ : {(double)successCount / totalTests * 100:F1}%"); + } + } /// diff --git a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.resx b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.resx index 35cb4b7..3d64678 100644 --- a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.resx +++ b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.resx @@ -123,6 +123,22 @@ 132, 17 + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK + YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X + /aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t + I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM + cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh + 6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD + lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A + HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb + 1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC + nOccAdABIDXXE1nzAAAAAElFTkSuQmCC + + 237, 17