fix: RFID duplicate validation and correct magnet direction calculation
- Add real-time RFID duplicate validation in map editor with automatic rollback - Remove RFID auto-assignment to maintain data consistency between editor and simulator - Fix magnet direction calculation to use actual forward direction angles instead of arbitrary assignment - Add node names to simulator combo boxes for better identification - Improve UI layout by drawing connection lines before text for better visibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{B2C3D4E5-0000-0000-0000-000000000000}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>AGVSimulator</RootNamespace>
|
||||
<AssemblyName>AGVSimulator</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
@@ -31,6 +31,9 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
namespace AGVSimulator.Controls
|
||||
{
|
||||
partial class SimulatorCanvas
|
||||
{
|
||||
/// <summary>
|
||||
/// 필수 디자이너 변수입니다.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// 사용 중인 모든 리소스를 정리합니다.
|
||||
/// </summary>
|
||||
/// <param name="disposing">관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (components != null)
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
|
||||
// 커스텀 리소스 정리
|
||||
CleanupResources();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region 구성 요소 디자이너에서 생성한 코드
|
||||
|
||||
/// <summary>
|
||||
/// 디자이너 지원에 필요한 메서드입니다.
|
||||
/// 이 메서드의 내용을 코드 편집기로 수정하지 마세요.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// SimulatorCanvas
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.White;
|
||||
this.Name = "SimulatorCanvas";
|
||||
this.Size = new System.Drawing.Size(800, 600);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,622 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using AGVMapEditor.Models;
|
||||
using AGVNavigationCore.Models;
|
||||
using AGVNavigationCore.PathFinding;
|
||||
using AGVSimulator.Models;
|
||||
|
||||
namespace AGVSimulator.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// AGV 시뮬레이션 시각화 캔버스
|
||||
/// </summary>
|
||||
public partial class SimulatorCanvas : UserControl
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private List<MapNode> _mapNodes;
|
||||
private List<VirtualAGV> _agvList;
|
||||
private PathResult _currentPath;
|
||||
|
||||
// 그래픽 설정
|
||||
private float _zoom = 1.0f;
|
||||
private Point _panOffset = Point.Empty;
|
||||
private bool _isPanning = false;
|
||||
private Point _lastMousePos = Point.Empty;
|
||||
|
||||
// 색상 설정
|
||||
private readonly Brush _normalNodeBrush = Brushes.LightBlue;
|
||||
private readonly Brush _rotationNodeBrush = Brushes.Yellow;
|
||||
private readonly Brush _dockingNodeBrush = Brushes.Orange;
|
||||
private readonly Brush _chargingNodeBrush = Brushes.Green;
|
||||
private readonly Brush _agvBrush = Brushes.Red;
|
||||
private readonly Brush _pathBrush = Brushes.Purple;
|
||||
|
||||
private readonly Pen _connectionPen = new Pen(Color.Gray, 2);
|
||||
private readonly Pen _pathPen = new Pen(Color.Purple, 3);
|
||||
private readonly Pen _agvPen = new Pen(Color.Red, 3);
|
||||
|
||||
// 크기 설정
|
||||
private const int NODE_SIZE = 20;
|
||||
private const int AGV_SIZE = 30;
|
||||
private const int CONNECTION_ARROW_SIZE = 8;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// 맵 노드 목록
|
||||
/// </summary>
|
||||
public List<MapNode> MapNodes
|
||||
{
|
||||
get => _mapNodes;
|
||||
set
|
||||
{
|
||||
_mapNodes = value;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AGV 목록
|
||||
/// </summary>
|
||||
public List<VirtualAGV> AGVList
|
||||
{
|
||||
get => _agvList;
|
||||
set
|
||||
{
|
||||
_agvList = value;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 경로
|
||||
/// </summary>
|
||||
public PathResult CurrentPath
|
||||
{
|
||||
get => _currentPath;
|
||||
set
|
||||
{
|
||||
_currentPath = value;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
public SimulatorCanvas()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeCanvas();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
private void InitializeCanvas()
|
||||
{
|
||||
_mapNodes = new List<MapNode>();
|
||||
_agvList = new List<VirtualAGV>();
|
||||
|
||||
SetStyle(ControlStyles.AllPaintingInWmPaint |
|
||||
ControlStyles.UserPaint |
|
||||
ControlStyles.DoubleBuffer |
|
||||
ControlStyles.ResizeRedraw, true);
|
||||
|
||||
BackColor = Color.White;
|
||||
|
||||
// 마우스 이벤트 연결
|
||||
MouseDown += OnMouseDown;
|
||||
MouseMove += OnMouseMove;
|
||||
MouseUp += OnMouseUp;
|
||||
MouseWheel += OnMouseWheel;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// AGV 추가
|
||||
/// </summary>
|
||||
public void AddAGV(VirtualAGV agv)
|
||||
{
|
||||
if (_agvList == null)
|
||||
_agvList = new List<VirtualAGV>();
|
||||
|
||||
_agvList.Add(agv);
|
||||
|
||||
// AGV 이벤트 연결
|
||||
agv.PositionChanged += OnAGVPositionChanged;
|
||||
agv.StateChanged += OnAGVStateChanged;
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AGV 제거
|
||||
/// </summary>
|
||||
public void RemoveAGV(string agvId)
|
||||
{
|
||||
var agv = _agvList?.FirstOrDefault(a => a.AgvId == agvId);
|
||||
if (agv != null)
|
||||
{
|
||||
// 이벤트 연결 해제
|
||||
agv.PositionChanged -= OnAGVPositionChanged;
|
||||
agv.StateChanged -= OnAGVStateChanged;
|
||||
|
||||
_agvList.Remove(agv);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 AGV 제거
|
||||
/// </summary>
|
||||
public void ClearAGVs()
|
||||
{
|
||||
if (_agvList != null)
|
||||
{
|
||||
foreach (var agv in _agvList)
|
||||
{
|
||||
agv.PositionChanged -= OnAGVPositionChanged;
|
||||
agv.StateChanged -= OnAGVStateChanged;
|
||||
}
|
||||
_agvList.Clear();
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 확대/축소 초기화
|
||||
/// </summary>
|
||||
public void ResetZoom()
|
||||
{
|
||||
_zoom = 1.0f;
|
||||
_panOffset = Point.Empty;
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 맵 전체 맞춤
|
||||
/// </summary>
|
||||
public void FitToMap()
|
||||
{
|
||||
if (_mapNodes == null || _mapNodes.Count == 0)
|
||||
return;
|
||||
|
||||
var minX = _mapNodes.Min(n => n.Position.X);
|
||||
var maxX = _mapNodes.Max(n => n.Position.X);
|
||||
var minY = _mapNodes.Min(n => n.Position.Y);
|
||||
var maxY = _mapNodes.Max(n => n.Position.Y);
|
||||
|
||||
var mapWidth = maxX - minX + 100; // 여백 추가
|
||||
var mapHeight = maxY - minY + 100;
|
||||
|
||||
var zoomX = (float)Width / mapWidth;
|
||||
var zoomY = (float)Height / mapHeight;
|
||||
_zoom = Math.Min(zoomX, zoomY) * 0.9f; // 약간의 여백
|
||||
|
||||
_panOffset = new Point(
|
||||
(int)((Width - mapWidth * _zoom) / 2 - minX * _zoom),
|
||||
(int)((Height - mapHeight * _zoom) / 2 - minY * _zoom)
|
||||
);
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private void OnAGVPositionChanged(object sender, Point newPosition)
|
||||
{
|
||||
Invalidate(); // AGV 위치 변경시 화면 갱신
|
||||
}
|
||||
|
||||
private void OnAGVStateChanged(object sender, AGVState newState)
|
||||
{
|
||||
Invalidate(); // AGV 상태 변경시 화면 갱신
|
||||
}
|
||||
|
||||
private void OnMouseDown(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.Button == MouseButtons.Right)
|
||||
{
|
||||
_isPanning = true;
|
||||
_lastMousePos = e.Location;
|
||||
Cursor = Cursors.Hand;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (_isPanning)
|
||||
{
|
||||
var deltaX = e.X - _lastMousePos.X;
|
||||
var deltaY = e.Y - _lastMousePos.Y;
|
||||
|
||||
_panOffset = new Point(
|
||||
_panOffset.X + deltaX,
|
||||
_panOffset.Y + deltaY
|
||||
);
|
||||
|
||||
_lastMousePos = e.Location;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseUp(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.Button == MouseButtons.Right)
|
||||
{
|
||||
_isPanning = false;
|
||||
Cursor = Cursors.Default;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseWheel(object sender, MouseEventArgs e)
|
||||
{
|
||||
var zoomFactor = e.Delta > 0 ? 1.1f : 0.9f;
|
||||
var newZoom = _zoom * zoomFactor;
|
||||
|
||||
if (newZoom >= 0.1f && newZoom <= 10.0f)
|
||||
{
|
||||
// 마우스 위치 기준으로 줌
|
||||
var mouseX = e.X - _panOffset.X;
|
||||
var mouseY = e.Y - _panOffset.Y;
|
||||
|
||||
_panOffset = new Point(
|
||||
(int)(_panOffset.X - mouseX * (zoomFactor - 1)),
|
||||
(int)(_panOffset.Y - mouseY * (zoomFactor - 1))
|
||||
);
|
||||
|
||||
_zoom = newZoom;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Painting
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
base.OnPaint(e);
|
||||
|
||||
var g = e.Graphics;
|
||||
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
|
||||
|
||||
// 변환 행렬 설정
|
||||
g.TranslateTransform(_panOffset.X, _panOffset.Y);
|
||||
g.ScaleTransform(_zoom, _zoom);
|
||||
|
||||
// 배경 그리드 그리기
|
||||
DrawGrid(g);
|
||||
|
||||
// 맵 노드 연결선 그리기
|
||||
DrawNodeConnections(g);
|
||||
|
||||
// 경로 그리기
|
||||
if (_currentPath != null && _currentPath.Success)
|
||||
{
|
||||
DrawPath(g);
|
||||
}
|
||||
|
||||
// 맵 노드 그리기
|
||||
DrawMapNodes(g);
|
||||
|
||||
// AGV 그리기
|
||||
DrawAGVs(g);
|
||||
|
||||
// 정보 표시 (변환 해제)
|
||||
g.ResetTransform();
|
||||
DrawInfo(g);
|
||||
}
|
||||
|
||||
private void DrawGrid(Graphics g)
|
||||
{
|
||||
var gridSize = 50;
|
||||
var pen = new Pen(Color.LightGray, 1);
|
||||
|
||||
var startX = -(int)(_panOffset.X / _zoom / gridSize) * gridSize;
|
||||
var startY = -(int)(_panOffset.Y / _zoom / gridSize) * gridSize;
|
||||
var endX = startX + (int)(Width / _zoom) + gridSize;
|
||||
var endY = startY + (int)(Height / _zoom) + gridSize;
|
||||
|
||||
for (int x = startX; x <= endX; x += gridSize)
|
||||
{
|
||||
g.DrawLine(pen, x, startY, x, endY);
|
||||
}
|
||||
|
||||
for (int y = startY; y <= endY; y += gridSize)
|
||||
{
|
||||
g.DrawLine(pen, startX, y, endX, y);
|
||||
}
|
||||
|
||||
pen.Dispose();
|
||||
}
|
||||
|
||||
private void DrawNodeConnections(Graphics g)
|
||||
{
|
||||
if (_mapNodes == null) return;
|
||||
|
||||
foreach (var node in _mapNodes)
|
||||
{
|
||||
if (node.ConnectedNodes != null)
|
||||
{
|
||||
foreach (var connectedNodeId in node.ConnectedNodes)
|
||||
{
|
||||
var connectedNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectedNodeId);
|
||||
if (connectedNode != null)
|
||||
{
|
||||
DrawConnection(g, node.Position, connectedNode.Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawConnection(Graphics g, Point from, Point to)
|
||||
{
|
||||
g.DrawLine(_connectionPen, from, to);
|
||||
|
||||
// 방향 화살표 그리기
|
||||
var angle = Math.Atan2(to.Y - from.Y, to.X - from.X);
|
||||
var arrowX = to.X - CONNECTION_ARROW_SIZE * Math.Cos(angle);
|
||||
var arrowY = to.Y - CONNECTION_ARROW_SIZE * Math.Sin(angle);
|
||||
|
||||
var arrowPoint1 = new PointF(
|
||||
(float)(arrowX - CONNECTION_ARROW_SIZE * Math.Cos(angle - Math.PI / 6)),
|
||||
(float)(arrowY - CONNECTION_ARROW_SIZE * Math.Sin(angle - Math.PI / 6))
|
||||
);
|
||||
|
||||
var arrowPoint2 = new PointF(
|
||||
(float)(arrowX - CONNECTION_ARROW_SIZE * Math.Cos(angle + Math.PI / 6)),
|
||||
(float)(arrowY - CONNECTION_ARROW_SIZE * Math.Sin(angle + Math.PI / 6))
|
||||
);
|
||||
|
||||
g.DrawLine(_connectionPen, to, arrowPoint1);
|
||||
g.DrawLine(_connectionPen, to, arrowPoint2);
|
||||
}
|
||||
|
||||
private void DrawPath(Graphics g)
|
||||
{
|
||||
if (_currentPath?.Path == null || _currentPath.Path.Count < 2)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _currentPath.Path.Count - 1; i++)
|
||||
{
|
||||
var currentNodeId = _currentPath.Path[i];
|
||||
var nextNodeId = _currentPath.Path[i + 1];
|
||||
|
||||
var currentNode = _mapNodes?.FirstOrDefault(n => n.NodeId == currentNodeId);
|
||||
var nextNode = _mapNodes?.FirstOrDefault(n => n.NodeId == nextNodeId);
|
||||
|
||||
if (currentNode != null && nextNode != null)
|
||||
{
|
||||
g.DrawLine(_pathPen, currentNode.Position, nextNode.Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMapNodes(Graphics g)
|
||||
{
|
||||
if (_mapNodes == null) return;
|
||||
|
||||
foreach (var node in _mapNodes)
|
||||
{
|
||||
DrawMapNode(g, node);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMapNode(Graphics g, MapNode node)
|
||||
{
|
||||
var brush = GetNodeBrush(node.Type);
|
||||
var rect = new Rectangle(
|
||||
node.Position.X - NODE_SIZE / 2,
|
||||
node.Position.Y - NODE_SIZE / 2,
|
||||
NODE_SIZE,
|
||||
NODE_SIZE
|
||||
);
|
||||
|
||||
// 노드 그리기
|
||||
if (node.Type == NodeType.Rotation)
|
||||
{
|
||||
g.FillEllipse(brush, rect); // 회전 노드는 원형
|
||||
}
|
||||
else
|
||||
{
|
||||
g.FillRectangle(brush, rect); // 일반 노드는 사각형
|
||||
}
|
||||
|
||||
g.DrawRectangle(Pens.Black, rect);
|
||||
|
||||
// 노드 ID 표시
|
||||
var font = new Font("Arial", 8);
|
||||
var textSize = g.MeasureString(node.NodeId, font);
|
||||
var textPos = new PointF(
|
||||
node.Position.X - textSize.Width / 2,
|
||||
node.Position.Y + NODE_SIZE / 2 + 2
|
||||
);
|
||||
|
||||
g.DrawString(node.NodeId, font, Brushes.Black, textPos);
|
||||
font.Dispose();
|
||||
}
|
||||
|
||||
private Brush GetNodeBrush(NodeType nodeType)
|
||||
{
|
||||
switch (nodeType)
|
||||
{
|
||||
case NodeType.Rotation: return _rotationNodeBrush;
|
||||
case NodeType.Docking: return _dockingNodeBrush;
|
||||
case NodeType.Charging: return _chargingNodeBrush;
|
||||
default: return _normalNodeBrush;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAGVs(Graphics g)
|
||||
{
|
||||
if (_agvList == null) return;
|
||||
|
||||
foreach (var agv in _agvList)
|
||||
{
|
||||
DrawAGV(g, agv);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAGV(Graphics g, VirtualAGV agv)
|
||||
{
|
||||
var position = agv.CurrentPosition;
|
||||
var rect = new Rectangle(
|
||||
position.X - AGV_SIZE / 2,
|
||||
position.Y - AGV_SIZE / 2,
|
||||
AGV_SIZE,
|
||||
AGV_SIZE
|
||||
);
|
||||
|
||||
// AGV 상태에 따른 색상 변경
|
||||
var brush = GetAGVBrush(agv.CurrentState);
|
||||
|
||||
// AGV 본체 그리기
|
||||
g.FillEllipse(brush, rect);
|
||||
g.DrawEllipse(_agvPen, rect);
|
||||
|
||||
// 방향 표시
|
||||
DrawAGVDirection(g, position, agv.CurrentDirection);
|
||||
|
||||
// AGV ID 표시
|
||||
var font = new Font("Arial", 10, FontStyle.Bold);
|
||||
var textSize = g.MeasureString(agv.AgvId, font);
|
||||
var textPos = new PointF(
|
||||
position.X - textSize.Width / 2,
|
||||
position.Y + AGV_SIZE / 2 + 5
|
||||
);
|
||||
|
||||
g.DrawString(agv.AgvId, font, Brushes.Black, textPos);
|
||||
font.Dispose();
|
||||
}
|
||||
|
||||
private Brush GetAGVBrush(AGVState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case AGVState.Moving: return Brushes.Blue;
|
||||
case AGVState.Rotating: return Brushes.Yellow;
|
||||
case AGVState.Docking: return Brushes.Orange;
|
||||
case AGVState.Charging: return Brushes.Green;
|
||||
case AGVState.Error: return Brushes.Red;
|
||||
default: return Brushes.Gray; // Idle
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAGVDirection(Graphics g, Point position, AgvDirection direction)
|
||||
{
|
||||
var arrowSize = 10;
|
||||
var pen = new Pen(Color.White, 2);
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case AgvDirection.Forward:
|
||||
// 위쪽 화살표
|
||||
g.DrawLine(pen, position.X, position.Y - arrowSize, position.X, position.Y + arrowSize);
|
||||
g.DrawLine(pen, position.X, position.Y - arrowSize, position.X - 5, position.Y - arrowSize + 5);
|
||||
g.DrawLine(pen, position.X, position.Y - arrowSize, position.X + 5, position.Y - arrowSize + 5);
|
||||
break;
|
||||
|
||||
case AgvDirection.Backward:
|
||||
// 아래쪽 화살표
|
||||
g.DrawLine(pen, position.X, position.Y - arrowSize, position.X, position.Y + arrowSize);
|
||||
g.DrawLine(pen, position.X, position.Y + arrowSize, position.X - 5, position.Y + arrowSize - 5);
|
||||
g.DrawLine(pen, position.X, position.Y + arrowSize, position.X + 5, position.Y + arrowSize - 5);
|
||||
break;
|
||||
|
||||
case AgvDirection.Left:
|
||||
// 왼쪽 화살표
|
||||
g.DrawLine(pen, position.X - arrowSize, position.Y, position.X + arrowSize, position.Y);
|
||||
g.DrawLine(pen, position.X - arrowSize, position.Y, position.X - arrowSize + 5, position.Y - 5);
|
||||
g.DrawLine(pen, position.X - arrowSize, position.Y, position.X - arrowSize + 5, position.Y + 5);
|
||||
break;
|
||||
|
||||
case AgvDirection.Right:
|
||||
// 오른쪽 화살표
|
||||
g.DrawLine(pen, position.X - arrowSize, position.Y, position.X + arrowSize, position.Y);
|
||||
g.DrawLine(pen, position.X + arrowSize, position.Y, position.X + arrowSize - 5, position.Y - 5);
|
||||
g.DrawLine(pen, position.X + arrowSize, position.Y, position.X + arrowSize - 5, position.Y + 5);
|
||||
break;
|
||||
}
|
||||
|
||||
pen.Dispose();
|
||||
}
|
||||
|
||||
private void DrawInfo(Graphics g)
|
||||
{
|
||||
var font = new Font("Arial", 10);
|
||||
var brush = Brushes.Black;
|
||||
var y = 10;
|
||||
|
||||
// 줌 레벨 표시
|
||||
g.DrawString($"줌: {_zoom:P0}", font, brush, new PointF(10, y));
|
||||
y += 20;
|
||||
|
||||
// AGV 정보 표시
|
||||
if (_agvList != null)
|
||||
{
|
||||
g.DrawString($"AGV 수: {_agvList.Count}", font, brush, new PointF(10, y));
|
||||
y += 20;
|
||||
|
||||
foreach (var agv in _agvList)
|
||||
{
|
||||
var info = $"{agv.AgvId}: {agv.CurrentState} ({agv.CurrentPosition.X},{agv.CurrentPosition.Y})";
|
||||
g.DrawString(info, font, brush, new PointF(10, y));
|
||||
y += 15;
|
||||
}
|
||||
}
|
||||
|
||||
// 경로 정보 표시
|
||||
if (_currentPath != null && _currentPath.Success)
|
||||
{
|
||||
y += 10;
|
||||
g.DrawString($"경로: {_currentPath.Path.Count}개 노드", font, brush, new PointF(10, y));
|
||||
y += 15;
|
||||
g.DrawString($"거리: {_currentPath.TotalDistance:F1}", font, brush, new PointF(10, y));
|
||||
y += 15;
|
||||
g.DrawString($"계산시간: {_currentPath.CalculationTimeMs}ms", font, brush, new PointF(10, y));
|
||||
}
|
||||
|
||||
font.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cleanup
|
||||
|
||||
private void CleanupResources()
|
||||
{
|
||||
// AGV 이벤트 연결 해제
|
||||
if (_agvList != null)
|
||||
{
|
||||
foreach (var agv in _agvList)
|
||||
{
|
||||
agv.PositionChanged -= OnAGVPositionChanged;
|
||||
agv.StateChanged -= OnAGVStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
// 리소스 정리
|
||||
_connectionPen?.Dispose();
|
||||
_pathPen?.Dispose();
|
||||
_agvPen?.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion. Classes that don't support this are
|
||||
serialized and stored with the mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
171
Cs_HMI/AGVSimulator/Forms/SimulatorForm.Designer.cs
generated
171
Cs_HMI/AGVSimulator/Forms/SimulatorForm.Designer.cs
generated
@@ -70,6 +70,7 @@ namespace AGVSimulator.Forms
|
||||
this.startSimulationToolStripButton = new System.Windows.Forms.ToolStripButton();
|
||||
this.stopSimulationToolStripButton = new System.Windows.Forms.ToolStripButton();
|
||||
this.resetToolStripButton = new System.Windows.Forms.ToolStripButton();
|
||||
this.btAllReset = new System.Windows.Forms.ToolStripButton();
|
||||
this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.fitToMapToolStripButton = new System.Windows.Forms.ToolStripButton();
|
||||
this.resetZoomToolStripButton = new System.Windows.Forms.ToolStripButton();
|
||||
@@ -85,6 +86,7 @@ namespace AGVSimulator.Forms
|
||||
this._clearPathButton = new System.Windows.Forms.Button();
|
||||
this._startPathButton = new System.Windows.Forms.Button();
|
||||
this._calculatePathButton = new System.Windows.Forms.Button();
|
||||
this._avoidRotationCheckBox = new System.Windows.Forms.CheckBox();
|
||||
this._targetNodeCombo = new System.Windows.Forms.ComboBox();
|
||||
this.targetNodeLabel = new System.Windows.Forms.Label();
|
||||
this._startNodeCombo = new System.Windows.Forms.ComboBox();
|
||||
@@ -93,13 +95,19 @@ namespace AGVSimulator.Forms
|
||||
this._setPositionButton = new System.Windows.Forms.Button();
|
||||
this._rfidTextBox = new System.Windows.Forms.TextBox();
|
||||
this._rfidLabel = new System.Windows.Forms.Label();
|
||||
this._directionCombo = new System.Windows.Forms.ComboBox();
|
||||
this._directionLabel = new System.Windows.Forms.Label();
|
||||
this._stopSimulationButton = new System.Windows.Forms.Button();
|
||||
this._startSimulationButton = new System.Windows.Forms.Button();
|
||||
this._removeAgvButton = new System.Windows.Forms.Button();
|
||||
this._addAgvButton = new System.Windows.Forms.Button();
|
||||
this._agvListCombo = new System.Windows.Forms.ComboBox();
|
||||
this._canvasPanel = new System.Windows.Forms.Panel();
|
||||
this.btAllReset = new System.Windows.Forms.ToolStripButton();
|
||||
this._agvInfoPanel = new System.Windows.Forms.Panel();
|
||||
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.Label();
|
||||
this._menuStrip.SuspendLayout();
|
||||
this._toolStrip.SuspendLayout();
|
||||
this._statusStrip.SuspendLayout();
|
||||
@@ -107,6 +115,7 @@ namespace AGVSimulator.Forms
|
||||
this._statusGroup.SuspendLayout();
|
||||
this._pathGroup.SuspendLayout();
|
||||
this._agvControlGroup.SuspendLayout();
|
||||
this._agvInfoPanel.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// _menuStrip
|
||||
@@ -139,7 +148,7 @@ namespace AGVSimulator.Forms
|
||||
//
|
||||
this.openMapToolStripMenuItem.Name = "openMapToolStripMenuItem";
|
||||
this.openMapToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O)));
|
||||
this.openMapToolStripMenuItem.Size = new System.Drawing.Size(183, 22);
|
||||
this.openMapToolStripMenuItem.Size = new System.Drawing.Size(221, 22);
|
||||
this.openMapToolStripMenuItem.Text = "맵 열기(&O)...";
|
||||
this.openMapToolStripMenuItem.Click += new System.EventHandler(this.OnOpenMap_Click);
|
||||
//
|
||||
@@ -147,33 +156,33 @@ namespace AGVSimulator.Forms
|
||||
//
|
||||
this.reloadMapToolStripMenuItem.Name = "reloadMapToolStripMenuItem";
|
||||
this.reloadMapToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.R)));
|
||||
this.reloadMapToolStripMenuItem.Size = new System.Drawing.Size(183, 22);
|
||||
this.reloadMapToolStripMenuItem.Size = new System.Drawing.Size(221, 22);
|
||||
this.reloadMapToolStripMenuItem.Text = "맵 다시열기(&R)";
|
||||
this.reloadMapToolStripMenuItem.Click += new System.EventHandler(this.OnReloadMap_Click);
|
||||
//
|
||||
// toolStripSeparator1
|
||||
//
|
||||
this.toolStripSeparator1.Name = "toolStripSeparator1";
|
||||
this.toolStripSeparator1.Size = new System.Drawing.Size(180, 6);
|
||||
this.toolStripSeparator1.Size = new System.Drawing.Size(218, 6);
|
||||
//
|
||||
// launchMapEditorToolStripMenuItem
|
||||
//
|
||||
this.launchMapEditorToolStripMenuItem.Name = "launchMapEditorToolStripMenuItem";
|
||||
this.launchMapEditorToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.M)));
|
||||
this.launchMapEditorToolStripMenuItem.Size = new System.Drawing.Size(183, 22);
|
||||
this.launchMapEditorToolStripMenuItem.Size = new System.Drawing.Size(221, 22);
|
||||
this.launchMapEditorToolStripMenuItem.Text = "MapEditor 실행(&M)";
|
||||
this.launchMapEditorToolStripMenuItem.Click += new System.EventHandler(this.OnLaunchMapEditor_Click);
|
||||
//
|
||||
// toolStripSeparator4
|
||||
//
|
||||
this.toolStripSeparator4.Name = "toolStripSeparator4";
|
||||
this.toolStripSeparator4.Size = new System.Drawing.Size(180, 6);
|
||||
this.toolStripSeparator4.Size = new System.Drawing.Size(218, 6);
|
||||
//
|
||||
// exitToolStripMenuItem
|
||||
//
|
||||
this.exitToolStripMenuItem.Name = "exitToolStripMenuItem";
|
||||
this.exitToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Alt | System.Windows.Forms.Keys.F4)));
|
||||
this.exitToolStripMenuItem.Size = new System.Drawing.Size(183, 22);
|
||||
this.exitToolStripMenuItem.Size = new System.Drawing.Size(221, 22);
|
||||
this.exitToolStripMenuItem.Text = "종료(&X)";
|
||||
this.exitToolStripMenuItem.Click += new System.EventHandler(this.OnExit_Click);
|
||||
//
|
||||
@@ -284,7 +293,7 @@ namespace AGVSimulator.Forms
|
||||
//
|
||||
this.reloadMapToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
|
||||
this.reloadMapToolStripButton.Name = "reloadMapToolStripButton";
|
||||
this.reloadMapToolStripButton.Size = new System.Drawing.Size(63, 22);
|
||||
this.reloadMapToolStripButton.Size = new System.Drawing.Size(59, 22);
|
||||
this.reloadMapToolStripButton.Text = "다시열기";
|
||||
this.reloadMapToolStripButton.ToolTipText = "현재 맵을 다시 로드합니다";
|
||||
this.reloadMapToolStripButton.Click += new System.EventHandler(this.OnReloadMap_Click);
|
||||
@@ -293,7 +302,7 @@ namespace AGVSimulator.Forms
|
||||
//
|
||||
this.launchMapEditorToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
|
||||
this.launchMapEditorToolStripButton.Name = "launchMapEditorToolStripButton";
|
||||
this.launchMapEditorToolStripButton.Size = new System.Drawing.Size(71, 22);
|
||||
this.launchMapEditorToolStripButton.Size = new System.Drawing.Size(66, 22);
|
||||
this.launchMapEditorToolStripButton.Text = "MapEditor";
|
||||
this.launchMapEditorToolStripButton.ToolTipText = "MapEditor를 실행합니다";
|
||||
this.launchMapEditorToolStripButton.Click += new System.EventHandler(this.OnLaunchMapEditor_Click);
|
||||
@@ -330,6 +339,15 @@ namespace AGVSimulator.Forms
|
||||
this.resetToolStripButton.ToolTipText = "시뮬레이션을 초기화합니다";
|
||||
this.resetToolStripButton.Click += new System.EventHandler(this.OnReset_Click);
|
||||
//
|
||||
// btAllReset
|
||||
//
|
||||
this.btAllReset.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
|
||||
this.btAllReset.Name = "btAllReset";
|
||||
this.btAllReset.Size = new System.Drawing.Size(71, 22);
|
||||
this.btAllReset.Text = "전체초기화";
|
||||
this.btAllReset.ToolTipText = "시뮬레이션을 초기화합니다";
|
||||
this.btAllReset.Click += new System.EventHandler(this.btAllReset_Click);
|
||||
//
|
||||
// toolStripSeparator3
|
||||
//
|
||||
this.toolStripSeparator3.Name = "toolStripSeparator3";
|
||||
@@ -382,9 +400,9 @@ namespace AGVSimulator.Forms
|
||||
this._controlPanel.Controls.Add(this._pathGroup);
|
||||
this._controlPanel.Controls.Add(this._agvControlGroup);
|
||||
this._controlPanel.Dock = System.Windows.Forms.DockStyle.Right;
|
||||
this._controlPanel.Location = new System.Drawing.Point(950, 49);
|
||||
this._controlPanel.Location = new System.Drawing.Point(967, 49);
|
||||
this._controlPanel.Name = "_controlPanel";
|
||||
this._controlPanel.Size = new System.Drawing.Size(250, 729);
|
||||
this._controlPanel.Size = new System.Drawing.Size(233, 729);
|
||||
this._controlPanel.TabIndex = 3;
|
||||
//
|
||||
// _statusGroup
|
||||
@@ -392,9 +410,10 @@ namespace AGVSimulator.Forms
|
||||
this._statusGroup.Controls.Add(this._pathLengthLabel);
|
||||
this._statusGroup.Controls.Add(this._agvCountLabel);
|
||||
this._statusGroup.Controls.Add(this._simulationStatusLabel);
|
||||
this._statusGroup.Location = new System.Drawing.Point(10, 356);
|
||||
this._statusGroup.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this._statusGroup.Location = new System.Drawing.Point(0, 404);
|
||||
this._statusGroup.Name = "_statusGroup";
|
||||
this._statusGroup.Size = new System.Drawing.Size(230, 100);
|
||||
this._statusGroup.Size = new System.Drawing.Size(233, 100);
|
||||
this._statusGroup.TabIndex = 3;
|
||||
this._statusGroup.TabStop = false;
|
||||
this._statusGroup.Text = "상태 정보";
|
||||
@@ -431,20 +450,22 @@ namespace AGVSimulator.Forms
|
||||
this._pathGroup.Controls.Add(this._clearPathButton);
|
||||
this._pathGroup.Controls.Add(this._startPathButton);
|
||||
this._pathGroup.Controls.Add(this._calculatePathButton);
|
||||
this._pathGroup.Controls.Add(this._avoidRotationCheckBox);
|
||||
this._pathGroup.Controls.Add(this._targetNodeCombo);
|
||||
this._pathGroup.Controls.Add(this.targetNodeLabel);
|
||||
this._pathGroup.Controls.Add(this._startNodeCombo);
|
||||
this._pathGroup.Controls.Add(this.startNodeLabel);
|
||||
this._pathGroup.Location = new System.Drawing.Point(10, 200);
|
||||
this._pathGroup.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this._pathGroup.Location = new System.Drawing.Point(0, 214);
|
||||
this._pathGroup.Name = "_pathGroup";
|
||||
this._pathGroup.Size = new System.Drawing.Size(230, 150);
|
||||
this._pathGroup.Size = new System.Drawing.Size(233, 190);
|
||||
this._pathGroup.TabIndex = 1;
|
||||
this._pathGroup.TabStop = false;
|
||||
this._pathGroup.Text = "경로 제어";
|
||||
//
|
||||
// _clearPathButton
|
||||
//
|
||||
this._clearPathButton.Location = new System.Drawing.Point(150, 120);
|
||||
this._clearPathButton.Location = new System.Drawing.Point(150, 148);
|
||||
this._clearPathButton.Name = "_clearPathButton";
|
||||
this._clearPathButton.Size = new System.Drawing.Size(70, 25);
|
||||
this._clearPathButton.TabIndex = 6;
|
||||
@@ -454,7 +475,7 @@ namespace AGVSimulator.Forms
|
||||
//
|
||||
// _startPathButton
|
||||
//
|
||||
this._startPathButton.Location = new System.Drawing.Point(80, 120);
|
||||
this._startPathButton.Location = new System.Drawing.Point(80, 148);
|
||||
this._startPathButton.Name = "_startPathButton";
|
||||
this._startPathButton.Size = new System.Drawing.Size(65, 25);
|
||||
this._startPathButton.TabIndex = 5;
|
||||
@@ -464,7 +485,7 @@ namespace AGVSimulator.Forms
|
||||
//
|
||||
// _calculatePathButton
|
||||
//
|
||||
this._calculatePathButton.Location = new System.Drawing.Point(10, 120);
|
||||
this._calculatePathButton.Location = new System.Drawing.Point(10, 148);
|
||||
this._calculatePathButton.Name = "_calculatePathButton";
|
||||
this._calculatePathButton.Size = new System.Drawing.Size(65, 25);
|
||||
this._calculatePathButton.TabIndex = 4;
|
||||
@@ -472,10 +493,20 @@ namespace AGVSimulator.Forms
|
||||
this._calculatePathButton.UseVisualStyleBackColor = true;
|
||||
this._calculatePathButton.Click += new System.EventHandler(this.OnCalculatePath_Click);
|
||||
//
|
||||
// _avoidRotationCheckBox
|
||||
//
|
||||
this._avoidRotationCheckBox.AutoSize = true;
|
||||
this._avoidRotationCheckBox.Location = new System.Drawing.Point(10, 126);
|
||||
this._avoidRotationCheckBox.Name = "_avoidRotationCheckBox";
|
||||
this._avoidRotationCheckBox.Size = new System.Drawing.Size(104, 16);
|
||||
this._avoidRotationCheckBox.TabIndex = 7;
|
||||
this._avoidRotationCheckBox.Text = "회전 구간 회피";
|
||||
this._avoidRotationCheckBox.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// _targetNodeCombo
|
||||
//
|
||||
this._targetNodeCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this._targetNodeCombo.Location = new System.Drawing.Point(10, 95);
|
||||
this._targetNodeCombo.Location = new System.Drawing.Point(10, 97);
|
||||
this._targetNodeCombo.Name = "_targetNodeCombo";
|
||||
this._targetNodeCombo.Size = new System.Drawing.Size(210, 20);
|
||||
this._targetNodeCombo.TabIndex = 3;
|
||||
@@ -511,14 +542,17 @@ namespace AGVSimulator.Forms
|
||||
this._agvControlGroup.Controls.Add(this._setPositionButton);
|
||||
this._agvControlGroup.Controls.Add(this._rfidTextBox);
|
||||
this._agvControlGroup.Controls.Add(this._rfidLabel);
|
||||
this._agvControlGroup.Controls.Add(this._directionCombo);
|
||||
this._agvControlGroup.Controls.Add(this._directionLabel);
|
||||
this._agvControlGroup.Controls.Add(this._stopSimulationButton);
|
||||
this._agvControlGroup.Controls.Add(this._startSimulationButton);
|
||||
this._agvControlGroup.Controls.Add(this._removeAgvButton);
|
||||
this._agvControlGroup.Controls.Add(this._addAgvButton);
|
||||
this._agvControlGroup.Controls.Add(this._agvListCombo);
|
||||
this._agvControlGroup.Location = new System.Drawing.Point(10, 10);
|
||||
this._agvControlGroup.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this._agvControlGroup.Location = new System.Drawing.Point(0, 0);
|
||||
this._agvControlGroup.Name = "_agvControlGroup";
|
||||
this._agvControlGroup.Size = new System.Drawing.Size(230, 180);
|
||||
this._agvControlGroup.Size = new System.Drawing.Size(233, 214);
|
||||
this._agvControlGroup.TabIndex = 0;
|
||||
this._agvControlGroup.TabStop = false;
|
||||
this._agvControlGroup.Text = "AGV 제어";
|
||||
@@ -527,7 +561,7 @@ namespace AGVSimulator.Forms
|
||||
//
|
||||
this._setPositionButton.Location = new System.Drawing.Point(160, 138);
|
||||
this._setPositionButton.Name = "_setPositionButton";
|
||||
this._setPositionButton.Size = new System.Drawing.Size(60, 25);
|
||||
this._setPositionButton.Size = new System.Drawing.Size(60, 67);
|
||||
this._setPositionButton.TabIndex = 7;
|
||||
this._setPositionButton.Text = "위치설정";
|
||||
this._setPositionButton.UseVisualStyleBackColor = true;
|
||||
@@ -550,6 +584,24 @@ namespace AGVSimulator.Forms
|
||||
this._rfidLabel.TabIndex = 5;
|
||||
this._rfidLabel.Text = "RFID 현재위치:";
|
||||
//
|
||||
// _directionCombo
|
||||
//
|
||||
this._directionCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this._directionCombo.FormattingEnabled = true;
|
||||
this._directionCombo.Location = new System.Drawing.Point(10, 185);
|
||||
this._directionCombo.Name = "_directionCombo";
|
||||
this._directionCombo.Size = new System.Drawing.Size(140, 20);
|
||||
this._directionCombo.TabIndex = 8;
|
||||
//
|
||||
// _directionLabel
|
||||
//
|
||||
this._directionLabel.AutoSize = true;
|
||||
this._directionLabel.Location = new System.Drawing.Point(10, 165);
|
||||
this._directionLabel.Name = "_directionLabel";
|
||||
this._directionLabel.Size = new System.Drawing.Size(85, 12);
|
||||
this._directionLabel.TabIndex = 9;
|
||||
this._directionLabel.Text = "모터 구동방향:";
|
||||
//
|
||||
// _stopSimulationButton
|
||||
//
|
||||
this._stopSimulationButton.Location = new System.Drawing.Point(120, 85);
|
||||
@@ -602,19 +654,65 @@ namespace AGVSimulator.Forms
|
||||
// _canvasPanel
|
||||
//
|
||||
this._canvasPanel.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this._canvasPanel.Location = new System.Drawing.Point(0, 49);
|
||||
this._canvasPanel.Location = new System.Drawing.Point(0, 109);
|
||||
this._canvasPanel.Name = "_canvasPanel";
|
||||
this._canvasPanel.Size = new System.Drawing.Size(950, 729);
|
||||
this._canvasPanel.Size = new System.Drawing.Size(967, 669);
|
||||
this._canvasPanel.TabIndex = 4;
|
||||
//
|
||||
// btAllReset
|
||||
// _agvInfoPanel
|
||||
//
|
||||
this.btAllReset.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
|
||||
this.btAllReset.Name = "btAllReset";
|
||||
this.btAllReset.Size = new System.Drawing.Size(71, 22);
|
||||
this.btAllReset.Text = "전체초기화";
|
||||
this.btAllReset.ToolTipText = "시뮬레이션을 초기화합니다";
|
||||
this.btAllReset.Click += new System.EventHandler(this.btAllReset_Click);
|
||||
this._agvInfoPanel.BackColor = System.Drawing.Color.LightBlue;
|
||||
this._agvInfoPanel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||
this._agvInfoPanel.Controls.Add(this._agvInfoTitleLabel);
|
||||
this._agvInfoPanel.Controls.Add(this._liftDirectionLabel);
|
||||
this._agvInfoPanel.Controls.Add(this._motorDirectionLabel);
|
||||
this._agvInfoPanel.Controls.Add(this._pathDebugLabel);
|
||||
this._agvInfoPanel.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this._agvInfoPanel.Location = new System.Drawing.Point(0, 49);
|
||||
this._agvInfoPanel.Name = "_agvInfoPanel";
|
||||
this._agvInfoPanel.Size = new System.Drawing.Size(967, 60);
|
||||
this._agvInfoPanel.TabIndex = 5;
|
||||
//
|
||||
// _agvInfoTitleLabel
|
||||
//
|
||||
this._agvInfoTitleLabel.AutoSize = true;
|
||||
this._agvInfoTitleLabel.Font = new System.Drawing.Font("맑은 고딕", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this._agvInfoTitleLabel.Location = new System.Drawing.Point(10, 12);
|
||||
this._agvInfoTitleLabel.Name = "_agvInfoTitleLabel";
|
||||
this._agvInfoTitleLabel.Size = new System.Drawing.Size(91, 15);
|
||||
this._agvInfoTitleLabel.TabIndex = 0;
|
||||
this._agvInfoTitleLabel.Text = "AGV 상태 정보:";
|
||||
//
|
||||
// _liftDirectionLabel
|
||||
//
|
||||
this._liftDirectionLabel.AutoSize = true;
|
||||
this._liftDirectionLabel.Font = new System.Drawing.Font("맑은 고딕", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this._liftDirectionLabel.Location = new System.Drawing.Point(120, 12);
|
||||
this._liftDirectionLabel.Name = "_liftDirectionLabel";
|
||||
this._liftDirectionLabel.Size = new System.Drawing.Size(83, 15);
|
||||
this._liftDirectionLabel.TabIndex = 1;
|
||||
this._liftDirectionLabel.Text = "리프트 방향: -";
|
||||
//
|
||||
// _motorDirectionLabel
|
||||
//
|
||||
this._motorDirectionLabel.AutoSize = true;
|
||||
this._motorDirectionLabel.Font = new System.Drawing.Font("맑은 고딕", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this._motorDirectionLabel.Location = new System.Drawing.Point(250, 12);
|
||||
this._motorDirectionLabel.Name = "_motorDirectionLabel";
|
||||
this._motorDirectionLabel.Size = new System.Drawing.Size(71, 15);
|
||||
this._motorDirectionLabel.TabIndex = 2;
|
||||
this._motorDirectionLabel.Text = "모터 방향: -";
|
||||
//
|
||||
// _pathDebugLabel
|
||||
//
|
||||
this._pathDebugLabel.AutoSize = true;
|
||||
this._pathDebugLabel.Font = new System.Drawing.Font("맑은 고딕", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
|
||||
this._pathDebugLabel.ForeColor = System.Drawing.Color.DarkBlue;
|
||||
this._pathDebugLabel.Location = new System.Drawing.Point(10, 30);
|
||||
this._pathDebugLabel.Name = "_pathDebugLabel";
|
||||
this._pathDebugLabel.Size = new System.Drawing.Size(114, 15);
|
||||
this._pathDebugLabel.TabIndex = 3;
|
||||
this._pathDebugLabel.Text = "경로: 설정되지 않음";
|
||||
//
|
||||
// SimulatorForm
|
||||
//
|
||||
@@ -622,6 +720,7 @@ namespace AGVSimulator.Forms
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(1200, 800);
|
||||
this.Controls.Add(this._canvasPanel);
|
||||
this.Controls.Add(this._agvInfoPanel);
|
||||
this.Controls.Add(this._controlPanel);
|
||||
this.Controls.Add(this._statusStrip);
|
||||
this.Controls.Add(this._toolStrip);
|
||||
@@ -644,6 +743,8 @@ namespace AGVSimulator.Forms
|
||||
this._pathGroup.PerformLayout();
|
||||
this._agvControlGroup.ResumeLayout(false);
|
||||
this._agvControlGroup.PerformLayout();
|
||||
this._agvInfoPanel.ResumeLayout(false);
|
||||
this._agvInfoPanel.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
@@ -692,6 +793,7 @@ namespace AGVSimulator.Forms
|
||||
private System.Windows.Forms.Button _calculatePathButton;
|
||||
private System.Windows.Forms.Button _startPathButton;
|
||||
private System.Windows.Forms.Button _clearPathButton;
|
||||
private System.Windows.Forms.CheckBox _avoidRotationCheckBox;
|
||||
private System.Windows.Forms.GroupBox _statusGroup;
|
||||
private System.Windows.Forms.Label _simulationStatusLabel;
|
||||
private System.Windows.Forms.Label _agvCountLabel;
|
||||
@@ -700,11 +802,18 @@ namespace AGVSimulator.Forms
|
||||
private System.Windows.Forms.Label _rfidLabel;
|
||||
private System.Windows.Forms.TextBox _rfidTextBox;
|
||||
private System.Windows.Forms.Button _setPositionButton;
|
||||
private System.Windows.Forms.ComboBox _directionCombo;
|
||||
private System.Windows.Forms.Label _directionLabel;
|
||||
private System.Windows.Forms.ToolStripButton btAllReset;
|
||||
private System.Windows.Forms.ToolStripMenuItem reloadMapToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem launchMapEditorToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripSeparator toolStripSeparator4;
|
||||
private System.Windows.Forms.ToolStripButton reloadMapToolStripButton;
|
||||
private System.Windows.Forms.ToolStripButton launchMapEditorToolStripButton;
|
||||
private System.Windows.Forms.Panel _agvInfoPanel;
|
||||
private System.Windows.Forms.Label _liftDirectionLabel;
|
||||
private System.Windows.Forms.Label _motorDirectionLabel;
|
||||
private System.Windows.Forms.Label _agvInfoTitleLabel;
|
||||
private System.Windows.Forms.Label _pathDebugLabel;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ namespace AGVSimulator.Forms
|
||||
private List<MapNode> _mapNodes;
|
||||
private NodeResolver _nodeResolver;
|
||||
private PathCalculator _pathCalculator;
|
||||
private AdvancedAGVPathfinder _advancedPathfinder;
|
||||
private List<VirtualAGV> _agvList;
|
||||
private SimulationState _simulationState;
|
||||
private Timer _simulationTimer;
|
||||
@@ -50,6 +51,9 @@ namespace AGVSimulator.Forms
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeForm();
|
||||
|
||||
// Load 이벤트 연결
|
||||
this.Load += SimulatorForm_Load;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -80,6 +84,8 @@ namespace AGVSimulator.Forms
|
||||
|
||||
// 초기 상태 설정
|
||||
UpdateUI();
|
||||
|
||||
// 마지막 맵 파일 자동 로드 확인은 Form_Load에서 수행
|
||||
}
|
||||
|
||||
|
||||
@@ -121,6 +127,12 @@ namespace AGVSimulator.Forms
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private void SimulatorForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
// 폼이 완전히 로드된 후 마지막 맵 파일 자동 로드 확인
|
||||
CheckAndLoadLastMapFile();
|
||||
}
|
||||
|
||||
private void OnOpenMap_Click(object sender, EventArgs e)
|
||||
{
|
||||
using (var openDialog = new OpenFileDialog())
|
||||
@@ -279,7 +291,39 @@ namespace AGVSimulator.Forms
|
||||
_pathCalculator.SetMapData(_mapNodes);
|
||||
}
|
||||
|
||||
var agvResult = _pathCalculator.FindAGVPath(startNode.NodeId, targetNode.NodeId);
|
||||
if (_advancedPathfinder == null)
|
||||
{
|
||||
_advancedPathfinder = new AdvancedAGVPathfinder(_mapNodes);
|
||||
}
|
||||
|
||||
// 현재 AGV 방향 가져오기
|
||||
var selectedAGV = _agvListCombo.SelectedItem as VirtualAGV;
|
||||
var currentDirection = selectedAGV?.CurrentDirection ?? AgvDirection.Forward;
|
||||
|
||||
// 고급 경로 계획 사용
|
||||
var advancedResult = _advancedPathfinder.FindAdvancedPath(startNode.NodeId, targetNode.NodeId, currentDirection);
|
||||
|
||||
if (advancedResult.Success)
|
||||
{
|
||||
// 고급 경로 결과를 기존 AGVPathResult 형태로 변환
|
||||
var agvResult1 = ConvertToAGVPathResult(advancedResult);
|
||||
|
||||
_simulatorCanvas.CurrentPath = agvResult1.ToPathResult();
|
||||
_pathLengthLabel.Text = $"경로 길이: {advancedResult.TotalDistance:F1}";
|
||||
_statusLabel.Text = $"고급 경로 계산 완료 ({advancedResult.CalculationTimeMs}ms)";
|
||||
|
||||
// 고급 경로 디버깅 정보 표시
|
||||
UpdateAdvancedPathDebugInfo(advancedResult);
|
||||
return;
|
||||
}
|
||||
|
||||
// 고급 경로 실패시 기존 방식으로 fallback
|
||||
// 회전 회피 옵션 설정
|
||||
var options = _avoidRotationCheckBox.Checked
|
||||
? PathfindingOptions.AvoidRotation
|
||||
: PathfindingOptions.Default;
|
||||
|
||||
var agvResult = _pathCalculator.FindAGVPath(startNode.NodeId, targetNode.NodeId, null, options);
|
||||
|
||||
if (agvResult.Success)
|
||||
{
|
||||
@@ -434,8 +478,12 @@ namespace AGVSimulator.Forms
|
||||
if (nodesWithRfid.Count == 0)
|
||||
return "RFID가 할당된 노드가 없습니다.";
|
||||
|
||||
// 처음 10개의 RFID만 표시
|
||||
var rfidList = nodesWithRfid.Take(10).Select(n => $"- {n.RfidId} → {n.NodeId}");
|
||||
// 처음 10개의 RFID만 표시 (노드 이름 포함)
|
||||
var rfidList = nodesWithRfid.Take(10).Select(n =>
|
||||
{
|
||||
var nodeNamePart = !string.IsNullOrEmpty(n.Name) ? $" {n.Name}" : "";
|
||||
return $"- {n.RfidId} → {n.NodeId}{nodeNamePart}";
|
||||
});
|
||||
var result = string.Join("\n", rfidList);
|
||||
|
||||
if (nodesWithRfid.Count > 10)
|
||||
@@ -457,8 +505,7 @@ namespace AGVSimulator.Forms
|
||||
_mapNodes = result.Nodes;
|
||||
_currentMapFilePath = filePath;
|
||||
|
||||
// RFID가 없는 노드들에 자동 할당
|
||||
MapLoader.AssignAutoRfidIds(_mapNodes);
|
||||
// RFID 자동 할당 제거 - 에디터에서 설정한 값 그대로 사용
|
||||
|
||||
// 시뮬레이터 캔버스에 맵 설정
|
||||
_simulatorCanvas.Nodes = _mapNodes;
|
||||
@@ -488,6 +535,35 @@ namespace AGVSimulator.Forms
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 마지막 맵 파일이 있는지 확인하고 사용자에게 로드할지 물어봄
|
||||
/// </summary>
|
||||
private void CheckAndLoadLastMapFile()
|
||||
{
|
||||
if (_config.AutoLoadLastMapFile && _config.HasValidLastMapFile())
|
||||
{
|
||||
string fileName = Path.GetFileName(_config.LastMapFilePath);
|
||||
var result = MessageBox.Show(
|
||||
$"마지막으로 사용한 맵 파일을 찾았습니다:\n\n{fileName}\n\n이 파일을 열까요?",
|
||||
"마지막 맵 파일 로드",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question);
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadMapFile(_config.LastMapFilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"맵 파일 로드 중 오류가 발생했습니다:\n{ex.Message}",
|
||||
"맵 파일 로드 오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNodeComboBoxes()
|
||||
{
|
||||
_startNodeCombo.Items.Clear();
|
||||
@@ -499,8 +575,9 @@ namespace AGVSimulator.Forms
|
||||
{
|
||||
if (node.IsActive && node.HasRfid())
|
||||
{
|
||||
// {rfid} - [{node}] 형식으로 ComboBoxItem 생성
|
||||
var displayText = $"{node.RfidId} - [{node.NodeId}]";
|
||||
// {rfid} - [{node}] {name} 형식으로 ComboBoxItem 생성
|
||||
var nodeNamePart = !string.IsNullOrEmpty(node.Name) ? $" {node.Name}" : "";
|
||||
var displayText = $"{node.RfidId} - [{node.NodeId}]{nodeNamePart}";
|
||||
var item = new ComboBoxItem<MapNode>(node, displayText);
|
||||
|
||||
_startNodeCombo.Items.Add(item);
|
||||
@@ -709,6 +786,111 @@ namespace AGVSimulator.Forms
|
||||
return node?.HasRfid() == true ? node.RfidId : nodeId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 고급 경로 결과를 기존 AGVPathResult 형태로 변환
|
||||
/// </summary>
|
||||
private AGVPathResult ConvertToAGVPathResult(AdvancedAGVPathfinder.AdvancedPathResult advancedResult)
|
||||
{
|
||||
var agvResult = new AGVPathResult();
|
||||
agvResult.Success = advancedResult.Success;
|
||||
agvResult.Path = advancedResult.GetSimplePath();
|
||||
agvResult.NodeMotorInfos = advancedResult.DetailedPath;
|
||||
agvResult.TotalDistance = advancedResult.TotalDistance;
|
||||
agvResult.CalculationTimeMs = advancedResult.CalculationTimeMs;
|
||||
agvResult.ExploredNodes = advancedResult.ExploredNodeCount;
|
||||
agvResult.ErrorMessage = advancedResult.ErrorMessage;
|
||||
return agvResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 고급 경로 디버깅 정보 업데이트
|
||||
/// </summary>
|
||||
private void UpdateAdvancedPathDebugInfo(AdvancedAGVPathfinder.AdvancedPathResult advancedResult)
|
||||
{
|
||||
if (advancedResult == null || !advancedResult.Success)
|
||||
{
|
||||
_pathDebugLabel.Text = "고급 경로: 설정되지 않음";
|
||||
return;
|
||||
}
|
||||
|
||||
// 노드 ID를 RFID로 변환한 경로 생성
|
||||
var pathWithRfid = advancedResult.GetSimplePath().Select(nodeId => GetRfidByNodeId(nodeId)).ToList();
|
||||
|
||||
// 콘솔 디버그 정보 출력
|
||||
Program.WriteLine($"[ADVANCED DEBUG] 고급 경로 계산 완료:");
|
||||
Program.WriteLine($" 전체 경로 (RFID): [{string.Join(" → ", pathWithRfid)}]");
|
||||
Program.WriteLine($" 전체 경로 (NodeID): [{string.Join(" → ", advancedResult.GetSimplePath())}]");
|
||||
Program.WriteLine($" 경로 노드 수: {advancedResult.DetailedPath.Count}");
|
||||
Program.WriteLine($" 방향 전환 필요: {advancedResult.RequiredDirectionChange}");
|
||||
|
||||
if (advancedResult.RequiredDirectionChange && !string.IsNullOrEmpty(advancedResult.DirectionChangeNode))
|
||||
{
|
||||
Program.WriteLine($" 방향 전환 노드: {advancedResult.DirectionChangeNode}");
|
||||
}
|
||||
|
||||
Program.WriteLine($" 설명: {advancedResult.PlanDescription}");
|
||||
|
||||
// 상세 경로 정보 출력
|
||||
for (int i = 0; i < advancedResult.DetailedPath.Count; i++)
|
||||
{
|
||||
var info = advancedResult.DetailedPath[i];
|
||||
var rfidId = GetRfidByNodeId(info.NodeId);
|
||||
var nextRfidId = info.NextNodeId != null ? GetRfidByNodeId(info.NextNodeId) : "END";
|
||||
|
||||
var flags = new List<string>();
|
||||
if (info.CanRotate) flags.Add("회전가능");
|
||||
if (info.IsDirectionChangePoint) flags.Add("방향전환");
|
||||
if (info.RequiresSpecialAction) flags.Add($"특수동작:{info.SpecialActionDescription}");
|
||||
if (info.MagnetDirection != MagnetDirection.Straight) flags.Add($"마그넷:{info.MagnetDirection}");
|
||||
|
||||
var flagsStr = flags.Count > 0 ? $" [{string.Join(", ", flags)}]" : "";
|
||||
Program.WriteLine($" {i}: {rfidId}({info.NodeId}) → {info.MotorDirection} → {nextRfidId}{flagsStr}");
|
||||
}
|
||||
|
||||
// 경로 문자열 구성 (마그넷 방향 포함)
|
||||
var pathWithDetails = new List<string>();
|
||||
for (int i = 0; i < advancedResult.DetailedPath.Count; i++)
|
||||
{
|
||||
var motorInfo = advancedResult.DetailedPath[i];
|
||||
var rfidId = GetRfidByNodeId(motorInfo.NodeId);
|
||||
string motorSymbol = motorInfo.MotorDirection == AgvDirection.Forward ? "[전진]" : "[후진]";
|
||||
|
||||
// 마그넷 방향 표시
|
||||
if (motorInfo.MagnetDirection != MagnetDirection.Straight)
|
||||
{
|
||||
string magnetSymbol = motorInfo.MagnetDirection == MagnetDirection.Left ? "[←]" : "[→]";
|
||||
motorSymbol += magnetSymbol;
|
||||
}
|
||||
|
||||
// 특수 동작 표시
|
||||
if (motorInfo.RequiresSpecialAction)
|
||||
motorSymbol += "[🔄]";
|
||||
else if (motorInfo.IsDirectionChangePoint && motorInfo.CanRotate)
|
||||
motorSymbol += "[↻]";
|
||||
|
||||
pathWithDetails.Add($"{rfidId}{motorSymbol}");
|
||||
}
|
||||
|
||||
string pathString = string.Join(" → ", pathWithDetails);
|
||||
|
||||
// UI에 표시 (길이 제한)
|
||||
if (pathString.Length > 100)
|
||||
{
|
||||
pathString = pathString.Substring(0, 97) + "...";
|
||||
}
|
||||
|
||||
// 통계 정보
|
||||
var forwardCount = advancedResult.DetailedPath.Count(m => m.MotorDirection == AgvDirection.Forward);
|
||||
var backwardCount = advancedResult.DetailedPath.Count(m => m.MotorDirection == AgvDirection.Backward);
|
||||
var magnetDirectionChanges = advancedResult.DetailedPath.Count(m => m.MagnetDirection != MagnetDirection.Straight);
|
||||
|
||||
string stats = $"전진: {forwardCount}, 후진: {backwardCount}";
|
||||
if (magnetDirectionChanges > 0)
|
||||
stats += $", 마그넷제어: {magnetDirectionChanges}";
|
||||
|
||||
_pathDebugLabel.Text = $"고급경로: {pathString} (총 {advancedResult.DetailedPath.Count}개 노드, {advancedResult.TotalDistance:F1}px, {stats})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 경로 디버깅 정보 업데이트 (RFID 값 표시, 모터방향 정보 포함)
|
||||
/// </summary>
|
||||
@@ -736,7 +918,14 @@ namespace AGVSimulator.Forms
|
||||
var info = agvResult.NodeMotorInfos[i];
|
||||
var rfidId = GetRfidByNodeId(info.NodeId);
|
||||
var nextRfidId = info.NextNodeId != null ? GetRfidByNodeId(info.NextNodeId) : "END";
|
||||
Program.WriteLine($" {i}: {rfidId}({info.NodeId}) → {info.MotorDirection} → {nextRfidId}");
|
||||
|
||||
var flags = new List<string>();
|
||||
if (info.CanRotate) flags.Add("회전가능");
|
||||
if (info.IsDirectionChangePoint) flags.Add("방향전환");
|
||||
if (info.RequiresSpecialAction) flags.Add($"특수동작:{info.SpecialActionDescription}");
|
||||
|
||||
var flagsStr = flags.Count > 0 ? $" [{string.Join(", ", flags)}]" : "";
|
||||
Program.WriteLine($" {i}: {rfidId}({info.NodeId}) → {info.MotorDirection} → {nextRfidId}{flagsStr}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -751,6 +940,13 @@ namespace AGVSimulator.Forms
|
||||
var motorInfo = agvResult.NodeMotorInfos[i];
|
||||
var rfidId = GetRfidByNodeId(motorInfo.NodeId);
|
||||
string motorSymbol = motorInfo.MotorDirection == AgvDirection.Forward ? "[전진]" : "[후진]";
|
||||
|
||||
// 특수 동작 표시 추가
|
||||
if (motorInfo.RequiresSpecialAction)
|
||||
motorSymbol += "[🔄]";
|
||||
else if (motorInfo.IsDirectionChangePoint && motorInfo.CanRotate)
|
||||
motorSymbol += "[↻]";
|
||||
|
||||
pathWithMotorInfo.Add($"{rfidId}{motorSymbol}");
|
||||
}
|
||||
pathString = string.Join(" → ", pathWithMotorInfo);
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace AGVSimulator.Models
|
||||
/// </summary>
|
||||
public bool AutoSave { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 프로그램 시작시 마지막 맵 파일을 자동으로 로드할지 여부
|
||||
/// </summary>
|
||||
public bool AutoLoadLastMapFile { get; set; } = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Methods
|
||||
@@ -105,10 +110,19 @@ namespace AGVSimulator.Models
|
||||
/// <returns>유효한 경로인지 여부</returns>
|
||||
public bool IsMapEditorPathValid()
|
||||
{
|
||||
return !string.IsNullOrEmpty(MapEditorExecutablePath) &&
|
||||
return !string.IsNullOrEmpty(MapEditorExecutablePath) &&
|
||||
File.Exists(MapEditorExecutablePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 마지막 맵 파일이 존재하는지 확인
|
||||
/// </summary>
|
||||
/// <returns>마지막 맵 파일이 유효한지 여부</returns>
|
||||
public bool HasValidLastMapFile()
|
||||
{
|
||||
return !string.IsNullOrEmpty(LastMapFilePath) && File.Exists(LastMapFilePath);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -9,21 +9,33 @@ namespace AGVSimulator
|
||||
/// </summary>
|
||||
static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// 콘솔 출력 (타임스탬프 포함)
|
||||
/// </summary>
|
||||
public static void WriteLine(string message)
|
||||
{
|
||||
string timestampedMessage = $"[{DateTime.Now:HH:mm:ss.fff}] {message}";
|
||||
Console.WriteLine(timestampedMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 애플리케이션의 주 진입점입니다.
|
||||
/// </summary>
|
||||
/// <param name="args">명령줄 인수</param>
|
||||
[STAThread]
|
||||
static void Main()
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
try
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new SimulatorForm());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[ERROR] 시뮬레이터 실행 중 오류: {ex.Message}");
|
||||
Console.WriteLine($"[ERROR] 스택 트레이스: {ex.StackTrace}");
|
||||
|
||||
MessageBox.Show($"시뮬레이터 실행 중 오류가 발생했습니다:\n{ex.Message}",
|
||||
"시스템 오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user