using System.Data; using System.Diagnostics; using System.Drawing.Imaging; using AntdUI; using DH.Commons.Base; using DH.Commons.Enums; using DH.Commons.Helper; using DH.Commons.Models; using DH.Devices.Camera; using HalconDotNet; using OpenCvSharp.Extensions; namespace DHSoftware.Views { public partial class VisualLocalizationWindow : Window { //采集状态 private volatile bool isCapturing=false; //定位状态 private volatile bool isLocationing = false; //算法 HDevEngine MyEngine = new HDevEngine(); HDevProcedure Procedure; HDevProcedureCall ProcCall; //背景图 HImage backImage = new HImage(); //当前相机 Do3ThinkCamera Do3ThinkCamera = new Do3ThinkCamera(); //定时器 private System.Threading.Timer Timer; List localizations = new List(); public VisualLocalizationWindow() { InitializeComponent(); Load += VisualLocalizationWindow_Load; btnSelectModel.Click += BtnSelectModel_Click; btnSelectBackImg.Click += BtnSelectBackImg_Click; btnAcquisition.Click += BtnAcquisition_Click; btnLocalization.Click += BtnLocalization_Click; btnForward.MouseDown += BtnForward_MouseDown; btnForward.MouseUp += BtnForward_MouseUp; btnReverse.MouseDown += BtnReverse_MouseDown; btnReverse.MouseUp += BtnReverse_MouseUp; btnSaveImg.Click += BtnSaveImg_Click; btnSavePos.Click += BtnSavePos_Click; sltCameraName.SelectedIndexChanged += SltCameraName_SelectedIndexChanged; btnMotorForward.MouseDown += BtnMotorForward_MouseDown; btnMotorForward.MouseUp += MotorButton_MouseUp; btnMotorReverse.MouseDown += BtnMotorReverse_MouseDown; btnMotorReverse.MouseUp += MotorButton_MouseUp; btnMotorZero.Click += BtnMotorZero_Click; btnSaveMotorPos.Click += BtnSaveMotorPos_Click; panelMotor.Visible = false; } private void BtnSaveMotorPos_Click(object? sender, EventArgs e) { //根据工位查找点位 PLCItem? pLCItem = ConfigModel.PLCBaseList? .FirstOrDefault()? .PLCItemList? .Where(it => it.Name == "相机步进位置").FirstOrDefault(); if (pLCItem == null) { AntdUI.Message.warn(this, $"未找到相机步进位置地址,请检查该地址是否存在于点位表!", autoClose: 3); return; } PLCItem? pLCItem1 = ConfigModel.GlobalList? .FirstOrDefault()? .StartProcessList? .Where(it => it.Name == "相机步进位置").FirstOrDefault(); if (pLCItem1 == null) { pLCItem1 = new PLCItem(); pLCItem1.Name = pLCItem.Name; pLCItem1.Address = pLCItem.Address; pLCItem1.Value = iptMotorPos.Text; pLCItem1.Type = pLCItem.Type; pLCItem1.StartExecute = true; ConfigModel.GlobalList? .FirstOrDefault()? .StartProcessList?.Add(pLCItem1); } else { pLCItem1.Value = iptMotorPos.Text; } ConfigHelper.SaveConfig(); AntdUI.Message.success(this, "保存成功!", autoClose: 3); } private CancellationTokenSource? _motorCts; private bool _isMotorOperating; private bool _isHoming; private CancellationTokenSource _cts; private bool _isBusy; // 电机正转控制 private async void BtnMotorForward_MouseDown(object sender, MouseEventArgs e) { if (_isBusy) return; _isBusy = true; try { // 获取并处理速度值 if (!int.TryParse(iptMotorSpeed.Text, out int speed) || speed == 0) { AntdUI.Message.warn(this, "速度值无效"); return; } int actualSpeed = Math.Abs(speed); // 正转取绝对值 // 启动PLC控制 _cts?.Dispose(); _cts = new CancellationTokenSource(); MainWindow.Instance.PLC.MotorSpeed(actualSpeed); MainWindow.Instance.PLC.MotorTest(true); // 实时更新循环 while (!_cts.IsCancellationRequested) { var pos = await Task.Run(() => MainWindow.Instance.PLC.ReadMotorRealPos()); iptMotorPos.Text = pos.ToString(); await Task.Delay(50); } } catch (Exception ex) { AntdUI.Message.error(this, $"正转异常: {ex.Message}"); } finally { MainWindow.Instance.PLC.MotorTest(false); _isBusy = false; } } // 电机反转控制 // 电机反转控制(带原点保护) private async void BtnMotorReverse_MouseDown(object sender, MouseEventArgs e) { if (_isBusy) return; _isBusy = true; try { // 获取并处理速度值 if (!int.TryParse(iptMotorSpeed.Text, out int speed) || speed == 0) { AntdUI.Message.warn(this, "速度值无效"); return; } int actualSpeed = -Math.Abs(speed); // 实时位置检查(启动前) int currentPos = MainWindow.Instance.PLC.ReadMotorRealPos(); if (currentPos <= 0) { AntdUI.Message.info(this, "已在原点位置"); return; } // 启动PLC控制 _cts?.Dispose(); _cts = new CancellationTokenSource(); MainWindow.Instance.PLC.MotorSpeed(actualSpeed); MainWindow.Instance.PLC.MotorTest(true); // 带保护的实时更新循环 while (!_cts.IsCancellationRequested) { currentPos = await Task.Run(() => MainWindow.Instance.PLC.ReadMotorRealPos()); // 位置边界保护 if (currentPos <= 0) { _cts.Cancel(); break; } // 更新UI this.Invoke((MethodInvoker)delegate { iptMotorPos.Text = currentPos.ToString(); }); await Task.Delay(50); } } catch (Exception ex) { AntdUI.Message.error(this, $"反转异常: {ex.Message}"); } finally { MainWindow.Instance.PLC.MotorTest(false); _isBusy = false; } } // 停止控制(正反转共用) private void MotorButton_MouseUp(object sender, MouseEventArgs e) { _cts?.Cancel(); iptMotorPos.Text = MainWindow.Instance.PLC.ReadMotorRealPos().ToString(); } // 回零操作 private async void BtnMotorZero_Click(object sender, EventArgs e) { if (_isBusy) return; _isBusy = true; btnMotorForward.Enabled = btnMotorReverse.Enabled = false; try { // 获取速度值 if (!int.TryParse(iptMotorSpeed.Text, out int speed) || speed <= 0) { AntdUI.Message.warn(this, "速度值无效"); return; } // 执行回零 MainWindow.Instance.PLC.MotorSpeed(speed); await Task.Run(() => MainWindow.Instance.PLC.MotorToZero(true)); // 等待回零完成 while (MainWindow.Instance.PLC.ReadMotorRealPos() != 0) { iptMotorPos.Text = MainWindow.Instance.PLC.ReadMotorRealPos().ToString(); await Task.Delay(100); } await Task.Run(() => MainWindow.Instance.PLC.MotorToZero(false)); } catch (Exception ex) { AntdUI.Message.error(this, $"回零失败: {ex.Message}"); } finally { btnMotorForward.Enabled = btnMotorReverse.Enabled = true; _isBusy = false; iptMotorPos.Text = MainWindow.Instance.PLC.ReadMotorRealPos().ToString(); } } private void SltCameraName_SelectedIndexChanged(object sender, IntEventArgs e) { try { localizations = VisualLocalization.LoadAll(); } catch { } string cameraName= sltCameraName.Text; Do3ThinkCamera = MainWindow.Instance.Cameras.Where(it => it.CameraName == cameraName).FirstOrDefault() ?? new Do3ThinkCamera(); VisualLocalization? visual= localizations.Where(it=>it.CameraName==cameraName).FirstOrDefault(); if (visual != null) { iptModel.Text = visual.ModelPath; iptBackImg.Text = visual.ImgPath; iptThreshold.Text = visual.Threshold; sltDirection.SelectedIndex = visual.Direction == "正方向" ? 0 : 1; iptSpeed.Text = visual.Speed; } if (Do3ThinkCamera.IsZoomCamera) { panelMotor.Visible=true; } else { panelMotor.Visible = false; } } private void BtnSavePos_Click(object? sender, EventArgs e) { var form = new SavePositionControl(this,Convert.ToInt32(iptPosition.Text)) { Size = new Size(300, 300) }; AntdUI.Modal.open(new AntdUI.Modal.Config(this, "", form, TType.None) { BtnHeight = 0, }); if (form.submit) { VisualLocalization visualLocalization = new VisualLocalization(); //保存用户操作到文件 visualLocalization.CameraName = sltCameraName.Text; visualLocalization.ModelPath=iptModel.Text; visualLocalization.ImgPath=iptBackImg.Text; visualLocalization.Threshold=iptThreshold.Text; visualLocalization.Direction=sltDirection.Text; visualLocalization.Speed=iptSpeed.Text; visualLocalization.Save(); } } /// /// 保存图像 /// /// /// private void BtnSaveImg_Click(object? sender, EventArgs e) { if (!isCapturing) { AntdUI.Message.warn(this, $"未开始采集,无法保存图像!", autoClose: 3); return; } Bitmap bitmap =imageViewerControl1.GetCurrentSnapshot(); using (SaveFileDialog saveDialog = new SaveFileDialog()) { saveDialog.Title = "保存图像文件"; saveDialog.Filter = "JPEG 图像|*.jpg"; saveDialog.FilterIndex = 0; saveDialog.AddExtension = true; saveDialog.OverwritePrompt = true; if (saveDialog.ShowDialog() == DialogResult.OK) { bitmap.Save(saveDialog.FileName, ImageFormat.Jpeg); AntdUI.Message.success(this, $"图像保存成功!", autoClose: 3); } else { AntdUI.Message.warn(this, $"取消图像保存操作!", autoClose: 3); } } } /// /// 定位 /// /// /// private void BtnLocalization_Click(object? sender, EventArgs e) { if (!isCapturing) { AntdUI.Message.warn(this, $"未开始采集,无法开始定位!", autoClose: 3); return; } if (!isLocationing) { bool direction =sltDirection.SelectedIndex==0?true:false; if (string.IsNullOrEmpty(iptSpeed.Text)) { AntdUI.Message.warn(this, $"请输入速度!", autoClose: 3); return; } int speed = 0; try { bool isValid = int.TryParse(iptSpeed.Text, out speed); if (!isValid) { AntdUI.Message.warn(this, $"输入的速度不是有效值!", autoClose: 3); return; } } catch (Exception ex) { } MainWindow.Instance.PLC.TurnSpeed(speed); MainWindow.Instance.PLC.TurnDirection(direction); MainWindow.Instance.PLC.TurnStart(true); isLocationing = true; btnLocalization.Text = "结束定位"; btnLocalization.Type = TTypeMini.Warn; } else { MainWindow.Instance.PLC.TurnStart(false); iptPosition.Text= MainWindow.Instance.PLC.ReadVisionPos().ToString(); isLocationing = false; btnLocalization.Text = "开始定位"; btnLocalization.Type = TTypeMini.Primary; } } /// /// 采集 /// /// /// private void BtnAcquisition_Click(object? sender, EventArgs e) { if (!isCapturing) { if (string.IsNullOrWhiteSpace(sltCameraName.Text)) { AntdUI.Message.warn(this, $"请选择相机!", autoClose: 3); return; } if (string.IsNullOrWhiteSpace(iptModel.Text)) { AntdUI.Message.warn(this, $"请选择算法!", autoClose: 3); return; } if (string.IsNullOrWhiteSpace(iptBackImg.Text)) { AntdUI.Message.warn(this, $"请选择背景图片!", autoClose: 3); return; } // 加载HALCON模型 if (!File.Exists(iptModel.Text)) { AntdUI.Message.warn(this, $"算法文件不存在!", autoClose: 3); return; } if (!File.Exists(iptBackImg.Text)) { AntdUI.Message.warn(this, $"图片文件不存在!", autoClose: 3); return; } //获取背景图 backImage = new HImage(); backImage.ReadImage(iptBackImg.Text); // 从完整路径获取过程名称 string procedureName = Path.GetFileNameWithoutExtension(iptModel.Text); string procedureDir = Path.GetDirectoryName(iptModel.Text); // 重新初始化HALCON引擎 MyEngine.SetProcedurePath(procedureDir); Procedure = new HDevProcedure(procedureName); ProcCall = new HDevProcedureCall(Procedure); if (MainWindow.Instance.PLC.Connected) { //启用视觉定位 MainWindow.Instance.PLC.VisionPos(true); } else { AntdUI.Message.warn(this, $"未连接PLC,无法视觉定位!", autoClose: 3); return; } Do3ThinkCamera=MainWindow.Instance.Cameras.Where(it=>it.CameraName==sltCameraName.Text).FirstOrDefault()??new Do3ThinkCamera(); Do3ThinkCamera.OnHImageOutput += OnCameraHImageOutput; Timer = new System.Threading.Timer(CaptureLoop, null, 0, 50); isCapturing = true; btnAcquisition.Text = "结束采集"; btnAcquisition.Type = TTypeMini.Warn; } else { if (isLocationing) { AntdUI.Message.warn(this, $"定位未结束,不能结束采集!", autoClose: 3); return; } MainWindow.Instance.PLC.VisionPos(false); Do3ThinkCamera.OnHImageOutput -= OnCameraHImageOutput; Timer?.Dispose(); isCapturing = false; btnAcquisition.Text = "开始采集"; btnAcquisition.Type = TTypeMini.Primary; } } /// /// 触发 /// /// private void CaptureLoop(object? state) { Do3ThinkCamera.Snapshot(); } private CancellationTokenSource? _rotateCts; private bool _isRotating; /// /// 统一旋转控制方法 /// private async Task RotateControlAsync(bool direction) { // 防止重复启动 if (_isRotating) return; _isRotating = true; try { if (!MainWindow.Instance.PLC.Connected) { this.Invoke(() => AntdUI.Message.warn(this, "未连接PLC!", autoClose: 3)); return; } // 输入验证 if (string.IsNullOrEmpty(iptSpeed.Text)) { this.Invoke(() => AntdUI.Message.warn(this, "请输入速度!", autoClose: 3)); return; } if (!int.TryParse(iptSpeed.Text, out int speed) || speed <= 0) { this.Invoke(() => AntdUI.Message.warn(this, "速度必须为正整数!", autoClose: 3)); return; } // 初始化取消令牌 _rotateCts?.Dispose(); _rotateCts = new CancellationTokenSource(); // 设置PLC参数 MainWindow.Instance.PLC.TurnSpeed(speed); MainWindow.Instance.PLC.TurnDirection(direction); MainWindow.Instance.PLC.TurnStart(true); // 异步更新循环 while (!_rotateCts.IsCancellationRequested) { var position = MainWindow.Instance.PLC.ReadVisionPos(); Debug.WriteLine(position.ToString()); this.BeginInvoke(() => iptPosition.Text = position.ToString()); await Task.Delay(100, _rotateCts.Token); } } catch (OperationCanceledException) { // 正常取消不处理 } catch (Exception ex) { this.Invoke(() => AntdUI.Message.error(this, $"操作异常:{ex.Message}")); } finally { // 确保停止转动 MainWindow.Instance.PLC.TurnStart(false); var finalPos = MainWindow.Instance.PLC.ReadVisionPos(); this.Invoke(() => iptPosition.Text = finalPos.ToString()); _isRotating = false; _rotateCts?.Dispose(); _rotateCts = null; } } /// /// 反转抬起 /// /// /// private void BtnReverse_MouseUp(object? sender, MouseEventArgs e) { _rotateCts?.Cancel(); } /// /// 反转按下 /// /// /// private async void BtnReverse_MouseDown(object? sender, MouseEventArgs e) { await RotateControlAsync(false); } /// /// 正转抬起 /// /// /// private void BtnForward_MouseUp(object? sender, MouseEventArgs e) { _rotateCts?.Cancel(); } /// /// 正转按下 /// /// /// private async void BtnForward_MouseDown(object? sender, MouseEventArgs e) { await RotateControlAsync(true); } /// /// 选择背景图 /// /// /// private void BtnSelectBackImg_Click(object? sender, EventArgs e) { using (OpenFileDialog openFileDialog = new OpenFileDialog()) { // 设置对话框标题 openFileDialog.Title = "选择背景图片"; // 限制文件后缀为 .hdvp openFileDialog.Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp"; // 禁止多选 openFileDialog.Multiselect = false; // 显示对话框并等待用户操作 if (openFileDialog.ShowDialog() == DialogResult.OK) { string filePath = openFileDialog.FileName; iptBackImg.Text = filePath; } } } /// /// 选择算法 /// /// /// private void BtnSelectModel_Click(object? sender, EventArgs e) { using (OpenFileDialog openFileDialog = new OpenFileDialog()) { // 设置对话框标题 openFileDialog.Title = "选择算法文件"; // 限制文件后缀为 .hdvp openFileDialog.Filter = "算法文件 (*.hdvp)|*.hdvp"; // 禁止多选 openFileDialog.Multiselect = false; // 显示对话框并等待用户操作 if (openFileDialog.ShowDialog() == DialogResult.OK) { string filePath = openFileDialog.FileName; iptModel.Text = filePath; } } } /// /// 加载事件 /// /// /// private void VisualLocalizationWindow_Load(object? sender, EventArgs e) { sltDirection.SelectedIndex = 0; sltCameraName.Items.Clear(); if (MainWindow.Instance.Cameras?.Count > 0) { foreach(var cam in MainWindow.Instance.Cameras) { sltCameraName.Items.Add(cam.CameraName); } } else { AntdUI.Message.warn(this, $"未找到启用相机!", autoClose: 3); } } /// /// 窗体对象实例 /// private static VisualLocalizationWindow _instance; internal static VisualLocalizationWindow Instance { get { if (_instance == null || _instance.IsDisposed) _instance = new VisualLocalizationWindow(); return _instance; } } /// /// 相机回调 /// /// /// /// private void OnCameraHImageOutput(DateTime dt, CameraBase camera, MatSet imageSet) { this.BeginInvoke(new MethodInvoker(delegate () { imageViewerControl1.Image = imageSet._mat.ToBitmap(); if (isLocationing) { HObject obj = OpenCVHelper.MatToHImage(imageSet._mat); HImage hImage = HalconHelper.ConvertHObjectToHImage(obj); // 调用 ProcCall 的方法 ProcCall.SetInputIconicParamObject("INPUT_Image", hImage); // 将图像输入Proc ProcCall.SetInputIconicParamObject("BackGroundPic", backImage); ProcCall.SetInputCtrlParamTuple("DistThreshold", Convert.ToInt32(iptThreshold.Text)); ProcCall.Execute(); double nNUm = ProcCall.GetOutputCtrlParamTuple("OUTPUT_Flag"); if (nNUm == 0) { MainWindow.Instance.PLC.TurnStart(false); iptPosition.Text = MainWindow.Instance.PLC.ReadVisionPos().ToString(); isLocationing = false; btnLocalization.Text = "开始定位"; btnLocalization.Type = TTypeMini.Primary; } } })); } } }