This commit is contained in:
chi
2025-06-17 17:34:26 +09:00
parent 4aee057726
commit 461a083a06
15 changed files with 614 additions and 165 deletions

View File

@@ -24,6 +24,7 @@ namespace AGVControl
public bool IsBidirectional { get; set; }
public float Distance { get; set; }
public List<uint> IntermediateRFIDs { get; set; } = new List<uint>();
public override bool Equals(object obj)
{
@@ -81,7 +82,7 @@ namespace AGVControl
private const int SELECTION_DISTANCE = 15; // 선택 가능 거리를 늘림
private bool isDraggingText = false;
private Point dragOffset;
private List<Point> currentPath;
// private List<Point> currentPath;
public MapText SelectedText => selectedText;
@@ -210,6 +211,7 @@ namespace AGVControl
public RFIDPoint FindRFIDPoint(uint rfidValue)
{
if (rfidPoints == null || rfidPoints.Any() == false) return null;
return rfidPoints.FirstOrDefault(r => r.RFIDValue == rfidValue);
}
@@ -220,6 +222,57 @@ namespace AGVControl
{
agv.CurrentPosition = rfidPoint.Location;
agv.CurrentRFID = rfidValue;
// 목적지가 설정되어 있고 경로가 있는 경우 검증
if (agv.TargetPosition != Point.Empty && agv.CurrentPath.Count > 0)
{
// 현재 위치가 경로에 있는지 확인
bool isOnPath = agv.CurrentPath.Contains(agv.CurrentPosition);
if (!isOnPath)
{
// 경로를 벗어났으므로 새로운 경로 계산
var pathResult = CalculatePath(agv.CurrentRFID, agv.TargetRFID);
if (pathResult.Success)
{
SetCurrentPath(pathResult.Path);
}
}
// AGV의 방향 결정
if (agv.CurrentPath.Count > 0)
{
// 다음 목적지 찾기
var nextPoint = agv.CurrentPath[0];
// 회전 가능 여부를 고려하여 방향 결정
agv.TargetDirection = DetermineDirection(agv.CurrentPosition, nextPoint, agv.TargetPosition);
}
}
// 목적지 RFID에 도착했고, 해당 RFID에 고정방향이 있으면 TargetDirection을 강제 설정
if (agv.TargetRFID == rfidValue)
{
var destRFID = FindRFIDPoint(rfidValue);
if (destRFID != null && destRFID.FixedDirection.HasValue)
{
agv.TargetDirection = destRFID.FixedDirection.Value;
}
}
this.Invalidate();
return true;
}
return false;
}
public bool SetTargetPosition(uint rfidValue)
{
var rfidPoint = FindRFIDPoint(rfidValue);
if (rfidPoint != null)
{
agv.TargetPosition = rfidPoint.Location;
agv.TargetRFID = rfidValue;
this.Invalidate();
return true;
}
@@ -566,7 +619,7 @@ namespace AGVControl
if (path.Count > 0)
{
DrawPath(path);
SetCurrentPath(path);
}
}
@@ -605,22 +658,22 @@ namespace AGVControl
return retval;
}
var path = CalculatePath(startPoint.Location, endPoint.Location);
if (path != null && path.Count > 0)
retval.Path = CalculatePath(startPoint.Location, endPoint.Location);
if (retval.Path != null && retval.Path.Any())
{
DrawPath(path);
//SetCurrentPath(retval.Path);
// 경로 상의 모든 RFID 값을 가져옴
var rfidPath = new List<uint>();
foreach (var point in path)
{
var rfid = GetRFIDPoints()
.FirstOrDefault(r => r.Location == point);
if (rfid != null)
{
rfidPath.Add(rfid.RFIDValue);
}
}
//// 경로 상의 모든 RFID 값을 가져옴
//var rfidPath = new List<uint>();
//foreach (var point in path)
//{
// var rfid = GetRFIDPoints()
// .FirstOrDefault(r => r.Location == point);
// if (rfid != null)
// {
// rfidPath.Add(rfid.RFIDValue);
// }
//}
retval.Success = true;
}
else
@@ -683,8 +736,13 @@ namespace AGVControl
}
else
{
SetCurrentPath(rlt.Path); //현재 경로로 설정함
MessageBox.Show($"경로가 계산되었습니다.\nRFID 순서: {string.Join(" -> ", rlt.Path)}",
"경로 계산", MessageBoxButtons.OK, MessageBoxIcon.Information);
if (SetTargetPosition(vend) == false)
{
MessageBox.Show("목적지 설정 실패");
}
}
break;
case "pos":
@@ -700,12 +758,12 @@ namespace AGVControl
od.Filter = "path data|*.route";
od.FilterIndex = 0;
od.RestoreDirectory = true;
if(filename.isEmpty()==false)
if (filename.isEmpty() == false)
{
od.FileName = System.IO.Path.GetFileName(filename);
od.InitialDirectory = System.IO.Path.GetDirectoryName(filename);
}
if (od.ShowDialog() == DialogResult.OK)
{
filename = od.FileName;
@@ -734,7 +792,7 @@ namespace AGVControl
if (od.ShowDialog() == DialogResult.OK)
{
this.LoadFromFile(filename, out string errmsg);
if (errmsg.isEmpty() == false) UTIL.MsgE(errmsg);
this.Invalidate();
@@ -935,20 +993,21 @@ namespace AGVControl
if (e.Button == MouseButtons.Left)
{
// 화면 좌표를 맵 좌표로 변환
var mapPoint = ScreenToMap(e.Location);
//RFID 포인트 찾기
var selected_rfid = rfidPoints.Where(t => t.Bounds.Expand(SELECTION_DISTANCE, SELECTION_DISTANCE).Contains(mapPoint)).FirstOrDefault();
if (selected_rfid != null)
{
var rlt = AR.UTIL.InputBox("rfid value", selected_rfid.RFIDValue.ToString());
if (rlt.Item1 == true)
{
selected_rfid.RFIDValue = uint.Parse(rlt.Item2);// dialog.InputText;
ProcessRFIDConnections();
this.Invalidate();
}
// 고정방향 순환 변경: 없음→전진→후진→없음
if (!selected_rfid.FixedDirection.HasValue)
selected_rfid.FixedDirection = Direction.Forward;
else if (selected_rfid.FixedDirection == Direction.Forward)
selected_rfid.FixedDirection = Direction.Backward;
else
selected_rfid.FixedDirection = null;
this.Invalidate();
return;
}
@@ -959,7 +1018,7 @@ namespace AGVControl
var rlt = AR.UTIL.InputBox("input", selected_txt.Text);
if (rlt.Item1 == true)
{
selected_txt.Text = rlt.Item2;// dialog.InputText;
selected_txt.Text = rlt.Item2;
this.Invalidate();
}
return;
@@ -1108,9 +1167,9 @@ namespace AGVControl
}
}
public void DrawPath(List<Point> path)
public void SetCurrentPath(List<Point> path)
{
currentPath = path;
agv.CurrentPath = path;
this.Invalidate();
}
@@ -1130,6 +1189,7 @@ namespace AGVControl
DrawPath(e.Graphics);
DrawAGV(e.Graphics);
DrawTargetFlag(e.Graphics); // 목적지 깃발 그리기 추가
// 선택된 개체 강조 표시
if (selectedRFID != null)
@@ -1180,51 +1240,85 @@ namespace AGVControl
var MarkerSize = 5;
var half = MarkerSize / 2f;
rfid.Bounds = new RectangleF(rfid.Location.X - half, rfid.Location.Y - half, MarkerSize, MarkerSize);
g.FillEllipse(Brushes.Green, rfid.Bounds);
//g.DrawString(rfid.RFIDValue, Font, Brushes.WhiteSmoke, rfid.Location.X + 5, rfid.Location.Y - 5);
// 회전 가능 여부에 따라 다른 색상 사용
using (var brush = new SolidBrush(rfid.IsRotatable ? Color.Yellow : Color.Green))
{
g.FillEllipse(brush, rfid.Bounds);
}
// 고정방향이 있으면 테두리 색상 표시
if (rfid.FixedDirection.HasValue)
{
Color borderColor = rfid.FixedDirection.Value == Direction.Forward ? Color.DeepSkyBlue : Color.Gold; using (var pen = new Pen(borderColor, 2))
{
g.DrawEllipse(pen, rfid.Bounds.Expand(5,5));
}
}
}
// RFID 포인트 그리기
// RFID 값 표시
foreach (var rfid in rfidPoints)
{
//g.FillEllipse(Brushes.Green, rfid.Location.X - 3, rfid.Location.Y - 3, 6, 6);
var tagstr = $"{rfid.RFIDValue}";
var fsize = g.MeasureString(tagstr, Font);
var rect = new RectangleF(rfid.Bounds.X - (fsize.Width / 2f) + 3,
rfid.Bounds.Y + 6,
fsize.Width,
fsize.Height);
//g.DrawRectangle(Pens.Red, rect);
g.DrawString(tagstr, Font, Brushes.WhiteSmoke, rect, new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center,
});
}
}
private void DrawAGV(Graphics g)
{
var agvsize = 30;
var halfsize = (int)(agvsize / 2);
var rect = new Rectangle(agv.CurrentPosition.X - halfsize,
agv.CurrentPosition.Y - halfsize,
agvsize, agvsize);
var recti = new Rectangle(rect.X + 5, rect.Y + 5, rect.Width - 10, rect.Height - 10);
// AGV의 현재 위치를 중심으로 하는 원
var circleRect = new Rectangle(
agv.CurrentPosition.X - halfsize,
agv.CurrentPosition.Y - halfsize,
agvsize, agvsize);
using (var sb = new SolidBrush(Color.FromArgb(150, Color.Gold)))
g.FillEllipse(sb, rect);
// 삼각형 화살표를 위한 포인트 배열
Point[] trianglePoints = new Point[3];
var arrowSize = halfsize - 5; // 삼각형 크기
using (var pen = new Pen(Color.Black))
g.DrawEllipse(pen, rect);
// AGV의 방향에 따라 삼각형 포인트 계산
switch (agv.CurrentDirection)
{
case Direction.Forward:
trianglePoints[0] = new Point(agv.CurrentPosition.X, agv.CurrentPosition.Y - arrowSize);
trianglePoints[1] = new Point(agv.CurrentPosition.X - arrowSize, agv.CurrentPosition.Y + arrowSize);
trianglePoints[2] = new Point(agv.CurrentPosition.X + arrowSize, agv.CurrentPosition.Y + arrowSize);
break;
case Direction.Backward:
trianglePoints[0] = new Point(agv.CurrentPosition.X, agv.CurrentPosition.Y + arrowSize);
trianglePoints[1] = new Point(agv.CurrentPosition.X - arrowSize, agv.CurrentPosition.Y - arrowSize);
trianglePoints[2] = new Point(agv.CurrentPosition.X + arrowSize, agv.CurrentPosition.Y - arrowSize);
break;
}
using (var sb = new SolidBrush(Color.FromArgb(150, Color.White)))
g.FillEllipse(sb, recti);
using (var pen = new Pen(Color.Black))
g.DrawEllipse(pen, recti);
// 원 그리기
Color bgcolor = agv.BatteryLevel > 80 ? Color.Lime : ( agv.BatteryLevel > 60 ? Color.Gold : Color.Tomato );
using (var circleBrush = new SolidBrush(Color.FromArgb(150, bgcolor)))
g.FillEllipse(circleBrush, circleRect);
using (var circlePen = new Pen(Color.Black, 2))
g.DrawEllipse(circlePen, circleRect);
// 삼각형 화살표 그리기
using (var arrowBrush = new SolidBrush(Color.FromArgb(200, Color.White)))
g.FillPolygon(arrowBrush, trianglePoints);
using (var arrowPen = new Pen(Color.Black, 2))
g.DrawPolygon(arrowPen, trianglePoints);
//g.DrawImage(Properties.Resources.ico_navi_40, circleRect);
}
private void DrawCustomLines(Graphics g)
@@ -1358,7 +1452,7 @@ namespace AGVControl
private void DrawPath(Graphics g)
{
if (currentPath == null || currentPath.Count < 2)
if (agv.CurrentPath == null || agv.CurrentPath.Count < 2)
return;
Color pathColor = Color.FromArgb(100, Color.Lime);
@@ -1367,9 +1461,9 @@ namespace AGVControl
using (Pen pathPen = new Pen(pathColor, pathWidth))
{
pathPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
for (int i = 0; i < currentPath.Count - 1; i++)
for (int i = 0; i < agv.CurrentPath.Count - 1; i++)
{
g.DrawLine(pathPen, currentPath[i], currentPath[i + 1]);
g.DrawLine(pathPen, agv.CurrentPath[i], agv.CurrentPath[i + 1]);
}
}
}
@@ -1728,5 +1822,85 @@ namespace AGVControl
g.DrawString(text, font, Brushes.Black, rect, format);
}
}
private void DrawTargetFlag(Graphics g)
{
if (agv.TargetPosition == Point.Empty) return;
// 바닥에 흰색 원 그리기
using (var baseBrush = new SolidBrush(Color.Red))
using (var basePen = new Pen(Color.Black, 1))
{
var baseSize = 8;
g.FillEllipse(baseBrush,
agv.TargetPosition.X - baseSize / 2,
agv.TargetPosition.Y - baseSize / 2,
baseSize, baseSize);
g.DrawEllipse(basePen,
agv.TargetPosition.X - baseSize / 2,
agv.TargetPosition.Y - baseSize / 2,
baseSize, baseSize);
}
// 깃대 그리기 (길이를 2/3로 줄임)
using (var polePen = new Pen(Color.Brown, 3))
{
var poleLength = 27; // 40 * 2/3 ≈ 27
g.DrawLine(polePen,
agv.TargetPosition.X,
agv.TargetPosition.Y,
agv.TargetPosition.X,
agv.TargetPosition.Y - poleLength);
}
// 깃발 그리기
Point[] flagPoints = new Point[3];
flagPoints[0] = new Point(agv.TargetPosition.X, agv.TargetPosition.Y - 27); // 깃대 길이에 맞춤
flagPoints[1] = new Point(agv.TargetPosition.X + 20, agv.TargetPosition.Y - 22);
flagPoints[2] = new Point(agv.TargetPosition.X, agv.TargetPosition.Y - 17);
using (var flagBrush = new SolidBrush(Color.Red))
using (var flagPen = new Pen(Color.DarkRed, 1))
{
g.FillPolygon(flagBrush, flagPoints);
g.DrawPolygon(flagPen, flagPoints);
}
}
// 회전 가능 여부 토글 함수
public void ToggleRotatable(uint rfidValue)
{
var rfidPoint = FindRFIDPoint(rfidValue);
if (rfidPoint != null)
{
rfidPoint.IsRotatable = !rfidPoint.IsRotatable;
this.Invalidate();
}
}
// 회전 가능 여부 확인 함수
public bool IsPointRotatable(uint rfidValue)
{
var rfidPoint = FindRFIDPoint(rfidValue);
return rfidPoint?.IsRotatable ?? false;
}
// 경로 계산 시 회전 가능 여부를 고려하여 방향 결정
private Direction DetermineDirection(Point current, Point next, Point target)
{
// 현재 위치가 회전 가능한 구간인 경우
var currentRFID = rfidPoints.FirstOrDefault(p => p.Location == current);
if (currentRFID?.IsRotatable ?? false)
{
// 목적지 방향으로 직접 방향 결정
if (target.X > current.X) return Direction.Forward;
if (target.X < current.X) return Direction.Backward;
if (target.Y > current.Y) return Direction.Forward;
if (target.Y < current.Y) return Direction.Backward;
}
// 회전 불가능한 구간인 경우 현재 진행 방향 유지
return agv.CurrentDirection;
}
}
}