fix: 맵 에디터 연결 버그 수정 및 기능 개선

주요 변경사항:
- ConnectedMapNodes 속성 추가로 런타임 객체 참조 지원
- 이미지 에디터 UI 개선 (ImageEditorCanvas 추가)
- 연결 생성 버그 수정: 양방향 연결 생성
- 연결 삭제 버그 수정: 양방향 모두 제거
- CleanupDuplicateConnections 비활성화 (단방향 변환 버그)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
backuppc
2025-10-30 09:34:10 +09:00
parent 0b59479d34
commit 3f6db7113f
18 changed files with 838 additions and 1303 deletions

View File

@@ -49,9 +49,15 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="Controls\ImageEditorCanvas.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Forms\ImageEditorForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\ImageEditorForm.Designer.cs">
<DependentUpon>ImageEditorForm.cs</DependentUpon>
</Compile>
<Compile Include="Models\EditorSettings.cs" />
<Compile Include="Models\ImagePathEditor.cs" />
<Compile Include="Models\MapImage.cs" />
@@ -86,7 +92,6 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Data\" />
<Folder Include="Utils\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,413 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace AGVMapEditor.Controls
{
/// <summary>
/// 이미지 편집용 사용자 정의 캔버스 컨트롤
/// 이미지 중앙 정렬, 크기 조정 핸들, 브러시 그리기 기능 제공
/// </summary>
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
/// <summary>
/// 화면 좌표를 이미지 좌표로 변환
/// </summary>
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();
}
/// <summary>
/// 표시 크기로 실제 이미지 리사이즈
/// </summary>
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);
}
}
}

View File

@@ -0,0 +1,188 @@
namespace AGVMapEditor.Forms
{
partial class ImageEditorForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

View File

@@ -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
/// </summary>
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);
}
}
}

View File

@@ -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;