fix: 그리드 화면 전체 표시 및 스케일 동적 계산

주요 변경사항:
- 그리드가 화면 전체를 덮도록 수정
- ScreenToWorld를 사용하여 정확한 월드 좌표 계산
- 스케일 표시를 동적으로 계산 (줌에 따라 변경)
- 스케일 정보를 별도 박스로 표시 (좌하단)
- 줌 정보와 스케일 정보 분리 표시

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
backuppc
2025-10-30 11:13:18 +09:00
parent 5378f702e7
commit b04d3aa0cd
7 changed files with 114 additions and 73 deletions

View File

@@ -68,30 +68,36 @@ namespace AGVNavigationCore.Controls
{
if (!_showGrid) return;
var bounds = GetVisibleBounds();
var gridSize = (int)(GRID_SIZE * _zoomFactor);
if (gridSize < 5) return; // 너무 작으면 그리지 않음
// 그리드 시작 위치 (월드 좌표에서 GRID_SIZE의 배수 찾기)
int startX = (bounds.Left / GRID_SIZE) * GRID_SIZE;
int startY = (bounds.Top / GRID_SIZE) * GRID_SIZE;
// 화면 전체를 덮는 월드 좌표 범위 계산
var topLeft = ScreenToWorld(new Point(0, 0));
var bottomRight = ScreenToWorld(new Point(Width, Height));
// 월드 좌표로 그리드 라인 계산 (Transform이 자동으로 적용됨)
for (int x = startX; x <= bounds.Right; x += GRID_SIZE)
// 그리드 시작 위치 (월드 좌표에서 GRID_SIZE의 배수로 정렬)
int startX = (topLeft.X / GRID_SIZE) * GRID_SIZE - GRID_SIZE;
int startY = (topLeft.Y / GRID_SIZE) * GRID_SIZE - GRID_SIZE;
int endX = bottomRight.X + GRID_SIZE;
int endY = bottomRight.Y + GRID_SIZE;
// 그리드 펜 (가는 선과 굵은 선)
using (var thinPen = new Pen(Color.FromArgb(200, 200, 200), 1))
using (var thickPen = new Pen(Color.FromArgb(150, 150, 150), 1))
{
if (x % (GRID_SIZE * 5) == 0)
g.DrawLine(new Pen(Color.Gray, 1), x, bounds.Top, x, bounds.Bottom);
else
g.DrawLine(_gridPen, x, bounds.Top, x, bounds.Bottom);
// 수직선 그리기
for (int x = startX; x <= endX; x += GRID_SIZE)
{
var pen = (x % (GRID_SIZE * 5) == 0) ? thickPen : thinPen;
g.DrawLine(pen, x, startY, x, endY);
}
for (int y = startY; y <= bounds.Bottom; y += GRID_SIZE)
// 수평선 그리기
for (int y = startY; y <= endY; y += GRID_SIZE)
{
if (y % (GRID_SIZE * 5) == 0)
g.DrawLine(new Pen(Color.Gray, 1), bounds.Left, y, bounds.Right, y);
else
g.DrawLine(_gridPen, bounds.Left, y, bounds.Right, y);
var pen = (y % (GRID_SIZE * 5) == 0) ? thickPen : thinPen;
g.DrawLine(pen, startX, y, endX, y);
}
}
}
@@ -1563,13 +1569,38 @@ namespace AGVNavigationCore.Controls
g.DrawImage(_companyLogo, logoRect);
}
// 측정 정보
// 줌 및 스케일 정보 (동적 계산)
// 스케일: 1픽셀 = GRID_SIZE / _zoomFactor mm
// 예: GRID_SIZE=10, zoom=1.0 → 1:10, zoom=0.1 → 1:100
double scaleRatio = GRID_SIZE / _zoomFactor;
var zoomText = $"Zoom: {_zoomFactor:P0}";
var scaleText = $"스케일: 1:{scaleRatio:F0}";
using (var font = new Font("맑은 고딕", 10, FontStyle.Bold))
using (var bgBrush = new SolidBrush(Color.FromArgb(220, Color.White)))
{
// 줌 정보 (좌하단)
var zoomSize = g.MeasureString(zoomText, font);
var zoomRect = new RectangleF(10, Height - zoomSize.Height - 15, zoomSize.Width + 10, zoomSize.Height + 5);
g.FillRectangle(bgBrush, zoomRect);
g.DrawRectangle(Pens.Gray, zoomRect.X, zoomRect.Y, zoomRect.Width, zoomRect.Height);
g.DrawString(zoomText, font, Brushes.Black, zoomRect.X + 5, zoomRect.Y + 2);
// 스케일 정보 (줌 정보 위에)
var scaleSize = g.MeasureString(scaleText, font);
var scaleRect = new RectangleF(10, Height - zoomSize.Height - scaleSize.Height - 25, scaleSize.Width + 10, scaleSize.Height + 5);
g.FillRectangle(bgBrush, scaleRect);
g.DrawRectangle(Pens.Gray, scaleRect.X, scaleRect.Y, scaleRect.Width, scaleRect.Height);
g.DrawString(scaleText, font, Brushes.Black, scaleRect.X + 5, scaleRect.Y + 2);
}
// 측정 정보 (우하단 - 사용자 정의 정보가 있을 경우)
if (!string.IsNullOrEmpty(_measurementInfo))
{
var font = new Font("Arial", 9);
var textBrush = new SolidBrush(Color.Black);
var backgroundBrush = new SolidBrush(Color.FromArgb(200, Color.White));
using (var font = new Font("맑은 고딕", 9))
using (var textBrush = new SolidBrush(Color.Black))
using (var backgroundBrush = new SolidBrush(Color.FromArgb(200, Color.White)))
{
var textSize = g.MeasureString(_measurementInfo, font);
var textRect = new Rectangle(
Width - (int)textSize.Width - 20,
@@ -1581,23 +1612,8 @@ namespace AGVNavigationCore.Controls
g.FillRectangle(backgroundBrush, textRect);
g.DrawRectangle(Pens.Gray, textRect);
g.DrawString(_measurementInfo, font, textBrush, textRect.X + 5, textRect.Y + 5);
font.Dispose();
textBrush.Dispose();
backgroundBrush.Dispose();
}
// 줌 정보
var zoomText = $"Zoom: {_zoomFactor:P0}";
var zoomFont = new Font("Arial", 10, FontStyle.Bold);
var zoomSize = g.MeasureString(zoomText, zoomFont);
var zoomPoint = new Point(10, Height - (int)zoomSize.Height - 10);
g.FillRectangle(new SolidBrush(Color.FromArgb(200, Color.White)),
zoomPoint.X - 5, zoomPoint.Y - 5,
zoomSize.Width + 10, zoomSize.Height + 10);
g.DrawString(zoomText, zoomFont, Brushes.Black, zoomPoint);
zoomFont.Dispose();
}
}
/// <summary>

