fix: Add motor direction parameter to magnet direction calculation in pathfinding
- Fixed critical issue in ConvertToDetailedPath where motor direction was not passed to GetRequiredMagnetDirection - Motor direction is essential for backward movement as Left/Right directions must be inverted - Modified AGVPathfinder.cs line 280 to pass currentDirection parameter - Ensures backward motor direction properly inverts magnet sensor directions feat: Add waypoint support to pathfinding system - Added FindPath overload with params string[] waypointNodeIds in AStarPathfinder - Supports sequential traversal through multiple intermediate nodes - Validates waypoints and prevents duplicates in sequence - Returns combined path result with aggregated metrics feat: Implement path result merging with DetailedPath preservation - Added CombineResults method in AStarPathfinder for intelligent path merging - Automatically deduplicates nodes when last of previous path equals first of current - Preserves DetailedPath information including motor and magnet directions - Essential for multi-segment path operations feat: Integrate magnet direction with motor direction awareness - Modified JunctionAnalyzer.GetRequiredMagnetDirection to accept AgvDirection parameter - Inverts Left/Right magnet directions when moving Backward - Properly handles motor direction context throughout pathfinding feat: Add automatic start node selection in simulator - Added SetStartNodeToCombo method to SimulatorForm - Automatically selects start node combo box when AGV position is set via RFID - Improves UI usability and workflow efficiency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -17,9 +17,9 @@ namespace AGVNavigationCore.Controls
|
||||
float BatteryLevel { get; }
|
||||
|
||||
// 이동 경로 정보 추가
|
||||
Point? TargetPosition { get; }
|
||||
Point? PrevPosition { get; }
|
||||
string CurrentNodeId { get; }
|
||||
string TargetNodeId { get; }
|
||||
string PrevNodeId { get; }
|
||||
DockingDirection DockingDirection { get; }
|
||||
|
||||
}
|
||||
|
||||
@@ -76,27 +76,21 @@ namespace AGVNavigationCore.Controls
|
||||
int startX = (bounds.Left / GRID_SIZE) * GRID_SIZE;
|
||||
int startY = (bounds.Top / GRID_SIZE) * GRID_SIZE;
|
||||
|
||||
// 월드 좌표로 그리드 라인 계산
|
||||
// 월드 좌표로 그리드 라인 계산 (Transform이 자동으로 적용됨)
|
||||
for (int x = startX; x <= bounds.Right; x += GRID_SIZE)
|
||||
{
|
||||
// 월드 좌표를 스크린 좌표로 변환
|
||||
int screenX = x * (int)_zoomFactor + _panOffset.X;
|
||||
|
||||
if (x % (GRID_SIZE * 5) == 0)
|
||||
g.DrawLine(new Pen(Color.Gray, 1), screenX, 0, screenX, Height);
|
||||
g.DrawLine(new Pen(Color.Gray, 1), x, bounds.Top, x, bounds.Bottom);
|
||||
else
|
||||
g.DrawLine(_gridPen, screenX, 0, screenX, Height);
|
||||
g.DrawLine(_gridPen, x, bounds.Top, x, bounds.Bottom);
|
||||
}
|
||||
|
||||
for (int y = startY; y <= bounds.Bottom; y += GRID_SIZE)
|
||||
{
|
||||
// 월드 좌표를 스크린 좌표로 변환
|
||||
int screenY = y * (int)_zoomFactor + _panOffset.Y;
|
||||
|
||||
if (y % (GRID_SIZE * 5) == 0)
|
||||
g.DrawLine(new Pen(Color.Gray, 1), 0, screenY, Width, screenY);
|
||||
g.DrawLine(new Pen(Color.Gray, 1), bounds.Left, y, bounds.Right, y);
|
||||
else
|
||||
g.DrawLine(_gridPen, 0, screenY, Width, screenY);
|
||||
g.DrawLine(_gridPen, bounds.Left, y, bounds.Right, y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,86 +234,8 @@ namespace AGVNavigationCore.Controls
|
||||
pathPen.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AGV 경로 및 모터방향 정보를 시각화
|
||||
/// </summary>
|
||||
/// <param name="g">Graphics 객체</param>
|
||||
/// <param name="agvResult">AGV 경로 계산 결과</param>
|
||||
private void DrawAGVPath(Graphics g, AGVPathResult agvResult)
|
||||
{
|
||||
if (agvResult?.NodeMotorInfos == null || agvResult.NodeMotorInfos.Count == 0) return;
|
||||
|
||||
|
||||
// 노드별 모터방향 정보를 기반으로 향상된 경로 표시
|
||||
for (int i = 0; i < agvResult.NodeMotorInfos.Count - 1; i++)
|
||||
{
|
||||
var currentMotorInfo = agvResult.NodeMotorInfos[i];
|
||||
var nextMotorInfo = agvResult.NodeMotorInfos[i + 1];
|
||||
|
||||
var currentNode = _nodes?.FirstOrDefault(n => n.NodeId == currentMotorInfo.NodeId);
|
||||
var nextNode = _nodes?.FirstOrDefault(n => n.NodeId == nextMotorInfo.NodeId);
|
||||
|
||||
if (currentNode != null && nextNode != null)
|
||||
{
|
||||
// 모터방향에 따른 색상 결정
|
||||
var motorDirection = currentMotorInfo.MotorDirection;
|
||||
Color pathColor = motorDirection == AgvDirection.Forward ? Color.Green : Color.Orange;
|
||||
|
||||
// 강조된 경로 선 그리기
|
||||
var enhancedPen = new Pen(pathColor, 6) { DashStyle = DashStyle.Solid };
|
||||
g.DrawLine(enhancedPen, currentNode.Position, nextNode.Position);
|
||||
|
||||
// 중간점에 모터방향 화살표 표시
|
||||
var midPoint = new Point(
|
||||
(currentNode.Position.X + nextNode.Position.X) / 2,
|
||||
(currentNode.Position.Y + nextNode.Position.Y) / 2
|
||||
);
|
||||
|
||||
var angle = Math.Atan2(nextNode.Position.Y - currentNode.Position.Y,
|
||||
nextNode.Position.X - currentNode.Position.X);
|
||||
|
||||
// 모터방향별 화살표 그리기
|
||||
DrawDirectionArrow(g, midPoint, angle, motorDirection);
|
||||
|
||||
// 노드 옆에 모터방향 텍스트 표시
|
||||
DrawMotorDirectionLabel(g, currentNode.Position, motorDirection);
|
||||
|
||||
enhancedPen.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// 마지막 노드의 모터방향 표시
|
||||
if (agvResult.NodeMotorInfos.Count > 0)
|
||||
{
|
||||
var lastMotorInfo = agvResult.NodeMotorInfos[agvResult.NodeMotorInfos.Count - 1];
|
||||
var lastNode = _nodes?.FirstOrDefault(n => n.NodeId == lastMotorInfo.NodeId);
|
||||
if (lastNode != null)
|
||||
{
|
||||
DrawMotorDirectionLabel(g, lastNode.Position, lastMotorInfo.MotorDirection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모터방향 레이블 표시
|
||||
/// </summary>
|
||||
/// <param name="g">Graphics 객체</param>
|
||||
/// <param name="nodePosition">노드 위치</param>
|
||||
/// <param name="motorDirection">모터방향</param>
|
||||
private void DrawMotorDirectionLabel(Graphics g, Point nodePosition, AgvDirection motorDirection)
|
||||
{
|
||||
string motorText = motorDirection == AgvDirection.Forward ? "전진" : "후진";
|
||||
Color textColor = motorDirection == AgvDirection.Forward ? Color.DarkGreen : Color.DarkOrange;
|
||||
|
||||
var font = new Font("맑은 고딕", 8, FontStyle.Bold);
|
||||
var brush = new SolidBrush(textColor);
|
||||
|
||||
// 노드 우측 상단에 모터방향 텍스트 표시
|
||||
var textPosition = new Point(nodePosition.X + NODE_RADIUS + 2, nodePosition.Y - NODE_RADIUS - 2);
|
||||
g.DrawString(motorText, font, brush, textPosition);
|
||||
|
||||
font.Dispose();
|
||||
brush.Dispose();
|
||||
}
|
||||
|
||||
private void DrawNodesOnly(Graphics g)
|
||||
{
|
||||
@@ -1027,7 +943,7 @@ namespace AGVNavigationCore.Controls
|
||||
const int liftDistance = AGV_SIZE / 2 + 2; // AGV 본체 면에 바로 붙도록
|
||||
|
||||
var currentPos = agv.CurrentPosition;
|
||||
var targetPos = agv.TargetPosition;
|
||||
var targetPos = agv.PrevPosition;
|
||||
var dockingDirection = agv.DockingDirection;
|
||||
var currentDirection = agv.CurrentDirection;
|
||||
|
||||
@@ -1194,7 +1110,7 @@ namespace AGVNavigationCore.Controls
|
||||
private void DrawAGVLiftDebugInfo(Graphics g, IAGV agv)
|
||||
{
|
||||
var currentPos = agv.CurrentPosition;
|
||||
var targetPos = agv.TargetPosition;
|
||||
var targetPos = agv.PrevPosition;
|
||||
|
||||
// 디버그 정보 (개발용)
|
||||
if (targetPos.HasValue)
|
||||
@@ -1331,7 +1247,7 @@ namespace AGVNavigationCore.Controls
|
||||
const int monitorDistance = AGV_SIZE / 2 + 2; // AGV 본체에서 거리 (리프트와 동일)
|
||||
|
||||
var currentPos = agv.CurrentPosition;
|
||||
var targetPos = agv.TargetPosition;
|
||||
var targetPos = agv.PrevPosition;
|
||||
var dockingDirection = agv.DockingDirection;
|
||||
var currentDirection = agv.CurrentDirection;
|
||||
|
||||
|
||||
@@ -215,21 +215,18 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
private Point ScreenToWorld(Point screenPoint)
|
||||
{
|
||||
// 변환 행렬 생성 (렌더링과 동일)
|
||||
var transform = new System.Drawing.Drawing2D.Matrix();
|
||||
transform.Scale(_zoomFactor, _zoomFactor);
|
||||
transform.Translate(_panOffset.X, _panOffset.Y);
|
||||
// 스크린 좌표를 월드 좌표로 변환
|
||||
// 역순으로: 팬 오프셋 제거 → 줌 적용
|
||||
float worldX = (screenPoint.X - _panOffset.X) / _zoomFactor;
|
||||
float worldY = (screenPoint.Y - _panOffset.Y) / _zoomFactor;
|
||||
|
||||
// 역변환 행렬로 화면 좌표를 월드 좌표로 변환
|
||||
transform.Invert();
|
||||
var points = new System.Drawing.PointF[] { new System.Drawing.PointF(screenPoint.X, screenPoint.Y) };
|
||||
transform.TransformPoints(points);
|
||||
|
||||
return new Point((int)points[0].X, (int)points[0].Y);
|
||||
return new Point((int)worldX, (int)worldY);
|
||||
}
|
||||
|
||||
private Point WorldToScreen(Point worldPoint)
|
||||
{
|
||||
// 월드 좌표를 스크린 좌표로 변환
|
||||
// 순서: 줌 적용 → 팬 오프셋 추가
|
||||
return new Point(
|
||||
(int)(worldPoint.X * _zoomFactor + _panOffset.X),
|
||||
(int)(worldPoint.Y * _zoomFactor + _panOffset.Y)
|
||||
|
||||
@@ -469,9 +469,10 @@ namespace AGVNavigationCore.Controls
|
||||
/// </summary>
|
||||
/// <param name="agvId">AGV ID</param>
|
||||
/// <param name="position">새로운 위치</param>
|
||||
public void SetAGVPosition(string agvId, Point position)
|
||||
public void SetAGVPosition(string agvId, MapNode node, AgvDirection direction)
|
||||
{
|
||||
UpdateAGVPosition(agvId, position);
|
||||
UpdateAGVPosition(agvId, node.Position);
|
||||
UpdateAGVDirection(agvId, direction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user