fix
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<OutputPath>..\..\..\..\..\..\Amkor\AGV4\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
|
||||
@@ -111,6 +111,7 @@ namespace AGVMapEditor.Forms
|
||||
// 이벤트 연결
|
||||
_mapCanvas.NodeAdded += OnNodeAdded;
|
||||
_mapCanvas.NodeSelected += OnNodeSelected;
|
||||
_mapCanvas.NodesSelected += OnNodesSelected; // 다중 선택 이벤트
|
||||
_mapCanvas.NodeMoved += OnNodeMoved;
|
||||
_mapCanvas.NodeDeleted += OnNodeDeleted;
|
||||
_mapCanvas.ConnectionDeleted += OnConnectionDeleted;
|
||||
@@ -184,8 +185,46 @@ namespace AGVMapEditor.Forms
|
||||
private void OnNodeSelected(object sender, MapNode node)
|
||||
{
|
||||
_selectedNode = node;
|
||||
UpdateNodeProperties();
|
||||
UpdateImageEditButton(); // 이미지 노드 선택 시 이미지 편집 버튼 활성화
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
// 빈 공간 클릭 시 캔버스 속성 표시
|
||||
ShowCanvasProperties();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 노드 클릭 시 노드 속성 표시
|
||||
UpdateNodeProperties();
|
||||
UpdateImageEditButton(); // 이미지 노드 선택 시 이미지 편집 버튼 활성화
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNodesSelected(object sender, List<MapNode> nodes)
|
||||
{
|
||||
// 다중 선택 시 처리
|
||||
if (nodes == null || nodes.Count == 0)
|
||||
{
|
||||
ShowCanvasProperties();
|
||||
return;
|
||||
}
|
||||
|
||||
if (nodes.Count == 1)
|
||||
{
|
||||
// 단일 선택은 기존 방식 사용
|
||||
_selectedNode = nodes[0];
|
||||
UpdateNodeProperties();
|
||||
UpdateImageEditButton();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 다중 선택: 상태바에 선택 개수 표시
|
||||
toolStripStatusLabel1.Text = $"{nodes.Count}개 노드 선택됨 - PropertyGrid에서 공통 속성 일괄 변경 가능";
|
||||
|
||||
// 다중 선택 PropertyWrapper 표시
|
||||
var multiWrapper = new MultiNodePropertyWrapper(nodes);
|
||||
_propertyGrid.SelectedObject = multiWrapper;
|
||||
_propertyGrid.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNodeMoved(object sender, MapNode node)
|
||||
@@ -589,6 +628,13 @@ namespace AGVMapEditor.Forms
|
||||
_mapCanvas.Nodes = _mapNodes;
|
||||
// RfidMappings 제거됨 - MapNode에 통합
|
||||
|
||||
// 🔥 맵 설정 적용 (배경색, 그리드 표시)
|
||||
if (result.Settings != null)
|
||||
{
|
||||
_mapCanvas.BackColor = System.Drawing.Color.FromArgb(result.Settings.BackgroundColorArgb);
|
||||
_mapCanvas.ShowGrid = result.Settings.ShowGrid;
|
||||
}
|
||||
|
||||
// 현재 파일 경로 업데이트
|
||||
_currentMapFile = filePath;
|
||||
_hasChanges = false;
|
||||
@@ -614,7 +660,38 @@ namespace AGVMapEditor.Forms
|
||||
|
||||
private void SaveMapToFile(string filePath)
|
||||
{
|
||||
if (MapLoader.SaveMapToFile(filePath, _mapNodes))
|
||||
// 🔥 백업 파일 생성 (기존 파일이 있을 경우)
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
// 날짜시간 포함 백업 파일명 생성
|
||||
var directory = Path.GetDirectoryName(filePath);
|
||||
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(filePath);
|
||||
var extension = Path.GetExtension(filePath);
|
||||
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||||
var backupFileName = $"{fileNameWithoutExt}_{timestamp}{extension}.bak";
|
||||
var backupFilePath = Path.Combine(directory, backupFileName);
|
||||
|
||||
// 기존 파일을 백업 파일로 복사
|
||||
File.Copy(filePath, backupFilePath, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 백업 파일 생성 실패 시 경고만 표시하고 계속 진행
|
||||
MessageBox.Show($"백업 파일 생성 실패: {ex.Message}\n원본 파일은 계속 저장됩니다.", "백업 경고",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
// 🔥 현재 캔버스 설정을 맵 파일에 저장
|
||||
var settings = new MapLoader.MapSettings
|
||||
{
|
||||
BackgroundColorArgb = _mapCanvas.BackColor.ToArgb(),
|
||||
ShowGrid = _mapCanvas.ShowGrid
|
||||
};
|
||||
|
||||
if (MapLoader.SaveMapToFile(filePath, _mapNodes, settings))
|
||||
{
|
||||
// 현재 파일 경로 업데이트
|
||||
_currentMapFile = filePath;
|
||||
@@ -892,7 +969,7 @@ namespace AGVMapEditor.Forms
|
||||
{
|
||||
if (_selectedNode == null)
|
||||
{
|
||||
ClearNodeProperties();
|
||||
ShowCanvasProperties();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -905,6 +982,16 @@ namespace AGVMapEditor.Forms
|
||||
UpdateImageEditButton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 캔버스 속성 표시 (배경색 등)
|
||||
/// </summary>
|
||||
private void ShowCanvasProperties()
|
||||
{
|
||||
var canvasWrapper = new CanvasPropertyWrapper(_mapCanvas);
|
||||
_propertyGrid.SelectedObject = canvasWrapper;
|
||||
DisableImageEditButton();
|
||||
}
|
||||
|
||||
private void ClearNodeProperties()
|
||||
{
|
||||
_propertyGrid.SelectedObject = null;
|
||||
@@ -1010,6 +1097,9 @@ namespace AGVMapEditor.Forms
|
||||
|
||||
private void PropertyGrid_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
|
||||
{
|
||||
// 변경된 속성명 디버그 출력
|
||||
System.Diagnostics.Debug.WriteLine($"[PropertyGrid] 속성 변경됨: {e.ChangedItem.PropertyDescriptor.Name}");
|
||||
|
||||
// RFID 값 변경시 중복 검사
|
||||
if (e.ChangedItem.PropertyDescriptor.Name == "RFID")
|
||||
{
|
||||
@@ -1031,14 +1121,39 @@ namespace AGVMapEditor.Forms
|
||||
_hasChanges = true;
|
||||
UpdateTitle();
|
||||
|
||||
// 현재 선택된 노드를 기억
|
||||
// 🔥 다중 선택 여부 확인 및 선택된 노드들 저장
|
||||
bool isMultiSelect = _propertyGrid.SelectedObject is MultiNodePropertyWrapper;
|
||||
List<MapNode> selectedNodes = null;
|
||||
if (isMultiSelect)
|
||||
{
|
||||
// 캔버스에서 현재 선택된 노드들 가져오기
|
||||
selectedNodes = new List<MapNode>(_mapCanvas.SelectedNodes);
|
||||
System.Diagnostics.Debug.WriteLine($"[PropertyGrid] 다중 선택 노드 수: {selectedNodes.Count}");
|
||||
}
|
||||
|
||||
// 현재 선택된 노드를 기억 (단일 선택인 경우만)
|
||||
var currentSelectedNode = _selectedNode;
|
||||
|
||||
|
||||
RefreshNodeList();
|
||||
RefreshMapCanvas();
|
||||
|
||||
// 선택된 노드를 다시 선택
|
||||
if (currentSelectedNode != null)
|
||||
|
||||
// 🔥 캔버스 강제 갱신 (bool 타입 속성 변경 시 특히 필요)
|
||||
_mapCanvas.Invalidate();
|
||||
_mapCanvas.Update();
|
||||
|
||||
// 🔥 다중 선택인 경우 MultiNodePropertyWrapper를 다시 생성하여 바인딩
|
||||
if (isMultiSelect && selectedNodes != null && selectedNodes.Count > 0)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[PropertyGrid] MultiNodePropertyWrapper 재생성: {selectedNodes.Count}개");
|
||||
var multiWrapper = new MultiNodePropertyWrapper(selectedNodes);
|
||||
_propertyGrid.SelectedObject = multiWrapper;
|
||||
}
|
||||
|
||||
// PropertyGrid 새로고침
|
||||
_propertyGrid.Refresh();
|
||||
|
||||
// 🔥 단일 선택인 경우에만 노드를 다시 선택 (다중 선택은 캔버스에서 유지)
|
||||
if (!isMultiSelect && currentSelectedNode != null)
|
||||
{
|
||||
var nodeIndex = _mapNodes.IndexOf(currentSelectedNode);
|
||||
if (nodeIndex >= 0)
|
||||
@@ -1162,5 +1277,96 @@ namespace AGVMapEditor.Forms
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region Multi-Node Selection
|
||||
|
||||
private void ShowMultiNodeContextMenu(List<MapNode> nodes)
|
||||
{
|
||||
// 다중 선택 시 간단한 다이얼로그 표시
|
||||
var result = MessageBox.Show(
|
||||
$"{nodes.Count}개의 노드가 선택되었습니다.\n\n" +
|
||||
"일괄 속성 변경을 하시겠습니까?\n\n" +
|
||||
"예: 글자색 변경\n" +
|
||||
"아니오: 배경색 변경\n" +
|
||||
"취소: 닫기",
|
||||
"다중 노드 속성 변경",
|
||||
MessageBoxButtons.YesNoCancel,
|
||||
MessageBoxIcon.Question);
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
BatchChangeForeColor();
|
||||
}
|
||||
else if (result == DialogResult.No)
|
||||
{
|
||||
BatchChangeBackColor();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 노드들의 글자색 일괄 변경
|
||||
/// </summary>
|
||||
public void BatchChangeForeColor()
|
||||
{
|
||||
var selectedNodes = _mapCanvas.SelectedNodes;
|
||||
if (selectedNodes == null || selectedNodes.Count == 0)
|
||||
{
|
||||
MessageBox.Show("선택된 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var colorDialog = new ColorDialog())
|
||||
{
|
||||
colorDialog.Color = selectedNodes[0].ForeColor;
|
||||
if (colorDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
foreach (var node in selectedNodes)
|
||||
{
|
||||
node.ForeColor = colorDialog.Color;
|
||||
node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
_hasChanges = true;
|
||||
UpdateTitle();
|
||||
RefreshMapCanvas();
|
||||
MessageBox.Show($"{selectedNodes.Count}개 노드의 글자색이 변경되었습니다.", "완료", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 노드들의 배경색 일괄 변경
|
||||
/// </summary>
|
||||
public void BatchChangeBackColor()
|
||||
{
|
||||
var selectedNodes = _mapCanvas.SelectedNodes;
|
||||
if (selectedNodes == null || selectedNodes.Count == 0)
|
||||
{
|
||||
MessageBox.Show("선택된 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var colorDialog = new ColorDialog())
|
||||
{
|
||||
colorDialog.Color = selectedNodes[0].DisplayColor;
|
||||
if (colorDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
foreach (var node in selectedNodes)
|
||||
{
|
||||
node.DisplayColor = colorDialog.Color;
|
||||
node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
_hasChanges = true;
|
||||
UpdateTitle();
|
||||
RefreshMapCanvas();
|
||||
MessageBox.Show($"{selectedNodes.Count}개 노드의 배경색이 변경되었습니다.", "완료", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Design;
|
||||
using AGVNavigationCore.Models;
|
||||
using AGVNavigationCore.Controls;
|
||||
|
||||
namespace AGVMapEditor.Models
|
||||
{
|
||||
@@ -521,6 +522,84 @@ namespace AGVMapEditor.Models
|
||||
}
|
||||
}
|
||||
|
||||
[Category("표시")]
|
||||
[DisplayName("노드 배경색")]
|
||||
[Description("노드 배경 색상")]
|
||||
public Color DisplayColor
|
||||
{
|
||||
get => _node.DisplayColor;
|
||||
set
|
||||
{
|
||||
_node.DisplayColor = value;
|
||||
_node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
[Category("표시")]
|
||||
[DisplayName("글자 색상")]
|
||||
[Description("노드 텍스트 색상 (NodeId, Name 등)")]
|
||||
public Color ForeColor
|
||||
{
|
||||
get => _node.ForeColor;
|
||||
set
|
||||
{
|
||||
_node.ForeColor = value;
|
||||
_node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
[Category("표시")]
|
||||
[DisplayName("글자 크기")]
|
||||
[Description("노드 텍스트 크기 (픽셀)")]
|
||||
public float TextFontSize
|
||||
{
|
||||
get => _node.TextFontSize;
|
||||
set
|
||||
{
|
||||
_node.TextFontSize = Math.Max(5.0f, Math.Min(20.0f, value));
|
||||
_node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
[Category("표시")]
|
||||
[DisplayName("글자 굵게")]
|
||||
[Description("노드 텍스트 볼드 표시")]
|
||||
public bool TextFontBold
|
||||
{
|
||||
get => _node.TextFontBold;
|
||||
set
|
||||
{
|
||||
_node.TextFontBold = value;
|
||||
_node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
[Category("표시")]
|
||||
[DisplayName("이름 말풍선 배경색")]
|
||||
[Description("노드 이름 말풍선(하단 표시) 배경색")]
|
||||
public Color NameBubbleBackColor
|
||||
{
|
||||
get => _node.NameBubbleBackColor;
|
||||
set
|
||||
{
|
||||
_node.NameBubbleBackColor = value;
|
||||
_node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
[Category("표시")]
|
||||
[DisplayName("이름 말풍선 글자색")]
|
||||
[Description("노드 이름 말풍선(하단 표시) 글자색")]
|
||||
public Color NameBubbleForeColor
|
||||
{
|
||||
get => _node.NameBubbleForeColor;
|
||||
set
|
||||
{
|
||||
_node.NameBubbleForeColor = value;
|
||||
_node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
[Category("정보")]
|
||||
[DisplayName("생성 일시")]
|
||||
[Description("노드가 생성된 일시")]
|
||||
@@ -540,5 +619,207 @@ namespace AGVMapEditor.Models
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 다중 노드 선택 시 공통 속성 편집용 래퍼
|
||||
/// </summary>
|
||||
public class MultiNodePropertyWrapper
|
||||
{
|
||||
private List<MapNode> _nodes;
|
||||
|
||||
public MultiNodePropertyWrapper(List<MapNode> nodes)
|
||||
{
|
||||
_nodes = nodes ?? new List<MapNode>();
|
||||
}
|
||||
|
||||
[Category("다중 선택")]
|
||||
[DisplayName("선택된 노드 수")]
|
||||
[Description("현재 선택된 노드의 개수")]
|
||||
[ReadOnly(true)]
|
||||
public int SelectedCount => _nodes.Count;
|
||||
|
||||
[Category("표시")]
|
||||
[DisplayName("노드 배경색")]
|
||||
[Description("선택된 모든 노드의 배경색을 일괄 변경")]
|
||||
public Color DisplayColor
|
||||
{
|
||||
get => _nodes.Count > 0 ? _nodes[0].DisplayColor : Color.Blue;
|
||||
set
|
||||
{
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
node.DisplayColor = value;
|
||||
node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("표시")]
|
||||
[DisplayName("글자 색상")]
|
||||
[Description("선택된 모든 노드의 글자 색상을 일괄 변경")]
|
||||
public Color ForeColor
|
||||
{
|
||||
get => _nodes.Count > 0 ? _nodes[0].ForeColor : Color.Black;
|
||||
set
|
||||
{
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
node.ForeColor = value;
|
||||
node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("표시")]
|
||||
[DisplayName("글자 크기")]
|
||||
[Description("선택된 모든 노드의 글자 크기를 일괄 변경 (5~20 픽셀)")]
|
||||
public float TextFontSize
|
||||
{
|
||||
get => _nodes.Count > 0 ? _nodes[0].TextFontSize : 7.0f;
|
||||
set
|
||||
{
|
||||
var validValue = Math.Max(5.0f, Math.Min(20.0f, value));
|
||||
System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontSize 변경 시작: {validValue}, 노드 수: {_nodes.Count}");
|
||||
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($" - 노드 {node.NodeId}: {node.TextFontSize} → {validValue}");
|
||||
node.TextFontSize = validValue;
|
||||
node.ModifiedDate = DateTime.Now;
|
||||
System.Diagnostics.Debug.WriteLine($" - 변경 후: {node.TextFontSize}");
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontSize 변경 완료");
|
||||
}
|
||||
}
|
||||
|
||||
[Category("표시")]
|
||||
[DisplayName("글자 굵게")]
|
||||
[Description("선택된 모든 노드의 글자 굵기를 일괄 변경")]
|
||||
public bool TextFontBold
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = _nodes.Count > 0 ? _nodes[0].TextFontBold : true;
|
||||
System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontBold GET: {result}");
|
||||
return result;
|
||||
}
|
||||
set
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontBold 변경 시작: {value}, 노드 수: {_nodes.Count}");
|
||||
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($" - 노드 {node.NodeId}: {node.TextFontBold} → {value}");
|
||||
node.TextFontBold = value;
|
||||
node.ModifiedDate = DateTime.Now;
|
||||
System.Diagnostics.Debug.WriteLine($" - 변경 후: {node.TextFontBold}");
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontBold 변경 완료");
|
||||
}
|
||||
}
|
||||
|
||||
[Category("표시")]
|
||||
[DisplayName("이름 말풍선 배경색")]
|
||||
[Description("선택된 모든 노드의 이름 말풍선(하단 표시) 배경색을 일괄 변경")]
|
||||
public Color NameBubbleBackColor
|
||||
{
|
||||
get => _nodes.Count > 0 ? _nodes[0].NameBubbleBackColor : Color.Gold;
|
||||
set
|
||||
{
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
node.NameBubbleBackColor = value;
|
||||
node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("표시")]
|
||||
[DisplayName("이름 말풍선 글자색")]
|
||||
[Description("선택된 모든 노드의 이름 말풍선(하단 표시) 글자색을 일괄 변경")]
|
||||
public Color NameBubbleForeColor
|
||||
{
|
||||
get => _nodes.Count > 0 ? _nodes[0].NameBubbleForeColor : Color.Black;
|
||||
set
|
||||
{
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
node.NameBubbleForeColor = value;
|
||||
node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("고급")]
|
||||
[DisplayName("활성화")]
|
||||
[Description("선택된 모든 노드의 활성화 상태를 일괄 변경")]
|
||||
public bool IsActive
|
||||
{
|
||||
get => _nodes.Count > 0 ? _nodes[0].IsActive : true;
|
||||
set
|
||||
{
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
node.IsActive = value;
|
||||
node.ModifiedDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Category("정보")]
|
||||
[DisplayName("안내")]
|
||||
[Description("다중 선택 시 공통 속성만 변경할 수 있습니다")]
|
||||
[ReadOnly(true)]
|
||||
public string HelpText => "위 속성을 변경하면 선택된 모든 노드에 일괄 적용됩니다.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 캔버스 속성 래퍼 (배경색 등 캔버스 전체 속성 편집)
|
||||
/// </summary>
|
||||
public class CanvasPropertyWrapper
|
||||
{
|
||||
private UnifiedAGVCanvas _canvas;
|
||||
|
||||
public CanvasPropertyWrapper(UnifiedAGVCanvas canvas)
|
||||
{
|
||||
_canvas = canvas;
|
||||
}
|
||||
|
||||
[Category("배경")]
|
||||
[DisplayName("배경색")]
|
||||
[Description("맵 캔버스 배경색")]
|
||||
public Color BackgroundColor
|
||||
{
|
||||
get => _canvas.BackColor;
|
||||
set
|
||||
{
|
||||
_canvas.BackColor = value;
|
||||
_canvas.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
[Category("그리드")]
|
||||
[DisplayName("그리드 표시")]
|
||||
[Description("그리드 표시 여부")]
|
||||
public bool ShowGrid
|
||||
{
|
||||
get => _canvas.ShowGrid;
|
||||
set
|
||||
{
|
||||
_canvas.ShowGrid = value;
|
||||
_canvas.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
[Category("정보")]
|
||||
[DisplayName("설명")]
|
||||
[Description("맵 캔버스 전체 속성")]
|
||||
[ReadOnly(true)]
|
||||
public string Description
|
||||
{
|
||||
get => "빈 공간을 클릭하면 캔버스 전체 속성을 편집할 수 있습니다.";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,6 +26,10 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
|
||||
var g = e.Graphics;
|
||||
|
||||
// 🔥 배경색 그리기 (변환 행렬 적용 전에 전체 화면을 배경색으로 채움)
|
||||
g.Clear(this.BackColor);
|
||||
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
g.InterpolationMode = InterpolationMode.High;
|
||||
|
||||
@@ -77,7 +81,8 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
|
||||
// UI 정보 그리기 (변환 없이)
|
||||
DrawUIInfo(g);
|
||||
if (_showGrid)
|
||||
DrawUIInfo(g);
|
||||
}
|
||||
|
||||
private void DrawGrid(Graphics g)
|
||||
@@ -689,8 +694,8 @@ namespace AGVNavigationCore.Controls
|
||||
var pulseRect = new Rectangle(rect.X - 4, rect.Y - 4, rect.Width + 8, rect.Height + 8);
|
||||
g.DrawEllipse(new Pen(Color.FromArgb(150, 0, 255, 255), 2) { DashStyle = DashStyle.Dash }, pulseRect);
|
||||
}
|
||||
// 선택된 노드 강조
|
||||
else if (node == _selectedNode)
|
||||
// 선택된 노드 강조 (단일 또는 다중)
|
||||
else if (node == _selectedNode || (_selectedNodes != null && _selectedNodes.Contains(node)))
|
||||
{
|
||||
g.DrawEllipse(_selectedNodePen, rect);
|
||||
}
|
||||
@@ -938,19 +943,22 @@ namespace AGVNavigationCore.Controls
|
||||
// 아래쪽에 표시할 값 (RFID 우선, 없으면 노드ID)
|
||||
if (node.HasRfid())
|
||||
{
|
||||
// RFID가 있는 경우: 순수 RFID 값만 표시 (진한 색상)
|
||||
// RFID가 있는 경우: 순수 RFID 값만 표시 (노드 전경색 사용)
|
||||
displayText = node.RfidId;
|
||||
textColor = Color.Black;
|
||||
textColor = node.ForeColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
// RFID가 없는 경우: 노드 ID 표시 (연한 색상)
|
||||
// RFID가 없는 경우: 노드 ID 표시 (노드 전경색의 50% 투명도)
|
||||
displayText = node.NodeId;
|
||||
textColor = Color.Gray;
|
||||
textColor = Color.FromArgb(128, node.ForeColor);
|
||||
}
|
||||
|
||||
var font = new Font("Arial", 7, FontStyle.Bold);
|
||||
var descFont = new Font("Arial", 8, FontStyle.Bold);
|
||||
// 🔥 노드의 폰트 설정 사용 (0 이하일 경우 기본값 7.0f 사용)
|
||||
var fontStyle = node.TextFontBold ? FontStyle.Bold : FontStyle.Regular;
|
||||
var fontSize = node.TextFontSize > 0 ? node.TextFontSize : 7.0f;
|
||||
var font = new Font("Arial", fontSize, fontStyle);
|
||||
var descFont = new Font("Arial", fontSize + 1, fontStyle);
|
||||
|
||||
// 메인 텍스트 크기 측정
|
||||
var textSize = g.MeasureString(displayText, font);
|
||||
@@ -971,12 +979,8 @@ namespace AGVNavigationCore.Controls
|
||||
// 설명 텍스트 그리기 (설명이 있는 경우에만)
|
||||
if (!string.IsNullOrEmpty(descriptionText))
|
||||
{
|
||||
// 노드 이름 입력 여부에 따라 색상 구분
|
||||
// 입력된 경우: 진한 색상 (잘 보이게)
|
||||
// 기본값인 경우: 흐린 색상 (현재처럼)
|
||||
Color descColor = string.IsNullOrEmpty(node.Name)
|
||||
? Color.FromArgb(120, Color.Black) // 입력 안됨: 흐린 색상
|
||||
: Color.FromArgb(200, Color.Black); // 입력됨: 진한 색상
|
||||
// 🔥 노드의 말풍선 글자색 사용 (NameBubbleForeColor)
|
||||
Color descColor = node.NameBubbleForeColor;
|
||||
|
||||
var rectpaddingx = 4;
|
||||
var rectpaddingy = 2;
|
||||
@@ -985,10 +989,10 @@ namespace AGVNavigationCore.Controls
|
||||
(int)descSize.Width + rectpaddingx * 2,
|
||||
(int)descSize.Height + rectpaddingy * 2);
|
||||
|
||||
// 라운드 사각형 그리기 (빨간 배경)
|
||||
using (var backgroundBrush = new SolidBrush(Color.Gold))
|
||||
// 라운드 사각형 그리기 (노드 이름 말풍선 배경색 사용)
|
||||
using (var backgroundBrush = new SolidBrush(node.NameBubbleBackColor))
|
||||
{
|
||||
DrawRoundedRectangle(g, backgroundBrush, roundRect, 3); // 모서리 반지름 6px
|
||||
DrawRoundedRectangle(g, backgroundBrush, roundRect, 3); // 모서리 반지름 3px
|
||||
}
|
||||
|
||||
// 라운드 사각형 테두리 그리기 (진한 빨간색)
|
||||
@@ -1287,26 +1291,19 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
private Brush GetNodeBrush(MapNode node)
|
||||
{
|
||||
// RFID가 없는 노드는 회색 계통으로 표시
|
||||
// 🔥 노드의 DisplayColor를 배경색으로 사용
|
||||
// RFID가 없는 노드는 DisplayColor를 50% 투명도로 표시
|
||||
bool hasRfid = node.HasRfid();
|
||||
|
||||
switch (node.Type)
|
||||
Color bgColor = node.DisplayColor;
|
||||
|
||||
// RFID가 없는 경우 투명도 50%
|
||||
if (!hasRfid)
|
||||
{
|
||||
case NodeType.Normal:
|
||||
return hasRfid ? _normalNodeBrush : new SolidBrush(Color.LightGray);
|
||||
case NodeType.Rotation:
|
||||
return hasRfid ? _rotationNodeBrush : new SolidBrush(Color.DarkGray);
|
||||
case NodeType.Docking:
|
||||
return hasRfid ? _dockingNodeBrush : new SolidBrush(Color.Gray);
|
||||
case NodeType.Charging:
|
||||
return hasRfid ? _chargingNodeBrush : new SolidBrush(Color.Silver);
|
||||
case NodeType.Label:
|
||||
return new SolidBrush(Color.Purple);
|
||||
case NodeType.Image:
|
||||
return new SolidBrush(Color.Brown);
|
||||
default:
|
||||
return hasRfid ? _normalNodeBrush : new SolidBrush(Color.LightGray);
|
||||
bgColor = Color.FromArgb(128, bgColor);
|
||||
}
|
||||
|
||||
return new SolidBrush(bgColor);
|
||||
}
|
||||
|
||||
private void DrawAGVs(Graphics g)
|
||||
|
||||
@@ -17,6 +17,54 @@ namespace AGVNavigationCore.Controls
|
||||
var worldPoint = ScreenToWorld(e.Location);
|
||||
var hitNode = GetNodeAt(worldPoint);
|
||||
|
||||
// 🔥 어떤 모드에서든 노드/빈 공간 클릭 시 선택 이벤트 발생 (속성창 업데이트)
|
||||
bool ctrlPressed = (ModifierKeys & Keys.Control) == Keys.Control;
|
||||
|
||||
if (hitNode != null)
|
||||
{
|
||||
// 노드 클릭
|
||||
if (ctrlPressed && _editMode == EditMode.Select)
|
||||
{
|
||||
// Ctrl+클릭: 다중 선택 토글
|
||||
if (_selectedNodes.Contains(hitNode))
|
||||
{
|
||||
_selectedNodes.Remove(hitNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedNodes.Add(hitNode);
|
||||
}
|
||||
|
||||
// 마지막 선택된 노드 업데이트 (단일 참조용)
|
||||
_selectedNode = _selectedNodes.Count > 0 ? _selectedNodes[_selectedNodes.Count - 1] : null;
|
||||
|
||||
// 다중 선택 이벤트만 발생 (OnNodesSelected에서 단일/다중 구분 처리)
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
Invalidate();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 일반 클릭: 단일 선택
|
||||
_selectedNode = hitNode;
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(hitNode);
|
||||
|
||||
// NodesSelected 이벤트만 발생 (OnNodesSelected에서 단일/다중 구분 처리)
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
else if (_editMode == EditMode.Select)
|
||||
{
|
||||
// 빈 공간 클릭 (Select 모드에서만) - 선택 해제
|
||||
_selectedNode = null;
|
||||
_selectedNodes.Clear();
|
||||
|
||||
// NodesSelected 이벤트만 발생 (OnNodesSelected에서 빈 리스트 처리)
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
switch (_editMode)
|
||||
{
|
||||
case EditMode.Select:
|
||||
@@ -76,7 +124,10 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
default:
|
||||
// 기본 동작: 노드 선택 이벤트 발생
|
||||
NodeSelected?.Invoke(this, hitNode);
|
||||
_selectedNode = hitNode;
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(hitNode);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -98,8 +149,11 @@ namespace AGVNavigationCore.Controls
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
// 노드 선택 이벤트도 발생 (속성창 업데이트)
|
||||
NodeSelected?.Invoke(this, node);
|
||||
// 더블클릭 시 해당 노드만 선택 (다중 선택 해제)
|
||||
_selectedNode = node;
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(node);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
}
|
||||
|
||||
private void HandleLabelNodeDoubleClick(MapNode node)
|
||||
@@ -118,14 +172,20 @@ namespace AGVNavigationCore.Controls
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
// 노드 선택 이벤트도 발생 (속성창 업데이트)
|
||||
NodeSelected?.Invoke(this, node);
|
||||
// 더블클릭 시 해당 노드만 선택 (다중 선택 해제)
|
||||
_selectedNode = node;
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(node);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
}
|
||||
|
||||
private void HandleImageNodeDoubleClick(MapNode node)
|
||||
{
|
||||
// 이미지 노드는 선택 이벤트만 발생 (MainForm에서 이미지 편집 버튼 활성화됨)
|
||||
NodeSelected?.Invoke(this, node);
|
||||
// 더블클릭 시 해당 노드만 선택 (다중 선택 해제)
|
||||
_selectedNode = node;
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(node);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
|
||||
// 이미지 편집 이벤트 발생 (MainForm에서 처리)
|
||||
ImageNodeDoubleClicked?.Invoke(this, node);
|
||||
@@ -519,13 +579,8 @@ namespace AGVNavigationCore.Controls
|
||||
{
|
||||
if (hitNode != null)
|
||||
{
|
||||
// 노드 선택
|
||||
if (hitNode != _selectedNode)
|
||||
{
|
||||
_selectedNode = hitNode;
|
||||
NodeSelected?.Invoke(this, hitNode);
|
||||
Invalidate();
|
||||
}
|
||||
// 노드 선택은 위쪽 MouseClick에서 이미 처리됨 (NodesSelected 이벤트 발생)
|
||||
// 여기서는 추가 처리 없음
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -565,10 +620,11 @@ namespace AGVNavigationCore.Controls
|
||||
else
|
||||
{
|
||||
// 빈 공간 클릭 시 선택 해제
|
||||
if (_selectedNode != null)
|
||||
if (_selectedNode != null || _selectedNodes.Count > 0)
|
||||
{
|
||||
_selectedNode = null;
|
||||
NodeSelected?.Invoke(this, null);
|
||||
_selectedNodes.Clear();
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
@@ -745,7 +801,13 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
if (hitNode != null)
|
||||
{
|
||||
_contextMenu.Items.Add("노드 속성...", null, (s, e) => NodeSelected?.Invoke(this, hitNode));
|
||||
_contextMenu.Items.Add("노드 속성...", null, (s, e) =>
|
||||
{
|
||||
_selectedNode = hitNode;
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(hitNode);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
});
|
||||
_contextMenu.Items.Add("노드 삭제", null, (s, e) => HandleDeleteClick(hitNode));
|
||||
_contextMenu.Items.Add("-");
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ namespace AGVNavigationCore.Controls
|
||||
// 맵 데이터
|
||||
private List<MapNode> _nodes;
|
||||
private MapNode _selectedNode;
|
||||
private List<MapNode> _selectedNodes; // 다중 선택
|
||||
private MapNode _hoveredNode;
|
||||
private MapNode _destinationNode;
|
||||
|
||||
@@ -95,6 +96,11 @@ namespace AGVNavigationCore.Controls
|
||||
private Point _connectionEndPoint;
|
||||
private int _mouseMoveCounter = 0; // 디버그용: MouseMove 실행 횟수
|
||||
|
||||
// 영역 선택 관련
|
||||
private bool _isAreaSelecting;
|
||||
private Point _areaSelectStart;
|
||||
private Point _areaSelectEnd;
|
||||
|
||||
// 그리드 및 줌 관련
|
||||
private bool _showGrid = true;
|
||||
private float _zoomFactor = 1.0f;
|
||||
@@ -141,6 +147,7 @@ namespace AGVNavigationCore.Controls
|
||||
// 맵 편집 이벤트
|
||||
public event EventHandler<MapNode> NodeAdded;
|
||||
public event EventHandler<MapNode> NodeSelected;
|
||||
public event EventHandler<List<MapNode>> NodesSelected; // 다중 선택 이벤트
|
||||
public event EventHandler<MapNode> NodeDeleted;
|
||||
public event EventHandler<MapNode> NodeMoved;
|
||||
public event EventHandler<(MapNode From, MapNode To)> ConnectionDeleted;
|
||||
@@ -212,10 +219,15 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 노드
|
||||
/// 선택된 노드 (단일)
|
||||
/// </summary>
|
||||
public MapNode SelectedNode => _selectedNode;
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 노드들 (다중)
|
||||
/// </summary>
|
||||
public List<MapNode> SelectedNodes => _selectedNodes ?? new List<MapNode>();
|
||||
|
||||
/// <summary>
|
||||
/// 노드 목록
|
||||
/// </summary>
|
||||
@@ -365,6 +377,7 @@ namespace AGVNavigationCore.Controls
|
||||
ControlStyles.ResizeRedraw, true);
|
||||
|
||||
_nodes = new List<MapNode>();
|
||||
_selectedNodes = new List<MapNode>(); // 다중 선택 리스트 초기화
|
||||
_agvList = new List<IAGV>();
|
||||
_agvPositions = new Dictionary<string, Point>();
|
||||
_agvDirections = new Dictionary<string, AgvDirection>();
|
||||
|
||||
@@ -12,6 +12,15 @@ namespace AGVNavigationCore.Models
|
||||
/// </summary>
|
||||
public static class MapLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// 맵 설정 정보 (배경색, 그리드 표시 등)
|
||||
/// </summary>
|
||||
public class MapSettings
|
||||
{
|
||||
public int BackgroundColorArgb { get; set; } = System.Drawing.Color.White.ToArgb();
|
||||
public bool ShowGrid { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 맵 파일 로딩 결과
|
||||
/// </summary>
|
||||
@@ -19,6 +28,7 @@ namespace AGVNavigationCore.Models
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public List<MapNode> Nodes { get; set; } = new List<MapNode>();
|
||||
public MapSettings Settings { get; set; } = new MapSettings();
|
||||
public string ErrorMessage { get; set; } = string.Empty;
|
||||
public string Version { get; set; } = string.Empty;
|
||||
public DateTime CreatedDate { get; set; }
|
||||
@@ -30,8 +40,9 @@ namespace AGVNavigationCore.Models
|
||||
public class MapFileData
|
||||
{
|
||||
public List<MapNode> Nodes { get; set; } = new List<MapNode>();
|
||||
public MapSettings Settings { get; set; } = new MapSettings();
|
||||
public DateTime CreatedDate { get; set; }
|
||||
public string Version { get; set; } = "1.0";
|
||||
public string Version { get; set; } = "1.1"; // 버전 업그레이드 (설정 추가)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -66,6 +77,7 @@ namespace AGVNavigationCore.Models
|
||||
if (mapData != null)
|
||||
{
|
||||
result.Nodes = mapData.Nodes ?? new List<MapNode>();
|
||||
result.Settings = mapData.Settings ?? new MapSettings(); // 설정 로드
|
||||
result.Version = mapData.Version ?? "1.0";
|
||||
result.CreatedDate = mapData.CreatedDate;
|
||||
|
||||
@@ -111,8 +123,9 @@ namespace AGVNavigationCore.Models
|
||||
/// </summary>
|
||||
/// <param name="filePath">저장할 파일 경로</param>
|
||||
/// <param name="nodes">맵 노드 목록</param>
|
||||
/// <param name="settings">맵 설정 (배경색, 그리드 표시 등)</param>
|
||||
/// <returns>저장 성공 여부</returns>
|
||||
public static bool SaveMapToFile(string filePath, List<MapNode> nodes)
|
||||
public static bool SaveMapToFile(string filePath, List<MapNode> nodes, MapSettings settings = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -122,8 +135,9 @@ namespace AGVNavigationCore.Models
|
||||
var mapData = new MapFileData
|
||||
{
|
||||
Nodes = nodes,
|
||||
Settings = settings ?? new MapSettings(), // 설정 저장
|
||||
CreatedDate = DateTime.Now,
|
||||
Version = "1.0"
|
||||
Version = "1.1"
|
||||
};
|
||||
|
||||
var json = JsonConvert.SerializeObject(mapData, Formatting.Indented);
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace AGVNavigationCore.Models
|
||||
public FontStyle FontStyle { get; set; } = FontStyle.Regular;
|
||||
|
||||
/// <summary>
|
||||
/// 라벨 전경색 (NodeType.Label인 경우 사용)
|
||||
/// 텍스트 전경색 (모든 노드 타입에서 사용)
|
||||
/// </summary>
|
||||
public Color ForeColor { get; set; } = Color.Black;
|
||||
|
||||
@@ -133,6 +133,33 @@ namespace AGVNavigationCore.Models
|
||||
/// </summary>
|
||||
public Color BackColor { get; set; } = Color.Transparent;
|
||||
|
||||
private float _textFontSize = 7.0f;
|
||||
|
||||
/// <summary>
|
||||
/// 텍스트 폰트 크기 (모든 노드 타입의 텍스트 표시에 사용, 픽셀 단위)
|
||||
/// 0 이하의 값이 설정되면 기본값 7.0f로 자동 설정
|
||||
/// </summary>
|
||||
public float TextFontSize
|
||||
{
|
||||
get => _textFontSize;
|
||||
set => _textFontSize = value > 0 ? value : 7.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 텍스트 볼드체 여부 (모든 노드 타입의 텍스트 표시에 사용)
|
||||
/// </summary>
|
||||
public bool TextFontBold { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 노드 이름 말풍선 배경색 (하단에 표시되는 노드 이름의 배경색)
|
||||
/// </summary>
|
||||
public Color NameBubbleBackColor { get; set; } = Color.Gold;
|
||||
|
||||
/// <summary>
|
||||
/// 노드 이름 말풍선 글자색 (하단에 표시되는 노드 이름의 글자색)
|
||||
/// </summary>
|
||||
public Color NameBubbleForeColor { get; set; } = Color.Black;
|
||||
|
||||
/// <summary>
|
||||
/// 라벨 배경 표시 여부 (NodeType.Label인 경우 사용)
|
||||
/// </summary>
|
||||
@@ -347,6 +374,10 @@ namespace AGVNavigationCore.Models
|
||||
FontStyle = FontStyle,
|
||||
ForeColor = ForeColor,
|
||||
BackColor = BackColor,
|
||||
TextFontSize = TextFontSize,
|
||||
TextFontBold = TextFontBold,
|
||||
NameBubbleBackColor = NameBubbleBackColor,
|
||||
NameBubbleForeColor = NameBubbleForeColor,
|
||||
ShowBackground = ShowBackground,
|
||||
Padding = Padding,
|
||||
ImagePath = ImagePath,
|
||||
|
||||
@@ -306,15 +306,15 @@ namespace AGVNavigationCore.Models
|
||||
}
|
||||
|
||||
// 5. 방향체크
|
||||
if(CurrentDirection != TargetNode.MotorDirection)
|
||||
{
|
||||
return new AGVCommand(
|
||||
MotorCommand.Stop,
|
||||
MagnetPosition.S,
|
||||
SpeedLevel.L,
|
||||
$"(재탐색요청)모터방향 불일치 현재위치:{_currentNode.NodeId}"
|
||||
);
|
||||
}
|
||||
//if(CurrentDirection != TargetNode.MotorDirection)
|
||||
//{
|
||||
// return new AGVCommand(
|
||||
// MotorCommand.Stop,
|
||||
// MagnetPosition.S,
|
||||
// SpeedLevel.L,
|
||||
// $"(재탐색요청)모터방향 불일치 현재위치:{_currentNode.NodeId}"
|
||||
// );
|
||||
//}
|
||||
|
||||
|
||||
//this.CurrentNodeId
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<OutputPath>..\..\..\..\..\..\Amkor\AGV4\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
|
||||
@@ -51,6 +51,8 @@ namespace AGVSimulator.Forms
|
||||
this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.openMapToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.reloadMapToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.맵저장SToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.맵다른이름으로저장ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.launchMapEditorToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator();
|
||||
@@ -65,7 +67,6 @@ namespace AGVSimulator.Forms
|
||||
this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this._toolStrip = new System.Windows.Forms.ToolStrip();
|
||||
this.openMapToolStripButton = new System.Windows.Forms.ToolStripButton();
|
||||
this.reloadMapToolStripButton = new System.Windows.Forms.ToolStripButton();
|
||||
this.launchMapEditorToolStripButton = new System.Windows.Forms.ToolStripButton();
|
||||
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
|
||||
@@ -79,6 +80,7 @@ namespace AGVSimulator.Forms
|
||||
this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.toolStripButton1 = new System.Windows.Forms.ToolStripButton();
|
||||
this.btPredict = new System.Windows.Forms.ToolStripButton();
|
||||
this.btMakeMap = new System.Windows.Forms.ToolStripButton();
|
||||
this._statusStrip = new System.Windows.Forms.StatusStrip();
|
||||
this._statusLabel = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this._coordLabel = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
@@ -109,12 +111,12 @@ namespace AGVSimulator.Forms
|
||||
this._addAgvButton = new System.Windows.Forms.Button();
|
||||
this._agvListCombo = new System.Windows.Forms.ComboBox();
|
||||
this._canvasPanel = new System.Windows.Forms.Panel();
|
||||
this.lbPredict = new System.Windows.Forms.RichTextBox();
|
||||
this._agvInfoPanel = new System.Windows.Forms.Panel();
|
||||
this._pathDebugLabel = new System.Windows.Forms.TextBox();
|
||||
this._agvInfoTitleLabel = new System.Windows.Forms.Label();
|
||||
this._liftDirectionLabel = new System.Windows.Forms.Label();
|
||||
this._motorDirectionLabel = new System.Windows.Forms.Label();
|
||||
this.lbPredict = new System.Windows.Forms.RichTextBox();
|
||||
this.timer1 = new System.Windows.Forms.Timer(this.components);
|
||||
this._menuStrip.SuspendLayout();
|
||||
this._toolStrip.SuspendLayout();
|
||||
@@ -145,6 +147,8 @@ namespace AGVSimulator.Forms
|
||||
this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.openMapToolStripMenuItem,
|
||||
this.reloadMapToolStripMenuItem,
|
||||
this.맵저장SToolStripMenuItem,
|
||||
this.맵다른이름으로저장ToolStripMenuItem,
|
||||
this.toolStripSeparator1,
|
||||
this.launchMapEditorToolStripMenuItem,
|
||||
this.toolStripSeparator4,
|
||||
@@ -169,6 +173,20 @@ namespace AGVSimulator.Forms
|
||||
this.reloadMapToolStripMenuItem.Text = "맵 다시열기(&R)";
|
||||
this.reloadMapToolStripMenuItem.Click += new System.EventHandler(this.OnReloadMap_Click);
|
||||
//
|
||||
// 맵저장SToolStripMenuItem
|
||||
//
|
||||
this.맵저장SToolStripMenuItem.Name = "맵저장SToolStripMenuItem";
|
||||
this.맵저장SToolStripMenuItem.Size = new System.Drawing.Size(221, 22);
|
||||
this.맵저장SToolStripMenuItem.Text = "맵 저장(&S)";
|
||||
this.맵저장SToolStripMenuItem.Click += new System.EventHandler(this.맵저장SToolStripMenuItem_Click);
|
||||
//
|
||||
// 맵다른이름으로저장ToolStripMenuItem
|
||||
//
|
||||
this.맵다른이름으로저장ToolStripMenuItem.Name = "맵다른이름으로저장ToolStripMenuItem";
|
||||
this.맵다른이름으로저장ToolStripMenuItem.Size = new System.Drawing.Size(221, 22);
|
||||
this.맵다른이름으로저장ToolStripMenuItem.Text = "맵 다른 이름으로 저장";
|
||||
this.맵다른이름으로저장ToolStripMenuItem.Click += new System.EventHandler(this.btMapSaveAs_Click);
|
||||
//
|
||||
// toolStripSeparator1
|
||||
//
|
||||
this.toolStripSeparator1.Name = "toolStripSeparator1";
|
||||
@@ -272,7 +290,6 @@ namespace AGVSimulator.Forms
|
||||
// _toolStrip
|
||||
//
|
||||
this._toolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.openMapToolStripButton,
|
||||
this.reloadMapToolStripButton,
|
||||
this.launchMapEditorToolStripButton,
|
||||
this.toolStripSeparator2,
|
||||
@@ -285,22 +302,14 @@ namespace AGVSimulator.Forms
|
||||
this.resetZoomToolStripButton,
|
||||
this.toolStripSeparator5,
|
||||
this.toolStripButton1,
|
||||
this.btPredict});
|
||||
this.btPredict,
|
||||
this.btMakeMap});
|
||||
this._toolStrip.Location = new System.Drawing.Point(0, 24);
|
||||
this._toolStrip.Name = "_toolStrip";
|
||||
this._toolStrip.Size = new System.Drawing.Size(1034, 25);
|
||||
this._toolStrip.TabIndex = 1;
|
||||
this._toolStrip.Text = "toolStrip";
|
||||
//
|
||||
// openMapToolStripButton
|
||||
//
|
||||
this.openMapToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
|
||||
this.openMapToolStripButton.Name = "openMapToolStripButton";
|
||||
this.openMapToolStripButton.Size = new System.Drawing.Size(51, 22);
|
||||
this.openMapToolStripButton.Text = "맵 열기";
|
||||
this.openMapToolStripButton.ToolTipText = "맵 파일을 엽니다";
|
||||
this.openMapToolStripButton.Click += new System.EventHandler(this.OnOpenMap_Click);
|
||||
//
|
||||
// reloadMapToolStripButton
|
||||
//
|
||||
this.reloadMapToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
|
||||
@@ -406,6 +415,15 @@ namespace AGVSimulator.Forms
|
||||
this.btPredict.Text = "다음 행동 예측";
|
||||
this.btPredict.Click += new System.EventHandler(this.btPredict_Click);
|
||||
//
|
||||
// btMakeMap
|
||||
//
|
||||
this.btMakeMap.Image = ((System.Drawing.Image)(resources.GetObject("btMakeMap.Image")));
|
||||
this.btMakeMap.ImageTransparentColor = System.Drawing.Color.Magenta;
|
||||
this.btMakeMap.Name = "btMakeMap";
|
||||
this.btMakeMap.Size = new System.Drawing.Size(63, 22);
|
||||
this.btMakeMap.Text = "맵기록";
|
||||
this.btMakeMap.Click += new System.EventHandler(this.btMakeMap_Click);
|
||||
//
|
||||
// _statusStrip
|
||||
//
|
||||
this._statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
@@ -701,6 +719,15 @@ namespace AGVSimulator.Forms
|
||||
this._canvasPanel.Size = new System.Drawing.Size(801, 560);
|
||||
this._canvasPanel.TabIndex = 4;
|
||||
//
|
||||
// lbPredict
|
||||
//
|
||||
this.lbPredict.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.lbPredict.Location = new System.Drawing.Point(0, 513);
|
||||
this.lbPredict.Name = "lbPredict";
|
||||
this.lbPredict.Size = new System.Drawing.Size(801, 47);
|
||||
this.lbPredict.TabIndex = 0;
|
||||
this.lbPredict.Text = "";
|
||||
//
|
||||
// _agvInfoPanel
|
||||
//
|
||||
this._agvInfoPanel.BackColor = System.Drawing.Color.LightBlue;
|
||||
@@ -757,15 +784,6 @@ namespace AGVSimulator.Forms
|
||||
this._motorDirectionLabel.TabIndex = 2;
|
||||
this._motorDirectionLabel.Text = "모터 방향: -";
|
||||
//
|
||||
// lbPredict
|
||||
//
|
||||
this.lbPredict.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.lbPredict.Location = new System.Drawing.Point(0, 513);
|
||||
this.lbPredict.Name = "lbPredict";
|
||||
this.lbPredict.Size = new System.Drawing.Size(801, 47);
|
||||
this.lbPredict.TabIndex = 0;
|
||||
this.lbPredict.Text = "";
|
||||
//
|
||||
// timer1
|
||||
//
|
||||
this.timer1.Interval = 500;
|
||||
@@ -825,7 +843,6 @@ namespace AGVSimulator.Forms
|
||||
private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStrip _toolStrip;
|
||||
private System.Windows.Forms.ToolStripButton openMapToolStripButton;
|
||||
private System.Windows.Forms.ToolStripSeparator toolStripSeparator2;
|
||||
private System.Windows.Forms.ToolStripButton startSimulationToolStripButton;
|
||||
private System.Windows.Forms.ToolStripButton stopSimulationToolStripButton;
|
||||
@@ -879,5 +896,8 @@ namespace AGVSimulator.Forms
|
||||
private System.Windows.Forms.ToolStripButton btPredict;
|
||||
private System.Windows.Forms.RichTextBox lbPredict;
|
||||
private System.Windows.Forms.Timer timer1;
|
||||
private System.Windows.Forms.ToolStripButton btMakeMap;
|
||||
private System.Windows.Forms.ToolStripMenuItem 맵저장SToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem 맵다른이름으로저장ToolStripMenuItem;
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,13 @@ namespace AGVSimulator.Forms
|
||||
private string _currentMapFilePath;
|
||||
private bool _isTargetCalcMode; // 타겟계산 모드 상태
|
||||
|
||||
// 맵 스캔 모드 관련
|
||||
private bool _isMapScanMode; // 맵 스캔 모드 상태
|
||||
private DateTime _lastNodeAddTime; // 마지막 노드 추가 시간
|
||||
private MapNode _lastScannedNode; // 마지막으로 스캔된 노드
|
||||
private int _scanNodeCounter; // 스캔 노드 카운터
|
||||
private AgvDirection _lastScanDirection; // 마지막 스캔 방향
|
||||
|
||||
// UI Controls - Designer에서 생성됨
|
||||
|
||||
#endregion
|
||||
@@ -238,7 +245,8 @@ namespace AGVSimulator.Forms
|
||||
UpdateAGVComboBox();
|
||||
UpdateUI();
|
||||
|
||||
_statusLabel.Text = $"{agvId} 추가됨";
|
||||
_statusLabel.Text = $"{agvId} 추가됨";
|
||||
_simulatorCanvas.FitToNodes();
|
||||
}
|
||||
|
||||
private void OnRemoveAGV_Click(object sender, EventArgs e)
|
||||
@@ -508,6 +516,165 @@ namespace AGVSimulator.Forms
|
||||
return closestNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 방향을 기호로 변환
|
||||
/// </summary>
|
||||
private string GetDirectionSymbol(AgvDirection direction)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case AgvDirection.Forward: return "→";
|
||||
case AgvDirection.Backward: return "←";
|
||||
case AgvDirection.Left: return "↺";
|
||||
case AgvDirection.Right: return "↻";
|
||||
default: return "-";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 맵 스캔 모드에서 RFID로부터 노드 생성
|
||||
/// </summary>
|
||||
private void CreateNodeFromRfidScan(string rfidId, VirtualAGV selectedAGV)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 현재 선택된 방향 확인 (최상단에서 먼저 확인)
|
||||
var directionItem = _directionCombo.SelectedItem as DirectionItem;
|
||||
var currentDirection = directionItem?.Direction ?? AgvDirection.Forward;
|
||||
|
||||
// 중복 RFID 확인
|
||||
var existingNode = _mapNodes?.FirstOrDefault(n => n.RfidId.Equals(rfidId, StringComparison.OrdinalIgnoreCase));
|
||||
if (existingNode != null)
|
||||
{
|
||||
// 이미 존재하는 노드로 이동
|
||||
Program.WriteLine($"[맵 스캔] RFID '{rfidId}'는 이미 존재합니다 (노드: {existingNode.NodeId})");
|
||||
|
||||
// 기존 노드로 AGV 위치 설정
|
||||
_simulatorCanvas.SetAGVPosition(selectedAGV.AgvId, existingNode, currentDirection);
|
||||
selectedAGV.SetPosition(existingNode, currentDirection);
|
||||
|
||||
_lastScannedNode = existingNode;
|
||||
_lastNodeAddTime = DateTime.Now;
|
||||
_lastScanDirection = currentDirection; // 방향 업데이트
|
||||
|
||||
_statusLabel.Text = $"기존 노드로 이동: {existingNode.NodeId} [{GetDirectionSymbol(currentDirection)}]";
|
||||
_rfidTextBox.Text = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// 새 노드 생성 위치 계산
|
||||
int newX = 100; // 기본 시작 X 위치
|
||||
int newY = 300; // 기본 시작 Y 위치
|
||||
|
||||
if (_lastScannedNode != null)
|
||||
{
|
||||
// 시간차 기반 X축 거리 계산
|
||||
var timeDiff = (DateTime.Now - _lastNodeAddTime).TotalSeconds;
|
||||
|
||||
// 10초당 10px, 최소 50px, 최대 100px
|
||||
int distanceX = Math.Max(50, Math.Min(100, (int)(timeDiff * 10)));
|
||||
|
||||
// 방향 전환 확인
|
||||
bool directionChanged = (_lastScanDirection != currentDirection);
|
||||
|
||||
if (directionChanged)
|
||||
{
|
||||
// 방향이 바뀌면 Y축을 50px 증가시켜서 겹치지 않게 함
|
||||
newY = _lastScannedNode.Position.Y + 50;
|
||||
newX = _lastScannedNode.Position.X; // X는 같은 위치에서 시작
|
||||
|
||||
Program.WriteLine($"[맵 스캔] 방향 전환: {_lastScanDirection} → {currentDirection}, Y축 +50px");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 방향이 같으면 Y축 유지
|
||||
newY = _lastScannedNode.Position.Y;
|
||||
|
||||
// 모터 방향에 따라 X축 증가/감소
|
||||
if (currentDirection == AgvDirection.Forward)
|
||||
{
|
||||
// 전진: X축 증가
|
||||
newX = _lastScannedNode.Position.X + distanceX;
|
||||
Program.WriteLine($"[맵 스캔] 전진 모드: X축 +{distanceX}px");
|
||||
}
|
||||
else if (currentDirection == AgvDirection.Backward)
|
||||
{
|
||||
// 후진: X축 감소
|
||||
newX = _lastScannedNode.Position.X - distanceX;
|
||||
Program.WriteLine($"[맵 스캔] 후진 모드: X축 -{distanceX}px");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 그 외(회전 등): 기본적으로 전진 방향 사용
|
||||
newX = _lastScannedNode.Position.X + distanceX;
|
||||
Program.WriteLine($"[맵 스캔] 기타 방향({currentDirection}): X축 +{distanceX}px");
|
||||
}
|
||||
}
|
||||
|
||||
Program.WriteLine($"[맵 스캔] 시간차: {timeDiff:F1}초 → 거리: {distanceX}px");
|
||||
}
|
||||
|
||||
// 새 노드 생성
|
||||
var newNodeId = $"{_scanNodeCounter:D3}";
|
||||
var newNode = new MapNode
|
||||
{
|
||||
NodeId = newNodeId,
|
||||
RfidId = rfidId,
|
||||
Position = new Point(newX, newY),
|
||||
Type = NodeType.Normal,
|
||||
IsActive = true,
|
||||
Name = $"N{_scanNodeCounter}"
|
||||
};
|
||||
|
||||
// 맵에 추가
|
||||
if (_mapNodes == null)
|
||||
_mapNodes = new List<MapNode>();
|
||||
|
||||
_mapNodes.Add(newNode);
|
||||
|
||||
// 이전 노드와 연결 생성
|
||||
if (_lastScannedNode != null)
|
||||
{
|
||||
// 양방향 연결 (ConnectedNodes에 추가 - JSON 저장됨)
|
||||
_lastScannedNode.AddConnection(newNode.NodeId);
|
||||
newNode.AddConnection(_lastScannedNode.NodeId);
|
||||
|
||||
Program.WriteLine($"[맵 스캔] 연결 생성: {_lastScannedNode.NodeId} ↔ {newNode.NodeId}");
|
||||
}
|
||||
|
||||
// AGV 위치 설정
|
||||
_simulatorCanvas.SetAGVPosition(selectedAGV.AgvId, newNode, currentDirection);
|
||||
selectedAGV.SetPosition(newNode, currentDirection);
|
||||
|
||||
// 캔버스 업데이트
|
||||
_simulatorCanvas.Nodes = _mapNodes;
|
||||
|
||||
// 화면을 새 노드 위치로 이동
|
||||
_simulatorCanvas.PanToNode(newNode.NodeId);
|
||||
_simulatorCanvas.Invalidate();
|
||||
|
||||
// 상태 업데이트
|
||||
_lastScannedNode = newNode;
|
||||
_lastNodeAddTime = DateTime.Now;
|
||||
_lastScanDirection = currentDirection; // 현재 방향 저장
|
||||
_scanNodeCounter++;
|
||||
|
||||
// UI 업데이트
|
||||
UpdateNodeComboBoxes();
|
||||
|
||||
_statusLabel.Text = $"노드 생성: {newNode.NodeId} (RFID: {rfidId}) [{GetDirectionSymbol(currentDirection)}] - 총 {_mapNodes.Count}개";
|
||||
_rfidTextBox.Text = "";
|
||||
|
||||
Program.WriteLine($"[맵 스캔] 노드 생성 완료: {newNode.NodeId} (RFID: {rfidId}) at ({newX}, {newY}), 방향: {currentDirection}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"노드 생성 중 오류 발생:\n{ex.Message}", "오류",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
Program.WriteLine($"[맵 스캔 오류] {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSetPosition_Click(object sender, EventArgs e)
|
||||
{
|
||||
SetAGVPositionByRfid();
|
||||
@@ -561,6 +728,19 @@ namespace AGVSimulator.Forms
|
||||
MessageBox.Show("RFID 값을 입력해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
// 선택된 방향 확인
|
||||
var selectedDirectionItem = _directionCombo.SelectedItem as DirectionItem;
|
||||
var selectedDirection = selectedDirectionItem?.Direction ?? AgvDirection.Forward;
|
||||
|
||||
|
||||
// 맵 스캔 모드일 때: 노드 자동 생성
|
||||
if (_isMapScanMode)
|
||||
{
|
||||
CreateNodeFromRfidScan(rfidId, selectedAGV);
|
||||
this._simulatorCanvas.FitToNodes();
|
||||
return;
|
||||
}
|
||||
|
||||
// RFID에 해당하는 노드 직접 찾기
|
||||
var targetNode = _mapNodes?.FirstOrDefault(n => n.RfidId.Equals(rfidId, StringComparison.OrdinalIgnoreCase));
|
||||
@@ -571,10 +751,7 @@ namespace AGVSimulator.Forms
|
||||
return;
|
||||
}
|
||||
|
||||
// 선택된 방향 확인
|
||||
var selectedDirectionItem = _directionCombo.SelectedItem as DirectionItem;
|
||||
var selectedDirection = selectedDirectionItem?.Direction ?? AgvDirection.Forward;
|
||||
|
||||
|
||||
//이전위치와 동일한지 체크한다.
|
||||
if (selectedAGV.CurrentNodeId == targetNode.NodeId && selectedAGV.CurrentDirection == selectedDirection)
|
||||
{
|
||||
@@ -1625,6 +1802,184 @@ namespace AGVSimulator.Forms
|
||||
var command = agv.Predict();
|
||||
this.lbPredict.Text = $"Motor:{command.Motor},Magnet:{command.Magnet},Speed:{command.Speed} : {command.Reason}";
|
||||
}
|
||||
|
||||
|
||||
private void btMakeMap_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (!_isMapScanMode)
|
||||
{
|
||||
// 스캔 모드 시작
|
||||
var result = MessageBox.Show(
|
||||
"맵 스캔 모드를 시작합니다.\n\n" +
|
||||
"RFID를 입력하면 자동으로 맵 노드가 생성되고\n" +
|
||||
"이전 노드와 연결됩니다.\n\n" +
|
||||
"기존 맵 데이터를 삭제하고 시작하시겠습니까?\n\n" +
|
||||
"예: 새 맵 시작\n" +
|
||||
"아니오: 기존 맵에 추가",
|
||||
"맵 스캔 모드",
|
||||
MessageBoxButtons.YesNoCancel,
|
||||
MessageBoxIcon.Question);
|
||||
|
||||
if (result == DialogResult.Cancel)
|
||||
return;
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
// 기존 맵 데이터 삭제
|
||||
_mapNodes?.Clear();
|
||||
_mapNodes = new List<MapNode>();
|
||||
_simulatorCanvas.Nodes = _mapNodes;
|
||||
_currentMapFilePath = string.Empty;
|
||||
UpdateNodeComboBoxes();
|
||||
_statusLabel.Text = "맵 초기화 완료 - 스캔 모드 시작";
|
||||
}
|
||||
|
||||
// 스캔 모드 활성화
|
||||
_isMapScanMode = true;
|
||||
_lastNodeAddTime = DateTime.Now;
|
||||
_lastScannedNode = null;
|
||||
_scanNodeCounter = 1;
|
||||
_lastScanDirection = AgvDirection.Forward; // 기본 방향은 전진
|
||||
btMakeMap.Text = "스캔 중지";
|
||||
btMakeMap.BackColor = Color.LightCoral;
|
||||
_statusLabel.Text = "맵 스캔 모드: RFID를 입력하여 노드를 생성하세요";
|
||||
|
||||
Program.WriteLine("[맵 스캔] 스캔 모드 시작");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 스캔 모드 종료
|
||||
_isMapScanMode = false;
|
||||
btMakeMap.Text = "맵 생성";
|
||||
btMakeMap.BackColor = SystemColors.Control;
|
||||
_statusLabel.Text = $"맵 스캔 완료 - {_mapNodes?.Count ?? 0}개 노드 생성됨";
|
||||
|
||||
Program.WriteLine($"[맵 스캔] 스캔 모드 종료 - 총 {_mapNodes?.Count ?? 0}개 노드");
|
||||
|
||||
// 맵 저장 권장
|
||||
if (_mapNodes != null && _mapNodes.Count > 0)
|
||||
{
|
||||
var saveResult = MessageBox.Show(
|
||||
$"맵 스캔이 완료되었습니다.\n\n" +
|
||||
$"생성된 노드: {_mapNodes.Count}개\n\n" +
|
||||
"맵을 저장하시겠습니까?",
|
||||
"맵 저장",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question);
|
||||
|
||||
if (saveResult == DialogResult.Yes)
|
||||
{
|
||||
btMapSaveAs_Click(sender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 맵 데이터를 파일에 저장 (MapLoader 공통 저장 로직 사용)
|
||||
/// </summary>
|
||||
private void SaveMapToFile(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
// MapLoader의 표준 저장 메서드 사용 (AGVMapEditor와 동일한 형식)
|
||||
bool success = MapLoader.SaveMapToFile(filePath, _mapNodes);
|
||||
|
||||
if (success)
|
||||
{
|
||||
Program.WriteLine($"[맵 저장] 파일 저장 완료: {filePath} ({_mapNodes.Count}개 노드)");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("맵 저장에 실패했습니다.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.WriteLine($"[맵 저장 오류] {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void btMapSaveAs_Click(object sender, EventArgs e)
|
||||
{
|
||||
// 맵 데이터 확인
|
||||
if (_mapNodes == null || _mapNodes.Count == 0)
|
||||
{
|
||||
MessageBox.Show("저장할 맵 데이터가 없습니다.", "알림",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var saveDialog = new SaveFileDialog())
|
||||
{
|
||||
saveDialog.Filter = "AGV Map Files (*.agvmap)|*.agvmap|모든 파일 (*.*)|*.*";
|
||||
saveDialog.Title = "맵 파일 저장";
|
||||
saveDialog.DefaultExt = "agvmap";
|
||||
|
||||
// 현재 파일이 있으면 기본 파일명으로 설정
|
||||
if (!string.IsNullOrEmpty(_currentMapFilePath))
|
||||
{
|
||||
saveDialog.FileName = Path.GetFileName(_currentMapFilePath);
|
||||
saveDialog.InitialDirectory = Path.GetDirectoryName(_currentMapFilePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 기본 파일명: 날짜_시간 형식
|
||||
saveDialog.FileName = $"ScanMap_{DateTime.Now:yyyyMMdd_HHmmss}.agvmap";
|
||||
}
|
||||
|
||||
if (saveDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
try
|
||||
{
|
||||
SaveMapToFile(saveDialog.FileName);
|
||||
_currentMapFilePath = saveDialog.FileName;
|
||||
|
||||
// 설정에 마지막 맵 파일 경로 저장
|
||||
_config.LastMapFilePath = _currentMapFilePath;
|
||||
if (_config.AutoSave)
|
||||
{
|
||||
_config.Save();
|
||||
}
|
||||
|
||||
_statusLabel.Text = $"맵 저장 완료: {Path.GetFileName(_currentMapFilePath)}";
|
||||
MessageBox.Show($"맵이 저장되었습니다.\n\n파일: {_currentMapFilePath}", "저장 완료",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"맵 저장 중 오류 발생:\n{ex.Message}", "저장 오류",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void 맵저장SToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
// 현재 맵 파일 경로가 있는 경우 해당 파일에 저장
|
||||
if (string.IsNullOrEmpty(_currentMapFilePath))
|
||||
{
|
||||
// 경로가 없으면 다른 이름으로 저장 다이얼로그 표시
|
||||
btMapSaveAs_Click(sender, e);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
SaveMapToFile(_currentMapFilePath);
|
||||
_statusLabel.Text = $"맵 저장 완료: {Path.GetFileName(_currentMapFilePath)}";
|
||||
MessageBox.Show($"맵이 저장되었습니다.\n\n파일: {_currentMapFilePath}", "저장 완료",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"맵 저장 중 오류 발생:\n{ex.Message}", "저장 오류",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -152,6 +152,21 @@
|
||||
HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
|
||||
1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
|
||||
nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
|
||||
</value>
|
||||
</data>
|
||||
<data name="btMakeMap.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK
|
||||
YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X
|
||||
/aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t
|
||||
I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM
|
||||
cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh
|
||||
6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD
|
||||
lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A
|
||||
HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
|
||||
1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
|
||||
nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
|
||||
</value>
|
||||
</data>
|
||||
<metadata name="_statusStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
|
||||
Reference in New Issue
Block a user