View File

@@ -83,7 +83,7 @@ namespace AGVNavigationCore.Controls
// UI 요소들
private Image _companyLogo;
private string _companyLogoPath = string.Empty;
private string _measurementInfo = "스케일: 1:100\n면적: 1000㎡\n최종 수정: " + DateTime.Now.ToString("yyyy-MM-dd");
private string _measurementInfo = string.Empty;
// 편집 관련 (EditMode에서만 사용)
private bool _isDragging;

View File

@@ -292,6 +292,23 @@ namespace AGVNavigationCore.PathFinding.Core
$"계산시간: {CalculationTimeMs}ms";
}
/// <summary>
/// 경로의 노드 정보를 포함
/// </summary>
/// <returns></returns>
public string GetDetailedPathInfo()
{
if (!Success)
{
return $"경로 계산 실패: {ErrorMessage} (계산시간: {CalculationTimeMs}ms)";
}
var data = DetailedPath.Select(t => {
return $"{t.RfidId}[{t.NodeId}] {t.MotorDirection.ToString().Substring(0,1)}-{t.MagnetDirection.ToString().Substring(0,1)}";
});
return string.Join(" → ",data);
}
/// <summary>
/// 단순 경로 목록 반환 (호환성용 - 노드 ID 문자열 목록)

View File

@@ -45,6 +45,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Forms\PathTestLogItem.cs" />
<Compile Include="Forms\ProgressLogForm.cs">
<SubType>Form</SubType>
</Compile>

View File

@@ -0,0 +1,25 @@
using System;
namespace AGVSimulator.Forms
{
/// <summary>
/// 경로 예측 테스트 결과 로그 항목
/// </summary>
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;
}
}
}

View File

@@ -7,26 +7,6 @@ using System.Windows.Forms;
namespace AGVSimulator.Forms
{
/// <summary>
/// 경로 예측 테스트 결과 로그 항목
/// </summary>
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;
}
}
/// <summary>
/// 경로 예측 테스트 진행 상황 로그 표시 폼

View File

@@ -325,7 +325,7 @@ namespace AGVSimulator.Forms
// 도킹 검증이 없는 경우 추가 검증 수행
if (advancedResult.DockingValidation == null || !advancedResult.DockingValidation.IsValidationRequired)
{
if(advancedResult.Path.Count < 1)
if (advancedResult.Path.Count < 1)
{
}
@@ -1382,13 +1382,15 @@ namespace AGVSimulator.Forms
{
logItem.Success = true;
logItem.Message = "성공";
logItem.DetailedPath = string.Join(" → ", currentPath.GetDetailedInfo());
//logItem.DetailedPath = string.Join(" → ", currentPath.GetDetailedInfo());
logItem.DetailedPath = currentPath.GetDetailedPathInfo();
}
else
{
logItem.Success = false;
logItem.Message = $"도킹 검증 실패: {dockingValidation.ValidationError}";
logItem.DetailedPath = string.Join(" → ", currentPath.GetDetailedInfo());
//logItem.DetailedPath = string.Join(" → ", currentPath.GetDetailedInfo());
logItem.DetailedPath = currentPath.GetDetailedPathInfo();
}
}
else