using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; namespace AGVNavigationCore.Models { /// /// 맵 노드 정보를 관리하는 클래스 /// 논리적 노드로서 실제 맵의 위치와 속성을 정의 /// public class MapNode { /// /// 논리적 노드 ID (맵 에디터에서 관리하는 고유 ID) /// 예: "N001", "N002", "LOADER1", "CHARGER1" /// public string NodeId { get; set; } = string.Empty; /// /// 노드 표시 이름 (사용자 친화적) /// 예: "로더1", "충전기1", "교차점A", "회전지점1" /// public string Name { get; set; } = string.Empty; /// /// 맵 상의 위치 좌표 (픽셀 단위) /// public Point Position { get; set; } = Point.Empty; /// /// 노드 타입 /// public NodeType Type { get; set; } = NodeType.Normal; /// /// 도킹 방향 (도킹/충전 노드인 경우에만 Forward/Backward, 일반 노드는 DontCare) /// public DockingDirection DockDirection { get; set; } = DockingDirection.DontCare; /// /// 연결된 노드 ID 목록 (경로 정보) /// public List ConnectedNodes { get; set; } = new List(); /// /// 회전 가능 여부 (180도 회전 가능한 지점) /// public bool CanRotate { get; set; } = false; /// /// 장비 ID (도킹/충전 스테이션인 경우) /// 예: "LOADER1", "CLEANER1", "BUFFER1", "CHARGER1" /// public string StationId { get; set; } = string.Empty; /// /// 장비 타입 (도킹/충전 스테이션인 경우) /// public StationType? StationType { get; set; } = null; /// /// 노드 생성 일자 /// public DateTime CreatedDate { get; set; } = DateTime.Now; /// /// 노드 수정 일자 /// public DateTime ModifiedDate { get; set; } = DateTime.Now; /// /// 노드 활성화 여부 /// public bool IsActive { get; set; } = true; /// /// 노드 색상 (맵 에디터 표시용) /// public Color DisplayColor { get; set; } = Color.Blue; /// /// RFID 태그 ID (이 노드에 매핑된 RFID) /// public string RfidId { get; set; } = string.Empty; /// /// RFID 상태 (정상, 손상, 교체예정 등) /// public string RfidStatus { get; set; } = "정상"; /// /// RFID 설치 위치 설명 (현장 작업자용) /// 예: "로더1번 앞", "충전기2번 입구", "복도 교차점" 등 /// public string RfidDescription { get; set; } = string.Empty; /// /// 라벨 텍스트 (NodeType.Label인 경우 사용) /// public string LabelText { get; set; } = string.Empty; /// /// 라벨 폰트 패밀리 (NodeType.Label인 경우 사용) /// public string FontFamily { get; set; } = "Arial"; /// /// 라벨 폰트 크기 (NodeType.Label인 경우 사용) /// public float FontSize { get; set; } = 12.0f; /// /// 라벨 폰트 스타일 (NodeType.Label인 경우 사용) /// public FontStyle FontStyle { get; set; } = FontStyle.Regular; /// /// 라벨 전경색 (NodeType.Label인 경우 사용) /// public Color ForeColor { get; set; } = Color.Black; /// /// 라벨 배경색 (NodeType.Label인 경우 사용) /// public Color BackColor { get; set; } = Color.Transparent; /// /// 라벨 배경 표시 여부 (NodeType.Label인 경우 사용) /// public bool ShowBackground { get; set; } = false; /// /// 이미지 파일 경로 (NodeType.Image인 경우 사용) /// public string ImagePath { get; set; } = string.Empty; /// /// 이미지 크기 배율 (NodeType.Image인 경우 사용) /// public SizeF Scale { get; set; } = new SizeF(1.0f, 1.0f); /// /// 이미지 투명도 (NodeType.Image인 경우 사용, 0.0~1.0) /// public float Opacity { get; set; } = 1.0f; /// /// 이미지 회전 각도 (NodeType.Image인 경우 사용, 도 단위) /// public float Rotation { get; set; } = 0.0f; /// /// 로딩된 이미지 (런타임에서만 사용, JSON 직렬화 제외) /// [Newtonsoft.Json.JsonIgnore] public Image LoadedImage { get; set; } /// /// 기본 생성자 /// public MapNode() { } /// /// 매개변수 생성자 /// /// 노드 ID /// 노드 이름 /// 위치 /// 노드 타입 public MapNode(string nodeId, string name, Point position, NodeType type) { NodeId = nodeId; Name = name; Position = position; Type = type; CreatedDate = DateTime.Now; ModifiedDate = DateTime.Now; // 타입별 기본 색상 설정 SetDefaultColorByType(type); } /// /// 노드 타입에 따른 기본 색상 설정 /// /// 노드 타입 public void SetDefaultColorByType(NodeType type) { switch (type) { case NodeType.Normal: DisplayColor = Color.Blue; break; case NodeType.Rotation: DisplayColor = Color.Orange; break; case NodeType.Docking: DisplayColor = Color.Green; break; case NodeType.Charging: DisplayColor = Color.Red; break; case NodeType.Label: DisplayColor = Color.Purple; break; case NodeType.Image: DisplayColor = Color.Brown; break; } } /// /// 다른 노드와의 연결 추가 /// /// 연결할 노드 ID public void AddConnection(string nodeId) { if (!ConnectedNodes.Contains(nodeId)) { ConnectedNodes.Add(nodeId); ModifiedDate = DateTime.Now; } } /// /// 다른 노드와의 연결 제거 /// /// 연결 해제할 노드 ID public void RemoveConnection(string nodeId) { if (ConnectedNodes.Remove(nodeId)) { ModifiedDate = DateTime.Now; } } /// /// 도킹 스테이션 설정 /// /// 장비 ID /// 장비 타입 /// 도킹 방향 public void SetDockingStation(string stationId, StationType stationType, DockingDirection dockDirection) { Type = NodeType.Docking; StationId = stationId; StationType = stationType; DockDirection = dockDirection; SetDefaultColorByType(NodeType.Docking); ModifiedDate = DateTime.Now; } /// /// 충전 스테이션 설정 /// /// 충전기 ID public void SetChargingStation(string stationId) { Type = NodeType.Charging; StationId = stationId; StationType = Models.StationType.Charger; DockDirection = DockingDirection.Forward; // 충전기는 항상 전진 도킹 SetDefaultColorByType(NodeType.Charging); ModifiedDate = DateTime.Now; } /// /// 문자열 표현 /// public override string ToString() { return $"{RfidId}({NodeId}): {Name} ({Type}) at ({Position.X}, {Position.Y})"; } /// /// 리스트박스 표시용 텍스트 (노드ID - 설명 - RFID 순서) /// public string DisplayText { get { var displayText = NodeId; if (!string.IsNullOrEmpty(Name)) { displayText += $" - {Name}"; } if (!string.IsNullOrEmpty(RfidId)) { displayText += $" - [{RfidId}]"; } return displayText; } } /// /// 노드 복사 /// /// 복사된 노드 public MapNode Clone() { var clone = new MapNode { NodeId = NodeId, Name = Name, Position = Position, Type = Type, DockDirection = DockDirection, ConnectedNodes = new List(ConnectedNodes), CanRotate = CanRotate, StationId = StationId, StationType = StationType, CreatedDate = CreatedDate, ModifiedDate = ModifiedDate, IsActive = IsActive, DisplayColor = DisplayColor, RfidId = RfidId, RfidStatus = RfidStatus, RfidDescription = RfidDescription, LabelText = LabelText, FontFamily = FontFamily, FontSize = FontSize, FontStyle = FontStyle, ForeColor = ForeColor, BackColor = BackColor, ShowBackground = ShowBackground, ImagePath = ImagePath, Scale = Scale, Opacity = Opacity, Rotation = Rotation }; return clone; } /// /// 이미지 로드 (256x256 이상일 경우 자동 리사이즈) /// /// 로드 성공 여부 public bool LoadImage() { if (Type != NodeType.Image) return false; try { if (!string.IsNullOrEmpty(ImagePath) && System.IO.File.Exists(ImagePath)) { LoadedImage?.Dispose(); var originalImage = Image.FromFile(ImagePath); // 이미지 크기 체크 및 리사이즈 if (originalImage.Width > 256 || originalImage.Height > 256) { LoadedImage = ResizeImage(originalImage, 256, 256); originalImage.Dispose(); } else { LoadedImage = originalImage; } return true; } } catch (Exception) { // 이미지 로드 실패 } return false; } /// /// 이미지 리사이즈 (비율 유지) /// /// 원본 이미지 /// 최대 너비 /// 최대 높이 /// 리사이즈된 이미지 private Image ResizeImage(Image image, int maxWidth, int maxHeight) { // 비율 계산 double ratioX = (double)maxWidth / image.Width; double ratioY = (double)maxHeight / image.Height; double ratio = Math.Min(ratioX, ratioY); // 새로운 크기 계산 int newWidth = (int)(image.Width * ratio); int newHeight = (int)(image.Height * ratio); // 리사이즈된 이미지 생성 var resizedImage = new Bitmap(newWidth, newHeight); using (var graphics = Graphics.FromImage(resizedImage)) { graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.DrawImage(image, 0, 0, newWidth, newHeight); } return resizedImage; } /// /// 실제 표시될 크기 계산 (이미지 노드인 경우) /// /// 실제 크기 public Size GetDisplaySize() { if (Type != NodeType.Image || LoadedImage == null) return Size.Empty; return new Size( (int)(LoadedImage.Width * Scale.Width), (int)(LoadedImage.Height * Scale.Height) ); } /// /// 리소스 정리 /// public void Dispose() { LoadedImage?.Dispose(); LoadedImage = null; } /// /// 경로 찾기에 사용 가능한 노드인지 확인 /// (라벨, 이미지 노드는 경로 찾기에서 제외) /// public bool IsNavigationNode() { return Type != NodeType.Label && Type != NodeType.Image && IsActive; } /// /// RFID가 할당되어 있는지 확인 /// public bool HasRfid() { return !string.IsNullOrEmpty(RfidId); } /// /// RFID 정보 설정 /// /// RFID ID /// 설치 위치 설명 /// RFID 상태 public void SetRfidInfo(string rfidId, string rfidDescription = "", string rfidStatus = "정상") { RfidId = rfidId; RfidDescription = rfidDescription; RfidStatus = rfidStatus; ModifiedDate = DateTime.Now; } /// /// RFID 정보 삭제 /// public void ClearRfidInfo() { RfidId = string.Empty; RfidDescription = string.Empty; RfidStatus = "정상"; ModifiedDate = DateTime.Now; } /// /// RFID 기반 표시 텍스트 (RFID ID 우선, 없으면 노드ID) /// public string GetRfidDisplayText() { return HasRfid() ? RfidId : NodeId; } } }