C3204
2026-01-15 a987bb05357a451c8476098067573059287dc008
添加运动控制菜单
已修改4个文件
已添加3个文件
766 ■■■■ 文件已修改
LB_SmartVision/Forms/Pages/CommunicatorPage/CommunicatorForm.Designer.cs 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
LB_SmartVision/Forms/Pages/CommunicatorPage/CommunicatorForm.cs 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
LB_VisionProcesses/Cameras/HRCameras/HRCamera.cs 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
LB_VisionProcesses/Cameras/LBCameras/LBCamera.cs 149 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
LB_VisionProcesses/MotionControl/BaseMotionControl.cs 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
LB_VisionProcesses/MotionControl/IMotionControl.cs 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
LB_VisionProcesses/MotionControl/MotionControlConfig.cs 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
LB_SmartVision/Forms/Pages/CommunicatorPage/CommunicatorForm.Designer.cs
@@ -36,10 +36,12 @@
            txtSendMsg = new TextBox();
            grpSetting = new GroupBox();
            txtAddress = new TextBox();
            cmbVarType = new ComboBox();
            cmbType = new ComboBox();
            txtIP = new TextBox();
            txtPort = new TextBox();
            lblPort = new Label();
            lbVarType = new Label();
            cmbIP = new ComboBox();
            lblType = new Label();
            lblAddress = new Label();
@@ -63,9 +65,10 @@
            label3.AutoSize = true;
            label3.Dock = DockStyle.Fill;
            label3.ForeColor = Color.CornflowerBlue;
            label3.Location = new Point(3, 100);
            label3.Location = new Point(4, 125);
            label3.Margin = new Padding(4, 0, 4, 0);
            label3.Name = "label3";
            label3.Size = new Size(655, 20);
            label3.Size = new Size(819, 25);
            label3.TabIndex = 18;
            label3.Text = "发送信息";
            label3.TextAlign = ContentAlignment.BottomLeft;
@@ -81,14 +84,15 @@
            tableLayoutPanel1.Controls.Add(label3, 0, 1);
            tableLayoutPanel1.Dock = DockStyle.Fill;
            tableLayoutPanel1.Location = new Point(0, 0);
            tableLayoutPanel1.Margin = new Padding(4);
            tableLayoutPanel1.Name = "tableLayoutPanel1";
            tableLayoutPanel1.RowCount = 5;
            tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 100F));
            tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 20F));
            tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 30F));
            tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 40F));
            tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 125F));
            tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 25F));
            tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 38F));
            tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 50F));
            tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
            tableLayoutPanel1.Size = new Size(661, 322);
            tableLayoutPanel1.Size = new Size(827, 402);
            tableLayoutPanel1.TabIndex = 26;
            // 
            // grpReceive
@@ -96,9 +100,11 @@
            grpReceive.Controls.Add(txtReceiveMsg);
            grpReceive.Dock = DockStyle.Fill;
            grpReceive.ForeColor = Color.CornflowerBlue;
            grpReceive.Location = new Point(3, 193);
            grpReceive.Location = new Point(4, 242);
            grpReceive.Margin = new Padding(4);
            grpReceive.Name = "grpReceive";
            grpReceive.Size = new Size(655, 126);
            grpReceive.Padding = new Padding(4);
            grpReceive.Size = new Size(819, 156);
            grpReceive.TabIndex = 30;
            grpReceive.TabStop = false;
            grpReceive.Text = "通讯记录";
