From 3408e3fc305a449af669f26baf11e0455a1a74a7 Mon Sep 17 00:00:00 2001 From: backuppc Date: Mon, 22 Dec 2025 16:02:57 +0900 Subject: [PATCH] ... --- .../Controls/UnifiedAGVCanvas.Events.cs | 85 ++++++++++++++++++- .../AGVNavigationCore/Models/Enums.cs | 8 +- .../AGVNavigationCore/Models/MapNode.cs | 2 +- .../PathFinding/Planning/AGVPathfinder.cs | 2 + .../PathFinding/Planning/NodeMotorInfo.cs | 2 +- Cs_HMI/Project/StateMachine/Step/_Util.cs | 13 +++ Cs_HMI/Project/StateMachine/_SPS.cs | 2 +- Cs_HMI/Project/StateMachine/_Xbee.cs | 17 +++- 8 files changed, 123 insertions(+), 8 deletions(-) diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs index 79eb226..da3bcf1 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs @@ -815,6 +815,9 @@ namespace AGVNavigationCore.Controls case StationType.Charger: DrawTriangleNodeShape(g, node, brush); break; + case StationType.Limit: + DrawRectangleNodeShape(g, node, brush); + break; default: DrawCircleNodeShape(g, node, brush); break; @@ -823,6 +826,83 @@ namespace AGVNavigationCore.Controls } + private void DrawRectangleNodeShape(Graphics g, MapNode node, Brush brush) + { + // 드래그 중인 노드는 약간 크게 그리기 + bool isDraggingThisNode = _isDragging && node == _selectedNode; + int sizeAdjustment = isDraggingThisNode ? 4 : 0; + + var rect = new Rectangle( + node.Position.X - NODE_RADIUS - sizeAdjustment, + node.Position.Y - NODE_RADIUS - sizeAdjustment, + NODE_SIZE + sizeAdjustment * 2, + NODE_SIZE + sizeAdjustment * 2 + ); + + // 드래그 중인 노드의 그림자 효과 + if (isDraggingThisNode) + { + var shadowRect = new Rectangle(rect.X + 3, rect.Y + 3, rect.Width, rect.Height); + using (var shadowBrush = new SolidBrush(Color.FromArgb(100, 0, 0, 0))) + { + g.FillRectangle(shadowBrush, shadowRect); + } + } + + // 노드 그리기 + g.FillRectangle(brush, rect); + g.DrawRectangle(Pens.Black, rect); + + // 드래그 중인 노드 강조 (가장 강력한 효과) + if (isDraggingThisNode) + { + // 청록색 두꺼운 테두리 + g.DrawRectangle(new Pen(Color.Cyan, 3), rect); + // 펄스 효과 + var pulseRect = new Rectangle(rect.X - 4, rect.Y - 4, rect.Width + 8, rect.Height + 8); + g.DrawRectangle(new Pen(Color.FromArgb(150, 0, 255, 255), 2) { DashStyle = DashStyle.Dash }, pulseRect); + } + // 선택된 노드 강조 (단일 또는 다중) + else if (node == _selectedNode || (_selectedNodes != null && _selectedNodes.Contains(node))) + { + g.DrawRectangle(_selectedNodePen, rect); + } + + // 목적지 노드 강조 + if (node == _destinationNode) + { + // 금색 테두리로 목적지 강조 + g.DrawRectangle(_destinationNodePen, rect); + + // 펄싱 효과를 위한 추가 원 그리기 + var pulseRect = new Rectangle(rect.X - 3, rect.Y - 3, rect.Width + 6, rect.Height + 6); + g.DrawRectangle(new Pen(Color.Gold, 2) { DashStyle = DashStyle.Dash }, pulseRect); + } + + // 호버된 노드 강조 (드래그 중이 아닐 때만) + if (node == _hoveredNode && !isDraggingThisNode) + { + var hoverRect = new Rectangle(rect.X - 2, rect.Y - 2, rect.Width + 4, rect.Height + 4); + g.DrawRectangle(new Pen(Color.Orange, 2), hoverRect); + } + + // RFID 중복 노드 표시 (빨간 X자) + if (_duplicateRfidNodes.Contains(node.Id)) + { + DrawDuplicateRfidMarker(g, node); + } + + // CanCross 가능 노드 표시 (교차지점으로 사용 가능) + if (node.DisableCross == true) + { + var crossRect = new Rectangle(rect.X - 3, rect.Y - 3, rect.Width + 6, rect.Height + 6); + g.DrawRectangle(new Pen(Color.DeepSkyBlue, 3), crossRect); + } + + g.DrawLine(Pens.Black, rect.X, rect.Y, rect.Right, rect.Bottom); + g.DrawLine(Pens.Black, rect.Right, rect.Top, rect.X, rect.Bottom); + + } private void DrawCircleNodeShape(Graphics g, MapNode node, Brush brush) { // 드래그 중인 노드는 약간 크게 그리기 @@ -1472,9 +1552,10 @@ namespace AGVNavigationCore.Controls case StationType.Normal: bgColor = Color.DeepSkyBlue; break; case StationType.Charger: bgColor = Color.Tomato; break; case StationType.Loader: - case StationType.UnLoader: bgColor = Color.Gold ; break; - case StationType.Clearner: bgColor = Color.DeepSkyBlue ; break; + case StationType.UnLoader: bgColor = Color.Gold; break; + case StationType.Clearner: bgColor = Color.DeepSkyBlue; break; case StationType.Buffer: bgColor = Color.WhiteSmoke; break; + case StationType.Limit: bgColor = Color.Red; break; default: bgColor = Color.White; break; } diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/Enums.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/Enums.cs index b46a4b5..b3bea81 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/Enums.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/Enums.cs @@ -73,7 +73,13 @@ namespace AGVNavigationCore.Models /// 버퍼 Buffer, /// 충전기 - Charger + Charger, + + /// + /// 끝점(더이상 이동불가) + /// + Limit, + } /// diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs index 51e3702..1d84d99 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs @@ -149,7 +149,7 @@ namespace AGVNavigationCore.Models public override string ToString() { - return $"{RfidId}({Id}): {AliasName} ({Type}) at ({Position.X}, {Position.Y})"; + return $"RFID:{RfidId}(NODE:{Id}): AS:{AliasName} ({Type}) at ({Position.X}, {Position.Y})"; } public bool IsNavigationNode() diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs index 0ad07df..6514dd5 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs @@ -464,6 +464,8 @@ namespace AGVNavigationCore.PathFinding.Planning { nodeInfo.Speed = mapNode.SpeedLimit; } + + detailedPath1.Add(nodeInfo); } // path1에 상세 경로 정보 설정 diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs index 597fc25..5c5cb71 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/NodeMotorInfo.cs @@ -108,7 +108,7 @@ namespace AGVNavigationCore.PathFinding.Planning /// public override string ToString() { - var result = $"{RfidId}[{NodeId}]:{MotorDirection}"; + var result = $"R{RfidId}[N{NodeId}]:{MotorDirection}"; // 마그넷 방향이 직진이 아닌 경우 표시 if (MagnetDirection != MagnetDirection.Straight) diff --git a/Cs_HMI/Project/StateMachine/Step/_Util.cs b/Cs_HMI/Project/StateMachine/Step/_Util.cs index 519bc6b..64e7d53 100644 --- a/Cs_HMI/Project/StateMachine/Step/_Util.cs +++ b/Cs_HMI/Project/StateMachine/Step/_Util.cs @@ -117,6 +117,11 @@ namespace Project PUB.sm.SetNewRunStep(ERunStep.READY); return false; } + else + { + PUB._mapCanvas.CurrentPath = PathResult.result; + PUB._virtualAGV.SetPath(PathResult.result); + } PUB.log.AddI($"경로생성 {PUB._virtualAGV.StartNode.RfidId} -> {PUB._virtualAGV.TargetNode.RfidId}"); } @@ -133,8 +138,16 @@ namespace Project return false; } + + //predict 를 이용하여 다음 이동을 모두 확인한다. var nextAction = PUB._virtualAGV.Predict(); + if(nextAction.Reason == AGVNavigationCore.Models.eAGVCommandReason.PathOut) + { + //경로이탈 + PUB._virtualAGV.CurrentPath.DetailedPath.Clear(); + return false; + } var message = $"[다음 행동 예측]\n\n" + $"모터: {nextAction.Motor}\n" + diff --git a/Cs_HMI/Project/StateMachine/_SPS.cs b/Cs_HMI/Project/StateMachine/_SPS.cs index 2b55fa3..47f7f86 100644 --- a/Cs_HMI/Project/StateMachine/_SPS.cs +++ b/Cs_HMI/Project/StateMachine/_SPS.cs @@ -72,7 +72,7 @@ namespace Project else if (PUB.BMS.IsValid == false) { var ts = VAR.TIME.RUN(eVarTime.LastConnTry_BAT); - if (ts.TotalSeconds > (PUB.setting.interval_bms * 2.5)) + if (ts.TotalSeconds > ( Math.Max(10,PUB.setting.interval_bms) * 2.5)) { this.BeginInvoke(new Action(() => { diff --git a/Cs_HMI/Project/StateMachine/_Xbee.cs b/Cs_HMI/Project/StateMachine/_Xbee.cs index 00504e7..2634f70 100644 --- a/Cs_HMI/Project/StateMachine/_Xbee.cs +++ b/Cs_HMI/Project/StateMachine/_Xbee.cs @@ -113,7 +113,7 @@ namespace Project case ENIGProtocol.AGVCommandHE.GotoAlias: case ENIGProtocol.AGVCommandHE.Goto: //move to tag - var datalength = cmd == ENIGProtocol.AGVCommandHE.GotoAlias ? 1 : 4; + var datalength = cmd == ENIGProtocol.AGVCommandHE.GotoAlias ? 2 : 1; if (data.Length > datalength) { var currTag = System.Text.Encoding.Default.GetString(data, 1, data.Length - 1).Trim(); @@ -255,7 +255,7 @@ namespace Project break; default: - PUB.logagv.AddE($"Unknown Command : {cmd}"); + PUB.logagv.AddE($"Unknown Command : {cmd} Sender:{e.ReceivedPacket.ID}, Target:{data[0]}"); PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.UnknownCommand, $"{cmd}"); break; } @@ -301,6 +301,19 @@ namespace Project if (advancedResult.DockingValidation == null || !advancedResult.DockingValidation.IsValidationRequired) advancedResult.DockingValidation = DockingValidator.ValidateDockingDirection(advancedResult, _mapNodes); + //마지막대상이 버퍼라면 시퀀스처리를 해야한다 + if (targetNode.StationType == StationType.Buffer&& advancedResult.DetailedPath.Any()) + { + var lastDetailPath = advancedResult.DetailedPath.Last(); + if (lastDetailPath.NodeId == targetNode.Id) //마지막노드 재확인 + { + //버퍼에 도킹할때에는 마지막 노드에서 멈추고 시퀀스를 적용해야한다 + advancedResult.DetailedPath = advancedResult.DetailedPath.Take(advancedResult.DetailedPath.Count - 1).ToList(); + Console.WriteLine("최종위치가 버퍼이므로 마지막 RFID에서 멈추도록 합니다"); + } + } + + _simulatorCanvas.CurrentPath = advancedResult; //_pathLengthLabel.Text = $"경로 길이: {advancedResult.TotalDistance:F1}"; //_statusLabel.Text = $"경로 계산 완료 ({advancedResult.CalculationTimeMs}ms)";