This commit is contained in:
backuppc
2026-02-12 09:58:01 +09:00
parent 2b3a9b3d1d
commit d6aed58516
17 changed files with 914 additions and 402 deletions

View File

@@ -374,11 +374,26 @@ namespace AGVNavigationCore.Controls
foreach (var targetNode in node.ConnectedMapNodes)
{
if (targetNode == null) continue;
// 강조된 연결은 나중에 그리기 위해 건너뜀
if (IsConnectionHighlighted(node.Id, targetNode.Id)) continue;
DrawConnection(g, node, targetNode);
}
}
}
// 1.1 강조된 연결 그리기 (항상 위에 표시되도록)
if (_highlightedConnection.HasValue)
{
var n1 = _nodes.FirstOrDefault(n => n.Id == _highlightedConnection.Value.FromNodeId);
var n2 = _nodes.FirstOrDefault(n => n.Id == _highlightedConnection.Value.ToNodeId);
if (n1 != null && n2 != null)
{
DrawConnection(g, n1, n2);
}
}
// 2. 마그넷 그리기 (별도 리스트 사용)
if (_magnets != null)
{
@@ -442,10 +457,17 @@ namespace AGVNavigationCore.Controls
g.DrawLine(_magnetPen, startPoint, endPoint);
}
// 호버된 마그넷 강조
if (magnet == _hoveredNode)
// 호버되거나 선택된 마그넷 강조 (선택: Red, 호버: Orange)
bool isHovered = (magnet == _hoveredNode);
bool isSelected = (magnet == _selectedNode);
if (isHovered || isSelected)
{
using (var highlightPen = new Pen(Color.Orange, 19))
Color highlightColor = isSelected ? Color.Red : Color.Orange;
// 선택된 상태에서 호버되면? -> 선택 색상 우선 (Red) 또는 명확한 구분 필요
// 여기서는 선택이 더 중요하므로 Red 유지
using (var highlightPen = new Pen(highlightColor, 19) { StartCap = LineCap.Round, EndCap = LineCap.Round })
{
if (magnet.ControlPoint != null)
{
@@ -470,15 +492,6 @@ namespace AGVNavigationCore.Controls
g.DrawLine(highlightPen, startPoint, endPoint);
}
}
// Redraw normal to keep it on top? No, highlight is usually outer.
// If I draw highlight AFTER, it covers.
// But DrawMagnet is void. If I draw highlight after, it's fine if I want it to glow.
// Actually _magnetPen is Width 15, very thick.
// If I draw highlight Width 19 *before* normal, it acts as border.
// But this method draws normal first.
// So I should refactor to calculate path first, then draw?
// Or just draw highlight on top with alpha?
// Let's draw highlight on top for now, maybe with alpha.
}
// 선택된 마그넷 핸들 그리기
@@ -859,7 +872,7 @@ namespace AGVNavigationCore.Controls
{
case NodeType.Normal:
var item = _selectedNode as MapNode;
if ( item.StationType == StationType.Charger1 || item.StationType == StationType.Charger2)
if ( item.StationType == StationType.Charger)
DrawTriangleGhost(g, ghostBrush);
else
DrawPentagonGhost(g, ghostBrush);
@@ -1049,13 +1062,12 @@ namespace AGVNavigationCore.Controls
switch (node.StationType)
{
case StationType.Loader:
case StationType.UnLoader:
case StationType.Clearner:
case StationType.Cleaner:
case StationType.Plating:
case StationType.Buffer:
DrawPentagonNodeShape(g, node, brush);
break;
case StationType.Charger1:
case StationType.Charger2:
case StationType.Charger:
DrawTriangleNodeShape(g, node, brush);
break;
case StationType.Limit:
@@ -1470,7 +1482,7 @@ namespace AGVNavigationCore.Controls
// 🔥 노드의 폰트 설정 사용 (0 이하일 경우 기본값 7.0f 사용)
var topFont = new Font("Arial", 9, FontStyle.Bold);
var btmFont = new Font("Arial", 12, FontStyle.Bold);
var btmFont = new Font("Arial", node.NodeTextFontSize, FontStyle.Bold);
// 메인 텍스트 크기 측정
var TopSize = g.MeasureString(TopIDText, topFont);
@@ -1496,8 +1508,7 @@ namespace AGVNavigationCore.Controls
Color bgColor = Color.White;
switch (node.StationType)
{
case StationType.Charger1:
case StationType.Charger2:
case StationType.Charger:
fgColor = Color.White;
bgColor = Color.Tomato;
break;
@@ -1505,12 +1516,12 @@ namespace AGVNavigationCore.Controls
fgColor = Color.Black;
bgColor = Color.White;
break;
case StationType.Clearner:
case StationType.Plating:
fgColor = Color.Black;
bgColor = Color.DeepSkyBlue;
break;
case StationType.Loader:
case StationType.UnLoader:
case StationType.Cleaner:
fgColor = Color.Black;
bgColor = Color.Gold;
break;
@@ -1519,6 +1530,8 @@ namespace AGVNavigationCore.Controls
break;
}
var rectpaddingx = 4;
@@ -1541,7 +1554,8 @@ namespace AGVNavigationCore.Controls
}
using (var descBrush = new SolidBrush(fgColor))
using (var descBrush = new SolidBrush(node.NodeTextForeColor))
{
g.DrawString(BottomLabelText, btmFont, descBrush, roundRect, new StringFormat
{
@@ -1793,12 +1807,16 @@ namespace AGVNavigationCore.Controls
switch (node.StationType)
{
case StationType.Normal: bgColor = Color.DeepSkyBlue; break;
case StationType.Charger1: bgColor = Color.Tomato; break;
case StationType.Charger2: bgColor = Color.Tomato; break;
case StationType.Normal:
if(node.CanTurnLeft || node.CanTurnRight)
bgColor = Color.Violet;
else
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.Cleaner: bgColor = Color.Gold; break;
case StationType.Plating: bgColor = Color.DeepSkyBlue; break;
case StationType.Buffer: bgColor = Color.WhiteSmoke; break;
case StationType.Limit: bgColor = Color.Red; break;
default: bgColor = Color.White; break;

View File

@@ -104,6 +104,10 @@ namespace AGVNavigationCore.Controls
HandleConnectClick(hitNode as MapNode);
break;
case EditMode.ConnectDirection:
HandleConnectDirectionClick(hitNode as MapNode);
break;
case EditMode.Delete:
HandleDeleteClick(hitNode);
break;
@@ -227,6 +231,23 @@ namespace AGVNavigationCore.Controls
if (handleIdx != -1)
{
_dragHandleIndex = handleIdx;
// 핸들 드래그 시 초기 오프셋 설정 (점프 현상 방지)
if (_selectedNode is MapMagnet magnet)
{
Point handlePos = Point.Empty;
if (handleIdx == 0) handlePos = magnet.StartPoint;
else if (handleIdx == 1) handlePos = magnet.EndPoint;
else if (handleIdx == 2 && magnet.ControlPoint != null)
handlePos = new Point((int)magnet.ControlPoint.X, (int)magnet.ControlPoint.Y);
_dragOffset = new Point(worldPoint.X - handlePos.X, worldPoint.Y - handlePos.Y);
}
else
{
_dragOffset = Point.Empty; // Mark 등은 오프셋 없이 마우스 포인터 기준 계산
}
_isDragging = true;
_isPanning = false;
Capture = true;
@@ -562,12 +583,11 @@ namespace AGVNavigationCore.Controls
switch (node.StationType)
{
case StationType.Loader:
case StationType.UnLoader:
case StationType.Clearner:
case StationType.Cleaner:
case StationType.Plating:
case StationType.Buffer:
return IsPointInPentagon(point, node);
case StationType.Charger2:
case StationType.Charger1:
case StationType.Charger:
return IsPointInTriangle(point, node);
default:
return IsPointInCircle(point, node);
@@ -887,7 +907,9 @@ namespace AGVNavigationCore.Controls
var newNode = new MapNode
{
Id = newNodeId,
Position = worldPoint
Position = worldPoint,
CanTurnLeft=false,
CanTurnRight= false,
};
_nodes.Add(newNode);
@@ -998,6 +1020,31 @@ namespace AGVNavigationCore.Controls
Invalidate();
}
private void HandleConnectDirectionClick(MapNode hitNode)
{
if (hitNode == null) return;
if (!_isConnectionMode)
{
// 연결 시작 (방향 설정)
_isConnectionMode = true;
_connectionStartNode = hitNode;
_selectedNode = hitNode;
}
else
{
// 연결 완료
if (_connectionStartNode != null && _connectionStartNode != hitNode)
{
// 기본값 S (Straight)로 방향 설정
SetMagnetDirection(_connectionStartNode, hitNode, MagnetPosition.S);
}
CancelConnection();
}
Invalidate();
}
private void HandleDeleteClick(MapNode hitNode)
{
if (hitNode == null) return;
@@ -1025,10 +1072,46 @@ namespace AGVNavigationCore.Controls
toNode.ConnectedNodes.Contains(fromNode.Id))
return;
// 양방향 연결 생성 (AGV가 양쪽 방향으로 이동 가능하도록)
// 양방향 연결 생성 (AGV가 양쪽 방향으로 이동 가능하도록)
fromNode.AddConnection(toNode.Id);
toNode.AddConnection(fromNode.Id);
// 🔥 화면 표시용 ConnectedMapNodes 리스트도 즉시 갱신해야 함
if (!fromNode.ConnectedMapNodes.Contains(toNode))
fromNode.ConnectedMapNodes.Add(toNode);
if (!toNode.ConnectedMapNodes.Contains(fromNode))
toNode.ConnectedMapNodes.Add(fromNode);
ConnectionCreated?.Invoke(this, (fromNode, toNode));
MapChanged?.Invoke(this, EventArgs.Empty);
}
public void SetMagnetDirection(MapNode fromNode, MapNode toNode, MagnetPosition direction)
{
if (fromNode == null || toNode == null) return;
// 이미 연결된 노드인지 확인 (연결되어 있어야 방향 설정 가능)
// 이미 연결된 노드인지 확인 (연결되어 있어야 방향 설정 가능)
if (!fromNode.ConnectedNodes.Contains(toNode.Id))
{
// 연결되어 있지 않으면 자동 연결
CreateConnection(fromNode, toNode);
}
if (fromNode.MagnetDirections == null)
fromNode.MagnetDirections = new Dictionary<string, MagnetPosition>();
if (fromNode.MagnetDirections.ContainsKey(toNode.Id))
{
fromNode.MagnetDirections[toNode.Id] = direction;
}
else
{
fromNode.MagnetDirections.Add(toNode.Id, direction);
}
MapChanged?.Invoke(this, EventArgs.Empty);
}

View File

@@ -20,7 +20,7 @@ namespace AGVNavigationCore.Controls
{
#region Constants
private const int NODE_SIZE = 24;
private const int NODE_SIZE = 18;
private const int NODE_RADIUS = NODE_SIZE / 2;
private const int GRID_SIZE = 20;
private const float CONNECTION_WIDTH = 1.0f;
@@ -56,6 +56,7 @@ namespace AGVNavigationCore.Controls
DeleteConnection, // 연결 삭제 모드
AddLabel, // 라벨 추가 모드
AddImage, // 이미지 추가 모드
ConnectDirection, // 방향 연결 모드
}
#endregion
@@ -139,7 +140,7 @@ namespace AGVNavigationCore.Controls
string _alertmesage = "";
bool showalert = false;
// 깜박임 효과를 위한 타이머 및 상태
private Timer _alertBlinkTimer;
private bool _isAlertBlinkOn = true;
@@ -176,7 +177,7 @@ namespace AGVNavigationCore.Controls
//_isAlertBlinkOn = !_isAlertBlinkOn;
//Invalidate();
}
// 브러쉬 및 펜
private Brush _normalNodeBrush;
@@ -220,6 +221,7 @@ namespace AGVNavigationCore.Controls
public event EventHandler<List<NodeBase>> NodesSelected; // 다중 선택 이벤트
public event EventHandler<NodeBase> NodeDeleted;
public event EventHandler<NodeBase> NodeMoved;
public event EventHandler<(MapNode From, MapNode To)> ConnectionCreated; // 연결 생성 이벤트 추가
public event EventHandler<(MapNode From, MapNode To)> ConnectionDeleted;
public event EventHandler<MapImage> ImageDoubleClicked;
public event EventHandler<MapLabel> LabelDoubleClicked;
@@ -266,6 +268,18 @@ namespace AGVNavigationCore.Controls
{
if (_nodes != null && _nodes.Contains(node))
{
// 🔥 삭제되는 노드와 연결된 다른 노드들의 연결 정보도 삭제
foreach (var otherNode in _nodes.ToList()) // ToList()로 복사본 순회 (안전장치)
{
if (otherNode == node) continue;
// 다른 노드 -> 삭제되는 노드 연결 제거
if (otherNode.ConnectedNodes.Contains(node.Id))
{
otherNode.RemoveConnection(node.Id);
}
}
_nodes.Remove(node);
Invalidate();
}
@@ -336,10 +350,10 @@ namespace AGVNavigationCore.Controls
{
// Zoom 값 범위 제한
float newZoom = Math.Max(0.1f, Math.Min(5.0f, zoom));
_panOffset = new PointF(panX, panY);
_zoomFactor = newZoom;
Invalidate();
}
@@ -408,12 +422,12 @@ namespace AGVNavigationCore.Controls
/// <summary>
/// 선택된 노드 (단일)
/// </summary>
public MapNode SelectedNode
public NodeBase SelectedNode
{
get { return this._selectedNode as MapNode; }
set
{
_selectedNode = value;
get { return this._selectedNode; }
set
{
_selectedNode = value;
if (value != null)
{
_selectedNodes.Clear();
@@ -423,7 +437,7 @@ namespace AGVNavigationCore.Controls
{
_selectedNodes.Clear();
}
Invalidate();
Invalidate();
}
}
@@ -746,8 +760,8 @@ namespace AGVNavigationCore.Controls
_destinationNodePen = new Pen(Color.Orange, 4);
_pathPen = new Pen(Color.Purple, 3);
_agvPen = new Pen(Color.Red, 3);
_highlightedConnectionPen = new Pen(Color.Red, 4) { DashStyle = DashStyle.Solid };
_magnetPen = new Pen(Color.FromArgb(100, Color.LightSkyBlue), 15) { DashStyle = DashStyle.Solid };
_highlightedConnectionPen = new Pen(Color.Red, 6) { DashStyle = DashStyle.Solid };
_magnetPen = new Pen(Color.FromArgb(100, Color.LightSkyBlue), 15) { DashStyle = DashStyle.Solid, StartCap = LineCap.Round, EndCap = LineCap.Round };
_markPen = new Pen(Color.White, 3); // 마크는 흰색 선으로 표시
}