@@ -106,142 +112,180 @@
            // txtReceiveMsg
            // 
            txtReceiveMsg.Dock = DockStyle.Fill;
            txtReceiveMsg.Location = new Point(3, 17);
            txtReceiveMsg.Location = new Point(4, 21);
            txtReceiveMsg.Margin = new Padding(4);
            txtReceiveMsg.Multiline = true;
            txtReceiveMsg.Name = "txtReceiveMsg";
            txtReceiveMsg.ReadOnly = true;
            txtReceiveMsg.ScrollBars = ScrollBars.Both;
            txtReceiveMsg.Size = new Size(649, 106);
            txtReceiveMsg.Size = new Size(811, 131);
            txtReceiveMsg.TabIndex = 0;
            // 
            // txtSendMsg
            // 
            txtSendMsg.Dock = DockStyle.Fill;
            txtSendMsg.Location = new Point(3, 123);
            txtSendMsg.Location = new Point(4, 154);
            txtSendMsg.Margin = new Padding(4);
            txtSendMsg.Name = "txtSendMsg";
            txtSendMsg.Size = new Size(655, 21);
            txtSendMsg.Size = new Size(819, 24);
            txtSendMsg.TabIndex = 29;
            // 
            // grpSetting
            // 
            grpSetting.BackColor = Color.FromArgb(32, 41, 50);
            grpSetting.Controls.Add(txtAddress);
            grpSetting.Controls.Add(cmbVarType);
            grpSetting.Controls.Add(cmbType);
            grpSetting.Controls.Add(txtIP);
            grpSetting.Controls.Add(txtPort);
            grpSetting.Controls.Add(lblPort);
            grpSetting.Controls.Add(lbVarType);
            grpSetting.Controls.Add(cmbIP);
            grpSetting.Controls.Add(lblType);
            grpSetting.Controls.Add(lblAddress);
            grpSetting.Controls.Add(lblIP);
            grpSetting.Dock = DockStyle.Fill;
            grpSetting.ForeColor = Color.CornflowerBlue;
            grpSetting.Location = new Point(3, 3);
            grpSetting.MinimumSize = new Size(261, 61);
            grpSetting.Location = new Point(4, 4);
            grpSetting.Margin = new Padding(4);
            grpSetting.MinimumSize = new Size(326, 76);
            grpSetting.Name = "grpSetting";
            grpSetting.Size = new Size(655, 94);
            grpSetting.Padding = new Padding(4);
            grpSetting.Size = new Size(819, 117);
            grpSetting.TabIndex = 28;
            grpSetting.TabStop = false;
            grpSetting.Text = "通讯配置";
            // 
            // txtAddress
            // 
            txtAddress.Location = new Point(251, 60);
            txtAddress.Location = new Point(314, 75);
            txtAddress.Margin = new Padding(4);
            txtAddress.Name = "txtAddress";
            txtAddress.Size = new Size(100, 21);
            txtAddress.Size = new Size(124, 24);
            txtAddress.TabIndex = 9;
            txtAddress.TextChanged += txtAddress_TextChanged;
            //
            // cmbVarType
            //
            cmbVarType.DropDownStyle = ComboBoxStyle.DropDownList;
            cmbVarType.FormattingEnabled = true;
            cmbVarType.Location = new Point(533, 32);
            cmbVarType.Margin = new Padding(4);
            cmbVarType.Name = "cmbVarType";
            cmbVarType.Size = new Size(124, 26);
            cmbVarType.TabIndex = 8;
            cmbVarType.SelectedIndexChanged += cmbVarType_SelectedIndexChanged;
            // 
            // cmbType
            // 
            cmbType.DropDownStyle = ComboBoxStyle.DropDownList;
            cmbType.FormattingEnabled = true;
            cmbType.Location = new Point(251, 23);
            cmbType.Location = new Point(314, 29);
            cmbType.Margin = new Padding(4);
            cmbType.Name = "cmbType";
            cmbType.Size = new Size(100, 23);
            cmbType.Size = new Size(124, 26);
            cmbType.TabIndex = 8;
            // 
            // txtIP
            // 
            txtIP.Location = new Point(79, 22);
            txtIP.Location = new Point(99, 28);
            txtIP.Margin = new Padding(4);
            txtIP.Name = "txtIP";
            txtIP.Size = new Size(100, 21);
            txtIP.Size = new Size(124, 24);
            txtIP.TabIndex = 7;
            // 
            // txtPort
            // 
            txtPort.Location = new Point(79, 60);
            txtPort.Location = new Point(99, 75);
            txtPort.Margin = new Padding(4);
            txtPort.Name = "txtPort";
            txtPort.Size = new Size(100, 21);
            txtPort.Size = new Size(124, 24);
            txtPort.TabIndex = 6;
            // 
            // lblPort
            // 
            lblPort.AutoSize = true;
            lblPort.Location = new Point(13, 63);
            lblPort.Location = new Point(16, 79);
            lblPort.Margin = new Padding(4, 0, 4, 0);
            lblPort.Name = "lblPort";
            lblPort.Size = new Size(31, 15);
            lblPort.Size = new Size(38, 18);
            lblPort.TabIndex = 4;
            lblPort.Text = "端口";
            //
            // lbVarType
            //
            lbVarType.AutoSize = true;
            lbVarType.Location = new Point(463, 35);
            lbVarType.Margin = new Padding(4, 0, 4, 0);
            lbVarType.Name = "lbVarType";
            lbVarType.Size = new Size(68, 18);
            lbVarType.TabIndex = 0;
            lbVarType.Text = "变量类型";
            // 
            // cmbIP
            // 
            cmbIP.FormattingEnabled = true;
            cmbIP.Location = new Point(79, 21);
            cmbIP.Location = new Point(99, 26);
            cmbIP.Margin = new Padding(4);
            cmbIP.Name = "cmbIP";
            cmbIP.Size = new Size(100, 23);
            cmbIP.Size = new Size(124, 26);
            cmbIP.TabIndex = 1;
            // 
            // lblType
            // 
            lblType.AutoSize = true;
            lblType.Location = new Point(205, 26);
            lblType.Location = new Point(246, 32);
            lblType.Margin = new Padding(4, 0, 4, 0);
            lblType.Name = "lblType";
            lblType.Size = new Size(31, 15);
            lblType.Size = new Size(38, 18);
            lblType.TabIndex = 0;
            lblType.Text = "型号";
            // 
            // lblAddress
            // 
            lblAddress.AutoSize = true;
            lblAddress.Location = new Point(205, 63);
            lblAddress.Location = new Point(242, 79);
            lblAddress.Margin = new Padding(4, 0, 4, 0);
            lblAddress.Name = "lblAddress";
            lblAddress.Size = new Size(31, 15);
            lblAddress.Size = new Size(68, 18);
            lblAddress.TabIndex = 0;
            lblAddress.Text = "地址";
            lblAddress.Text = "变量地址";
            // 
            // lblIP
            // 
            lblIP.AutoSize = true;
            lblIP.Location = new Point(13, 26);
            lblIP.Location = new Point(16, 32);
            lblIP.Margin = new Padding(4, 0, 4, 0);
            lblIP.Name = "lblIP";
            lblIP.Size = new Size(31, 15);
            lblIP.Size = new Size(38, 18);
            lblIP.TabIndex = 0;
            lblIP.Text = "地址";
            // 
            // tableLayoutPanel2
            // 
            tableLayoutPanel2.ColumnCount = 3;
            tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 80F));
            tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 80F));
            tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 100F));
            tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 100F));
            tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
            tableLayoutPanel2.Controls.Add(btnRuleSend, 1, 0);
            tableLayoutPanel2.Controls.Add(btnSend, 0, 0);
            tableLayoutPanel2.Dock = DockStyle.Fill;
            tableLayoutPanel2.Location = new Point(3, 153);
            tableLayoutPanel2.Location = new Point(4, 192);
            tableLayoutPanel2.Margin = new Padding(4);
            tableLayoutPanel2.Name = "tableLayoutPanel2";
            tableLayoutPanel2.RowCount = 1;
            tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
            tableLayoutPanel2.Size = new Size(655, 34);
            tableLayoutPanel2.Size = new Size(819, 42);
            tableLayoutPanel2.TabIndex = 27;
            // 
            // btnRuleSend
            // 
            btnRuleSend.Dock = DockStyle.Fill;
            btnRuleSend.ForeColor = Color.CornflowerBlue;
            btnRuleSend.Location = new Point(83, 3);
            btnRuleSend.Location = new Point(104, 4);
            btnRuleSend.Margin = new Padding(4);
            btnRuleSend.Name = "btnRuleSend";
            btnRuleSend.Size = new Size(74, 28);
            btnRuleSend.Size = new Size(92, 34);
            btnRuleSend.TabIndex = 14;
            btnRuleSend.Text = "校验发送";
            btnRuleSend.UseVisualStyleBackColor = true;
