DHDHSoftware/DHSoftware/Views/VisualLocalizationWindow.cs
2025-04-22 18:03:12 +08:00

739 lines
26 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<VisualLocalization> localizations = new List<VisualLocalization>();
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();
}
}
/// <summary>
/// 保存图像
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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);
}
}
}
/// <summary>
/// 定位
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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;
}
}
/// <summary>
/// 采集
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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;
}
}
/// <summary>
/// 触发
/// </summary>
/// <param name="state"></param>
private void CaptureLoop(object? state)
{
Do3ThinkCamera.Snapshot();
}
private CancellationTokenSource? _rotateCts;
private bool _isRotating;
/// <summary>
/// 统一旋转控制方法
/// </summary>
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;
}
}
/// <summary>
/// 反转抬起
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnReverse_MouseUp(object? sender, MouseEventArgs e)
{
_rotateCts?.Cancel();
}
/// <summary>
/// 反转按下
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void BtnReverse_MouseDown(object? sender, MouseEventArgs e)
{
await RotateControlAsync(false);
}
/// <summary>
/// 正转抬起
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnForward_MouseUp(object? sender, MouseEventArgs e)
{
_rotateCts?.Cancel();
}
/// <summary>
/// 正转按下
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void BtnForward_MouseDown(object? sender, MouseEventArgs e)
{
await RotateControlAsync(true);
}
/// <summary>
/// 选择背景图
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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;
}
}
}
/// <summary>
/// 选择算法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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;
}
}
}
/// <summary>
/// 加载事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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);
}
}
/// <summary>
/// 窗体对象实例
/// </summary>
private static VisualLocalizationWindow _instance;
internal static VisualLocalizationWindow Instance
{
get
{
if (_instance == null || _instance.IsDisposed)
_instance = new VisualLocalizationWindow();
return _instance;
}
}
/// <summary>
/// 相机回调
/// </summary>
/// <param name="dt"></param>
/// <param name="camera"></param>
/// <param name="imageSet"></param>
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;
}
}
}));
}
}
}