using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using Project.StateMachine; using COMM; using AR; using AGVNavigationCore.Models; namespace Project.ViewForm { public partial class fAuto : Form { public fAuto() { InitializeComponent(); this.FormClosed += FAuto_FormClosed; PUB.sm.StepChanged += Sm_StepChanged; this.ctlAuto1.ButtonClick += CtlAuto1_ButtonClick; if (PUB.sm.Step == eSMStep.INIT || PUB.sm.Step == eSMStep.SYNC) this.ctlAuto1.Scean = CtlAuto.eScean.Progress; else this.ctlAuto1.Scean = CtlAuto.eScean.Normal; InitializeMapCanvas(); //PUB.mapctl = new AGVControl.MapControl(); //PUB.mapctl.Dock = DockStyle.Fill; //PUB.mapctl.Visible = true; //PUB.mapctl.Font = this.panel1.Font; //PUB.mapctl.BackColor = Color.FromArgb(32, 32, 32); //this.panel1.Controls.Add(PUB.mapctl); } private void InitializeMapCanvas() { PUB._mapCanvas = new AGVNavigationCore.Controls.UnifiedAGVCanvas(); PUB._mapCanvas.Dock = DockStyle.Fill; PUB._mapCanvas.ShowGrid = false; PUB._mapCanvas.BackColor = Color.FromArgb(32, 32, 32); PUB._mapCanvas.ForeColor = Color.White; // RfidMappings 제거 - MapNode에 통합됨 // 이벤트 연결 //PUB._mapCanvas.NodeAdded += OnNodeAdded; // 이벤트 연결 //PUB._mapCanvas.NodeAdded += OnNodeAdded; PUB._mapCanvas.NodesSelected += OnNodeSelected; //PUB._mapCanvas.NodeMoved += OnNodeMoved; //PUB._mapCanvas.NodeDeleted += OnNodeDeleted; //PUB._mapCanvas.ConnectionDeleted += OnConnectionDeleted; //PUB._mapCanvas.ImageNodeDoubleClicked += OnImageNodeDoubleClicked; //PUB._mapCanvas.MapChanged += OnMapChanged; // 스플리터 패널에 맵 캔버스 추가 panel1.Controls.Add(PUB._mapCanvas); } private void OnNodeSelected(object sender, List nodes, MouseEventArgs e) { if (e.Button != MouseButtons.Right) return; var node = nodes.FirstOrDefault(); if (nodes == null) return; // 도킹 가능한 노드인지 또는 작업 노드인지 확인 bool isDockingNode = node.Type == NodeType.Loader || node.Type == NodeType.UnLoader || node.Type == NodeType.Buffer || node.Type == NodeType.Clearner || node.Type == NodeType.Charging; if (!isDockingNode) return; ContextMenuStrip menu = new ContextMenuStrip(); // PickOn var pickOn = new ToolStripMenuItem("Pick On (Move & Pick)"); pickOn.Click += (s, args) => ExecuteManualCommand(node, ENIGProtocol.AGVCommandHE.PickOn); menu.Items.Add(pickOn); // PickOff var pickOff = new ToolStripMenuItem("Pick Off (Move & Drop)"); pickOff.Click += (s, args) => ExecuteManualCommand(node, ENIGProtocol.AGVCommandHE.PickOff); menu.Items.Add(pickOff); // Charge if (node.Type == NodeType.Charging) { var charge = new ToolStripMenuItem("Charge (Move & Charge)"); charge.Click += (s, args) => ExecuteManualCommand(node, ENIGProtocol.AGVCommandHE.Charger); menu.Items.Add(charge); } menu.Show(Cursor.Position); } private void ExecuteManualCommand(MapNode targetNode, ENIGProtocol.AGVCommandHE cmd) { if (PUB._virtualAGV.CurrentNode == null) { MessageBox.Show("AGV의 현재 위치를 알 수 없습니다."); return; } if (PUB.sm.RunStep != eSMStep.IDLE && PUB.sm.RunStep != eSMStep.READY) { if (MessageBox.Show("현재 대기상태가 아닙니다. 강제로 실행하시겠습니까?", "Warning", MessageBoxButtons.YesNo) == DialogResult.No) return; } // 1. 경로 생성 var pathFinder = new AGVNavigationCore.PathFinding.Planning.AGVPathfinder(PUB._mapNodes); // 현재위치에서 목표위치까지 var result = pathFinder.FindPath(PUB._virtualAGV.CurrentNode, targetNode); if (!result.Success || result.Path == null || result.Path.Count == 0) { MessageBox.Show("경로를 찾을 수 없습니다."); return; } // 2. 상태 설정 PUB.log.AddI($"[Manual Command] {cmd} to {targetNode.Name}({targetNode.NodeId})"); // Path 변환 (Node 리스트 -> AGVPathResult) // VirtualAGV.SetPath가 필요할 수 있음. 혹은 Goto 로직을 수동으로 구성. // _SM_RUN_GOTO에서는 PUB._virtualAGV.CurrentPath를 사용함. // AGVPathResult 생성 필요. var detailedPath = AGVNavigationCore.PathFinding.Planning.AGVPathfinder.MakeDetailData(result.Path, PUB._mapNodes); PUB._virtualAGV.SetPath(result.Path, detailedPath); PUB._virtualAGV.TargetNode = targetNode; // 3. 작업 설정 PUB.NextWorkCmd = cmd; // 4. 실행 PUB.sm.SetNewRunStep(ERunStep.GOTO); // GOTO -> Arrive -> _IN sequence execution } // 툴바 버튼 이벤트 연결 //WireToolbarButtonEvents(); } private void fAuto_Load(object sender, EventArgs e) { ctlAuto1.dev_agv = PUB.AGV; // ctlAuto1.dev_plc = PUB.PLC; ctlAuto1.dev_bms = PUB.BMS; ctlAuto1.dev_xbe = PUB.XBE; PUB.AGV.DataReceive += AGV_DataReceive; //auto load var mapPath = new System.IO.DirectoryInfo("route"); if (mapPath.Exists == false) mapPath.Create(); //맵파일로딩 if (PUB.setting.LastMapFile.isEmpty()) PUB.setting.LastMapFile = System.IO.Path.Combine(mapPath.FullName, "default.agvmap"); System.IO.FileInfo filePath = new System.IO.FileInfo(PUB.setting.LastMapFile); if (filePath.Exists == false) filePath = new System.IO.FileInfo(System.IO.Path.Combine(mapPath.FullName, "default.agvmap")); if (filePath.Exists == false) //그래도없다면 맵폴더에서 파일을 찾아본다. { var files = mapPath.GetFiles("*.agvmap"); if (files.Any()) filePath = files[0]; } if (filePath.Exists) { var result = MapLoader.LoadMapFromFile(filePath.FullName); if (result.Success) { if (PUB._mapNodes == null) PUB._mapNodes = new List(); else PUB._mapNodes.Clear(); PUB._mapNodes.AddRange(result.Nodes); // 맵 캔버스에 데이터 설정 PUB._mapCanvas.Nodes = PUB._mapNodes; PUB._mapCanvas.MapFileName = filePath.FullName; // 🔥 맵 설정 적용 (배경색, 그리드 표시) if (result.Settings != null) { PUB._mapCanvas.BackColor = System.Drawing.Color.FromArgb(result.Settings.BackgroundColorArgb); PUB._mapCanvas.ShowGrid = result.Settings.ShowGrid; } // 🔥 가상 AGV 초기화 (첫 노드 위치에 생성) if (PUB._virtualAGV == null && PUB._mapNodes.Count > 0) { var startNode = PUB._mapNodes.FirstOrDefault(n => n.IsNavigationNode()); if (startNode != null) { PUB._virtualAGV = new VirtualAGV(PUB.setting.MCID, startNode.Position, AgvDirection.Forward); PUB._virtualAGV.LowBatteryThreshold = PUB.setting.BatteryLimit_Low; PUB._virtualAGV.SetPosition(startNode, AgvDirection.Forward); // 캔버스에 AGV 리스트 설정 var agvList = new System.Collections.Generic.List { PUB._virtualAGV }; PUB._mapCanvas.AGVList = agvList; PUB.log.Add($"가상 AGV 생성: {startNode.NodeId} 위치"); } } else if (PUB._virtualAGV != null) { PUB._virtualAGV.LowBatteryThreshold = PUB.setting.BatteryLimit_Low; // 기존 AGV가 있으면 캔버스에 다시 연결 var agvList = new System.Collections.Generic.List { PUB._virtualAGV }; PUB._mapCanvas.AGVList = agvList; } // 맵 로드 후 자동으로 맵에 맞춤 PUB._mapCanvas.FitToNodes(); PUB.log.Add($"맵 파일 로드 완료: {filePath.Name}, 노드 수: {result.Nodes.Count}"); } else { PUB.log.Add($"맵 파일 로딩 실패: {result.ErrorMessage}"); MessageBox.Show($"맵 파일 로딩 실패: {result.ErrorMessage}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error); } } else { PUB.log.Add($"맵 파일을 찾을 수 없습니다: {filePath.FullName}"); } //var fn = string.Empty; //if (files.Any() == false) //{ // fn = AR.UTIL.MakePath("sample.route"); //} //else if (files.Count() == 1) //{ // fn = files.First().FullName; //} //if (fn.isEmpty() == false) //{ // var fi = new System.IO.FileInfo(AR.UTIL.CurrentPath + "\\sample.route"); // if (fi.Exists) // { // PUB.log.Add($"autoload : {fi.FullName}"); // var rlt = PUB.mapctl.LoadFromFile(fi.FullName, out string errmsg); // if (rlt == false) AR.UTIL.MsgE(errmsg); // } //} this.timer1.Start(); } private void AGV_DataReceive(object sender, arDev.Narumi.DataEventArgs e) { switch (e.DataType) { case arDev.Narumi.DataType.TAG: //_AGV 파일에서 위치조정을 함 //var tagno = PUB.AGV.data.TagNo; //PUB.mapctl.SetCurrentPosition(tagno); break; } } private void CtlAuto1_ButtonClick(CtlAuto.UIButton idx) { if (idx.cmd.Equals("EMG")) { PUB.log.Add("ui reset click"); PUB.AGV.AGVErrorReset(); } } private void Sm_StepChanged(object sender, StateMachine.StateMachine.StepChangeEventArgs e) { if (e.New == eSMStep.INIT || e.New == eSMStep.SYNC) this.ctlAuto1.Scean = CtlAuto.eScean.Progress; else this.ctlAuto1.Scean = CtlAuto.eScean.Normal; } private void FAuto_FormClosed(object sender, FormClosedEventArgs e) { timer1.Stop(); PUB.sm.StepChanged -= Sm_StepChanged; this.ctlAuto1.ButtonClick -= CtlAuto1_ButtonClick; PUB.AGV.DataReceive -= AGV_DataReceive; } bool tmrun = false; private void fAuto_VisibleChanged(object sender, EventArgs e) { this.timer1.Enabled = this.Visible; if (timer1.Enabled) timer1.Start(); else timer1.Stop(); } private void timer1_Tick_1(object sender, EventArgs e) { //if (this.Visible == false) return; //if (tmrun == true) return; //tmrun = true; //this.ctlAuto1.OnUpdateMode = true; //if (this.ctlAuto1.Scean == CtlAuto.eScean.Progress) //{ // ctlAuto1.ProgressVal = PUB.Result.SMSG_ProgressValue; // ctlAuto1.ProgressMax = PUB.Result.SMSG_ProgressMax; // ctlAuto1.StatusMessage = VAR.STR?.Get(eVarString.StatusMessage) ?? string.Empty; //} //this.ctlAuto1.StopMessage = string.Empty; //if (PUB.sm.Step == StateMachine.eSMStep.RUN) //{ // this.ctlAuto1.runStep = PUB.sm.RunStep; //} //else //{ // this.ctlAuto1.runStep = ERunStep.READY; //} //this.ctlAuto1.OnUpdateMode = false; //this.ctlAuto1.Invalidate(); //PUB.mapctl.PredictNextAction(); //tmrun = false; } } }