@@ -251,9 +295,10 @@
            // 
            btnSend.Dock = DockStyle.Fill;
            btnSend.ForeColor = Color.CornflowerBlue;
            btnSend.Location = new Point(3, 3);
            btnSend.Location = new Point(4, 4);
            btnSend.Margin = new Padding(4);
            btnSend.Name = "btnSend";
            btnSend.Size = new Size(74, 28);
            btnSend.Size = new Size(92, 34);
            btnSend.TabIndex = 12;
            btnSend.Text = "发送";
            btnSend.UseVisualStyleBackColor = true;
@@ -268,11 +313,12 @@
            themeForm_Communicator.Font = new Font("Microsoft Sans Serif", 9F);
            themeForm_Communicator.Image = (Image)resources.GetObject("themeForm_Communicator.Image");
            themeForm_Communicator.Location = new Point(0, 0);
            themeForm_Communicator.Margin = new Padding(4);
            themeForm_Communicator.Name = "themeForm_Communicator";
            themeForm_Communicator.Padding = new Padding(10, 70, 10, 9);
            themeForm_Communicator.Padding = new Padding(12, 88, 12, 11);
            themeForm_Communicator.RoundCorners = true;
            themeForm_Communicator.Sizable = true;
            themeForm_Communicator.Size = new Size(681, 401);
            themeForm_Communicator.Size = new Size(851, 501);
            themeForm_Communicator.SmartBounds = true;
            themeForm_Communicator.StartPosition = FormStartPosition.WindowsDefaultLocation;
            themeForm_Communicator.TabIndex = 27;
@@ -288,7 +334,8 @@
            controlBox1.EnableMaximizeButton = true;
            controlBox1.EnableMinimizeButton = true;
            controlBox1.ForeColor = Color.FromArgb(155, 155, 155);
            controlBox1.Location = new Point(581, 18);
            controlBox1.Location = new Point(726, 18);
            controlBox1.Margin = new Padding(4);
            controlBox1.MaximizeHoverColor = Color.FromArgb(74, 74, 74);
            controlBox1.MinimizeHoverColor = Color.FromArgb(63, 63, 65);
            controlBox1.Name = "controlBox1";
@@ -300,19 +347,21 @@
            // 
            panel_CommunicatorForm.Controls.Add(tableLayoutPanel1);
            panel_CommunicatorForm.Dock = DockStyle.Fill;
            panel_CommunicatorForm.Location = new Point(10, 70);
            panel_CommunicatorForm.Location = new Point(12, 88);
            panel_CommunicatorForm.Margin = new Padding(4);
            panel_CommunicatorForm.Name = "panel_CommunicatorForm";
            panel_CommunicatorForm.Size = new Size(661, 322);
            panel_CommunicatorForm.Size = new Size(827, 402);
            panel_CommunicatorForm.TabIndex = 27;
            // 
            // CommunicatorForm
            // 
            AutoScaleDimensions = new SizeF(96F, 96F);
            AutoScaleDimensions = new SizeF(120F, 120F);
            AutoScaleMode = AutoScaleMode.Dpi;
            ClientSize = new Size(681, 401);
            ClientSize = new Size(851, 501);
            Controls.Add(themeForm_Communicator);
            FormBorderStyle = FormBorderStyle.None;
            MinimumSize = new Size(261, 61);
            Margin = new Padding(4);
            MinimumSize = new Size(326, 76);
            Name = "CommunicatorForm";
            Text = "通讯设置";
            TransparencyKey = Color.Fuchsia;
