diff --git a/Cs_HMI/AGVLogic/AGVMapEditor/AGVMapEditor.csproj b/Cs_HMI/AGVLogic/AGVMapEditor/AGVMapEditor.csproj index 3d7f65b..66fc387 100644 --- a/Cs_HMI/AGVLogic/AGVMapEditor/AGVMapEditor.csproj +++ b/Cs_HMI/AGVLogic/AGVMapEditor/AGVMapEditor.csproj @@ -49,9 +49,15 @@ + + UserControl + Form + + ImageEditorForm.cs + @@ -86,7 +92,6 @@ - \ No newline at end of file diff --git a/Cs_HMI/AGVLogic/AGVMapEditor/Controls/ImageEditorCanvas.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Controls/ImageEditorCanvas.cs new file mode 100644 index 0000000..ad153f8 --- /dev/null +++ b/Cs_HMI/AGVLogic/AGVMapEditor/Controls/ImageEditorCanvas.cs @@ -0,0 +1,413 @@ +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace AGVMapEditor.Controls +{ + /// + /// 이미지 편집용 사용자 정의 캔버스 컨트롤 + /// 이미지 중앙 정렬, 크기 조정 핸들, 브러시 그리기 기능 제공 + /// + public class ImageEditorCanvas : UserControl + { + private Bitmap _editingImage; + private Graphics _imageGraphics; + private Rectangle _imageRect = Rectangle.Empty; + private float _imageDisplayWidth = 0; + private float _imageDisplayHeight = 0; + + // 브러시 그리기 + private bool _isDrawing = false; + private Point _lastDrawPoint = Point.Empty; + private Color _drawColor = Color.Black; + private int _brushSize = 3; + private bool _brushModeEnabled = false; + + // 크기 조정 + private bool _isResizing = false; + private ResizeHandle _activeHandle = ResizeHandle.None; + private Point _resizeStartPoint = Point.Empty; + private float _resizeStartWidth = 0; + private float _resizeStartHeight = 0; + private const int HANDLE_SIZE = 8; + + private enum ResizeHandle + { + None, + TopLeft, Top, TopRight, + Right, BottomRight, Bottom, + BottomLeft, Left + } + + public ImageEditorCanvas() + { + this.DoubleBuffered = true; + this.BackColor = Color.White; + this.AutoScroll = true; + } + + #region Properties + + public Bitmap EditingImage + { + get => _editingImage; + set + { + _editingImage = value; + if (_editingImage != null) + { + _imageGraphics?.Dispose(); + _imageGraphics = Graphics.FromImage(_editingImage); + _imageDisplayWidth = _editingImage.Width; + _imageDisplayHeight = _editingImage.Height; + UpdateImageRect(); + } + Invalidate(); + } + } + + public Color DrawColor + { + get => _drawColor; + set => _drawColor = value; + } + + public int BrushSize + { + get => _brushSize; + set => _brushSize = value; + } + + public bool BrushModeEnabled + { + get => _brushModeEnabled; + set => _brushModeEnabled = value; + } + + public Size ImageDisplaySize => new Size((int)_imageDisplayWidth, (int)_imageDisplayHeight); + + #endregion + + #region Paint + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + if (_editingImage == null) + { + e.Graphics.Clear(BackColor); + return; + } + + // 배경 채우기 + e.Graphics.Clear(BackColor); + + // 이미지 영역 업데이트 + UpdateImageRect(); + + // 이미지 그리기 (고품질) + e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + e.Graphics.DrawImage(_editingImage, _imageRect); + + // 크기 조정 핸들 그리기 + DrawResizeHandles(e.Graphics); + } + + private void UpdateImageRect() + { + if (_editingImage == null || Width == 0 || Height == 0) + { + _imageRect = Rectangle.Empty; + return; + } + + // 이미지를 중앙 정렬 + float x = (Width - _imageDisplayWidth) / 2f; + float y = (Height - _imageDisplayHeight) / 2f; + + // 음수 방지 + if (x < 0) x = 0; + if (y < 0) y = 0; + + _imageRect = new Rectangle((int)x, (int)y, (int)_imageDisplayWidth, (int)_imageDisplayHeight); + } + + private void DrawResizeHandles(Graphics g) + { + if (_imageRect.IsEmpty) + return; + + var handles = GetResizeHandles(); + foreach (var handle in handles) + { + g.FillRectangle(Brushes.Blue, handle); + g.DrawRectangle(Pens.White, handle); + } + } + + private Rectangle[] GetResizeHandles() + { + int x = _imageRect.X; + int y = _imageRect.Y; + int w = _imageRect.Width; + int h = _imageRect.Height; + int hs = HANDLE_SIZE; + + return new Rectangle[] + { + new Rectangle(x - hs/2, y - hs/2, hs, hs), // TopLeft + new Rectangle(x + w/2 - hs/2, y - hs/2, hs, hs), // Top + new Rectangle(x + w - hs/2, y - hs/2, hs, hs), // TopRight + new Rectangle(x + w - hs/2, y + h/2 - hs/2, hs, hs), // Right + new Rectangle(x + w - hs/2, y + h - hs/2, hs, hs), // BottomRight + new Rectangle(x + w/2 - hs/2, y + h - hs/2, hs, hs), // Bottom + new Rectangle(x - hs/2, y + h - hs/2, hs, hs), // BottomLeft + new Rectangle(x - hs/2, y + h/2 - hs/2, hs, hs) // Left + }; + } + + #endregion + + #region Mouse Events + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (_editingImage == null || e.Button != MouseButtons.Left) + return; + + // 크기 조정 핸들 확인 + _activeHandle = GetHandleAtPoint(e.Location); + if (_activeHandle != ResizeHandle.None) + { + _isResizing = true; + _resizeStartPoint = e.Location; + _resizeStartWidth = _imageDisplayWidth; + _resizeStartHeight = _imageDisplayHeight; + return; + } + + // 브러시 모드: 그리기 + if (_brushModeEnabled && _imageRect.Contains(e.Location)) + { + _isDrawing = true; + _lastDrawPoint = ImagePointFromScreen(e.Location); + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (_editingImage == null) + return; + + // 크기 조정 중 + if (_isResizing && _activeHandle != ResizeHandle.None) + { + ResizeImageDisplay(e.Location); + return; + } + + // 크기 조정 핸들 위에 마우스가 있으면 커서 변경 + var handle = GetHandleAtPoint(e.Location); + if (handle != ResizeHandle.None) + { + Cursor = GetCursorForHandle(handle); + return; + } + else + { + Cursor = Cursors.Default; + } + + // 브러시 모드: 그리기 + if (_isDrawing && _lastDrawPoint != Point.Empty && _brushModeEnabled && _imageRect.Contains(e.Location)) + { + Point currentImagePoint = ImagePointFromScreen(e.Location); + _imageGraphics.DrawLine(new Pen(_drawColor, _brushSize), _lastDrawPoint, currentImagePoint); + _lastDrawPoint = currentImagePoint; + Invalidate(); + } + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + + if (_isResizing) + { + _isResizing = false; + _activeHandle = ResizeHandle.None; + } + + if (_isDrawing) + { + _isDrawing = false; + _lastDrawPoint = Point.Empty; + } + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + UpdateImageRect(); + Invalidate(); + } + + #endregion + + #region Helper Methods + + /// + /// 화면 좌표를 이미지 좌표로 변환 + /// + private Point ImagePointFromScreen(Point screenPoint) + { + if (_imageRect.IsEmpty || _editingImage == null) + return Point.Empty; + + // 화면 좌표를 이미지 비율로 변환 + float scaleX = (float)_editingImage.Width / _imageRect.Width; + float scaleY = (float)_editingImage.Height / _imageRect.Height; + + int imageX = (int)((screenPoint.X - _imageRect.X) * scaleX); + int imageY = (int)((screenPoint.Y - _imageRect.Y) * scaleY); + + return new Point(imageX, imageY); + } + + private ResizeHandle GetHandleAtPoint(Point pt) + { + var handles = GetResizeHandles(); + var handleTypes = new[] + { + ResizeHandle.TopLeft, ResizeHandle.Top, ResizeHandle.TopRight, + ResizeHandle.Right, ResizeHandle.BottomRight, ResizeHandle.Bottom, + ResizeHandle.BottomLeft, ResizeHandle.Left + }; + + for (int i = 0; i < handles.Length; i++) + { + if (handles[i].Contains(pt)) + return handleTypes[i]; + } + + return ResizeHandle.None; + } + + private Cursor GetCursorForHandle(ResizeHandle handle) + { + switch (handle) + { + case ResizeHandle.TopLeft: + case ResizeHandle.BottomRight: + return Cursors.SizeNWSE; + case ResizeHandle.TopRight: + case ResizeHandle.BottomLeft: + return Cursors.SizeNESW; + case ResizeHandle.Top: + case ResizeHandle.Bottom: + return Cursors.SizeNS; + case ResizeHandle.Left: + case ResizeHandle.Right: + return Cursors.SizeWE; + default: + return Cursors.Default; + } + } + + private void ResizeImageDisplay(Point currentPoint) + { + int deltaX = currentPoint.X - _resizeStartPoint.X; + int deltaY = currentPoint.Y - _resizeStartPoint.Y; + + float newWidth = _resizeStartWidth; + float newHeight = _resizeStartHeight; + + switch (_activeHandle) + { + case ResizeHandle.TopLeft: + newWidth -= deltaX; + newHeight -= deltaY; + break; + case ResizeHandle.Top: + newHeight -= deltaY; + break; + case ResizeHandle.TopRight: + newWidth += deltaX; + newHeight -= deltaY; + break; + case ResizeHandle.Right: + newWidth += deltaX; + break; + case ResizeHandle.BottomRight: + newWidth += deltaX; + newHeight += deltaY; + break; + case ResizeHandle.Bottom: + newHeight += deltaY; + break; + case ResizeHandle.BottomLeft: + newWidth -= deltaX; + newHeight += deltaY; + break; + case ResizeHandle.Left: + newWidth -= deltaX; + break; + } + + // 최소 크기 제한 + if (newWidth < 50) newWidth = 50; + if (newHeight < 50) newHeight = 50; + + _imageDisplayWidth = newWidth; + _imageDisplayHeight = newHeight; + + UpdateImageRect(); + Invalidate(); + } + + /// + /// 표시 크기로 실제 이미지 리사이즈 + /// + public Bitmap GetResizedImage() + { + if (_editingImage == null) + return null; + + int targetWidth = (int)_imageDisplayWidth; + int targetHeight = (int)_imageDisplayHeight; + + // 크기가 같으면 원본 반환 + if (targetWidth == _editingImage.Width && targetHeight == _editingImage.Height) + return new Bitmap(_editingImage); + + // 리사이즈 + var resized = new Bitmap(targetWidth, targetHeight); + using (var g = Graphics.FromImage(resized)) + { + g.CompositingQuality = CompositingQuality.HighQuality; + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.SmoothingMode = SmoothingMode.HighQuality; + g.DrawImage(_editingImage, 0, 0, targetWidth, targetHeight); + } + + return resized; + } + + #endregion + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _imageGraphics?.Dispose(); + } + base.Dispose(disposing); + } + } +} diff --git a/Cs_HMI/AGVLogic/AGVMapEditor/Forms/ImageEditorForm.Designer.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Forms/ImageEditorForm.Designer.cs new file mode 100644 index 0000000..864a786 --- /dev/null +++ b/Cs_HMI/AGVLogic/AGVMapEditor/Forms/ImageEditorForm.Designer.cs @@ -0,0 +1,188 @@ +namespace AGVMapEditor.Forms +{ + partial class ImageEditorForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.toolPanel = new System.Windows.Forms.Panel(); + this.chkBrushMode = new System.Windows.Forms.CheckBox(); + this.btnColor = new System.Windows.Forms.Button(); + this.trackBrush = new System.Windows.Forms.TrackBar(); + this.lblBrush = new System.Windows.Forms.Label(); + this.btnSave = new System.Windows.Forms.Button(); + this.btnResize = new System.Windows.Forms.Button(); + this.btnOpen = new System.Windows.Forms.Button(); + this.canvasPanel = new System.Windows.Forms.Panel(); + this.imageCanvas = new AGVMapEditor.Controls.ImageEditorCanvas(); + this.toolPanel.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trackBrush)).BeginInit(); + this.canvasPanel.SuspendLayout(); + this.SuspendLayout(); + // + // toolPanel + // + this.toolPanel.BackColor = System.Drawing.Color.LightGray; + this.toolPanel.Controls.Add(this.chkBrushMode); + this.toolPanel.Controls.Add(this.btnColor); + this.toolPanel.Controls.Add(this.trackBrush); + this.toolPanel.Controls.Add(this.lblBrush); + this.toolPanel.Controls.Add(this.btnSave); + this.toolPanel.Controls.Add(this.btnResize); + this.toolPanel.Controls.Add(this.btnOpen); + this.toolPanel.Dock = System.Windows.Forms.DockStyle.Top; + this.toolPanel.Location = new System.Drawing.Point(0, 0); + this.toolPanel.Name = "toolPanel"; + this.toolPanel.Size = new System.Drawing.Size(800, 50); + this.toolPanel.TabIndex = 0; + // + // chkBrushMode + // + this.chkBrushMode.AutoSize = true; + this.chkBrushMode.Location = new System.Drawing.Point(590, 15); + this.chkBrushMode.Name = "chkBrushMode"; + this.chkBrushMode.Size = new System.Drawing.Size(96, 16); + this.chkBrushMode.TabIndex = 6; + this.chkBrushMode.Text = "브러시 모드"; + this.chkBrushMode.UseVisualStyleBackColor = true; + // + // btnColor + // + this.btnColor.BackColor = System.Drawing.Color.Black; + this.btnColor.ForeColor = System.Drawing.Color.White; + this.btnColor.Location = new System.Drawing.Point(520, 10); + this.btnColor.Name = "btnColor"; + this.btnColor.Size = new System.Drawing.Size(60, 23); + this.btnColor.TabIndex = 5; + this.btnColor.Text = "색상"; + this.btnColor.UseVisualStyleBackColor = false; + this.btnColor.Click += new System.EventHandler(this.BtnColor_Click); + // + // trackBrush + // + this.trackBrush.Location = new System.Drawing.Point(410, 10); + this.trackBrush.Maximum = 20; + this.trackBrush.Minimum = 1; + this.trackBrush.Name = "trackBrush"; + this.trackBrush.Size = new System.Drawing.Size(100, 45); + this.trackBrush.TabIndex = 4; + this.trackBrush.Value = 3; + this.trackBrush.ValueChanged += new System.EventHandler(this.TrackBrush_ValueChanged); + // + // lblBrush + // + this.lblBrush.AutoSize = true; + this.lblBrush.Location = new System.Drawing.Point(350, 15); + this.lblBrush.Name = "lblBrush"; + this.lblBrush.Size = new System.Drawing.Size(54, 12); + this.lblBrush.TabIndex = 3; + this.lblBrush.Text = "브러시:"; + // + // btnSave + // + this.btnSave.Location = new System.Drawing.Point(230, 10); + this.btnSave.Name = "btnSave"; + this.btnSave.Size = new System.Drawing.Size(100, 23); + this.btnSave.TabIndex = 2; + this.btnSave.Text = "저장 및 닫기"; + this.btnSave.UseVisualStyleBackColor = true; + this.btnSave.Click += new System.EventHandler(this.BtnSave_Click); + // + // btnResize + // + this.btnResize.Location = new System.Drawing.Point(120, 10); + this.btnResize.Name = "btnResize"; + this.btnResize.Size = new System.Drawing.Size(100, 23); + this.btnResize.TabIndex = 1; + this.btnResize.Text = "크기 조정"; + this.btnResize.UseVisualStyleBackColor = true; + this.btnResize.Click += new System.EventHandler(this.BtnResize_Click); + // + // btnOpen + // + this.btnOpen.Location = new System.Drawing.Point(10, 10); + this.btnOpen.Name = "btnOpen"; + this.btnOpen.Size = new System.Drawing.Size(100, 23); + this.btnOpen.TabIndex = 0; + this.btnOpen.Text = "이미지 열기"; + this.btnOpen.UseVisualStyleBackColor = true; + this.btnOpen.Click += new System.EventHandler(this.BtnOpen_Click); + // + // canvasPanel + // + this.canvasPanel.AutoScroll = true; + this.canvasPanel.BackColor = System.Drawing.Color.White; + this.canvasPanel.Controls.Add(this.imageCanvas); + this.canvasPanel.Dock = System.Windows.Forms.DockStyle.Fill; + this.canvasPanel.Location = new System.Drawing.Point(0, 50); + this.canvasPanel.Name = "canvasPanel"; + this.canvasPanel.Size = new System.Drawing.Size(800, 550); + this.canvasPanel.TabIndex = 1; + // + // imageCanvas + // + this.imageCanvas.BackColor = System.Drawing.Color.White; + this.imageCanvas.BrushModeEnabled = false; + this.imageCanvas.BrushSize = 3; + this.imageCanvas.Dock = System.Windows.Forms.DockStyle.Fill; + this.imageCanvas.DrawColor = System.Drawing.Color.Black; + this.imageCanvas.Location = new System.Drawing.Point(0, 0); + this.imageCanvas.Name = "imageCanvas"; + this.imageCanvas.Size = new System.Drawing.Size(800, 550); + this.imageCanvas.TabIndex = 0; + // + // ImageEditorForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 600); + this.Controls.Add(this.canvasPanel); + this.Controls.Add(this.toolPanel); + this.Name = "ImageEditorForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "이미지 편집기"; + this.toolPanel.ResumeLayout(false); + this.toolPanel.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trackBrush)).EndInit(); + this.canvasPanel.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Panel toolPanel; + private System.Windows.Forms.Button btnOpen; + private System.Windows.Forms.Button btnResize; + private System.Windows.Forms.Button btnSave; + private System.Windows.Forms.Label lblBrush; + private System.Windows.Forms.TrackBar trackBrush; + private System.Windows.Forms.Button btnColor; + private System.Windows.Forms.CheckBox chkBrushMode; + private System.Windows.Forms.Panel canvasPanel; + private AGVMapEditor.Controls.ImageEditorCanvas imageCanvas; + } +} diff --git a/Cs_HMI/AGVLogic/AGVMapEditor/Forms/ImageEditorForm.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Forms/ImageEditorForm.cs index 68bfa7d..74272cc 100644 --- a/Cs_HMI/AGVLogic/AGVMapEditor/Forms/ImageEditorForm.cs +++ b/Cs_HMI/AGVLogic/AGVMapEditor/Forms/ImageEditorForm.cs @@ -1,7 +1,6 @@ using System; using System.Drawing; using System.Drawing.Drawing2D; -using System.Linq; using System.Windows.Forms; using AGVNavigationCore.Models; using AGVNavigationCore.Utils; @@ -14,17 +13,11 @@ namespace AGVMapEditor.Forms /// public partial class ImageEditorForm : Form { - private Bitmap _editingImage; - private Graphics _graphics; - private bool _isDrawing = false; - private Point _lastPoint = Point.Empty; - private Color _drawColor = Color.Black; - private int _brushSize = 3; private MapNode _targetNode; public ImageEditorForm(MapNode imageNode = null) { - //InitializeComponent(); + InitializeComponent(); _targetNode = imageNode; SetupUI(); @@ -36,64 +29,25 @@ namespace AGVMapEditor.Forms private void SetupUI() { - this.Text = "이미지 편집기"; - this.Size = new Size(800, 600); - this.StartPosition = FormStartPosition.CenterScreen; + // 캔버스 초기 설정 + imageCanvas.BrushSize = trackBrush.Value; + imageCanvas.BrushModeEnabled = chkBrushMode.Checked; + imageCanvas.BackColor = Color.FromArgb(32,32,32); - // 패널: 도구 모음 - var toolPanel = new Panel { Dock = DockStyle.Top, Height = 50, BackColor = Color.LightGray }; + // 이벤트 연결 + chkBrushMode.CheckedChanged += (s, e) => imageCanvas.BrushModeEnabled = chkBrushMode.Checked; + } - // 버튼: 이미지 열기 - var btnOpen = new Button { Text = "이미지 열기", Width = 100, Left = 10, Top = 10 }; - btnOpen.Click += BtnOpen_Click; - toolPanel.Controls.Add(btnOpen); - - // 버튼: 크기 조정 - var btnResize = new Button { Text = "크기 조정", Width = 100, Left = 120, Top = 10 }; - btnResize.Click += BtnResize_Click; - toolPanel.Controls.Add(btnResize); - - // 버튼: 저장 - var btnSave = new Button { Text = "저장 및 닫기", Width = 100, Left = 230, Top = 10 }; - btnSave.Click += BtnSave_Click; - toolPanel.Controls.Add(btnSave); - - // 라벨: 브러시 크기 - var lblBrush = new Label { Text = "브러시:", Left = 350, Top = 15, Width = 50 }; - toolPanel.Controls.Add(lblBrush); - - // 트랙바: 브러시 크기 조절 - var trackBrush = new TrackBar { Left = 410, Top = 10, Width = 100, Minimum = 1, Maximum = 20, Value = 3 }; - trackBrush.ValueChanged += (s, e) => _brushSize = trackBrush.Value; - toolPanel.Controls.Add(trackBrush); - - // 버튼: 색상 선택 - var btnColor = new Button { Text = "색상", Width = 60, Left = 520, Top = 10, BackColor = Color.Black, ForeColor = Color.White }; - btnColor.Click += BtnColor_Click; - toolPanel.Controls.Add(btnColor); - - // 패널: 캔버스 - var canvasPanel = new Panel { Dock = DockStyle.Fill, AutoScroll = true, BackColor = Color.White }; - var pictureBox = new PictureBox { SizeMode = PictureBoxSizeMode.AutoSize }; - pictureBox.Name = "pictureBoxCanvas"; - pictureBox.MouseDown += PictureBox_MouseDown; - pictureBox.MouseMove += PictureBox_MouseMove; - pictureBox.MouseUp += PictureBox_MouseUp; - canvasPanel.Controls.Add(pictureBox); - - this.Controls.Add(canvasPanel); - this.Controls.Add(toolPanel); + private void TrackBrush_ValueChanged(object sender, EventArgs e) + { + imageCanvas.BrushSize = trackBrush.Value; } private void LoadImageFromNode(MapNode node) { if (node.LoadedImage != null) { - _editingImage?.Dispose(); - _graphics?.Dispose(); - _editingImage = new Bitmap(node.LoadedImage); - _graphics = Graphics.FromImage(_editingImage); - UpdateCanvas(); + imageCanvas.EditingImage = new Bitmap(node.LoadedImage); } } @@ -112,25 +66,22 @@ namespace AGVMapEditor.Forms { try { - _editingImage?.Dispose(); - _graphics?.Dispose(); - var loadedImage = Image.FromFile(filePath); // 이미지 크기가 크면 자동 축소 (최대 512x512) + Bitmap finalImage; if (loadedImage.Width > 512 || loadedImage.Height > 512) { - _editingImage = ResizeImage(loadedImage, 512, 512); + finalImage = ResizeImage(loadedImage, 512, 512); loadedImage.Dispose(); } else { - _editingImage = new Bitmap(loadedImage); + finalImage = new Bitmap(loadedImage); loadedImage.Dispose(); } - _graphics = Graphics.FromImage(_editingImage); - UpdateCanvas(); + imageCanvas.EditingImage = finalImage; } catch (Exception ex) { @@ -140,12 +91,14 @@ namespace AGVMapEditor.Forms private void BtnResize_Click(object sender, EventArgs e) { - if (_editingImage == null) + if (imageCanvas.EditingImage == null) { MessageBox.Show("먼저 이미지를 로드하세요."); return; } + var currentSize = imageCanvas.ImageDisplaySize; + using (var form = new Form()) { form.Text = "이미지 크기 조정"; @@ -153,10 +106,10 @@ namespace AGVMapEditor.Forms form.StartPosition = FormStartPosition.CenterParent; var lblWidth = new Label { Text = "너비:", Left = 10, Top = 10, Width = 50 }; - var txtWidth = new TextBox { Left = 70, Top = 10, Width = 100, Text = _editingImage.Width.ToString() }; + var txtWidth = new TextBox { Left = 70, Top = 10, Width = 100, Text = currentSize.Width.ToString() }; var lblHeight = new Label { Text = "높이:", Left = 10, Top = 40, Width = 50 }; - var txtHeight = new TextBox { Left = 70, Top = 40, Width = 100, Text = _editingImage.Height.ToString() }; + var txtHeight = new TextBox { Left = 70, Top = 40, Width = 100, Text = currentSize.Height.ToString() }; var btnOk = new Button { Text = "적용", DialogResult = DialogResult.OK, Left = 70, Top = 70, Width = 100 }; var btnCancel = new Button { Text = "취소", DialogResult = DialogResult.Cancel, Left = 180, Top = 70, Width = 70 }; @@ -174,12 +127,8 @@ namespace AGVMapEditor.Forms { if (width > 0 && height > 0) { - _graphics?.Dispose(); - var resized = new Bitmap(_editingImage, width, height); - _editingImage.Dispose(); - _editingImage = resized; - _graphics = Graphics.FromImage(_editingImage); - UpdateCanvas(); + var resized = new Bitmap(imageCanvas.EditingImage, width, height); + imageCanvas.EditingImage = resized; } } } @@ -188,19 +137,19 @@ namespace AGVMapEditor.Forms private void BtnColor_Click(object sender, EventArgs e) { - using (var cfd = new ColorDialog { Color = _drawColor }) + using (var cfd = new ColorDialog { Color = imageCanvas.DrawColor }) { if (cfd.ShowDialog() == DialogResult.OK) { - _drawColor = cfd.Color; - (sender as Button).BackColor = _drawColor; + imageCanvas.DrawColor = cfd.Color; + (sender as Button).BackColor = cfd.Color; } } } private void BtnSave_Click(object sender, EventArgs e) { - if (_editingImage == null) + if (imageCanvas.EditingImage == null) { MessageBox.Show("저장할 이미지가 없습니다."); return; @@ -208,10 +157,22 @@ namespace AGVMapEditor.Forms if (_targetNode != null && _targetNode.Type == NodeType.Image) { + // 표시 크기로 리사이즈된 이미지 가져오기 + var finalImage = imageCanvas.GetResizedImage(); + + if (finalImage == null) + { + MessageBox.Show("이미지 처리 중 오류가 발생했습니다."); + return; + } + + var displaySize = imageCanvas.ImageDisplaySize; + MessageBox.Show($"이미지 크기: {displaySize.Width}x{displaySize.Height}로 저장됩니다."); + // 이미지를 Base64로 변환하여 저장 - _targetNode.ImageBase64 = ImageConverterUtil.ImageToBase64(_editingImage, System.Drawing.Imaging.ImageFormat.Png); + _targetNode.ImageBase64 = ImageConverterUtil.ImageToBase64(finalImage, System.Drawing.Imaging.ImageFormat.Png); _targetNode.LoadedImage?.Dispose(); - _targetNode.LoadedImage = new Bitmap(_editingImage); + _targetNode.LoadedImage = finalImage; MessageBox.Show("이미지가 저장되었습니다."); this.DialogResult = DialogResult.OK; @@ -219,41 +180,6 @@ namespace AGVMapEditor.Forms } } - private void PictureBox_MouseDown(object sender, MouseEventArgs e) - { - if (_editingImage != null && e.Button == MouseButtons.Left) - { - _isDrawing = true; - _lastPoint = e.Location; - } - } - - private void PictureBox_MouseMove(object sender, MouseEventArgs e) - { - if (_isDrawing && _lastPoint != Point.Empty) - { - _graphics.DrawLine(new Pen(_drawColor, _brushSize), _lastPoint, e.Location); - _lastPoint = e.Location; - UpdateCanvas(); - } - } - - private void PictureBox_MouseUp(object sender, MouseEventArgs e) - { - _isDrawing = false; - _lastPoint = Point.Empty; - } - - private void UpdateCanvas() - { - var pictureBox = this.Controls.Find("pictureBoxCanvas", true).FirstOrDefault() as PictureBox; - if (pictureBox != null) - { - pictureBox.Image?.Dispose(); - pictureBox.Image = new Bitmap(_editingImage); - } - } - private Bitmap ResizeImage(Image image, int maxWidth, int maxHeight) { double ratioX = (double)maxWidth / image.Width; @@ -273,15 +199,5 @@ namespace AGVMapEditor.Forms } return resized; } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - _editingImage?.Dispose(); - _graphics?.Dispose(); - } - base.Dispose(disposing); - } } } diff --git a/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.cs b/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.cs index 4cb133e..fa8614a 100644 --- a/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.cs +++ b/Cs_HMI/AGVLogic/AGVMapEditor/Forms/MainForm.cs @@ -1186,14 +1186,25 @@ namespace AGVMapEditor.Forms if (fromNode != null && toNode != null) { - // 단일 연결 삭제 (어느 방향에 저장되어 있는지 확인 후 삭제) + // 양방향 연결 삭제 (양쪽 방향 모두 제거) + bool removed = false; + if (fromNode.ConnectedNodes.Contains(toNode.NodeId)) { fromNode.RemoveConnection(toNode.NodeId); + removed = true; } - else if (toNode.ConnectedNodes.Contains(fromNode.NodeId)) + + if (toNode.ConnectedNodes.Contains(fromNode.NodeId)) { toNode.RemoveConnection(fromNode.NodeId); + removed = true; + } + + if (!removed) + { + MessageBox.Show("연결을 찾을 수 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; } _hasChanges = true; diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs index b6c4efa..aacf23f 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs @@ -101,11 +101,10 @@ namespace AGVNavigationCore.Controls foreach (var node in _nodes) { - if (node.ConnectedNodes == null) continue; + if (node.ConnectedMapNodes == null) continue; - foreach (var connectedNodeId in node.ConnectedNodes) + foreach (var targetNode in node.ConnectedMapNodes) { - var targetNode = _nodes.FirstOrDefault(n => n.NodeId == connectedNodeId); if (targetNode == null) continue; DrawConnection(g, node, targetNode); @@ -302,7 +301,7 @@ namespace AGVNavigationCore.Controls if (node == null) continue; // 교차로 판정: 3개 이상의 노드가 연결된 경우 - if (node.ConnectedNodes != null && node.ConnectedNodes.Count >= JUNCTION_CONNECTIONS) + if (node.ConnectedMapNodes != null && node.ConnectedMapNodes.Count >= JUNCTION_CONNECTIONS) { DrawJunctionHighlight(g, node); } diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs index a7d1387..1499fca 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs @@ -567,15 +567,9 @@ namespace AGVNavigationCore.Controls toNode.ConnectedNodes.Contains(fromNode.NodeId)) return; - // 단일 연결 생성 (사전순으로 정렬하여 일관성 유지) - if (string.Compare(fromNode.NodeId, toNode.NodeId, StringComparison.Ordinal) < 0) - { - fromNode.AddConnection(toNode.NodeId); - } - else - { - toNode.AddConnection(fromNode.NodeId); - } + // 양방향 연결 생성 (AGV가 양쪽 방향으로 이동 가능하도록) + fromNode.AddConnection(toNode.NodeId); + toNode.AddConnection(fromNode.NodeId); MapChanged?.Invoke(this, EventArgs.Empty); } diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapLoader.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapLoader.cs index f626947..d2176ec 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapLoader.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapLoader.cs @@ -78,12 +78,13 @@ namespace AGVNavigationCore.Models // 중복된 NodeId 정리 FixDuplicateNodeIds(result.Nodes); - // 중복 연결 정리 (양방향 중복 제거) - CleanupDuplicateConnections(result.Nodes); - // 양방향 연결 자동 설정 (A→B가 있으면 B→A도 설정) + // 주의: CleanupDuplicateConnections()는 제거됨 - 양방향 연결을 단방향으로 변환하는 버그가 있었음 EnsureBidirectionalConnections(result.Nodes); + // ConnectedMapNodes 채우기 (string ID → MapNode 객체 참조) + ResolveConnectedMapNodes(result.Nodes); + // 이미지 노드들의 이미지 로드 LoadImageNodes(result.Nodes); @@ -145,6 +146,35 @@ namespace AGVNavigationCore.Models } } + /// + /// ConnectedMapNodes 채우기 (ConnectedNodes의 string ID → MapNode 객체 변환) + /// + /// 맵 노드 목록 + private static void ResolveConnectedMapNodes(List mapNodes) + { + if (mapNodes == null || mapNodes.Count == 0) return; + + // 빠른 조회를 위한 Dictionary 생성 + var nodeDict = mapNodes.ToDictionary(n => n.NodeId, n => n); + + foreach (var node in mapNodes) + { + // ConnectedMapNodes 초기화 + node.ConnectedMapNodes.Clear(); + + if (node.ConnectedNodes != null && node.ConnectedNodes.Count > 0) + { + foreach (var connectedNodeId in node.ConnectedNodes) + { + if (nodeDict.TryGetValue(connectedNodeId, out var connectedNode)) + { + node.ConnectedMapNodes.Add(connectedNode); + } + } + } + } + } + /// /// 기존 Description 데이터를 Name 필드로 마이그레이션 /// JSON 파일에서 Description 필드가 있는 경우 Name으로 이동 @@ -269,9 +299,12 @@ namespace AGVNavigationCore.Models } /// - /// 중복 연결을 정리합니다. 양방향 중복 연결을 단일 연결로 통합합니다. + /// [사용 중지됨] 중복 연결을 정리합니다. 양방향 중복 연결을 단일 연결로 통합합니다. + /// 주의: 이 함수는 버그가 있어 사용 중지됨 - 양방향 연결을 단방향으로 변환하여 경로 탐색 실패 발생 + /// AGV 시스템에서는 모든 연결이 양방향이어야 하므로 EnsureBidirectionalConnections()만 사용 /// /// 맵 노드 목록 + [Obsolete("이 함수는 양방향 연결을 단방향으로 변환하는 버그가 있습니다. 사용하지 마세요.")] private static void CleanupDuplicateConnections(List mapNodes) { if (mapNodes == null || mapNodes.Count == 0) return; diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs index 96064ba..64e2600 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Models/MapNode.cs @@ -44,6 +44,12 @@ namespace AGVNavigationCore.Models /// public List ConnectedNodes { get; set; } = new List(); + /// + /// 연결된 노드 객체 목록 (런타임 전용, JSON 무시) + /// + [Newtonsoft.Json.JsonIgnore] + public List ConnectedMapNodes { get; set; } = new List(); + /// /// 회전 가능 여부 (180도 회전 가능한 지점) /// diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Analysis/JunctionAnalyzer.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Analysis/JunctionAnalyzer.cs index f093621..5fcd92d 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Analysis/JunctionAnalyzer.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Analysis/JunctionAnalyzer.cs @@ -92,15 +92,18 @@ namespace AGVNavigationCore.PathFinding.Analysis var connected = new HashSet(); // 직접 연결된 노드들 - foreach (var connectedId in node.ConnectedNodes) + foreach (var connectedNode in node.ConnectedMapNodes) { - connected.Add(connectedId); + if (connectedNode != null) + { + connected.Add(connectedNode.NodeId); + } } // 역방향 연결된 노드들 (다른 노드에서 이 노드로 연결) foreach (var otherNode in _mapNodes) { - if (otherNode.NodeId != node.NodeId && otherNode.ConnectedNodes.Contains(node.NodeId)) + if (otherNode.NodeId != node.NodeId && otherNode.ConnectedMapNodes.Any(n => n.NodeId == node.NodeId)) { connected.Add(otherNode.NodeId); } diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs index ed8105e..1df76c8 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Core/AStarPathfinder.cs @@ -66,17 +66,17 @@ namespace AGVNavigationCore.PathFinding.Core { var pathNode = _nodeMap[mapNode.NodeId]; - foreach (var connectedNode in mapNode.ConnectedNodes) + foreach (var connectedNode in mapNode.ConnectedMapNodes) { - if (_nodeMap.ContainsKey(connectedNode)) + if (connectedNode != null && _nodeMap.ContainsKey(connectedNode.NodeId)) { // 양방향 연결 생성 (단일 연결이 양방향을 의미) - if (!pathNode.ConnectedNodes.Contains(connectedNode)) + if (!pathNode.ConnectedNodes.Contains(connectedNode.NodeId)) { - pathNode.ConnectedNodes.Add(connectedNode); + pathNode.ConnectedNodes.Add(connectedNode.NodeId); } - var connectedPathNode = _nodeMap[connectedNode]; + var connectedPathNode = _nodeMap[connectedNode.NodeId]; if (!connectedPathNode.ConnectedNodes.Contains(mapNode.NodeId)) { connectedPathNode.ConnectedNodes.Add(mapNode.NodeId); diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs index aebb94c..177ec0f 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/AGVPathfinder.cs @@ -22,7 +22,7 @@ namespace AGVNavigationCore.PathFinding.Planning private readonly JunctionAnalyzer _junctionAnalyzer; private readonly DirectionChangePlanner _directionChangePlanner; - + public AGVPathfinder(List mapNodes) { @@ -50,9 +50,13 @@ namespace AGVNavigationCore.PathFinding.Planning n.IsNavigationNode() && n.ConnectedNodes != null && n.ConnectedNodes.Count >= 3 && + n.ConnectedMapNodes.Where(t => t.Type == NodeType.Docking || t.Type == NodeType.Charging).Any() == false && n.NodeId != startNode.NodeId ).ToList(); + // docking 포인트가 연결된 노드는 제거한다. + + if (junctions.Count == 0) return null; @@ -96,7 +100,8 @@ namespace AGVNavigationCore.PathFinding.Planning pathNode.IsActive && pathNode.IsNavigationNode() && pathNode.ConnectedNodes != null && - pathNode.ConnectedNodes.Count >= 3) + pathNode.ConnectedNodes.Count >= 3 && + pathNode.ConnectedMapNodes.Where(t => t.Type == NodeType.Docking || t.Type == NodeType.Charging).Any() == false) { if (pathNode.NodeId.Equals(StartNode.NodeId) == false) return pathNode; @@ -107,7 +112,7 @@ namespace AGVNavigationCore.PathFinding.Planning } public AGVPathResult FindPath_test(MapNode startNode, MapNode targetNode, - MapNode prevNode, AgvDirection prevDirection, AgvDirection currentDirection) + MapNode prevNode, AgvDirection prevDirection, AgvDirection currentDirection, bool crossignore = false) { // 입력 검증 if (startNode == null) @@ -117,7 +122,7 @@ namespace AGVNavigationCore.PathFinding.Planning if (prevNode == null) return AGVPathResult.CreateFailure("이전위치 노드가 null입니다.", 0, 0); if (startNode.NodeId == targetNode.NodeId && targetNode.DockDirection.MatchAGVDirection(prevDirection)) - return AGVPathResult.CreateFailure("목적지와 현재위치가 동일합니다.", 0, 0); + return AGVPathResult.CreateSuccess(new List { startNode,startNode }, new List(), 0, 0); var ReverseDirection = (currentDirection == AgvDirection.Forward ? AgvDirection.Backward : AgvDirection.Forward); @@ -203,6 +208,39 @@ namespace AGVNavigationCore.PathFinding.Planning // return pathResult; //} + //현재 내 포인트가 교차로라면.. 무조건 왓던 방향 혹은 그 반대방향으로 이동해서 경로를 계산해야한다. + //교차로에 멈춰있을때에는 바로 방향전환을 할 수없으니. 정/역(straight)로 이동해서 다시 계산을 해야한다 + if (crossignore == false && startNode.ConnectedNodes.Count > 2) + { + //진행방향으로 이동했을때 나오는 노드를 사용한다. + if (nextNodeForward != null) + { + var Path0 = _basicPathfinder.FindPath(startNode.NodeId, nextNodeForward.NodeId); + Path0.PrevNode = prevNode; + Path0.PrevDirection = prevDirection; + MakeDetailData(Path0, prevDirection); + + var Path1 = FindPath_test(nextNodeForward, targetNode, startNode, prevDirection, currentDirection, true); + Path1.PrevNode = startNode; + Path1.PrevDirection = prevDirection; + //MakeDetailData(Path1, ReverseDirection); + + var combinedResult0 = Path0; + combinedResult0 = _basicPathfinder.CombineResults(combinedResult0, Path1); + MakeMagnetDirection(combinedResult0); + return combinedResult0; + } + else if (nextNodeBackward != null) + { + return AGVPathResult.CreateFailure("backward 처리코드가 없습니다 오류", 0, 0); + } + else + { + return AGVPathResult.CreateFailure("교차로에서 시작하는 조건중 forward/backrad 노드 검색 실패", 0, 0); + } + } + + //3. 도킹방향이 일치하지 않으니 교차로에서 방향을 회전시켜야 한다 //최단거리(=minpath)경로에 속하는 교차로가 있다면 그것을 사용하고 없다면 가장 가까운 교차로를 찾는다. var JunctionInPath = FindNearestJunctionOnPath(pathResult); @@ -224,19 +262,23 @@ namespace AGVNavigationCore.PathFinding.Planning path1.PrevNode = prevNode; path1.PrevDirection = prevDirection; - //다음좌표를 보고 교차로가 진행방향인지 반대방향인지 체크한다.(!모터의 정/역방향을 말하는것이 아님) + //다음좌표를 보고 교차로가 진행방향인지 반대방향인지 체크한다. bool ReverseCheck = false; - if (path1.Path.Count > 1 && nextNodeForward != null && nextNodeForward.NodeId.Equals(path1.Path[1].NodeId)) - { - ReverseCheck = false; //현재 진행 방향으로 이동해야한다 - MakeDetailData(path1, currentDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정) - } - else if (path1.Path.Count > 1 && nextNodeBackward != null && nextNodeBackward.NodeId.Equals(path1.Path[1].NodeId)) + //if (path1.Path.Count > 1 && nextNodeForward != null && nextNodeForward.NodeId.Equals(path1.Path[1].NodeId)) + //{ + // ReverseCheck = false; //현재 진행 방향으로 이동해야한다 + // MakeDetailData(path1, currentDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정) + //} + if (path1.Path.Count > 1 && nextNodeBackward != null && nextNodeBackward.NodeId.Equals(path1.Path[1].NodeId)) { ReverseCheck = true; //현재 방향의 반대방향으로 이동해야한다 MakeDetailData(path1, ReverseDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정) } - else return AGVPathResult.CreateFailure("교차로까지 계산된 경로에 현재 위치정보로 추측을 할 수 없습니다", 0, 0); + else + { + ReverseCheck = false; //현재 진행 방향으로 이동해야한다 + MakeDetailData(path1, currentDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정) + } diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/DirectionChangePlanner.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/DirectionChangePlanner.cs index b16a692..73c5167 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/DirectionChangePlanner.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/PathFinding/Planning/DirectionChangePlanner.cs @@ -232,15 +232,18 @@ namespace AGVNavigationCore.PathFinding.Planning var connected = new HashSet(); // 직접 연결 - foreach (var connectedId in node.ConnectedNodes) + foreach (var connectedNode in node.ConnectedMapNodes) { - connected.Add(connectedId); + if (connectedNode != null) + { + connected.Add(connectedNode.NodeId); + } } // 역방향 연결 foreach (var otherNode in _mapNodes) { - if (otherNode.NodeId != nodeId && otherNode.ConnectedNodes.Contains(nodeId)) + if (otherNode.NodeId != nodeId && otherNode.ConnectedMapNodes.Any(n => n.NodeId == nodeId)) { connected.Add(otherNode.NodeId); } @@ -728,9 +731,9 @@ namespace AGVNavigationCore.PathFinding.Planning string currentNode = path[i].NodeId; string nextNode = path[i + 1].NodeId; - // 두 노드간 직접 연결성 확인 (맵 노드의 ConnectedNodes 리스트 사용) + // 두 노드간 직접 연결성 확인 (맵 노드의 ConnectedMapNodes 리스트 사용) var currentMapNode = _mapNodes.FirstOrDefault(n => n.NodeId == currentNode); - if (currentMapNode == null || !currentMapNode.ConnectedNodes.Contains(nextNode)) + if (currentMapNode == null || !currentMapNode.ConnectedMapNodes.Any(n => n.NodeId == nextNode)) { return PathValidationResult.CreateInvalid(currentNode, nextNode, $"노드 {currentNode}와 {nextNode} 사이에 연결이 없음"); } diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs index 389b94e..55bd1b0 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs @@ -69,22 +69,18 @@ namespace AGVNavigationCore.Utils InitializeJunctionAnalyzer(allNodes); // 현재 노드에 연결된 노드들 중 이전 노드가 아닌 노드들만 필터링 - var connectedNodeIds = currentNode.ConnectedNodes; - if (connectedNodeIds == null || connectedNodeIds.Count == 0) + var connectedMapNodes = currentNode.ConnectedMapNodes; + if (connectedMapNodes == null || connectedMapNodes.Count == 0) return null; List candidateNodes = new List(); if (prevDirection == direction) { - candidateNodes = allNodes.Where(n => n.NodeId != prevNode.NodeId && - connectedNodeIds.Contains(n.NodeId) - ).ToList(); + candidateNodes = connectedMapNodes.Where(n => n.NodeId != prevNode.NodeId).ToList(); } else { - candidateNodes = allNodes.Where(n => - connectedNodeIds.Contains(n.NodeId) - ).ToList(); + candidateNodes = connectedMapNodes.ToList(); } if (candidateNodes.Count == 0) @@ -375,13 +371,11 @@ namespace AGVNavigationCore.Utils if (currentNode == null || prevNode == null || allNodes == null) return (null, 0, "입력 파라미터가 null입니다"); - var connectedNodeIds = currentNode.ConnectedNodes; - if (connectedNodeIds == null || connectedNodeIds.Count == 0) + var connectedMapNodes = currentNode.ConnectedMapNodes; + if (connectedMapNodes == null || connectedMapNodes.Count == 0) return (null, 0, "연결된 노드가 없습니다"); - var candidateNodes = allNodes.Where(n => - connectedNodeIds.Contains(n.NodeId) - ).ToList(); + var candidateNodes = connectedMapNodes.ToList(); if (candidateNodes.Count == 0) return (null, 0, "후보 노드가 없습니다"); diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DockingValidator.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DockingValidator.cs index c435922..e276cf5 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DockingValidator.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DockingValidator.cs @@ -28,6 +28,12 @@ namespace AGVNavigationCore.Utils System.Diagnostics.Debug.WriteLine($"[DockingValidator] 도킹 검증 불필요: 경로 없음"); return DockingValidationResult.CreateNotRequired(); } + if (pathResult.DetailedPath.Any() == false && pathResult.Path.Any() && pathResult.Path.Count == 2 && + pathResult.Path[0].NodeId == pathResult.Path[1].NodeId) + { + System.Diagnostics.Debug.WriteLine($"[DockingValidator] 도킹 검증 불필요: 동일포인트"); + return DockingValidationResult.CreateNotRequired(); + } // 목적지 노드 가져오기 (Path는 이제 List) var LastNode = pathResult.Path[pathResult.Path.Count - 1]; diff --git a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs index adb7be7..843d2b4 100644 --- a/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs +++ b/Cs_HMI/AGVLogic/AGVSimulator/Forms/SimulatorForm.cs @@ -319,11 +319,16 @@ namespace AGVSimulator.Forms // 고급 경로 계획 사용 (노드 객체 직접 전달) var advancedResult = _advancedPathfinder.FindPath_test(startNode, targetNode, prevNode, prevDir, currentDirection); + _simulatorCanvas.FitToNodes(); if (advancedResult.Success) { // 도킹 검증이 없는 경우 추가 검증 수행 if (advancedResult.DockingValidation == null || !advancedResult.DockingValidation.IsValidationRequired) { + if(advancedResult.Path.Count < 1) + { + + } advancedResult.DockingValidation = DockingValidator.ValidateDockingDirection(advancedResult, _mapNodes); } @@ -606,7 +611,7 @@ namespace AGVSimulator.Forms _rfidTextBox.Text = ""; // 입력 필드 초기화 // 시뮬레이터 캔버스의 해당 노드로 이동 - _simulatorCanvas.PanToNode(targetNode.NodeId); + //_simulatorCanvas.PanToNode(targetNode.NodeId); // 시작 노드 콤보박스를 현재 위치로 자동 선택 SetStartNodeToCombo(targetNode.NodeId); @@ -1414,13 +1419,12 @@ namespace AGVSimulator.Forms foreach (var nodeA in _mapNodes) { - if (nodeA.ConnectedNodes == null || nodeA.ConnectedNodes.Count == 0) + if (nodeA.ConnectedMapNodes == null || nodeA.ConnectedMapNodes.Count == 0) continue; - // 연결된 노드 ID를 실제 MapNode 객체로 변환 - foreach (var connectedNodeId in nodeA.ConnectedNodes) + // 연결된 노드 객체 순회 + foreach (var nodeB in nodeA.ConnectedMapNodes) { - var nodeB = _mapNodes.FirstOrDefault(n => n.NodeId == connectedNodeId); if (nodeB == null) continue; @@ -1428,7 +1432,7 @@ namespace AGVSimulator.Forms var pairKey1 = $"{nodeA.NodeId}→{nodeB.NodeId}"; var pairKey2 = $"{nodeB.NodeId}→{nodeA.NodeId}"; - if (!processedPairs.Contains(pairKey1) && !processedPairs.Contains(pairKey2)) + if (nodeA.HasRfid() && nodeB.HasRfid() && !processedPairs.Contains(pairKey1) && !processedPairs.Contains(pairKey2)) { pairs.Add((nodeA, nodeB)); processedPairs.Add(pairKey1); diff --git a/Cs_HMI/Data/MapData.json b/Cs_HMI/Data/MapData.json deleted file mode 100644 index ecbac87..0000000 --- a/Cs_HMI/Data/MapData.json +++ /dev/null @@ -1,1050 +0,0 @@ -{ - "Nodes": [ - { - "NodeId": "N001", - "Name": "UNLOADER", - "Position": "65, 229", - "Type": 2, - "DockDirection": 2, - "ConnectedNodes": [], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:34:44.9548285+09:00", - "ModifiedDate": "2025-09-15T11:19:44.6879389+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "001", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N001 - UNLOADER - [001]" - }, - { - "NodeId": "N002", - "Name": "N002", - "Position": "206, 244", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N001" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:34:48.2957516+09:00", - "ModifiedDate": "2025-09-15T10:16:10.1841326+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "002", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N002 - N002 - [002]" - }, - { - "NodeId": "N003", - "Name": "N003", - "Position": "278, 278", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N002" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:34:49.2226656+09:00", - "ModifiedDate": "2025-09-15T10:16:09.1753358+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "003", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N003 - N003 - [003]" - }, - { - "NodeId": "N004", - "Name": "N004", - "Position": "380, 340", - "Type": 1, - "DockDirection": 0, - "ConnectedNodes": [ - "N003", - "N022", - "N031" - ], - "CanRotate": true, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:34:50.1681027+09:00", - "ModifiedDate": "2025-09-15T11:18:47.8876112+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "004", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N004 - N004 - [004]" - }, - { - "NodeId": "N006", - "Name": "N006", - "Position": "520, 220", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N007" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:34:51.1111368+09:00", - "ModifiedDate": "2025-09-15T10:16:24.8315093+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "013", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N006 - N006 - [013]" - }, - { - "NodeId": "N007", - "Name": "N007", - "Position": "600, 180", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:34:51.9266982+09:00", - "ModifiedDate": "2025-09-11T11:46:43.5813583+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "014", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N007 - N007 - [014]" - }, - { - "NodeId": "N008", - "Name": "N008", - "Position": "299, 456", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N009", - "N031" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:34:53.9595825+09:00", - "ModifiedDate": "2025-09-15T11:18:49.8874367+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "009", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N008 - N008 - [009]" - }, - { - "NodeId": "N009", - "Name": "N009", - "Position": "193, 477", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N010" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:34:54.5035702+09:00", - "ModifiedDate": "2025-09-15T10:16:14.696211+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "010", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N009 - N009 - [010]" - }, - { - "NodeId": "N010", - "Name": "TOPS", - "Position": "52, 466", - "Type": 2, - "DockDirection": 2, - "ConnectedNodes": [], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:34:55.0563237+09:00", - "ModifiedDate": "2025-09-15T11:19:40.1582831+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "011", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N010 - TOPS - [011]" - }, - { - "NodeId": "N011", - "Name": "N011", - "Position": "460, 420", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N012", - "N004", - "N015" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:34:55.8875335+09:00", - "ModifiedDate": "2025-09-15T10:16:28.6957855+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "005", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N011 - N011 - [005]" - }, - { - "NodeId": "N012", - "Name": "N012", - "Position": "540, 480", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N013" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:34:56.3678144+09:00", - "ModifiedDate": "2025-09-11T11:46:27.9224943+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "006", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N012 - N012 - [006]" - }, - { - "NodeId": "N013", - "Name": "N013", - "Position": "620, 520", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N014" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:34:56.8390845+09:00", - "ModifiedDate": "2025-09-11T11:46:29.5788308+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "007", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N013 - N013 - [007]" - }, - { - "NodeId": "N014", - "Name": "LOADER", - "Position": "720, 580", - "Type": 2, - "DockDirection": 2, - "ConnectedNodes": [], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:34:57.2549726+09:00", - "ModifiedDate": "2025-09-15T11:19:35.3431797+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "008", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N014 - LOADER - [008]" - }, - { - "NodeId": "N019", - "Name": "CHARGER #2", - "Position": "679, 199", - "Type": 3, - "DockDirection": 1, - "ConnectedNodes": [ - "N007" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:35:56.5359098+09:00", - "ModifiedDate": "2025-09-15T11:19:49.2931335+09:00", - "IsActive": true, - "DisplayColor": "Red", - "RfidId": "015", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N019 - CHARGER #2 - [015]" - }, - { - "NodeId": "N022", - "Name": "N022", - "Position": "459, 279", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N023", - "N006" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T08:36:48.0311551+09:00", - "ModifiedDate": "2025-09-15T10:16:22.8799696+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "012", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N022 - N022 - [012]" - }, - { - "NodeId": "N023", - "Name": "N023", - "Position": "440, 220", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N024" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T09:41:36.8738794+09:00", - "ModifiedDate": "2025-09-15T10:16:20.0378544+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "016", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N023 - N023 - [016]" - }, - { - "NodeId": "N024", - "Name": "N024", - "Position": "500, 160", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N025" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T09:41:37.4551853+09:00", - "ModifiedDate": "2025-09-15T10:16:20.8801598+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "017", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N024 - N024 - [017]" - }, - { - "NodeId": "N025", - "Name": "N025", - "Position": "600, 120", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N026" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T09:41:38.0142374+09:00", - "ModifiedDate": "2025-09-15T10:16:21.6723809+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "018", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N025 - N025 - [018]" - }, - { - "NodeId": "N026", - "Name": "CHARGER #1", - "Position": "660, 100", - "Type": 3, - "DockDirection": 1, - "ConnectedNodes": [], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T09:41:38.5834487+09:00", - "ModifiedDate": "2025-09-15T11:19:58.0225184+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "019", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N026 - CHARGER #1 - [019]" - }, - { - "NodeId": "LBL001", - "Name": "Amkor Technology Korea", - "Position": "58, 64", - "Type": 4, - "DockDirection": 0, - "ConnectedNodes": [], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T11:08:22.4048927+09:00", - "ModifiedDate": "2025-09-15T11:22:15.1196535+09:00", - "IsActive": true, - "DisplayColor": "Purple", - "RfidId": "", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "Amkor Technology Korea", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "255, 255, 192", - "ShowBackground": true, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "LBL001 - Amkor Technology Korea" - }, - { - "NodeId": "IMG001", - "Name": "logo", - "Position": "720, 371", - "Type": 5, - "DockDirection": 0, - "ConnectedNodes": [], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-11T11:08:44.7897541+09:00", - "ModifiedDate": "2025-09-17T15:39:07.5229808+09:00", - "IsActive": true, - "DisplayColor": "Brown", - "RfidId": "", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "C:\\Data\\Users\\Pictures\\짤방\\아아악.png", - "Scale": "0.7, 0.7", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "IMG001 - logo" - }, - { - "NodeId": "N015", - "Name": "", - "Position": "436, 485", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N016" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-12T17:22:47.8065756+09:00", - "ModifiedDate": "2025-09-15T15:40:38.2050196+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "037", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N015 - [037]" - }, - { - "NodeId": "N016", - "Name": "", - "Position": "425, 524", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N017" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-12T17:22:48.6628848+09:00", - "ModifiedDate": "2025-09-15T15:40:36.7952276+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "036", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N016 - [036]" - }, - { - "NodeId": "N017", - "Name": "", - "Position": "387, 557", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N018" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-12T17:22:49.8138877+09:00", - "ModifiedDate": "2025-09-15T15:40:35.5342054+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "035", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N017 - [035]" - }, - { - "NodeId": "N018", - "Name": "", - "Position": "314, 549", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N005" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-12T17:22:50.6790623+09:00", - "ModifiedDate": "2025-09-15T15:40:33.4719206+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "034", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N018 - [034]" - }, - { - "NodeId": "N005", - "Name": "", - "Position": "229, 553", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N020" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-12T17:22:51.5267199+09:00", - "ModifiedDate": "2025-09-15T15:40:31.7321878+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "033", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N005 - [033]" - }, - { - "NodeId": "N020", - "Name": "", - "Position": "148, 545", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-12T17:22:52.3666114+09:00", - "ModifiedDate": "2025-09-15T15:40:30.1486235+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "032", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N020 - [032]" - }, - { - "NodeId": "N021", - "Name": "", - "Position": "66, 547", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N020" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-12T17:22:53.0958619+09:00", - "ModifiedDate": "2025-09-15T15:40:27.7345798+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "031", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N021 - [031]" - }, - { - "NodeId": "N027", - "Name": "BUF1", - "Position": "65, 644", - "Type": 2, - "DockDirection": 2, - "ConnectedNodes": [ - "N021" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-12T17:22:54.7345704+09:00", - "ModifiedDate": "2025-09-16T16:25:24.8062758+09:00", - "IsActive": true, - "DisplayColor": "Green", - "RfidId": "041", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N027 - BUF1 - [041]" - }, - { - "NodeId": "N028", - "Name": "BUF2", - "Position": "149, 645", - "Type": 2, - "DockDirection": 2, - "ConnectedNodes": [ - "N020" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-12T17:22:55.5263512+09:00", - "ModifiedDate": "2025-09-16T16:25:28.6358219+09:00", - "IsActive": true, - "DisplayColor": "Green", - "RfidId": "040", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N028 - BUF2 - [040]" - }, - { - "NodeId": "N029", - "Name": "BUF3", - "Position": "231, 639", - "Type": 2, - "DockDirection": 2, - "ConnectedNodes": [ - "N005" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-12T17:22:56.6623294+09:00", - "ModifiedDate": "2025-09-16T16:25:34.5699894+09:00", - "IsActive": true, - "DisplayColor": "Green", - "RfidId": "039", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N029 - BUF3 - [039]" - }, - { - "NodeId": "N030", - "Name": "BUF4", - "Position": "314, 639", - "Type": 2, - "DockDirection": 2, - "ConnectedNodes": [ - "N018" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-12T17:22:57.5510908+09:00", - "ModifiedDate": "2025-09-16T16:25:40.3838199+09:00", - "IsActive": true, - "DisplayColor": "Green", - "RfidId": "038", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N030 - BUF4 - [038]" - }, - { - "NodeId": "N031", - "Name": "", - "Position": "337, 397", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-09-15T11:18:40.5366059+09:00", - "ModifiedDate": "2025-09-15T15:40:24.0443882+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "030", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImagePath": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N031 - [030]" - } - ], - "CreatedDate": "2025-09-17T15:39:10.9736288+09:00", - "Version": "1.0" -} \ No newline at end of file diff --git a/Cs_HMI/Data/NewMap.agvmap b/Cs_HMI/Data/NewMap.agvmap index df05d4c..6806987 100644 --- a/Cs_HMI/Data/NewMap.agvmap +++ b/Cs_HMI/Data/NewMap.agvmap @@ -46,7 +46,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T08:34:48.2957516+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "002", @@ -79,7 +79,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T08:34:49.2226656+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "003", @@ -114,7 +114,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T08:34:50.1681027+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "004", @@ -180,7 +180,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T08:34:51.9266982+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "014", @@ -246,7 +246,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T08:34:54.5035702+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "010", @@ -278,7 +278,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T08:34:55.0563237+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "011", @@ -312,7 +312,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T08:34:55.8875335+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "005", @@ -345,7 +345,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T08:34:56.3678144+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "006", @@ -378,7 +378,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T08:34:56.8390845+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "007", @@ -410,7 +410,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T08:34:57.2549726+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "008", @@ -442,7 +442,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T08:35:56.5359098+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Red", "RfidId": "015", @@ -476,7 +476,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T08:36:48.0311551+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "012", @@ -509,7 +509,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T09:41:36.8738794+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "016", @@ -542,7 +542,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T09:41:37.4551853+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "017", @@ -575,7 +575,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T09:41:38.0142374+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "018", @@ -607,7 +607,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-11T09:41:38.5834487+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "019", @@ -659,7 +659,7 @@ { "NodeId": "IMG001", "Name": "logo", - "Position": "720, 371", + "Position": "671, 333", "Type": 5, "DockDirection": 0, "ConnectedNodes": [], @@ -680,7 +680,7 @@ "ForeColor": "Black", "BackColor": "Transparent", "ShowBackground": false, - "ImageBase64": "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", + "ImageBase64": "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", "Scale": "0.7, 0.7", "Opacity": 1.0, "Rotation": 0.0, @@ -700,7 +700,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-12T17:22:47.8065756+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "037", @@ -733,7 +733,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-12T17:22:48.6628848+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "036", @@ -760,14 +760,13 @@ "DockDirection": 0, "ConnectedNodes": [ "N018", - "N032", "N016" ], "CanRotate": false, "StationId": "", "StationType": null, "CreatedDate": "2025-09-12T17:22:49.8138877+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T09:29:49.778537+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "035", @@ -794,13 +793,14 @@ "DockDirection": 0, "ConnectedNodes": [ "N030", - "N017" + "N017", + "N005" ], "CanRotate": false, "StationId": "", "StationType": null, "CreatedDate": "2025-09-12T17:22:50.6790623+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "034", @@ -868,7 +868,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-12T17:22:52.3666114+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "032", @@ -901,7 +901,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-12T17:22:53.0958619+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "031", @@ -933,7 +933,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-12T17:22:54.7345704+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Green", "RfidId": "041", @@ -965,7 +965,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-12T17:22:55.5263512+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Green", "RfidId": "040", @@ -997,7 +997,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-12T17:22:56.6623294+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Green", "RfidId": "039", @@ -1029,7 +1029,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-12T17:22:57.5510908+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Green", "RfidId": "038", @@ -1062,7 +1062,7 @@ "StationId": "", "StationType": null, "CreatedDate": "2025-09-15T11:18:40.5366059+09:00", - "ModifiedDate": "2025-10-27T13:44:39.9998473+09:00", + "ModifiedDate": "2025-10-30T08:45:31.2491186+09:00", "IsActive": true, "DisplayColor": "Blue", "RfidId": "030", @@ -1080,40 +1080,8 @@ "Opacity": 1.0, "Rotation": 0.0, "DisplayText": "N031 - [030]" - }, - { - "NodeId": "N032", - "Name": "", - "Position": "416, 625", - "Type": 0, - "DockDirection": 0, - "ConnectedNodes": [ - "N017" - ], - "CanRotate": false, - "StationId": "", - "StationType": null, - "CreatedDate": "2025-10-27T13:44:12.9351531+09:00", - "ModifiedDate": "2025-10-27T13:44:12.9351531+09:00", - "IsActive": true, - "DisplayColor": "Blue", - "RfidId": "", - "RfidStatus": "정상", - "RfidDescription": "", - "LabelText": "", - "FontFamily": "Arial", - "FontSize": 12.0, - "FontStyle": 0, - "ForeColor": "Black", - "BackColor": "Transparent", - "ShowBackground": false, - "ImageBase64": "", - "Scale": "1, 1", - "Opacity": 1.0, - "Rotation": 0.0, - "DisplayText": "N032" } ], - "CreatedDate": "2025-10-27T13:44:45.04346+09:00", + "CreatedDate": "2025-10-30T09:30:01.9333244+09:00", "Version": "1.0" } \ No newline at end of file