@@ -351,5 +400,7 @@
        private ComboBox cmbType;
        private Label lblAddress;
        private TextBox txtAddress;
        private ComboBox cmbVarType;
        private Label lbVarType;
    }
}
LB_SmartVision/Forms/Pages/CommunicatorPage/CommunicatorForm.cs
@@ -28,10 +28,14 @@
        {
            InitializeComponent();
            cmbType.Items.Clear();
            if (communicator != null && !string.IsNullOrEmpty(communicator.CommunicatorConnections["型号"]?.ToString()))
            {
                cmbType.Items.Add(communicator.CommunicatorConnections["型号"]?.ToString());
            }
            cmbVarType.Items.Clear();
            if (!string.IsNullOrEmpty(communicator.CommunicatorConnections["变量类型"]?.ToString()))
            {
                cmbVarType.Items.Add(communicator.CommunicatorConnections["变量类型"]?.ToString());
            }
        }
@@ -39,42 +43,37 @@
        {
            InitializeComponent();
            if (communicator == null)
            {
                return;
            }
            cmbIP.Enabled = false;
            txtIP.Enabled = false;
            txtPort.Enabled = false;
            lblType.Visible = false;
            lblAddress.Visible = false;
            cmbType.Visible = false;
            txtAddress.Visible = false;
            lbVarType.Visible = false;
            cmbVarType.Visible = false;
            this.Text = name;
            if (communicator is UARTPort)
            {
                //统计可用端口
                SerialPortStream temp = new SerialPortStream();
                string[] ArryPort = temp.GetPortNames();
                for (int i = 0; i < ArryPort.Length; i++)
                {
                    cmbIP.Items.Add(ArryPort[i]);
                }
                cmbIP.Text = communicator.CommunicatorConnections["地址"]?.ToString();
                txtPort.Text = communicator.CommunicatorConnections["端口"]?.ToString();
                txtIP.Visible = false;
                cmbIP.Visible = true;
                this.btnRuleSend.Visible = true;
                lblType.Visible = false;
                lblAddress.Visible = false;
                cmbType.Visible = false;
                txtAddress.Visible = false;
                lblIP.Text = "COM口";
                lblPort.Text = "波特率";
            }
@@ -84,15 +83,11 @@
                txtPort.SelectedText = communicator.CommunicatorConnections["端口"]?.ToString();
                txtIP.Visible = true;
                cmbIP.Visible = false;
                this.btnRuleSend.Visible = false;
                lblType.Visible = false;
                lblAddress.Visible = false;
                cmbType.Visible = false;
                txtAddress.Visible = false;
                lblIP.Text = "地址";
                lblPort.Text = "端口";
            }
@@ -101,7 +96,6 @@
                txtIP.Text = communicator.CommunicatorConnections["地址"]?.ToString();
                txtPort.Text = communicator.CommunicatorConnections["端口"]?.ToString();
                cmbType.Items.Clear();
                if (!string.IsNullOrEmpty(communicator.CommunicatorConnections["型号"]?.ToString()))
                {
                    cmbType.Items.Add(communicator.CommunicatorConnections["型号"]?.ToString());
@@ -111,15 +105,20 @@
                {
                    txtAddress.Text = communicator.CommunicatorConnections["变量地址"]?.ToString();
                }
                if (!string.IsNullOrEmpty(communicator.CommunicatorConnections["变量类型"]?.ToString()))
                {
                    cmbVarType.Items.Add(communicator.CommunicatorConnections["变量类型"]?.ToString());
                    cmbVarType.Text = communicator.CommunicatorConnections["变量类型"]?.ToString();
                }
                lbVarType.Visible = true;
                cmbVarType.Visible = true;
                txtIP.Visible = true;
                cmbIP.Visible = false;
                this.btnRuleSend.Visible = false;
                lblType.Visible = true;
                lblAddress.Visible = true;
                cmbType.Visible = true;
                txtAddress.Visible = true;
                lblIP.Text = "IP";
                lblPort.Text = "æ§½";
            }
@@ -267,5 +266,18 @@
                communicatorChanged(communicator);
            }
        }
        private void cmbVarType_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (communicator != null && !communicator.CommunicatorConnections.Contains("变量类型"))
            {
                communicator.CommunicatorConnections.Add("变量类型", cmbVarType.Text);
            }
            else if (communicator != null)
            {
                communicator.CommunicatorConnections["变量类型"] = cmbVarType.Text;
                communicatorChanged(communicator);
            }
        }
    }
}
LB_VisionProcesses/Cameras/HRCameras/HRCamera.cs
@@ -1,5 +1,6 @@
using HalconDotNet;
using LB_SmartVisionCommon;
using LB_VisionProcesses.Cameras.LBCameras;
using MVSDK_Net;
using System;
using System.Collections.Generic;
@@ -28,6 +29,8 @@
        private Thread _callbackThread; // å›žè°ƒå¤„理线程
        private List<IMVDefine.IMV_Frame> _frameList; // å›¾åƒç¼“存列表
        private readonly object _frameLock = new object(); // å¸§ç¼“存锁
        // æ–°å¢žï¼šCollectedImages操作锁,保证线程安全
        private readonly object _collectedImagesLock = new object();
        private IMVDefine.IMV_EPixelType type = IMVDefine.IMV_EPixelType.gvspPixelMono8;
        private IMVDefine.IMV_PixelConvertParam stPixelConvertParam = new IMVDefine.IMV_PixelConvertParam();
@@ -217,30 +220,135 @@
            }
            try
            {
                // 1. å°†å¸§è½¬æ¢ä¸ºBitmap
                Bitmap bitmap = ConvertFrameToBitmap(frame);
                // é‡Šæ”¾å¸§æ•°æ®
                // release frame
                // é‡Šæ”¾åŽŸå§‹å¸§æ•°æ®ï¼ˆSDK层面释放)
                _camera.IMV_ReleaseFrame(ref frame);
                Task.Factory.StartNew(() =>
                // 2. ç©ºå€¼æ ¡éªŒï¼šè½¬æ¢å¤±è´¥åˆ™ç›´æŽ¥è¿”回
                if (bitmap == null)
                {
                    CallBackImg = (Bitmap)bitmap.Clone();
                    if (CallBackImg == null)
                    AsyncLogHelper.Warn(SN + "帧转换为Bitmap失败,跳过处理");
                    return;
                }
                // 3. çº¿ç¨‹å®‰å…¨åœ°å°†Bitmap添加到CollectedImages字典
                lock (_collectedImagesLock)
                {
                    // ç¡®ä¿å½“前相机SN对应的列表存在
                    if (!CollectedImages.ContainsKey(SN))
                    {
                        return;
                        CollectedImages[SN] = new List<Bitmap>();
                    }
                    if (GetTriggerMode(out TriggerMode mode, out TriggerSource source))
                    {
                        if (mode == TriggerMode.On && source != TriggerSource.Software)
                            TriggerRunMessageReceived?.Invoke(SN, source.ToString());  // è§¦å‘运行事件
                    }
                    bitmap.Dispose();
                });
                    CollectedImages[SN].Add(bitmap);
                    AsyncLogHelper.Info(SN + $"图像已加入缓存,当前缓存数量:{CollectedImages[SN].Count}");
                }
                // 4. å¤„理CollectedImages中的图像:遍历消费列表第一个元素直到为空
                ProcessCollectedImages();
                //Task.Factory.StartNew(() =>
                //{
                //    CallBackImg = (Bitmap)bitmap.Clone();
                //    if (CallBackImg == null)
                //    {
                //        return;
                //    }
                //    if (GetTriggerMode(out TriggerMode mode, out TriggerSource source))
                //    {
                //        if (mode == TriggerMode.On && source != TriggerSource.Software)
                //            TriggerRunMessageReceived?.Invoke(SN, source.ToString());  // è§¦å‘运行事件
                //    }
                //    bitmap.Dispose();
                //});
            }
            catch { }
            AsyncLogHelper.Info(SN + "Get frame blockId = {0}" + frame.frameInfo.blockId);
        }
        /// <summary>
        /// å¤„理CollectedImages中的缓存图像
        /// æ ¸å¿ƒé€»è¾‘:遍历取第一个图像 -> èµ‹å€¼ç»™CallBackImg -> è§¦å‘事件 -> é‡Šæ”¾å¹¶ç§»é™¤
        /// </summary>
        private void ProcessCollectedImages()
        {
            Task.Factory.StartNew(() =>
            {
                // åŠ é”ä¿è¯çº¿ç¨‹å®‰å…¨ï¼Œé˜²æ­¢å¤šçº¿ç¨‹åŒæ—¶æ“ä½œåˆ—è¡¨
                lock (_collectedImagesLock)
                {
                    // æ ¡éªŒå½“前相机的图像列表是否存在且有数据
                    if (!CollectedImages.ContainsKey(SN) || CollectedImages[SN].Count == 0)
                    {
                        AsyncLogHelper.Info(SN + "当前无缓存图像,跳过处理");
                        return;
                    }
                    // å¾ªçŽ¯å¤„ç†ï¼šç›´åˆ°åˆ—è¡¨ä¸ºç©º
                    while (CollectedImages[SN].Count > 0)
                    {
                        try
                        {
                            // 1 å–列表第一个索引的图像赋值给CallBackImg
                            Bitmap firstBitmap = CollectedImages[SN][0];
                            ImageGrabbed?.Invoke(this, new LBCameraEventArgs(SN, firstBitmap, true));
                            CallBackImg = (Bitmap)firstBitmap.Clone(); // å…‹éš†é¿å…åŽŸå¯¹è±¡è¢«é‡Šæ”¾åŽå¼•ç”¨å¤±æ•ˆ
                            // 2 èŽ·å–è§¦å‘æ¨¡å¼å¹¶åˆ¤æ–­æ˜¯å¦è§¦å‘è¿è¡Œäº‹ä»¶
                            if (GetTriggerMode(out TriggerMode mode, out TriggerSource source))
                            {
                                // ç¡¬è§¦å‘模式下触发运行事件
                                if (mode == TriggerMode.On && source != TriggerSource.Software)
                                {
                                    AsyncLogHelper.Info(SN + $"触发硬触发事件,触发源:{source}");
                                    TriggerRunMessageReceived?.Invoke(SN, source.ToString());
                                }
                            }
                            else
                            {
                                AsyncLogHelper.Warn(SN + "获取触发模式失败,跳过事件触发");
                            }
                            // 3 é‡Šæ”¾ç¬¬ä¸€ä¸ªå›¾åƒèµ„源并从列表移除
                            // å…ˆé‡Šæ”¾Bitmap内存,再移除列表元素
                            firstBitmap.Dispose();
                            CollectedImages[SN].RemoveAt(0);
                            AsyncLogHelper.Info(SN + $"已消费缓存图像,剩余缓存数量:{CollectedImages[SN].Count}");
                        }
                        catch (Exception ex)
                        {
                            AsyncLogHelper.Error(SN + $"处理缓存图像异常:{ex.Message}", ex);
                            // å•个图像处理失败时,移除该图像避免阻塞后续处理
                            if (CollectedImages[SN].Count > 0)
                            {
                                try
                                {
                                    CollectedImages[SN][0]?.Dispose(); // å°è¯•释放
                                    CollectedImages[SN].RemoveAt(0);
                                }
                                catch (Exception innerEx)
                                {
                                    AsyncLogHelper.Error(SN + $"清理异常图像失败:{innerEx.Message}", innerEx);
                                }
                            }
                            // å•个图像处理失败不终止循环,继续处理下一个
                            // 4. æ‰€æœ‰å›¾åƒå¤„理完成后,清空CallBackImg
                            if (CallBackImg != null)
                            {
                                CallBackImg.Dispose();
                                CallBackImg = null;
                            }
                            continue;
                        }
                        // 4. æ‰€æœ‰å›¾åƒå¤„理完成后,清空CallBackImg
                        if (CallBackImg != null)
                        {
                            CallBackImg.Dispose();
                            CallBackImg = null;
                        }
                    }
                }
            });
        }
        /// <summary>
        /// å›¾åƒæ˜¯å¦ä¸ºMono格式
        /// </summary>
        /// <param name="enType"></param>
LB_VisionProcesses/Cameras/LBCameras/LBCamera.cs
@@ -51,6 +51,8 @@
        // ä¸´æ—¶è¡Œç¼“冲,用于接收回调数据
        private byte[] _tempLineBuffer = null;
        private bool _isContinuous = false;
        // æ–°å¢žï¼šCollectedImages操作锁,保证线程安全
        private readonly object _collectedImagesLock = new object();
        public LBCamera()
        {
@@ -568,37 +570,140 @@
                _frameCount++;
                AsyncLogHelper.Info($"LBCamera[{SN}]: Frame {_frameCount} generated ({width}x{height})");
                // å¼‚步触发事件,避免阻塞SDK回调线程
                Task.Factory.StartNew(() =>
                //空值校验:转换失败则直接返回
                if (bmp == null)
                {
                    try
                    AsyncLogHelper.Warn(SN + "帧转换为Bitmap失败,跳过处理");
                    return;
                }
                // çº¿ç¨‹å®‰å…¨åœ°å°†Bitmap添加到CollectedImages字典
                lock (_collectedImagesLock)
                {
                    // ç¡®ä¿å½“前相机SN对应的列表存在
                    if (!CollectedImages.ContainsKey(SN))
                    {
                        ImageGrabbed?.Invoke(this, new LBCameraEventArgs(SN, bmp, true));
                        CallBackImg = (Bitmap)bmp.Clone();
                        if (CallBackImg == null)
                        {
                            return;
                        }
                        if (GetTriggerMode(out TriggerMode mode, out TriggerSource source))
                        {
                            if (mode == TriggerMode.On && source != TriggerSource.Software)
                                TriggerRunMessageReceived?.Invoke(SN, source.ToString());  // è§¦å‘运行事件
                        }
                        bmp.Dispose();
                        CollectedImages[SN] = new List<Bitmap>();
                    }
                    catch (Exception ex)
                    {
                        AsyncLogHelper.Error($"LBCamera: Event Invoke error - {ex.Message}");
                        bmp.Dispose(); // å¼‚常时释放资源
                    }
                });
                    CollectedImages[SN].Add(bmp);
                    AsyncLogHelper.Info(SN + $"图像已加入缓存,当前缓存数量:{CollectedImages[SN].Count}");
                }
                // å¤„理CollectedImages中的图像:遍历消费列表第一个元素直到为空
                ProcessCollectedImages();
                //// å¼‚步触发事件,避免阻塞SDK回调线程
                //Task.Factory.StartNew(() =>
                //{
                //    try
                //    {
                //        ImageGrabbed?.Invoke(this, new LBCameraEventArgs(SN, bmp, true));
                //        CallBackImg = (Bitmap)bmp.Clone();
                //        if (CallBackImg == null)
                //        {
                //            return;
                //        }
                //        if (GetTriggerMode(out TriggerMode mode, out TriggerSource source))
                //        {
                //            if (mode == TriggerMode.On && source != TriggerSource.Software)
                //                TriggerRunMessageReceived?.Invoke(SN, source.ToString());  // è§¦å‘运行事件
                //        }
                //        bmp.Dispose();
                //    }
                //    catch (Exception ex)
                //    {
                //        AsyncLogHelper.Error($"LBCamera: Event Invoke error - {ex.Message}");
                //        bmp.Dispose(); // å¼‚常时释放资源
                //    }
                //});
            }
            catch (Exception ex)
            {
                AsyncLogHelper.Error($"LBCamera: CreateBitmap error - {ex.Message}");
            }
        }
        /// <summary>
        /// å¤„理CollectedImages中的缓存图像
        /// æ ¸å¿ƒé€»è¾‘:遍历取第一个图像 -> èµ‹å€¼ç»™CallBackImg -> è§¦å‘事件 -> é‡Šæ”¾å¹¶ç§»é™¤
        /// </summary>
        private void ProcessCollectedImages()
        {
            Task.Factory.StartNew(() =>
            {
                // åŠ é”ä¿è¯çº¿ç¨‹å®‰å…¨ï¼Œé˜²æ­¢å¤šçº¿ç¨‹åŒæ—¶æ“ä½œåˆ—è¡¨
                lock (_collectedImagesLock)
                {
                    // æ ¡éªŒå½“前相机的图像列表是否存在且有数据
                    if (!CollectedImages.ContainsKey(SN) || CollectedImages[SN].Count == 0)
                    {
                        AsyncLogHelper.Info(SN + "当前无缓存图像,跳过处理");
                        return;
                    }
                    // å¾ªçŽ¯å¤„ç†ï¼šç›´åˆ°åˆ—è¡¨ä¸ºç©º
                    while (CollectedImages[SN].Count > 0)
                    {
                        try
                        {
                            // 1 å–列表第一个索引的图像赋值给CallBackImg
                            Bitmap firstBitmap = CollectedImages[SN][0];
                            ImageGrabbed?.Invoke(this, new LBCameraEventArgs(SN, firstBitmap, true));
                            CallBackImg = (Bitmap)firstBitmap.Clone(); // å…‹éš†é¿å…åŽŸå¯¹è±¡è¢«é‡Šæ”¾åŽå¼•ç”¨å¤±æ•ˆ
                            // 2 èŽ·å–è§¦å‘æ¨¡å¼å¹¶åˆ¤æ–­æ˜¯å¦è§¦å‘è¿è¡Œäº‹ä»¶
                            if (GetTriggerMode(out TriggerMode mode, out TriggerSource source))
                            {
                                // ç¡¬è§¦å‘模式下触发运行事件
                                if (mode == TriggerMode.On && source != TriggerSource.Software)
                                {
                                    AsyncLogHelper.Info(SN + $"触发硬触发事件,触发源:{source}");
                                    TriggerRunMessageReceived?.Invoke(SN, source.ToString());
                                }
                            }
                            else
                            {
                                AsyncLogHelper.Warn(SN + "获取触发模式失败,跳过事件触发");
                            }
                            // 3 é‡Šæ”¾ç¬¬ä¸€ä¸ªå›¾åƒèµ„源并从列表移除
                            // å…ˆé‡Šæ”¾Bitmap内存,再移除列表元素
                            firstBitmap.Dispose();
                            CollectedImages[SN].RemoveAt(0);
                            AsyncLogHelper.Info(SN + $"已消费缓存图像,剩余缓存数量:{CollectedImages[SN].Count}");
                        }
                        catch (Exception ex)
                        {
                            AsyncLogHelper.Error(SN + $"处理缓存图像异常:{ex.Message}", ex);
                            // å•个图像处理失败时,移除该图像避免阻塞后续处理
                            if (CollectedImages[SN].Count > 0)
                            {
                                try
                                {
                                    CollectedImages[SN][0]?.Dispose(); // å°è¯•释放
                                    CollectedImages[SN].RemoveAt(0);
                                }
                                catch (Exception innerEx)
                                {
                                    AsyncLogHelper.Error(SN + $"清理异常图像失败:{innerEx.Message}", innerEx);
                                }
                            }
                            // å•个图像处理失败不终止循环,继续处理下一个
                            // 4. æ‰€æœ‰å›¾åƒå¤„理完成后,清空CallBackImg
                            if (CallBackImg != null)
                            {
                                CallBackImg.Dispose();
                                CallBackImg = null;
                            }
                            continue;
                        }
                        // 4. æ‰€æœ‰å›¾åƒå¤„理完成后,清空CallBackImg
                        if (CallBackImg != null)
                        {
                            CallBackImg.Dispose();
                            CallBackImg = null;
                        }
                    }
                }
            });
        }
        private void SyncConfigFromCamera()
        {
LB_VisionProcesses/MotionControl/BaseMotionControl.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LB_VisionProcesses.MotionControl
{
    public class BaseMotionControl : IMotionControl
    {
        /// <summary>
        /// è¿åŠ¨æŽ§åˆ¶åç§°
        /// </summary>
        public string MotionControlName { get; set; } = string.Empty;
        /// <summary>
        /// æ˜¯å¦è¿žæŽ¥
        /// </summary>
        public bool bConnected = true;
        public bool Connect()
        {
            throw new NotImplementedException();
        }
        public bool Disconnect()
        {
            throw new NotImplementedException();
        }
        public void Dispose()
        {
            throw new NotImplementedException();
        }
        public string ReceiveMsg()
        {
            throw new NotImplementedException();
        }
        public void SendHeartbeat()
        {
            throw new NotImplementedException();
        }
        public bool SendMessage(string message)
        {
            throw new NotImplementedException();
        }
    }
}
LB_VisionProcesses/MotionControl/IMotionControl.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LB_VisionProcesses.MotionControl
{
    public interface IMotionControl : IDisposable
    {
        /// <summary>
        /// è¿žæŽ¥é€šè®¯
        /// </summary>
        /// <returns></returns>
        bool Connect();
        /// <summary>
        /// æ–­å¼€è¿žæŽ¥
        /// </summary>
        /// <returns></returns>
        bool Disconnect();
        /// <summary>
        /// æŽ¥æ”¶æ¶ˆæ¯
        /// </summary>
        /// <returns></returns>
        string ReceiveMsg();
        /// <summary>
        /// å‘送消息
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        bool SendMessage(string message);
        /// <summary>
        /// å‘送心跳信号
        /// </summary>
        void SendHeartbeat();
    }
}
LB_VisionProcesses/MotionControl/MotionControlConfig.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,189 @@
using LB_VisionProcesses.Communicators;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LB_VisionProcesses.MotionControl
{
    [Serializable]
    [Process("运动控制模块", Category = "运动控制工具", Description = "创建运动控制工具")]
    public class MotionControlConfig : IProcess
    {
        /// <summary>
        /// è¿åŠ¨æŽ§åˆ¶æ¨¡å—é›†åˆ(Key:通讯名,Value:通讯句柄)
        /// </summary>
        public ConcurrentDictionary<string, BaseMotionControl> dicMotionControls { get; set; }
        /// <summary>
        /// é€šè®¯é›†åˆ(Key:通讯名,Value:通讯句柄)
        /// </summary>
        public ConcurrentDictionary<string, BaseCommunicator> dicCommunicators { get; set; }
        public MotionControlConfig(ConcurrentDictionary<string, BaseMotionControl> dicMotionControl)
        {
            this.dicMotionControls = dicMotionControl;
            strProcessClass = "LB_VisionProcesses.Communicators.MotionControlConfig";
            Params.Inputs.Add("运动控制名称", "");
            //Params.Inputs.Add("Xè½´PLC地址", "");
            //Params.Inputs.Add("X轴检测位置", "");
            //Params.Inputs.Add("Yè½´PLC地址", "");
            //Params.Inputs.Add("Zè½´PLC地址", "");
            //Params.Inputs.Add("Wè½´PLC地址", "");
            Params.Outputs.Add("收到的信息", "");
        }
        public override object Clone()
        {
            return MemberwiseClone();
        }
        public override void Dispose()
        {
            return;
        }
        public override void InitRunParams()
        {
            Result = true;
            Msg = "";
            if (Record != null)
            {
                Record.Dispose();
            }
        }
        /// <summary>
        /// åŠ è½½ç®—æ³•
        /// </summary>
        /// <param name="fullPath">完整路径带.json</param>
        /// <returns></returns>
        public override bool Load(string fullPath)
        {
            try
            {
                if (string.IsNullOrEmpty(fullPath))
                    return false;
                if (!fullPath.Contains(".json"))
                {
                    Debug.WriteLine("文件路径不完整");
                    return false;
                }
                if (string.IsNullOrEmpty(fullPath) || fullPath.Trim() == "")
                {
                    Debug.WriteLine("文件路径不完整");
                    return false;
                }
                // èŽ·å–ä¸å¸¦æ–‡ä»¶åçš„ç›®å½•è·¯å¾„
                string directoryPath = Path.GetDirectoryName(fullPath);
                strProcessName = Path.GetFileNameWithoutExtension(fullPath);
                if (!File.Exists(fullPath))
                {
                    Debug.WriteLine("文件不存在创建空文件");
                    Save(directoryPath);
                    return true;
                }
                string strJson = string.Empty;
                using (StreamReader streamReader = new StreamReader(fullPath, Encoding.UTF8))
                {
                    strJson = streamReader.ReadToEnd();
                    streamReader.Close();
                }
                Params = JsonConvert.DeserializeObject<ProcessParams>(strJson);
                if (Params == null)
                    return false;
                Params.FixDeserializedData();
                return true;
            }
            catch { return false; }
        }
        public override bool Run()
        {
            InitRunParams();
            Params.Outputs["收到消息"] = "";
            string MotionControlName = Params.Inputs["运动控制名称"].ToString();
            if (!dicMotionControls.ContainsKey(MotionControlName))
            {
                Msg = $"运动控制[{MotionControlName}]不存在";
                Result = false;
                return Result;
            }
            BaseMotionControl BaseMotionControl = dicMotionControls[MotionControlName];
            if (BaseMotionControl == null)
            {
                Msg = $"运动控制[{MotionControlName}]未实例化";
                Result = false;
                return Result;
            }
            if (!BaseMotionControl.bConnected)
            {
                Msg = $"运动控制[{MotionControlName}]未连接";
                Result = false;
                return Result;
            }
            return true;
        }
        /// <summary>
        /// ä¿å­˜ç®—法
        /// </summary>
        /// <param name="filePath">不带.json</param>
        /// <returns></returns>
        public override bool Save(string filePath = null)
        {
            try
            {
                if (string.IsNullOrEmpty(filePath) || filePath.Trim() == "")
                {
                    Debug.WriteLine("文件路径不完整");
                    return false;
                }
                string strJson = string.Empty;
                var settings = new JsonSerializerSettings
                {
                    Formatting = Newtonsoft.Json.Formatting.Indented,
                    // è‡ªå®šä¹‰ç¼©è¿›ï¼ˆ4空格)
                    ContractResolver = new DefaultContractResolver
                    {
                        NamingStrategy = new CamelCaseNamingStrategy()
                    }
                };
                strJson = JsonConvert.SerializeObject(Params, settings);
                Params = JsonConvert.DeserializeObject<ProcessParams>(strJson);
                if (Params == null)
                    return false;
                //判断文件夹是否存在,防呆输入为文件名称
                if (!Directory.Exists(filePath))
                {
                    try
                    {
                        Directory.CreateDirectory(filePath);
                    }
                    catch (Exception)
                    { }
                }
                File.WriteAllText(filePath + "//" + strProcessName + ".json", strJson, Encoding.UTF8);
                return true;
            }
            catch { return false; }
        }
    }
}