using Advantech.Motion;
using HalconDotNet;
using Microsoft.VisualBasic;
using Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenCvSharp;
using ProductionControl.Device;
using ProductionControl.UI;
using ProductionControl.Utils;
using SqlSugar;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using static ProductionControl.Device.AxisDev;
using static ProductionControl.Device.DefectLib;
using static ProductionControl.Device.SizeLib;

namespace ProductionControl
{
    public partial class FrmMain : Form
    {
        WebService webService = new WebService();

        private List<string> productCodeList = new List<string>();
        private DevContainer devContainer = new DevContainer();
        private Service.ProductService svcProduct = new Service.ProductService();
        private Service.OrderService svcOrder = new Service.OrderService();
        private bool bExitApp = false;
        //禁用蜂鸣器，门磁
        private bool disableBuzzer, disableDoorSensor;

        //线宽,张力，厚度，PT
        private List<double> lstTension = new List<double>();
        private List<double> lstHeight = new List<double>();
        private List<double> lstLineWidth = new List<double>();
        private List<double> lstPT = new List<double>();

        private Queue<scannerGBmpLoc> scannerGBmpQueue = new Queue<scannerGBmpLoc>();
        private Queue<scannerCBmpLoc> scannerCBmpQueue = new Queue<scannerCBmpLoc>();
        //private int scannerCBmpIndex = 0;//scannerCBmpQueue中使用当前项的计数器
        private int compBmpIndex = 0;//比对777图的索引
        private int defectBmpNum = 0;
        private int defectBmpNumResult = 0;
        /// <summary>
        /// 比对失败的图片
        /// </summary>
        private List<Bitmap> lstCompareFailZoomImage = new List<Bitmap>();

        private object myLock=new object();
        private WarningEnum warningLevel;//警告等级
        private CurrentPTEnum currentPT;//当前点位
        private CurrentStateEnum currentState;//当前状态 
        /// <summary>
        /// 当前流程ID，暂停/继续时使用
        /// </summary>
        private int currProcessIndex = -1;
        private Models.Product currProductModel = null;//当前产品

        private bool autoMakeTagRuning = false;
        private bool isProductRevise = false;

        //产品-厚度base校正点位索引
        private int ProductPT_HeightBaseNum = 0; 
         
        private Stopwatch stopWatch = new Stopwatch();
        private Order order = new Order();
        //--333输出变量供后面多个777使用
        public HObject contoursAffineTrans1_Out;

        private System.Timers.Timer timer = new System.Timers.Timer();
        private class scannerGBmpLoc
        {
            public scannerGBmpLoc(Mat mat, double xmm, double ymm)
            {
                bmp = mat;
                Xmm = xmm;
                Ymm = ymm;
            }
            public Mat bmp { get; private set; }
            public double Xmm { get; private set; }
            public double Ymm { get; private set; }
        }
        private class scannerCBmpLoc
        {
            public scannerCBmpLoc(string path, double posX, double posY)
            {
                Path = path;
                PosX=posX;
                PosY=posY;
            }
            public scannerCBmpLoc(Bitmap bmp, double posX, double posY)
            {
                BMP = bmp;
                PosX = posX;
                PosY = posY;
            }
            public Bitmap BMP { get; private set; }
            public string Path { get; private set; }
            public double PosX { get; private set; }
            public double PosY { get; private set; }
        }
        public FrmMain()
        {
            InitializeComponent();
            this.dgvProcess.AutoGenerateColumns = false;
            this.tsbtnCloseDev.Visible = false;
            this.tsslLoginInfo.Text = $"操作员：{Config.loginUser.Code}({Config.loginUser.Name})";
            this.tsslLoginTime.Text = $" 登录时间：{DateTime.Now.ToString("yyyy-MM-dd HH:mm")}";

            //显示行号与列宽度自动调整
            dgvProcess.RowHeadersVisible = true;
            dgvProcess.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders;
            dgvProcess.RowPostPaint += (sender, e) =>
            {
                Utils.Util.showRowNum_onDataGrid_RowPostPaint(this.dgvProcess, sender, e);
            };
            checkCustomerVer(); 
        }
        private void checkCustomerVer()
        {
            //if(Config.CustomerVer=="A")
            //    this.tsMenuOrderProduct.Visible = false;
        }
        private void FrmMian_Load(object sender, EventArgs e)
        { 
            if (Config.loginUser.RoleInfo.Code!="admin") 
                checkRoleRight();
            loadProductCodeList();

            this.statusStrip1.LayoutStyle = System.Windows.Forms.ToolStripLayoutStyle.HorizontalStackWithOverflow;
            this.tsslLoginTime.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
            this.tsslLoginInfo.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
            this.tsAxisState.Text = "";

            // 
            webService.LogEvent = (warning, msg) =>
            {
                AddTextEvent("HTTP服务", msg, warning);
                webService.LogEvent = null;
            };
            webService.start();

            //读取硬盘剩余空间
            Config.CheckDisk();
        }
        private void loadProductCodeList()
        {
            try
            {
                productCodeList = svcProduct.GetList().Select(m => m.Code).OrderBy(m=>m).ToList(); 
            }
            catch(Exception ex)
            {
                AddTextEvent("启动", "加载产品料号失败:" + ex.Message,WarningEnum.High);
            }
        }
        private void checkRoleRight()
        {
            this.流程管理ToolStripMenuItem.Enabled = (Config.loginUser.RoleInfo.RightList.FirstOrDefault(m => m.Code == "Step") != null);
            this.产品管理ToolStripMenuItem.Enabled = (Config.loginUser.RoleInfo.RightList.FirstOrDefault(m => m.Code == "Product") != null);
            this.设备调试ToolStripMenuItem.Enabled = (Config.loginUser.RoleInfo.RightList.FirstOrDefault(m => m.Code == "Debug") != null);

            //this.tsMenuOrderQuery.Enabled = (Config.loginUser.RoleInfo.RightList.FirstOrDefault(m => m.Code == "Order") != null);
            //this.tsMenuStatistics.Enabled = (Config.loginUser.RoleInfo.RightList.FirstOrDefault(m => m.Code == "Statistics") != null);

            this.角色管理ToolStripMenuItem.Enabled = (Config.loginUser.RoleInfo.RightList.FirstOrDefault(m => m.Code == "Role") != null);
            this.用户管理ToolStripMenuItem.Enabled = (Config.loginUser.RoleInfo.RightList.FirstOrDefault(m => m.Code == "User") != null);

            this.tsbtnProductRevise.Enabled = (Config.loginUser.RoleInfo.RightList.FirstOrDefault(m => m.Code == "HeightBase") != null);
            this.tsMenuSysSetting.Enabled = (Config.loginUser.RoleInfo.RightList.FirstOrDefault(m => m.Code == "SysSetting") != null);
            this.tsMenuCmdSetting.Enabled = (Config.loginUser.RoleInfo.RightList.FirstOrDefault(m => m.Code == "CmdSetting") != null);
            this.tsMenuPTSetting.Enabled = (Config.loginUser.RoleInfo.RightList.FirstOrDefault(m => m.Code == "PTSetting") != null); 
        }
        private void txtProductName_KeyPress(object sender, KeyPressEventArgs e)
        {
            //if (e.KeyChar == '\r' && devContainer.state && currentState == CurrentStateEnum.ScanBarcode)// && currentPT== CurrentPTEnum.UpPT )
            //{
            //    runStep();
            //}
        }
        /// <summary>
        /// 删除修复台重复sn图片
        /// </summary>
        private void DeleteRepairImage(string sn)
        {
            List<string> imagePath = new List<string>
            {
                Config.Defect_SavePath,
                Config.Defect_Small_SavePath,
                Config.Defect_Compress_SavePath,
                Config.SizeBmp_Zoom_Image_SavePath,
                Config.SizeBmp_SavePath,
                Config.SizeBmp_Compare_SavePath
            };
            //删除文件
            //Task.Factory.StartNew(() =>
            //{
                for (int i = 0; i < imagePath.Count; i++)
                {
                    string dirPath = Util.createSubDir(imagePath[i],
                                                            new List<string> { DateTime.Now.ToString("yyyyMMdd"), sn });
                    if (!Directory.Exists(dirPath))
                    {
                        return;
                    }
                    string[] array = Directory.GetFiles(dirPath);
                    foreach (string text in array)
                    {
                        try
                        {
                            API.OutputDebugString("清除文件：" + text + "...");
                            File.Delete(text);
                        }
                        catch (Exception ex)
                        {
                            API.OutputDebugString("Ex1:" + ex.Message);
                        }
                    }
                }             

            //});
        }

        //后台线程运行
        /// <summary>
        /// 中断工序运行
        /// </summary>
        /// <returns></returns>
        private bool isBreakProcessRun()
        {
            return warningLevel != WarningEnum.Normal || currentState != CurrentStateEnum.运行中;
        }
        private void runStep()
        { 
            compBmpIndex = defectBmpNum = defectBmpNumResult = 0; 
            AddTextEvent("启动", "等待扫码枪扫码...");
            if (devContainer.devCodeScanner != null)
            {
                devContainer.devCodeScanner.stop();
                devContainer.devCodeScanner = null;
            }
            devContainer.devCodeScanner = new CodeScannerDev();
            if (!devContainer.devCodeScanner.start())
            {
                AddTextEvent("扫码枪", "扫码枪初始化失败！");
                return;
            }
            //2023-12-06 无输入料号防呆
            if(string.IsNullOrEmpty(this.txtProductCode.Text))
            {
                AddTextEvent("料号", "未选择料号！");
                return;
            }
            //2023-10-20 不使用扫码枪事件处理
            //devContainer.devCodeScanner.ScanerEvent = (code) =>
            string code = this.txtProductCode.Text;
            {
                if (!devContainer.state || currentState != CurrentStateEnum.等待扫码 || string.IsNullOrWhiteSpace(code))
                    return;

                //
                scannerGBmpQueue.Clear();
                scannerCBmpQueue.Clear();
                lstCompareFailZoomImage.Clear();
                contoursAffineTrans1_Out = null;
                Thread threadtest = new System.Threading.Thread(() =>
                {
                    int errStep = 0;
                    try
                    {
                        var model = svcProduct.GetModelNav(code);
                        if (model != null && model.StepInfo.ProcessList.Count > 0)
                        {
                            errStep = 1;
                            //根据产品设置动态加载外观检测模型文件
                            string onnxFile;
                            if (!string.IsNullOrWhiteSpace(model.DefectModelFile))
                                onnxFile = $"{Application.StartupPath}\\onnxFiles\\{model.DefectModelFile}";
                            else
                                onnxFile = $"{Application.StartupPath}\\onnxFiles\\default.onnx";
                            devContainer.libDefect.loadModelFile(onnxFile);

                            //
                            errStep = 2;
                            string sn = "";
                            AutoResetEvent waitEvent = new AutoResetEvent(false);
                            this.Invoke(new System.Action(() =>
                            {
                                FrmInput frm = new FrmInput(null, "请输入网版编码:");
                                if (frm.ShowDialog() == DialogResult.OK && !string.IsNullOrWhiteSpace(frm.inputData))
                                    sn = frm.inputData;
                                waitEvent.Set();
                            }));
                            errStep = 3;
                            waitEvent.WaitOne();
                            if (string.IsNullOrWhiteSpace(sn))
                            {
                                AddTextEvent("扫码枪", $"料号{code}未输入网版编码，流程中止!");
                                return;
                            }
                            AddTextEvent("扫码枪", $"料号：{code}，网版编码：{sn}");
                            //是否闪退需观察
                            errStep = 4;
                            devContainer.devCodeScanner.stop();
                            devContainer.devCodeScanner = null;
                            errStep = 5;
                            //删除重复sn用于修复台的图片
                            AddTextEvent("扫码枪", $"删除{sn}重复用于修复台的图片...");
                            DeleteRepairImage(sn);

                            //判断SN数量是否达到批次上限
                            //创建表达式
                            if (!string.IsNullOrWhiteSpace(model.BatchId) && model.TargetCount > 0)
                            {
                                var exp1 = Expressionable.Create<Order>()
                                    .And(m => m.ProductId == model.Id)
                                    .And(m => m.BatchId == model.BatchId)
                                    .And(m => m.SN != sn)
                                    .ToExpression();//注意 这一句 不能少
                                errStep = 6;
                                if (svcOrder.Count(exp1) >= model.TargetCount)
                                {
                                    AddTextEvent("扫码枪", $"当前产品本批次检测数已达目标数量，请更换检测批次号!");
                                    return;
                                }

                                errStep = 7;
                            }
                            //

                            this.Invoke(new System.Action(() =>
                            {
                                this.txtProductSN.Text = sn;
                            }));

                            //  
                            model.HeightBaseDec = "";
                            currentState = CurrentStateEnum.运行中;
                            currentPT = CurrentPTEnum.Moving;
                            currProductModel = model;
                            AddTextEvent("扫码枪", $"{model.Name} {model.Spec} [{model.Code}]");
                            this.Invoke(new System.Action(() =>
                            {
                                this.txtProductCode.Text = model.Code;
                                this.txtProductName.Text = model.Name;
                                this.dgvProcess.DataSource = new BindingSource(model.StepInfo.ProcessList, null);
                                devContainer.libFor.clear();
                                devContainer.libIF.clear();
                                tsbtnSizeTag.Enabled = tsbtnSizeImage.Enabled = tsbtnDefectImage.Enabled = true;
                            }));
                            errStep = 8;
                            //   
                            ProductPT_HeightBaseNum = 0;
                            this.setButtonEnabled(this.tsbtnPause, true);
                            this.setButtonEnabled(this.tsbtnStopNow, true);
                            order = new Order();
                            order.Qualified = true;//默认合格，中间有一项指标不合格则改为false
                            order.ProductId = currProductModel.Id;
                            order.StepId = (int)currProductModel.StepId;
                            order.SN = sn;
                            order.BatchId = currProductModel.BatchId;
                            errStep = 9;
                            int nextStepId = 0;
                            do
                            {
                                nextStepId = nextProcess(model, nextStepId);
                            } while (nextStepId >= 0 && !isBreakProcessRun());
                            errStep = 10;
                        }
                        else
                            AddTextEvent("扫码枪", model != null ? $"料号{code}不存在!" : $"产品({model.Name})未配置检测流程!");
                    }
                    catch (Exception ex)
                    {
                        //AddTextEvent("扫码后", $"errcode({errStep}):" + ex.Message);//会有空对象引用，但不知哪里报的
                        //warning(WarningEnum.High);
                    }
            });
                threadtest.IsBackground = true;
                threadtest.Start();

            };
        }
        
        //model.Type=1 系统校正
        /// <summary>
        /// 
        /// </summary>
        /// <param name="model"></param>
        /// <param name="stepIndex">0-n</param>
        /// <returns>大于等于0正常工序； -1:结束 -2：异常</returns>
        private int nextProcess(Models.Product model, int stepIndex)
        {
            try
            {
                //记录当前index
                this.currProcessIndex = stepIndex;
                this.Invoke(new System.Action(() =>
                {
                    try
                    {
                        this.dgvProcess.Rows[stepIndex].Selected = true;
                        dgvProcess.CurrentCell = dgvProcess.Rows[stepIndex].Cells[1];
                    }
                    catch { }
                }));
                lock (myLock)
                {
                    if (isBreakProcessRun())
                        return stepIndex;
                }
                //开始计时
                if(!isProductRevise && model.StepInfo.StartTimeIndex>0 && model.StepInfo.StartTimeIndex==stepIndex+1)
                    stopWatch.Restart();

                var processList = isProductRevise ? model.ReviseStepInfo.ProcessList : model.StepInfo.ProcessList;
                var processInfo = processList[stepIndex];
                string processName = processInfo.ProcessName;  
                //AddTextEvent($"{stepIndex + 1}-{processName}", $"工序开始...");
                string jsonParams = null;//配方

                if (model.ProductProcessList != null && model.ProductProcessList.Count > 0)//使用产品配方
                {
                    ProductProcess productProcessParams = model.ProductProcessList.First(m => m.ProcessCode == processInfo.ProcessCode);
                    if (productProcessParams != null)
                    {
                        jsonParams = productProcessParams.ProcessParams;
                        AddTextEvent($"{stepIndex + 1}-{processName}", $"使用产品专用配方:{jsonParams}");
                    }
                }
                if (jsonParams == null)//使用流程默认配方
                {
                    jsonParams = processInfo.ProcessParams;
                    if (jsonParams == null)  throw new Exception("配方为null!!");
                    AddTextEvent($"{stepIndex + 1}-{processName}", $"使用流程默认配方:{jsonParams}");
                }

                //
                JObject processParam = JObject.Parse(jsonParams);
                if (!processParam.ContainsKey("Disable") || !processParam.Value<bool>("Disable"))
                {
                    AutoResetEvent endEvent;
                    uint sleepPre = processParam.Value<uint>("SleepPre");
                    uint sleepLater = processParam.Value<uint>("SleepLater");
                    if (sleepPre > 0)
                        Thread.Sleep((int)sleepPre);
                    double limitThresholdVal, lowerThresholdVal; //张力，厚度，Size,Defect

                    //======Switch 工序类型
                    bool asynRun;
                    string gbxBmpPath = "";

                    Models.Attachment attachmentFile;
                    int liStatocStepIndex = stepIndex;
                    switch (processInfo.ProcessCode)
                    {
                        case "IOCard":
                            #region
                            var direction = (IODirectionEnum)processParam.Value<int>("Direction");
                            if (direction == IODirectionEnum.输入 || direction == IODirectionEnum.输入输出)
                            {
                                uint IN_Waiting_Timeout = processParam.Value<uint>("IN_Waiting_Timeout");
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"等待I/O输入信号{(IN_Waiting_Timeout > 0 ? $"(超时时长: {IN_Waiting_Timeout})" : "...")}");
                                string[] inValue = processParam.Value<JArray>("IN_OP_SHOW").ToObject<List<string>>().ToArray();
                                uint inWaitingTime = 0;
                                while (true)
                                {
                                    if (isBreakProcessRun())
                                        return stepIndex; 

                                    if (Util.compareIOInput(inValue, devContainer.devIOCard.DIData))
                                        break;

                                    Thread.Sleep(10);
                                    inWaitingTime += 10;
                                    if (IN_Waiting_Timeout>0 && inWaitingTime >= IN_Waiting_Timeout)
                                    {
                                        AddTextEvent($"{stepIndex + 1}-{processName}", $"输入等待超时告警!", WarningEnum.High);
                                        warning(WarningEnum.Low);//暂停  
                                        return stepIndex;
                                    }
                                }
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"I/O输入信号对比完成！");
                            }

                            if (direction == IODirectionEnum.输出 || direction == IODirectionEnum.输入输出)
                                devContainer.io_output($"{stepIndex + 1}-{processName}", processParam);
                            #endregion
                            break;
                        case "Tension":
                            #region
                            //limitThresholdVal = processParam.Value<double>("LimitThresholdVal");
                            //lowerThresholdVal = processParam.Value<double>("LowerThresholdVal");
                            //double tensionValue = 0;
                            //int timeoutTension = 0;
                            //do
                            //{
                            //    tensionValue = devContainer.devTension.getValue();
                            //    setDgvContentCol(liStatocStepIndex, $"张力值:{tensionValue}");
                            //    Thread.Sleep(10);
                            //    timeoutTension += 10;
                            //    if (tensionValue <= 0 && timeoutTension >= 600)//3秒超时
                            //    {
                            //        AddTextEvent($"{stepIndex + 1}-{processName}", $"张力测量失败，确认设备是否正常!", WarningEnum.High);
                            //        warning(WarningEnum.Low);//暂停 
                            //        devContainer.io_output(CMDName.张力读取结束输出);
                            //        currProcessIndex--;
                            //        return stepIndex;
                            //    }
                            //} while (tensionValue <= 0);
                            //devContainer.io_output(CMDName.张力读取结束输出);

                            //lstTension.Add(tensionValue);
                            //updateTensionValue(model.TensionBaseValue+model.TensionUpFloatValue,model.TensionBaseValue-model.TensionDownFloatValue);
                            //if (limitThresholdVal > 0 && tensionValue >= limitThresholdVal)
                            //{
                            //    AddTextEvent($"{stepIndex + 1}-{processName}", $"读取数据:{tensionValue}，达到上限阀值:{limitThresholdVal}，进行下料...", WarningEnum.Low);
                            //    currProcessIndex = stepIndex + 1;
                            //    warning(WarningEnum.Low);//暂停
                            //    return stepIndex;
                            //}
                            //else if (lowerThresholdVal > 0 && tensionValue < lowerThresholdVal)
                            //{
                            //    AddTextEvent($"{stepIndex + 1}-{processName}", $"读取数据:{tensionValue}，达到下限阀值:{lowerThresholdVal}，进行下料...", WarningEnum.Low);
                            //    currProcessIndex = stepIndex + 1; 
                            //    warning(WarningEnum.Low);//暂停 
                            //    return stepIndex;
                            //}
                            //AddTextEvent($"{stepIndex + 1}-{processName}", $"读取数据:{tensionValue}");
                            #endregion
                            break;
                        case "Height":
                            #region
                            if (Config.SkipHeight)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"设备禁用，忽略此步骤！");
                                setDgvContentCol(liStatocStepIndex, $"设备禁用，忽略此步骤!");
                                break;
                            }
                            while (!devContainer.devAxis.isReady())//因启用轴异步功能,使用前需等待
                            {
                                Thread.Sleep(100);
                                if (isBreakProcessRun())
                                {
                                    //currProcessIndex = stepIndex;//本工序没执行，step不变
                                    return stepIndex;
                                }
                            }

                            limitThresholdVal = processParam.Value<double>("LimitThresholdVal");
                            lowerThresholdVal = processParam.Value<double>("LowerThresholdVal");
                            bool IsRevise = processParam.Value<bool>("IsRevise");
                            double relBaseValue = processParam.Value<double>("RelBaseValue"); //配方中相对偏移值
                            var heightValue = devContainer.devHeight.getHeight();
                            heightValue = Math.Round(heightValue, 2);//保留2位小数
                            File.AppendAllText(Application.StartupPath + "\\厚度测量记录.txt", heightValue+"\r\n");//TEMP
                            if (!IsRevise && heightValue < 0)
                            { 
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"读取数据:{heightValue}，异常数据，终止任务！", WarningEnum.Low);
                                warning(WarningEnum.Low);//暂停
                                return stepIndex;
                            }
                            //厚度Base校正
                            if (isProductRevise)
                            {
                                setDgvContentCol(liStatocStepIndex, $"厚度值:{heightValue}");
                                model.HeightBaseDec += heightValue + ";";
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"读取数据:{heightValue}");
                            }
                            else if (IsRevise)
                            {
                                var reviseHeight = heightValue + relBaseValue;
                                model.HeightBaseDec += reviseHeight + ";";
                                setDgvContentCol(liStatocStepIndex, $"base值:{reviseHeight} = 厚度值:{heightValue}+偏移值:{relBaseValue}");
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"base值:{reviseHeight} = 厚度值:{heightValue}+偏移值:{relBaseValue}，校正索引队列:{model.HeightBaseDec}");
                            }
                            else
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"读取数据:{heightValue}，base值:{model.HeightBaseDec}，偏移值:{relBaseValue}，当前base索引:{ProductPT_HeightBaseNum}");
                                double heightDev = 0;
                                var heightDev_BaseList=model.HeightBaseDec.Split(new char[] {';',','});
                                if (heightDev_BaseList.Count() > ProductPT_HeightBaseNum && heightDev_BaseList[ProductPT_HeightBaseNum].Trim() != "")
                                    heightDev = Convert.ToDouble(heightDev_BaseList[ProductPT_HeightBaseNum].Trim());
                                ProductPT_HeightBaseNum++;

                                double heightValue2 =Math.Abs(heightDev - heightValue ) + relBaseValue;
                                heightValue2 = Math.Round(heightValue2, 2);
                                setDgvContentCol(liStatocStepIndex, $"厚度值:{heightValue2}");
                                lstHeight.Add(heightValue2);
                                updateHeightValue(model.HeightBaseValue + model.HeightUpFloatValue, model.HeightBaseValue - model.HeightDownFloatValue);
                                if (limitThresholdVal > 0 && heightValue2 >= limitThresholdVal)
                                {
                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"读取数据:{heightValue2}，达到上限阀值:{limitThresholdVal}，进行下料...", WarningEnum.Low);
                                    currProcessIndex = stepIndex + 1;
                                    warning(WarningEnum.Low);//终止 
                                    return stepIndex;
                                }
                                else if (lowerThresholdVal > 0 && heightValue2 < lowerThresholdVal)
                                {
                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"读取数据:{heightValue2}，达到下限阀值:{lowerThresholdVal}，进行下料...", WarningEnum.Low);
                                    currProcessIndex = stepIndex + 1; 
                                    warning(WarningEnum.Low);//终止 
                                    return stepIndex;
                                }

                                AddTextEvent($"{stepIndex + 1}-{processName}", $"厚度值:{heightValue2} (Base值:{heightDev} - 读取数据:{heightValue} + 偏移值:{relBaseValue})");
                            }
                            #endregion
                            break;
                        case "Axis":
                            #region  多轴后遗留问题：暂停后再启动不知哪几个轴已执行
                            JObject processParamTmp = new JObject();
                            //兼容旧版单轴
                            if (!processParam.ContainsKey("0") && !processParam.ContainsKey("1") && !processParam.ContainsKey("2") && !processParam.ContainsKey("3"))
                            { 
                                processParam.Add("Enable", true);
                                processParamTmp.Add(processParam.Value<int>("AxisIndex").ToString(), processParam);
                                processParam = processParamTmp;
                            }
                            //- 
                            foreach (var processParamSub in processParam.Properties())
                            { 
                                processParam = processParamSub.Value as JObject;
                                AddTextEvent($"{stepIndex + 1}-{processName}", processParam.ToString());
                                if (!processParam.ContainsKey("Disable") || !processParam.Value<bool>("Disable"))
                                {
                                    //asynRun = processParam.Value<bool>("AsynRun");//异步
                                    int AxisIndex = processParam.Value<int>("AxisIndex");
                                    int DeviceType = processParam.Value<int>("DeviceType");

                                    double VelLow = processParam.Value<double>("VelLow");
                                    double VelHigh = processParam.Value<double>("VelHigh");
                                    double Acc = processParam.Value<double>("Acc");
                                    double Dec = processParam.Value<double>("Dec");

                                    AxMoveMode MoveMode = (AxMoveMode)processParam.Value<int>("MoveMode");//绝对位置
                                    double PPUValue = processParam.Value<double>("Value");

                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"轴{AxisIndex}准备({(MoveMode == AxMoveMode.绝对位置 ? "绝对" : "相对")})运动至{PPUValue}(当前轴状态:{((AxisState)devContainer.devAxis.AxState[AxisIndex]).ToString()})...");

                                    //移动Axis前等待厚度传感器收回
                                    if (!Config.SkipHeight)
                                    {
                                        while (devContainer.devHeight.getHeight() < (double)Math.Abs(Config.HeightDev_SafeValue))
                                        {
                                            if (isBreakProcessRun())
                                                return stepIndex;
                                            Thread.Sleep(10);
                                        }
                                    }
                                    while (!devContainer.devAxis.isReady(AxisIndex))
                                    {
                                        Thread.Sleep(100);
                                        if (isBreakProcessRun())
                                            return stepIndex;
                                    }
                                    devContainer.devAxis.setAxisVelParam(VelLow, VelHigh, Acc, Dec, AxisIndex);
                                    if (!devContainer.devAxis.move_ptp(AxisIndex, PPUValue, MoveMode))
                                    {
                                        AddTextEvent($"{stepIndex + 1}-{processName}", $"轴{AxisIndex}运动失败！", WarningEnum.Low);
                                        warning(WarningEnum.Low);//终止
                                        return stepIndex;
                                    }

                                    //多轴运行自动使用异步，全部执行后强制等待各轴完成 asynRun属性已无用
                                    //AddTextEvent($"{stepIndex + 1}-{processName}", $"等待轴{AxisIndex}运行完成...");
                                    //while (!asynRun && !devContainer.devAxis.isReady(AxisIndex))
                                    //{
                                    //    Thread.Sleep(100);
                                    //    if (isBreakProcessRun())
                                    //    {
                                    //        currProcessIndex = stepIndex + 1;
                                    //        return stepIndex;
                                    //    }
                                    //}
                                    //if (devContainer.devAxis.isReady(AxisIndex))
                                    //    AddTextEvent($"{stepIndex + 1}-{processName}", $"轴{AxisIndex}运行完成,当前命令位置:{devContainer.devAxis.getCmdPos_mm(AxisIndex)},反馈位置:{devContainer.devAxis.getActualPos_mm(AxisIndex)}");
                                }
                            }
                            //多轴同时运行后强制等待各轴完成
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"等待轴组运行完成...");
                            while (!devContainer.devAxis.isReady())
                            {
                                Thread.Sleep(100);
                                if (isBreakProcessRun())
                                {
                                    currProcessIndex = stepIndex + 1;
                                    return stepIndex;
                                }
                            }
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"轴组运行完成。");
                            #endregion
                            break;
                        case "AxisTag":
                            #region
                            //asynRun = processParam.Value<bool>("AsynRun");//异步
                            string axisSizeTag= processParam.Value<string>("SizeTag");
                            int useIndex = processParam.Value<int>("UseIndex");//消费posePT中的索引
                            AxMoveMode TagMoveMode = (AxMoveMode)processParam.Value<int>("MoveMode");//绝对位置
                            var AxisIndexList = processParam.Value<JArray>("AxisIndexList").ToObject<List<int>>();

                            double TagVelLow = processParam.Value<double>("VelLow");
                            double TagVelHigh = processParam.Value<double>("VelHigh");
                            double TagAcc = processParam.Value<double>("Acc");
                            double TagDec = processParam.Value<double>("Dec");
                            var sizeTagObj = order.SizeTagDataList.LastOrDefault(m => m.SizeTag == axisSizeTag);//用最新的last
                            if (sizeTagObj == null)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"偏移校正轴工序找不到数据提供者Tag:{axisSizeTag}！", WarningEnum.High);
                                warning(WarningEnum.High);
                                return stepIndex;
                            } 
                            string[] posePT= sizeTagObj.posePT.Split(',');
                            if(posePT.Length < useIndex+ AxisIndexList.Count)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"Tag:{axisSizeTag}对应消费索引:{useIndex},Axis数量:{AxisIndexList.Count} 超出postPT:{sizeTagObj.posePT} 范围!", WarningEnum.High);
                                warning(WarningEnum.High);
                                return stepIndex;
                            }

                            double[] TagPPUValue = new double[AxisIndexList.Count];
                            for (int i = 0; i < AxisIndexList.Count; i++)
                                TagPPUValue[i] = double.Parse(posePT[useIndex + i]);
                            string axisTagMsg = $"消费Tag:{axisSizeTag},索引:{useIndex}, 轴:{string.Join(",", AxisIndexList.ToList())},数据:{(TagMoveMode== AxMoveMode.绝对位置 ? "绝对" : "相对")}({string.Join(",", TagPPUValue.ToList())})";
                            AddTextEvent($"{stepIndex + 1}-{processName}", axisTagMsg);
                            setDgvContentCol(liStatocStepIndex, axisTagMsg);

                            //移动Axis前等待厚度传感器收回
                            if (!Config.SkipHeight)
                            {
                                while (devContainer.devHeight.getHeight() < (double)Math.Abs(Config.HeightDev_SafeValue))
                                {
                                    if (isBreakProcessRun())
                                        return stepIndex; //如果是相对位置，这里返回会有问题
                                    Thread.Sleep(10);
                                }
                            }
                            for (int i = 0; i < AxisIndexList.Count && i < TagPPUValue.Length; i++)
                            {
                                while (!devContainer.devAxis.isReady(AxisIndexList[i]))
                                {
                                    Thread.Sleep(100);
                                    if (isBreakProcessRun())
                                        return stepIndex;
                                }
                                devContainer.devAxis.setAxisVelParam(TagVelLow, TagVelHigh, TagAcc, TagDec, AxisIndexList[i]);
                                if (!devContainer.devAxis.move_ptp(AxisIndexList[i], TagPPUValue[i], TagMoveMode))
                                {
                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"轴{AxisIndexList[i]}运动失败！", WarningEnum.Low);
                                    warning(WarningEnum.Low);//终止
                                    return stepIndex;//如果是相对位置，这里返回会有问题
                                }

                                //AddTextEvent($"{stepIndex + 1}-{processName}", $"等待轴{AxisIndexList[i]}运行完成...");
                                //while (!asynRun && !devContainer.devAxis.isReady(AxisIndexList[i]))
                                //{
                                //    Thread.Sleep(100);
                                //    if (isBreakProcessRun())
                                //    {
                                //        currProcessIndex = stepIndex + 1;
                                //        return stepIndex;//如果是相对位置，这里返回会有问题
                                //    }
                                //}
                                //if (devContainer.devAxis.isReady(AxisIndexList[i]))
                                //    AddTextEvent($"{stepIndex + 1}-{processName}", $"轴{AxisIndexList[i]}运行完成,当前命令位置:{devContainer.devAxis.getCmdPos_mm(AxisIndexList[i])},反馈位置:{devContainer.devAxis.getActualPos_mm(AxisIndexList[i])}");
                            }
                            if (sizeTagObj.ConsumeStepIndex == null) sizeTagObj.ConsumeStepIndex = "";
                            sizeTagObj.ConsumeStepIndex += $"{stepIndex + 1}-{useIndex}, ";//消费工序ID

                            //多轴同时运行后强制等待各轴完成
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"等待轴组运行完成...");
                            while (!devContainer.devAxis.isReady())
                            {
                                Thread.Sleep(100);
                                if (isBreakProcessRun())
                                {
                                    currProcessIndex = stepIndex + 1;
                                    return stepIndex;
                                }
                            }
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"轴组运行完成。");
                            #endregion
                            break;
                        case "Light":
                            #region 
                            if (Config.SkipLight)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"设备禁用，忽略此步骤！");
                                setDgvContentCol(liStatocStepIndex, $"设备禁用，忽略此步骤!");
                                break;
                            }
                            int ChannelIndex = processParam.Value<int>("ChannelIndex"); //通道
                            int DigitalValue = processParam.Value<int>("DigitalValue"); //亮度
                            int nowDiaitalValue = devContainer.devLight.getDigitalValue(ChannelIndex);
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"通道{ChannelIndex}当前值:{nowDiaitalValue},准备更新值:{DigitalValue}...");
                            devContainer.devLight.setDigitalValue(ChannelIndex, DigitalValue);
                            nowDiaitalValue = devContainer.devLight.getDigitalValue(ChannelIndex);
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"通道{ChannelIndex}更新后当前值:{nowDiaitalValue}。");
                            #endregion
                            break;
                        case "Scanner_GENTL":
                            #region
                            if (Config.SkipScannerGL)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"设备禁用，忽略此步骤！");
                                setDgvContentCol(liStatocStepIndex, $"设备禁用，忽略此步骤!");
                                break;
                            }
                            while (!devContainer.devAxis.isReady())//因启用轴异步功能,使用前需等待
                            {
                                Thread.Sleep(100);
                                if (isBreakProcessRun())
                                {
                                    //currProcessIndex = stepIndex;//本工序没执行，step不变
                                    return stepIndex;
                                }
                            }

                            AIEngineLibEnum AIEngineLib = AIEngineLibEnum.缺陷库;
                            if(processParam.ContainsKey("AIEngineLib"))
                                AIEngineLib=(AIEngineLibEnum)processParam.Value<int>("AIEngineLib");
                            float ExposureTime = processParam.Value<float>("ExposureTime"); //曝光
                            float Gain = processParam.Value<float>("Gain"); //增益
                            float ResultingFrameRate = processParam.Value<float>("ResultingFrameRate"); //帧率

                            AddTextEvent($"{stepIndex + 1}-{processName}", $"相机开始采集照片...");
                            devContainer.devScannerGentl.setParam(ExposureTime, Gain, ResultingFrameRate);

                            endEvent = new AutoResetEvent(false);
                            devContainer.devScannerGentl.ScanEvent = (num, bmpout) =>
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"相机采集照片完成。");
                                //----缺陷队列
                                //bool cloneUse = false;
                                if (AIEngineLib == AIEngineLibEnum.缺陷库 || AIEngineLib == AIEngineLibEnum.缺陷与测量库)
                                {
                                    //cloneUse = true;
                                    var mat = OpenCvSharp.Extensions.BitmapConverter.ToMat(bmpout);
                                    scannerGBmpQueue.Enqueue(new scannerGBmpLoc(mat,
                                        devContainer.devAxis.getActualPos_mm(1),
                                        devContainer.devAxis.getActualPos_mm(2)));//Dequeue 
                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"缺陷图像队列数量: {scannerGBmpQueue.Count}");
                                }
                                if (AIEngineLib == AIEngineLibEnum.测量库 || AIEngineLib == AIEngineLibEnum.缺陷与测量库)
                                {
                                    //v1.2.2. 修改传图
                                    //scannerCBmpQueue.Enqueue(new scannerCBmpLoc(
                                    //    cloneUse?(Bitmap)bmpout.Clone(): bmpout,
                                    //    devContainer.devAxis.getActualPos_mm(1),
                                    //    devContainer.devAxis.getActualPos_mm(2)));//Dequeue
                                    scannerCBmpQueue.Enqueue(new scannerCBmpLoc(
                                         (Bitmap)bmpout.Clone(),
                                        devContainer.devAxis.getActualPos_mm(1),
                                        devContainer.devAxis.getActualPos_mm(2)));//Dequeue
                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"添加尺寸图像队列，X:{devContainer.devAxis.getActualPos_mm(1)},y:{devContainer.devAxis.getActualPos_mm(2)},数量: {scannerCBmpQueue.Count}");
                                }
                                endEvent.Set();//线程返回
                            };
                            //AddTextEvent($"{stepIndex + 1}-{processName}", $"软触发拍照...");
                            if (!devContainer.devScannerGentl.scan(1))//软触发拍照
                            {
                                devContainer.devScannerGentl.ScanEvent = null;
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"相机采集照片失败！", WarningEnum.Low);
                                warning(WarningEnum.Low);//终止
                                return stepIndex;
                            } 
                            if (!endEvent.WaitOne(10000))
                            {
                                devContainer.devScannerGentl.ScanEvent = null;
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"相机采集照片超时！", WarningEnum.Low);
                                warning(WarningEnum.Low);//终止
                                return stepIndex;
                            } 
                            devContainer.devScannerGentl.ScanEvent = null;
                            #endregion
                            break;
                        case "Scanner_CC":
                            #region
                            if (Config.SkipScannerCC)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"设备禁用，忽略此步骤！");
                                setDgvContentCol(liStatocStepIndex, $"设备禁用，忽略此步骤!");
                                break;
                            }
                            while (!devContainer.devAxis.isReady())//因启用轴异步功能,使用前需等待
                            {
                                Thread.Sleep(100);
                                if (isBreakProcessRun())
                                {
                                    //currProcessIndex = stepIndex;//本工序没执行，step不变
                                    return stepIndex;
                                }
                            }

                            float ExposureTimeCC = processParam.Value<float>("ExposureTime"); //曝光
                            float GainCC = processParam.Value<float>("Gain"); //增益
                            float ResultingFrameRateCC = processParam.Value<float>("ResultingFrameRate"); //帧率

                            AddTextEvent($"{stepIndex + 1}-{processName}", $"相机开始采集照片...");
                            devContainer.devScannerCC.setParam(ExposureTimeCC, GainCC, ResultingFrameRateCC);

                            AddTextEvent($"{stepIndex + 1}-{processName}", $"相机参数设置完成。");
                            AutoResetEvent endEventCC = new AutoResetEvent(false);
                            //devContainer.devScannerCC.ScanEvent = (num, bmp2) =>
                            //{
                            //    AddTextEvent($"{stepIndex + 1}-{processName}", $"相机采集照片完成.");
                            //    scannerCBmpQueue.Enqueue(bmp2);//Dequeue
                            //    endEventCC.Set();//线程返回
                            //};
                            devContainer.devScannerCC.ScanEventPath += new System.Action<int, string>((num, path2) =>
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"相机采集照片完成.");
                                scannerCBmpQueue.Enqueue(new scannerCBmpLoc(path2, 
                                    devContainer.devAxis.getActualPos_mm(0),
                                    devContainer.devAxis.getActualPos_mm(2)));//Dequeue
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"添加尺寸图像队列，X:{ devContainer.devAxis.getActualPos_mm(0)},y: { devContainer.devAxis.getActualPos_mm(2)},数量: { scannerCBmpQueue.Count}");
                                endEventCC.Set();//线程返回
                            });
                            if (!devContainer.devScannerCC.scan(1))//软触发拍照
                            {
                                devContainer.devScannerCC.ScanEventPath = null;
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"相机采集照片失败！", WarningEnum.Low);
                                warning(WarningEnum.Low);//终止
                                return stepIndex;
                            }

                            if (!endEventCC.WaitOne(10000))
                            {
                                devContainer.devScannerCC.ScanEventPath = null;
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"相机采集照片超时！", WarningEnum.Low);
                                warning(WarningEnum.Low);//终止
                                return stepIndex;
                            }
                            devContainer.devScannerCC.ScanEventPath = null;
                            #endregion
                            break;
                        case "SmallAxis":
                            #region 
                            if (Config.SkipSmallAxis)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"设备禁用，忽略此步骤！");
                                setDgvContentCol(liStatocStepIndex, $"设备禁用，忽略此步骤!");
                                break;
                            }
                            int CmdPos = processParam.Value<int>("CmdPos"); //命令脉冲
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"开始由起始位置{devContainer.devSmallAxis.getCurrPPU()}运动到{CmdPos}...");
                            devContainer.devSmallAxis.gotoPos(CmdPos, true);
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"运动完成，当前位置:{devContainer.devSmallAxis.getCurrPPU()}");
                            #endregion
                            break;
                        case "Size":
                            #region
                            asynRun = processParam.Value<bool>("AsynRun");//异步
                            limitThresholdVal = processParam.Value<double>("LimitThresholdVal");
                            lowerThresholdVal = processParam.Value<double>("LowerThresholdVal");
                            int sizeIndex = processParam.Value<int>("Index");
                            string sizeTag = processParam.ContainsKey("SizeTag") ? processParam.Value<string>("SizeTag") : "";


                            //2023-10-27
                            bool useMap = false;
                            List<double> getPosList = new List<double>();
                            try
                            {
                                useMap = processParam.Value<bool>("UseMapPoints");
                                if (useMap) { 
                                    var list = model.GetPointList.Split(',');
                                    List<double> dList = new List<double>();
                                    if (list.Length < 28)
                                    {
                                        for (int i = 0; i < 28; i++)
                                        {
                                            dList.Add(0);
                                        }
                                    }
                                    else
                                    {
                                        for (int i = 0; i < list.Length; i++)
                                        {
                                            dList.Add(double.Parse(list[i]));
                                        }
                                    }
                                    getPosList = dList;
                                }
                                else
                                {
                                    for (int i = 0; i < 28; i++)
                                        getPosList.Add(0);
                                }
                                //getPosList = processParam.Value<JArray>("GetPointList").ToObject<List<double>>();
                            }
                            catch
                            {
                                for (int i = 0; i < 28; i++)
                                    getPosList.Add(0);
                            }


                            double[] getPosArray = getPosList.ToArray();
                            if (scannerCBmpQueue.Count < 1)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"尺寸检测异常，无源图像!!", WarningEnum.Low);
                                warning(WarningEnum.Low);//暂停
                                return stepIndex;
                            }
                            var bmpCBmpQueue = scannerCBmpQueue.Dequeue();
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"开始尺寸检测，index:{sizeIndex},posX:{bmpCBmpQueue.PosX},posY:{bmpCBmpQueue.PosY},图像队列数量: {scannerCBmpQueue.Count}...");

                            attachmentFile = model.AttachmentList.FirstOrDefault(x => x.Type == 0);
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"尺寸检测，index:{sizeIndex},{model.AttachmentList.Count}|{(attachmentFile == null ? "null": attachmentFile.NameTimestamp+ attachmentFile.ExtendName)}");
                            if (attachmentFile != null)
                            {
                                gbxBmpPath = Application.StartupPath + $"\\Attachment\\product\\{attachmentFile.NameTimestamp}";
                                if (!File.Exists(gbxBmpPath+ attachmentFile.ExtendName)) gbxBmpPath = "";
                            }
                            if ((sizeIndex == 333 || sizeIndex == 777) && gbxBmpPath == "")
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"尺寸检测，index:{sizeIndex},图纸不存在！", WarningEnum.Low);
                            //2023-10-27
                            //if ((sizeIndex == 3333)&&(getPosList != null) && (getPosList.Count() != 28))
                            //    AddTextEvent($"{stepIndex + 1}-{processName}", $"尺寸检测，index:{sizeIndex},图纸读点不存在！", WarningEnum.Low);
                            if (useMap&&(sizeIndex == 3333) && (getPosArray != null) && (getPosArray.Count() == 28) && (getPosArray[0] != 0))
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"开始图纸读点，index:{sizeIndex},PT1:{getPosArray[0]},PT2:{getPosArray[2]},PT3:{getPosArray[4]},PT4:{getPosArray[6]},PT5:{getPosArray[8]}," +
                                    $"线宽1:[{getPosArray[10]},{getPosArray[11]}],线宽2:[{getPosArray[12]},{getPosArray[13]}],线宽3:[{getPosArray[14]},{getPosArray[15]}]," +
                                    $"线宽4:[{getPosArray[16]},{getPosArray[17]}],线宽5:[{getPosArray[18]},{getPosArray[19]}],线宽6:[{getPosArray[20]},{getPosArray[21]}]," +
                                    $"线宽7:[{getPosArray[22]},{getPosArray[23]}],线宽8:[{getPosArray[24]},{getPosArray[25]}],线宽9:[{getPosArray[26]},{getPosArray[27]}]");
                            //需要偏移校正，index=0时不能异步 //10,20,30...　
                            endEvent = new AutoResetEvent(false);
                            devContainer.libSize.add(new SizeTask()
                            {
                                stepIndex = stepIndex,
                                processName = processName,
                                sizeTag = sizeTag,
                                engineName = processParam.Value<string>("EngineName"),
                                bmp = bmpCBmpQueue.BMP == null ? bmpCBmpQueue.BMP : (Bitmap)bmpCBmpQueue.BMP.Clone(),//bmp/file_path二选一，优先bmp
                                file_path = bmpCBmpQueue.Path,
                                drawingPagePath = gbxBmpPath,
                                posX= bmpCBmpQueue.PosX,
                                posY= bmpCBmpQueue.PosY,
                                //2023-10-27
                                PTandLinePos = getPosArray,
                                index = sizeIndex,// scannerCBmpIndex++,
                                ContoursAffineTrans1_Out=this.contoursAffineTrans1_Out,//只有777时才使用最近333输出的结果 
                                finishEvent = (res) =>
                                {
                                    try
                                    {
                                        //比对
                                        if (res.index == 777)//比对
                                        {
                                            if (res.isSucceed)
                                            {
                                                setDgvContentCol(liStatocStepIndex, $"index:{res.index}-{compBmpIndex}，posX:{res.posX},posY:{res.posY},图像比对:{(res.CompResult ? "通过" : "未通过")} ");
                                                AddTextEvent($"{res.stepIndex + 1}-{res.processName}", $"图像比对，index:{res.index}-{compBmpIndex},posX:{res.posX},posY:{res.posY}，结果:{(res.CompResult ? "通过" : "未通过")}");
                                                // 
                                                if (order.CompareResult < 2)
                                                    order.CompareResult = res.CompResult ? 1 : 2;
                                                updateCompareResult(res.CompResult);//更新比对看板 
                                                if (!res.CompResult)
                                                {
                                                    AddTextEvent($"{res.stepIndex + 1}-{res.processName}", $"图像比对，未通过结果:{JsonConvert.SerializeObject(res.defectInfor2RestorationDesk)}");
                                                    //转为图纸上坐标位置
                                                    if (res.defectInfor2RestorationDeskPage != null && res.defectInfor2RestorationDeskPage.Count > 0)
                                                    {
                                                        //AddTextEvent($"{res.stepIndex + 1}-{res.processName}", $"转换到图纸后坐标数据：{JsonConvert.SerializeObject(res.defectInfor2RestorationDeskPage)}");
                                                        if (order.DefectInfoList == null)
                                                            order.DefectInfoList = new List<DefectInfo>();
                                                        foreach (var item in res.defectInfor2RestorationDeskPage)
                                                            order.DefectInfoList.Add(new DefectInfo()
                                                            {
                                                                Type = 1,
                                                                Code = item[3],
                                                                X = double.Parse(item[1]),
                                                                Y = double.Parse(item[2]),
                                                                ZXD = double.Parse(item[4]),
                                                                ModifyUserCode = Config.loginUser.Code,
                                                                CreateUserCode = Config.loginUser.Code,
                                                            });
                                                    }

                                                    //比对失败的图片 -- 用于修复台调用
                                                    Bitmap bmpCompareFailZoomImage = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(res.Zoom_Image_mat);
                                                    lstCompareFailZoomImage.Add(bmpCompareFailZoomImage);
                                                    if (Config.SizeBmp_Zoom_Image_SavePath != "" && Directory.Exists(Config.SizeBmp_Zoom_Image_SavePath))
                                                    {
                                                        string path = Util.createSubDir(Config.SizeBmp_Zoom_Image_SavePath, new List<string> { order.CreateTime.ToString("yyyyMMdd"), order.SN });
                                                        //path += $"Size_SN{order.SN}_I{res.index}_X{res.Defects_X}_Y{res.Defects_Y}_C0_{ model.StepInfo.Name}";
                                                        path += $"Size_SN{order.SN}_I{compBmpIndex}_X{res.posX}_Y{res.posY}_C0_{ model.StepInfo.Name}";
                                                        bmpCompareFailZoomImage.Save(path + ".bmp", ImageFormat.Bmp);
                                                        if (res.defectInfor2RestorationDesk != null && res.defectInfor2RestorationDesk.Count > 0)
                                                            File.WriteAllText(path + ".json", JsonConvert.SerializeObject(res.defectInfor2RestorationDesk));
                                                    }
                                                    //保存原图
                                                    if (Config.SizeBmp_Compare_SavePath != "" && Directory.Exists(Config.SizeBmp_Compare_SavePath))
                                                    {
                                                        string path = Util.createSubDir(Config.SizeBmp_Compare_SavePath, new List<string> { order.CreateTime.ToString("yyyyMMdd"), order.SN });
                                                        path += $"Size_SN{order.SN}_I{res.index}_X{res.posX}_Y{res.posY}_{ model.StepInfo.Name}.bmp";
                                                        AddTextEvent($"{res.stepIndex + 1}-{processName}", $"未通过图片保存:{path}");
                                                        if (res.bmp != null)
                                                            res.bmp.Save(path, ImageFormat.Bmp);
                                                        else
                                                            API.CopyFile(res.file_path, path, false);//比.NET(File.Copy)更快 
                                                    }
                                                }
                                            }
                                            else
                                            {
                                                //2023-12-29 加入比对失败当作NG处理
                                                order.CompareResult =  2;
                                                updateCompareResult(false);//更新比对看板 

                                                //warning(WarningEnum.Low);//暂停
                                                setDgvContentCol(liStatocStepIndex, $"index:{res.index}，图像比对失败！");
                                                AddTextEvent($"{res.stepIndex + 1}-{res.processName}", $"图像比对失败，index:{res.index}-{compBmpIndex}.", WarningEnum.Low);
                                            }
                                            compBmpIndex++;
                                        }
                                        //MARK
                                        else if (res.index == 111 || res.index == 222 || res.index == 333 || res.index == 444)
                                        { 
                                            AddTextEvent($"{res.stepIndex + 1}-{processName}", $"Mark点 Index={res.index}，结果记录...");
                                            if (res.index == 333) this.contoursAffineTrans1_Out = res.ContoursAffineTrans1_Out;//不管成功失败都替换
                                            if (res.isSucceed)
                                            {
                                                Thread.Sleep(100);
                                                AddTextEvent($"{res.stepIndex + 1}-{processName}", $"Mark点 Index={res.index}; 当前值：{string.Join(",", res.MarkPointList)}");
                                                JArray markDatas;
                                                if (string.IsNullOrWhiteSpace(order.MarkData))
                                                    markDatas = new JArray() { 0, 0, 0, 0, 0, 0, 0, 0 };
                                                else
                                                    markDatas = JArray.Parse(order.MarkData);
                                                for (int i = 0; i < res.MarkPointList.Count(); i++)
                                                    if (res.MarkPointList[i] != 0)
                                                        markDatas[i] = res.MarkPointList[i];
                                                order.MarkData = markDatas.ToString();
                                                setDgvContentCol(liStatocStepIndex, $"index:{res.index}，Mark点:{order.MarkData} ");
                                                AddTextEvent($"{res.stepIndex + 1}-{processName}", $"Mark点 Index={res.index};合并后：{order.MarkData}");
                                            }
                                            else
                                            {
                                                //warning(WarningEnum.Low);//暂停
                                                setDgvContentCol(liStatocStepIndex, $"index:{res.index}，Mark点计算失败！");
                                                AddTextEvent($"{res.stepIndex + 1}-{processName}", $"Mark点计算失败，index:{res.index}.", WarningEnum.Low);
                                                //2023-10-20 make暂停
                                                if (Config.OpenMarkErrorStop)
                                                    warning(WarningEnum.Low);//暂停
                                            }

                                            //保存
                                            if (Config.SizeBmp_SavePath != "" && Directory.Exists(Config.SizeBmp_SavePath))
                                            {
                                                string path = Util.createSubDir(Config.SizeBmp_SavePath, new List<string> { order.CreateTime.ToString("yyyyMMdd"), order.SN });
                                                path += $"Size_SN{order.SN}_I{res.index}_X{res.posX}_Y{res.posY}_{ model.StepInfo.Name}.bmp";
                                                if (res.bmp != null)
                                                    res.bmp.Save(path, ImageFormat.Bmp);
                                                else
                                                    API.CopyFile(res.file_path, path, false);//比.NET(File.Copy)更快 
                                            }
                                        }
                                        else
                                        { 
                                            int roundIndex = res.index % 10;
                                            if (res.isSucceed)
                                            {
                                                string tagOutData = "";
                                                if (res.index == 3333 && !string.IsNullOrWhiteSpace(res.sizeTag))
                                                {
                                                    tagOutData = $"Tag:{res.sizeTag},posePT:[{string.Join(",", res.posePT)}]";
                                                    if (res.posePT.Length < 2 || res.posePT.Length % 2 != 0)
                                                    {
                                                        AddTextEvent($"{res.stepIndex + 1}-{res.processName}", $"尺寸检测输出Tag对应posePT非法: {tagOutData}", WarningEnum.High);
                                                        warning(WarningEnum.High);//急停
                                                        return;
                                                    }
                                                    //2023-10-27
                                                    AddTextEvent($"{res.stepIndex + 1}-{res.processName}", $"Tag对应posePT: {tagOutData}", WarningEnum.Normal);

                                                    if (order.SizeTagDataList == null)
                                                        order.SizeTagDataList = new List<SizeTagData>();
                                                    order.SizeTagDataList.Add(new SizeTagData()
                                                    {
                                                        SizeTag = res.sizeTag,
                                                        CreateStepIndex = res.stepIndex + 1,
                                                        posePT= string.Join(",", res.posePT)// 回转 Array.ConvertAll(sNums , double.Parse);
                                                    });

                                                }

                                                setDgvContentCol(liStatocStepIndex, $"index:{res.index}，PT1:{res.PT1}，PT2:{res.PT2}，Shanxian:{res.Shanxian}，Circle_Xmm:{res.Circle_Xmm}，Circle_Ymm:{res.Circle_Ymm}，offsetX:{res.offsetX}，offsetY:{res.offsetY}, {tagOutData}");
                                                AddTextEvent($"{res.stepIndex + 1}-{res.processName}", $"尺寸检测完成 index:{res.index}，PT1:{res.PT1}，PT2:{res.PT2}，Shanxian:{res.Shanxian}，Circle_Xmm:{res.Circle_Xmm}，Circle_Ymm:{res.Circle_Ymm}，offsetX:{res.offsetX}，offsetY:{res.offsetY}, {tagOutData} ");
                                                //测量
                                                //------TEST
                                                if (res.index>20 && res.index<30)
                                                {
                                                    lock (lstPT)
                                                    {
                                                        lstPT.Add(Math.Round(res.PT1 + Config.PT_Offset, 4)); 
                                                        updatePTValueTest(model.PTBaseValue + model.PTUpFloatValue, model.PTBaseValue - model.PTDownFloatValue);
                                                    }
                                                }
                                                //------
                                                if (roundIndex > 0)//1-9测量
                                                {
                                                    if (res.index < 10)//11-13 (PT: 7-9 => 21-29（PT1）) 不用管，李工处理
                                                    {
                                                        //if (roundIndex >= 7)
                                                        //{
                                                        //    lock (lstPT)
                                                        //    {
                                                        //        lstPT.Add(Math.Round(res.PT1, 4));
                                                        //        lstPT.Add(res.index == 8 ? 0 : Math.Round(res.PT2, 4));
                                                        //        updatePTValue(model.PTBaseValue + model.PTUpFloatValue, model.PTBaseValue - model.PTDownFloatValue);
                                                        //    }
                                                        //}

                                                        lock (lstLineWidth)
                                                        {
                                                            lstLineWidth.Add(res.Shanxian > 0 ? Math.Round(res.Shanxian + Config.LineWidth_Offset, 2) : 0);
                                                            updateLineWidthValue(model.LineWidthBaseValue + model.LineWidthUpFloatValue, model.LineWidthBaseValue - model.LineWidthDownFloatValue);
                                                        }
                                                    }
                                                }
                                                //校正偏移 10,20,30...:偏移
                                                else
                                                {
                                                    AxMoveMode axSizeMode = AxMoveMode.绝对位置;
                                                    double xPos = 0;
                                                    double yPos = 0;
                                                    //绝对偏移 
                                                    if (res.offsetX != 0 || res.offsetY != 0)
                                                    {
                                                        xPos += res.offsetX;
                                                        yPos += res.offsetY;
                                                        AddTextEvent($"{res.stepIndex + 1}-{res.processName}", $"offsetX/Y绝对校正, 0轴:{xPos}mm, 2轴:{yPos}mm");
                                                    }
                                                    else if (res.Circle_Xmm != 0 || res.Circle_Ymm != 0)//相对偏移校正
                                                    {
                                                        axSizeMode = AxMoveMode.相对位置;
                                                        AddTextEvent($"{res.stepIndex + 1}-{res.processName}", $"原点相对校正, 0轴:{res.Circle_Xmm}mm, 2轴:{res.Circle_Ymm}mm");
                                                        if (res.Circle_Xmm != 0) xPos = res.Circle_Xmm;
                                                        if (res.Circle_Ymm != 0) yPos = res.Circle_Ymm;
                                                    }
                                                    //当前工序直接移动                                                   
                                                    if (xPos != 0 || yPos != 0)
                                                    {
                                                        devContainer.devAxis.setAxisVelParam(0.1, 1, 1, 1, 0);
                                                        devContainer.devAxis.setAxisVelParam(0.1, 1, 1, 1, 2);
                                                        if (xPos != 0) devContainer.devAxis.move_ptp(0, xPos, axSizeMode);
                                                        if (yPos != 0) devContainer.devAxis.move_ptp(2, yPos, axSizeMode);
                                                        while (!devContainer.devAxis.isReady(0) || !devContainer.devAxis.isReady(2))
                                                        {
                                                            Thread.Sleep(100);
                                                            if (isBreakProcessRun())
                                                                break;
                                                        }
                                                    }
                                                }
                                                 
                                                //保存
                                                //string sizeFileName = Config.Size_SavePath + "\\" + DateTime.Now.ToString("yyyyMMdd_HHmmss_fff");
                                                //API.CopyFile(res.file_path, defectFileName + ".bmp", false);//更快
                                                //File.WriteAllText(defectFileName + ".json", JsonConvert.SerializeObject(res.informationList));
                                            }
                                            //失败
                                            else
                                            {
                                                //------TEST
                                                if (res.index > 20 && res.index < 30)
                                                {
                                                    lock (lstPT)
                                                    {
                                                        lstPT.Add(0);
                                                        updatePTValueTest(model.PTBaseValue + model.PTUpFloatValue, model.PTBaseValue - model.PTDownFloatValue);
                                                    }
                                                }
                                                //------

                                                if (roundIndex > 0)
                                                {
                                                    //if (roundIndex >= 7)
                                                    //{
                                                    //    lock (lstPT)
                                                    //    {
                                                    //        lstPT.Add(0);
                                                    //        lstPT.Add(0);
                                                    //        updatePTValue(model.PTBaseValue + model.PTUpFloatValue, model.PTBaseValue - model.PTDownFloatValue);
                                                    //    }
                                                    //}
                                                
                                                    lock (lstLineWidth)
                                                    {
                                                        lstLineWidth.Add(0);
                                                        updateLineWidthValue(model.LineWidthBaseValue + model.LineWidthUpFloatValue, model.LineWidthBaseValue - model.LineWidthDownFloatValue);
                                                    }
                                                }
                                                setDgvContentCol(liStatocStepIndex, $"失败:{res.resultInfo}");
                                                AddTextEvent($"{res.stepIndex + 1}-{res.processName}", $"尺寸检测失败index:{res.index}:{res.resultInfo}");
                                                //warning(WarningEnum.Low);//暂停  这里不能暂停，stepIndex和scannerBmpQueue队列也不对了
                                            }
                                             
                                            //保存
                                            if (Config.SizeBmp_SavePath != "" && Directory.Exists(Config.SizeBmp_SavePath))
                                            {
                                                string path = Util.createSubDir(Config.SizeBmp_SavePath, new List<string> { order.CreateTime.ToString("yyyyMMdd"), order.SN });
                                                path += $"Size_SN{order.SN}_I{res.index}_X{res.posX}_Y{res.posY}_{ model.StepInfo.Name}.bmp";
                                                if (res.bmp != null)
                                                    res.bmp.Save(path, ImageFormat.Bmp);
                                                else
                                                    API.CopyFile(res.file_path, path, false);//比.NET(File.Copy)更快 
                                            }
                                        }
                                    }
                                    catch(Exception ex)
                                    {
                                        AddTextEvent($"{res.stepIndex + 1}-{res.processName}", $"尺寸检测回调处理异常 index:{res.index}，ex={ex.Message}");
                                    }

                                    //
                                    //if (!asynRun)//是否异步执行endEvent.Set()都没问题
                                    endEvent.Set();//roundIndex＝０成功或失败线程返回
                                    //---
                                    if (res.bmp != null)
                                    {
                                        res.bmp.Dispose();
                                        res.bmp = null;
                                    }
                                    else
                                    {
                                        API.DeleteFile(res.file_path);
                                    }
                                }
                            });

                            //需等上面异步回调中的相对偏移校正完成再继续
                            if (!asynRun || sizeIndex % 10==0)
                            {
                                if (!endEvent.WaitOne(60000))
                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"{sizeIndex}等待超时，忽略继续！", WarningEnum.Low);
                            }
                            #endregion
                            break;
                        case "Defect":
                            #region
                            limitThresholdVal = processParam.Value<double>("LimitThresholdVal");
                            lowerThresholdVal = processParam.Value<double>("LowerThresholdVal");
                            if (scannerGBmpQueue.Count < 1)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"缺陷检测异常，无源图像!!", WarningEnum.Low);
                                warning(WarningEnum.Low);//终止
                                return stepIndex;
                            }
                            var bmpLoc = scannerGBmpQueue.Dequeue();
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"开始缺陷检测，源图索引:{defectBmpNum},图像队列数量: {scannerGBmpQueue.Count}...");
                            string[] aarCut_size = processParam.Value<string>("CutSize").Split(',');
                            string[] aarResize = processParam.Value<string>("Resize").Split(',');
                            //图纸
                            attachmentFile = model.AttachmentList.FirstOrDefault(x => x.Type == 0);
                            if (attachmentFile != null)
                            {
                                gbxBmpPath = Application.StartupPath + $"\\Attachment\\product\\{attachmentFile.NameTimestamp}";
                                if (!File.Exists(gbxBmpPath + attachmentFile.ExtendName)) gbxBmpPath = "";
                            }
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"图纸路径:{gbxBmpPath}");
                            devContainer.libDefect.add(new DefectTask()
                            {
                                stepIndex= stepIndex,
                                processName= processName,
                                drawingPagePath= gbxBmpPath,
                                index = defectBmpNum++,
                                bmp = bmpLoc.bmp,
                                Xmm = bmpLoc.Xmm,
                                Ymm = bmpLoc.Ymm,
                                cut_size = new System.Drawing.Size(Convert.ToInt32(aarCut_size[0]), Convert.ToInt32(aarCut_size[1])),
                                resize = new System.Drawing.Size(Convert.ToInt32(aarResize[0]), Convert.ToInt32(aarResize[1])),
                                thresholds = processParam.Value<float>("Thresholds"),
                                thresholdsClass = processParam.Value<string>("ThresholdsClass"),
                                recAreaThreshold= getProductAreaThreshol(model), //qxName,面积; qxName,面积; qxName,面积;
                                finishEvent = (res) =>
                                {
                                    if (res.isSucceed)
                                    {
                                        setDgvContentCol(liStatocStepIndex, $"源图索引:{res.index}，缺陷数:{res.defectCount}，处理时间(ms):{string.Join("->", res.stopwatch.Select(i => i.ToString()).ToArray())}");
                                        AddTextEvent($"{res.stepIndex + 1}-{res.processName}", $"缺陷检测完成(源图索引:{res.index})，缺陷数:{res.defectCount}，处理时间(ms):{string.Join("->", res.stopwatch.Select(i => i.ToString()).ToArray())}");
                                        string path;
                                        if (res.defectCount > 0)
                                        { 
                                            //UI显示小图 (含统计缺陷类型数量)
                                            showDefectSmallBmps(res.bmps_tag, res.bmps_cut, res.Xmm, res.Ymm, res.informationList);
                                            if (res.defectInfor2RestorationDeskPage != null && res.defectInfor2RestorationDeskPage.Count > 0)
                                            {
                                                AddTextEvent($"{res.stepIndex + 1}-{res.processName}", $"转换后坐标数据：{JsonConvert.SerializeObject(res.defectInfor2RestorationDeskPage)}");
                                                if (order.DefectInfoList == null) 
                                                    order.DefectInfoList = new List<DefectInfo>();
                                                foreach (var item in res.defectInfor2RestorationDeskPage) 
                                                    order.DefectInfoList.Add(new DefectInfo()
                                                    {
                                                        Type = 0,
                                                        Code = item[3],
                                                        X = double.Parse(item[1]),
                                                        Y = double.Parse(item[2]),
                                                        ZXD = double.Parse(item[4]),
                                                        ModifyUserCode = Config.loginUser.Code,
                                                        CreateUserCode = Config.loginUser.Code,
                                                    });
                                            }
                                            //保存原始大图
                                            if (Config.Defect_SavePath != "" && Directory.Exists(Config.Defect_SavePath))
                                            {
                                                path = Util.createSubDir(Config.Defect_SavePath, new List<string> { order.CreateTime.ToString("yyyyMMdd"), order.SN });
                                                path += $"Defect_SN{order.SN}_I{res.index}_X{res.Xmm}_Y{res.Ymm}_C{res.defectCount}_{ model.StepInfo.Name}";
                                                //API.CopyFile(res.file_path, defectFileName + ".bmp", false);//比.NET(File.Copy)更快
                                                //res.bmp.SaveImage(path + ".bmp");//opencv保存格式与BMP不同
                                                OpenCvSharp.Extensions.BitmapConverter.ToBitmap(res.bmp).Save(path + ".bmp", ImageFormat.Bmp);
                                                File.WriteAllText(path + ".json", JsonConvert.SerializeObject(res.informationList));
                                            }

                                            //保存小图
                                            if (Config.Defect_Small_SavePath != "" && Directory.Exists(Config.Defect_Small_SavePath))
                                            {
                                                path = Util.createSubDir(Config.Defect_Small_SavePath, new List<string> { order.CreateTime.ToString("yyyyMMdd"), order.SN });
                                                path += $"Defect_SN{order.SN}_I{res.index}_X{res.Xmm}_Y{res.Ymm}_{ model.StepInfo.Name}";
                                                for (int i = 0; i < res.bmps_tag.Count(); i++)
                                                    res.bmps_tag[i].Save(path + $"_i{i}.bmp", ImageFormat.Bmp);
                                            }

                                            //保存压缩大图 -- 用于修复台调用
                                            if (Config.Defect_Compress_SavePath != "" && Directory.Exists(Config.Defect_Compress_SavePath))
                                            {
                                                path = Util.createSubDir(Config.Defect_Compress_SavePath, new List<string> { order.CreateTime.ToString("yyyyMMdd"), order.SN });
                                                path += $"Defect_SN{order.SN}_I{res.index}_X{res.Xmm}_Y{res.Ymm}_C{res.defectCount}_{ model.StepInfo.Name}";
                                                //res.bmpCompress.SaveImage(path + ".bmp");//opencv保存格式与BMP不同
                                                OpenCvSharp.Extensions.BitmapConverter.ToBitmap(res.bmpCompress).Save(path + ".bmp", ImageFormat.Bmp);
                                                File.WriteAllText(path + ".json", JsonConvert.SerializeObject(res.defectInfor2RestorationDesk));
                                            }
                                        }
                                        else//没有缺陷
                                        { 
                                            if (Config.SaveAllDefectImg && Config.Defect_SavePath != "" && Directory.Exists(Config.Defect_SavePath))
                                            {
                                                path = Util.createSubDir(Config.Defect_SavePath, new List<string> { order.CreateTime.ToString("yyyyMMdd"), order.SN });
                                                path += $"Defect_SN{order.SN}_I{res.index}_X{res.Xmm}_Y{res.Ymm}_C{res.defectCount}_{ model.StepInfo.Name}";
                                                //API.CopyFile(res.file_path, defectFileName + ".bmp", false);//比.NET(File.Copy)更快
                                                //res.bmp.SaveImage(path + ".bmp");//opencv保存格式与BMP不同
                                                OpenCvSharp.Extensions.BitmapConverter.ToBitmap(res.bmp).Save(path + ".bmp", ImageFormat.Bmp);

                                            }
                                        }
                                    }
                                    else
                                    {
                                        setDgvContentCol(liStatocStepIndex, $"失败:{res.resultInfo}");
                                        AddTextEvent($"{res.stepIndex + 1}-{res.processName}", $"缺陷检测失败:{res.resultInfo}");
                                        //warning(WarningEnum.Low);//暂停 这里不能暂停，stepIndex和scannerBmpQueue队列也不对了
                                    }
                                    defectBmpNumResult++;
                                    foreach (var item in res.bmps_cut)
                                        item.Dispose();
                                    res.bmp.Dispose(); 
                                    res.bmp = null;
                                    res.bmps_tag = null;
                                    if (res.bmpCompress != null)
                                    {
                                        res.bmpCompress.Dispose();
                                        res.bmpCompress = null;
                                    }
                                    System.GC.Collect();
                                }
                            });
                            #endregion
                            break;
                        case "For":
                            #region
                            long UniqueId = processParam.Value<long>("UniqueId");
                            int GotoStepIndex = processParam.Value<int>("GotoStepIndex");//1-n
                            int LimitNum = processParam.Value<int>("LimitNum");//1-n
                            bool Reset = processParam.Value<bool>("Reset");
                            if (GotoStepIndex - 1 == stepIndex)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"For死循环！！！");
                                warning(WarningEnum.High);
                                return stepIndex;
                            }
                            if (!devContainer.libFor.dicData.ContainsKey(UniqueId))
                                devContainer.libFor.dicData.Add(UniqueId, 0);
                            //
                            int Num = devContainer.libFor.dicData[UniqueId];
                            Num++;
                            if (Num <= LimitNum)
                            {
                                if (Num == LimitNum)
                                {
                                    setDgvContentCol(liStatocStepIndex, $"第[{Num}/{LimitNum}]次，循环完成");
                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"第[{Num}/{LimitNum}]次，循环完成。");
                                }
                                else
                                {
                                    setDgvContentCol(liStatocStepIndex, $"第[{Num}/{LimitNum}]次");
                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"第[{Num}/{LimitNum}]次跳转到步骤[{GotoStepIndex}]...");
                                    stepIndex = GotoStepIndex - 2;
                                }
                                devContainer.libFor.dicData[UniqueId] = Num;
                            }
                            else
                            { 
                                setDgvContentCol(liStatocStepIndex, $"已失效不执行");
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"本循环已失效不执行！");
                            }

                            //达到limit重置0
                            if (devContainer.libFor.dicData[UniqueId] >= LimitNum && Reset)
                            {
                                devContainer.libFor.dicData[UniqueId] = 0;
                                setDgvContentCol(liStatocStepIndex, $"第[0/{LimitNum}]次");
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"计数器已重置。");
                            }
                            #endregion
                            break;
                        case "If":
                            #region
                            long UniqueId_if = processParam.Value<long>("UniqueId");
                            int GotoStepIndex_if = processParam.Value<int>("GotoStepIndex");//1-n
                            int LimitNum_if = processParam.Value<int>("LimitNum");//1-n
                            bool Reset_if = processParam.Value<bool>("Reset");
                            if (GotoStepIndex_if - 1 == stepIndex)
                            {
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"If死循环，不可自我跳转！！！");
                                warning(WarningEnum.High);
                                return stepIndex;
                            }
                            //
                            if (!devContainer.libIF.dicData.ContainsKey(UniqueId_if))
                                devContainer.libIF.dicData.Add(UniqueId_if, 0);
                            //
                            int Num_if = devContainer.libIF.dicData[UniqueId_if];
                            Num_if++;
                            if (Num_if <= LimitNum_if)
                            {
                                if (Num_if == LimitNum_if)
                                {
                                    setDgvContentCol(liStatocStepIndex, $"第[{Num_if}/{LimitNum_if}]次，跳转至[{GotoStepIndex_if}]");
                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"计数器[{Num_if}/{LimitNum_if}]，跳转至步骤[{GotoStepIndex_if}]...");
                                    stepIndex = GotoStepIndex_if - 2;
                                }
                                else
                                {
                                    setDgvContentCol(liStatocStepIndex, $"第[{Num_if}/{LimitNum_if}]次，不跳转");
                                    AddTextEvent($"{stepIndex + 1}-{processName}", $"计数器[{Num_if}/{LimitNum_if}]，不跳转。");
                                }
                                //
                                devContainer.libIF.dicData[UniqueId_if] = Num_if;
                            }
                            else
                            {
                                setDgvContentCol(liStatocStepIndex, $"已失效不执行");
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"本IF已失效不执行。");
                            }

                            //达到limit重置0
                            if (devContainer.libIF.dicData[UniqueId_if] >= LimitNum_if && Reset_if)
                            {
                                devContainer.libIF.dicData[UniqueId_if] = 0;
                                setDgvContentCol(liStatocStepIndex, $"第[0/{LimitNum_if}]次"); 
                                AddTextEvent($"{stepIndex + 1}-{processName}", $"计数器已重置。");
                            }
                            #endregion
                            break;
                        default:
                            AddTextEvent($"{stepIndex + 1}-{processName}", $"未知工序：{processInfo.ProcessCode}");
                            warning(WarningEnum.High);
                            return stepIndex;
                    }
                    if (sleepLater > 0) Thread.Sleep((int)sleepLater);
                } 

                //============结束，判断是否自动下料 
                if (stepIndex == processList.Count - 1)
                {
                    //厚度校正 直接更新并保存
                    if (isProductRevise)
                    {
                        isProductRevise = false;
                        if (MessageBox.Show($"厚度校正完成，是否保存？\r\nBase值：{model.HeightBaseDec}", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                        {
                            //保存  //更新指定列
                            if (svcProduct.Update(it => new Product() { HeightBaseDec = model.HeightBaseDec }, it => it.Id == model.Id))
                                AddTextEvent($"产品校正", $"保存成功。");
                            else
                                AddTextEvent($"产品校正", $"保存失败！！");
                        }
                        //下料 
                        currentState = CurrentStateEnum.自动流程结束;
                        gotoDownPT();
                    }
                    else//生产
                    {
                        //等待缺陷图显示完成
                        while (defectBmpNum != defectBmpNumResult)
                            Thread.Sleep(100);

                        //判断是否合格 
                        DefectCodeEnum defectCode;
                        string defectNames = "";
                        if (model.QualifiedCriterionList!= null && model.QualifiedCriterionList.Count >0) 
                        {
                            int itemDefectCount;
                            foreach (var item in model.QualifiedCriterionList)
                            {
                                defectCode = EnumUtil.Convert2Enum<DefectCodeEnum>(item.DefectCode);
                                itemDefectCount = getDefectCountFromCode(order, defectCode);
                                if (item.MaxDefectCount>-1 && itemDefectCount > item.MaxDefectCount)
                                {
                                    order.Qualified = false;
                                    defectNames += $"{((DefectNameEnum)(int)defectCode).ToString()}({itemDefectCount}),"; 
                                }
                            }
                        }

                        stopWatch.Stop();
                        long timeLen = stopWatch.ElapsedMilliseconds / 1000;
                        this.BeginInvoke(new System.Action(() =>
                        {
                            lblTimeLen.Visible = true;
                            lblTimeLen.Text = $"检测时长: {timeLen} 秒";
                            if (defectNames != "")
                            {
                                lblDefectResult.Text = "未通过";
                                lblDefectResultCount.Text = defectNames.Substring(0, defectNames.Length - 1);
                                this.toolTip1.SetToolTip(lblDefectResultCount, lblDefectResultCount.Text);
                                lblDefectResult.ForeColor = lblDefectResultCount.ForeColor = Color.Red;
                            }
                        }));  
                        order.TimeLen = timeLen;
                        order.DefectCount = (int)this.gboxDefectList.Tag;
                        order.Succeed = true;
                        order.ModifyUserCode = order.CreateUserCode = Config.loginUser.Code;

                        order.Abnormalities = "";//无异常
                        order.RepairCode = "";//无修复人员
                        //如SN检测已存在，先删除
                        var oldSNOrder= svcOrder.GetFirst(m=> m.SN==order.SN);
                        if(oldSNOrder!=null)
                        {
                            AddTextEvent("删除记录", $"删除上一重复SN检测记录:SN={oldSNOrder.SN}, Date={oldSNOrder.CreateTime}");
                            svcOrder.DelNav(oldSNOrder);
                        }
                        if (!svcOrder.InsertNav(order))//导航插入
                            AddTextEvent("保存失败", $"保存生产记录失败！");
                        //更新本批次检测数量
                        if (!string.IsNullOrWhiteSpace(model.BatchId))
                        {
                            var expOrder = Expressionable.Create<Order>()
                                .And(m => m.ProductId == model.Id)
                                .And(m => m.BatchId == model.BatchId)
                                .ToExpression();//注意 这一句 不能少
                            currProductModel.CompleteCount = svcOrder.Count(expOrder);
                        }
                        //以主键为条件更新CompleteCount单列值 
                        svcProduct.Update(it => new Product() { CompleteCount = currProductModel.CompleteCount }, it => it.Id == currProductModel.Id);

                        if (Config.MakeTag && order.DefectCount > 0)//要打标 and 有缺陷图
                        {
                            AddTextEvent("完成", $"工序结束，用时 {order.TimeLen} 秒；缺陷 {order.DefectCount} 张.");
                                //+(Config.MakeTag ?  "开始自动打标...":"手动打标..."));
                            currentPT = CurrentPTEnum.MakeTag;
                            currentState = CurrentStateEnum.打标中;
                            setButtonEnabled(tsbtnGoDownPT, true);
                            setButtonEnabled(tsbtnReset, true);
                            setButtonEnabled(btnMakeTags, true);
                            //if (!Config.MakeTag)//自动打标
                            {
                                this.Invoke(new System.Action(() =>
                                {
                                    btnMakeTag_Click(null, null);
                                })); 
                            }
                        }
                        else//下料
                        {
                            AddTextEvent("完成", $"用时 {order.TimeLen} 秒，进行下料...");
                            currentState = CurrentStateEnum.自动流程结束; 
                            gotoDownPT();
                        }
                    }

                    currProcessIndex = -1;
                    return -1;
                }
                else //继续
                {
                    return ++stepIndex;
                    //return nextProcess(model, ++stepIndex);
                }
            }
            catch (Exception ex)
            {
                AddTextEvent("工序", $"[{stepIndex+1}] Err:" + ex.Message+"\n"+ex.StackTrace);
                warning(WarningEnum.Low);
                return -2;
            }
        }
        private Dictionary<string, float> getProductAreaThreshol(Product m)
        {
            Dictionary<string, float> dic = new Dictionary<string, float>();
            foreach(var item in m.QualifiedCriterionList)
                dic.Add(item.DefectCode, (float)(item.Size * 25.4 * 25.4 / m.HoleCount / m.HoleCount) );//网目 => mm^2

            //全缺陷项
            var lstDefect = Utils.EnumUtil.GetArrayList<DefectCodeEnum>();
            foreach (DictionaryEntry item in lstDefect)
            {
                string code = item.Value.ToString();
                if (!dic.ContainsKey(code))
                    dic.Add(code, 0); 
            }

            return dic;
        }
        private void setDgvContentCol(int rowIndex, string info)
        {
            int row = rowIndex;//why -1 ???
            this.Invoke(new System.Action(() =>
            {
                this.dgvProcess.Rows[row].Cells["colValue"].Value = info;
            }));
        }
        /// <summary>
        /// 报警,只响应low,high
        /// </summary>
        private void warning(WarningEnum level, bool buzzer = true)
        {
            if (level == WarningEnum.Normal)
                return;

            lock(myLock)
                warningLevel = level;
            if (level == WarningEnum.Low)//暂停
            {
                currentState = CurrentStateEnum.暂停;
                pauseCommand(buzzer);
            }
            else if (level == WarningEnum.High)//急停
            {
                currentState = CurrentStateEnum.急停;
                devContainer.devAxis.stopNow();

                stopNowCommand();
            }

            //启用报警消除按钮
            this.Invoke(new System.Action(() =>
            {
                tsbtnWarning.Enabled = true;
            }));
        }  
        private void updateTensionValue(double upperLimit,double lowerLimit)
        {
            int count = lstTension.Count();
            if (count < 1) return;
            switch (count)
            {
                case 1:
                    order.Tension1 = lstTension[count - 1]; 
                    AddTextEvent("张力值", $"张力1:{order.Tension1}");
                    break;
                case 2:
                    order.Tension2 = lstTension[count - 1];
                    AddTextEvent("张力值", $"张力2:{order.Tension2}");
                    break;
                case 3:
                    order.Tension3 = lstTension[count - 1];
                    AddTextEvent("张力值", $"张力3:{order.Tension3}");
                    break;
                case 4:
                    order.Tension4 = lstTension[count - 1];
                    AddTextEvent("张力值", $"张力4:{order.Tension4}");
                    break;
                case 5:
                    order.Tension5 = lstTension[count - 1];
                    AddTextEvent("张力值", $"张力5:{order.Tension5}");
                    break;
            }

            double value = Math.Round(lstTension.Average(), 2);
            double valueMax = lstTension.Max();
            double valueMin = lstTension.Min();
            if (order != null) order.TensionValue = value;
            Color color = (upperLimit+ lowerLimit>0 && (value > upperLimit || value < lowerLimit)) ? Color.Red : Color.White;
            Color colorMax = (upperLimit + lowerLimit > 0 && (valueMax > upperLimit || valueMax < lowerLimit)) ? Color.Red : Color.White;
            Color colorMin = (upperLimit + lowerLimit > 0 && (valueMin > upperLimit || valueMin < lowerLimit)) ? Color.Red : Color.White;
            this.BeginInvoke(new System.Action(() =>
            { 
                this.lblTension.Text = Math.Round(value, 2).ToString(); 
                this.lblTension.ForeColor = color;

                this.lblTensionMax.Text = Math.Round(valueMax, 2).ToString();
                this.lblTensionMax.ForeColor = colorMax;

                this.lblTensionMin.Text = Math.Round(valueMin, 2).ToString();
                this.lblTensionMin.ForeColor = colorMin;
            }));
            //不合格
            if (color == Color.Red) order.Qualified = false;
        }
        private void updateHeightValue(double upperLimit, double lowerLimit)
        {
            int count = lstHeight.Count();
            if (count < 1) return;
            AddTextEvent("厚度", string.Join(",", lstHeight));
            switch (count)
            {
                case 1:
                    order.Height1 = lstHeight[count - 1];
                    AddTextEvent("厚度值", $"厚度1:{order.Height1}");
                    break;
                case 2:
                    order.Height2 = lstHeight[count - 1];
                    AddTextEvent("厚度值", $"厚度2:{order.Height2}");
                    break;
                case 3:
                    order.Height3 = lstHeight[count - 1];
                    AddTextEvent("厚度值", $"厚度3:{order.Height3}");
                    break;
                case 4:
                    order.Height4 = lstHeight[count - 1];
                    AddTextEvent("厚度值", $"厚度4:{order.Height4}");
                    break;
                case 5:
                    order.Height5 = lstHeight[count - 1];
                    AddTextEvent("厚度值", $"厚度5:{order.Height5}");
                    break;
            }

            double value = Math.Round(lstHeight.Average(), 2);
            double valueMax = lstHeight.Max();
            double valueMin = lstHeight.Min();
            if (order != null) order.HeightValue = value;
            Color color = (upperLimit + lowerLimit > 0 && (value > upperLimit || value < lowerLimit)) ? Color.Red : Color.White;
            Color colorMax = (upperLimit + lowerLimit > 0 && (valueMax > upperLimit || valueMax < lowerLimit)) ? Color.Red : Color.White;
            Color colorMin = (upperLimit + lowerLimit > 0 && (valueMin > upperLimit || valueMin < lowerLimit)) ? Color.Red : Color.White;
            this.Invoke(new System.Action(() =>
            {
                this.lblHeight.Text = Math.Round(value, 2).ToString();
                this.lblHeight.ForeColor = color;

                this.lblHeightMax.Text = Math.Round(valueMax, 2).ToString();
                this.lblHeightMax.ForeColor = colorMax;

                this.lblHeightMin.Text = Math.Round(valueMin, 2).ToString();
                this.lblHeightMin.ForeColor = colorMin;
            }));
            //不合格
            if (color == Color.Red) order.Qualified = false;
        }
        private void updateLineWidthValue(double upperLimit, double lowerLimit)
        {
            int count = lstLineWidth.Count();
            if (count < 1) return; 
            switch (count)
            {
                case 1:
                    order.LineWidth1 = lstLineWidth[count - 1];
                    AddTextEvent("线宽值", $"线宽1:{order.LineWidth1}");
                    break;
                case 2:
                    order.LineWidth2 = lstLineWidth[count - 1];
                    AddTextEvent("线宽值", $"线宽2:{order.LineWidth2}");
                    break;
                case 3:
                    order.LineWidth3 = lstLineWidth[count - 1];
                    AddTextEvent("线宽值", $"线宽3:{order.LineWidth3}");
                    break;
                case 4:
                    order.LineWidth4 = lstLineWidth[count - 1];
                    AddTextEvent("线宽值", $"线宽4:{order.LineWidth4}");
                    break;
                case 5:
                    order.LineWidth5 = lstLineWidth[count - 1];
                    AddTextEvent("线宽值", $"线宽5:{order.LineWidth5}");
                    break;
                case 6:
                    order.LineWidth6 = lstLineWidth[count - 1];
                    AddTextEvent("线宽值", $"线宽6:{order.LineWidth6}");
                    break;
                case 7:
                    order.LineWidth7 = lstLineWidth[count - 1];
                    AddTextEvent("线宽值", $"线宽7:{order.LineWidth7}");
                    break;
                case 8:
                    order.LineWidth8 = lstLineWidth[count - 1];
                    AddTextEvent("线宽值", $"线宽8:{order.LineWidth8}");
                    break;
                case 9:
                    order.LineWidth9 = lstLineWidth[count - 1];
                    AddTextEvent("线宽值", $"线宽9:{order.LineWidth9}");
                    break;
            }
            List<double> lstValidValue = new List<double>(); 
            for (int i = 0; i < count; i++)
            {
                if (lstLineWidth[i] > 0) 
                    lstValidValue.Add(lstLineWidth[i]); 
            }
            if (lstValidValue.Count < 1) return;//WLQ 前面失败时会向lstLineWidth.add(0)

            double value = Math.Round(lstValidValue.Average(), 2); 
            double valueMax = lstValidValue.Max();
            double valueMin = lstValidValue.Min();
            if (order != null) order.LineWidthValue = value;
            Color color = (upperLimit + lowerLimit > 0 && (value > upperLimit || value < lowerLimit)) ? Color.Red : Color.White;
            Color colorMax = (upperLimit + lowerLimit > 0 && (valueMax > upperLimit || valueMax < lowerLimit)) ? Color.Red : Color.White;
            Color colorMin = (upperLimit + lowerLimit > 0 && (valueMin > upperLimit || valueMin < lowerLimit)) ? Color.Red : Color.White;
            this.Invoke(new System.Action(() =>
            {
                this.lblLineWidth.Text = Math.Round(value, 2).ToString();
                this.lblLineWidth.ForeColor = color;

                this.lblLineWidthMax.Text = Math.Round(valueMax, 2).ToString();
                this.lblLineWidthMax.ForeColor = colorMax;

                this.lblLineWidthMin.Text = Math.Round(valueMin, 2).ToString();
                this.lblLineWidthMin.ForeColor = colorMin;
            }));
            //不合格
            if (color == Color.Red) order.Qualified = false;
        }
        private void updatePTValue(double upperLimit, double lowerLimit)
        {
            //每次加2个：PT1,PT2 
            int count = lstPT.Count();
            if (count < 1) return; 
            switch (count)
            {
                case 2:
                    order.PT1 = lstPT[count - 2];
                    order.PT2 = lstPT[count - 1];
                    AddTextEvent("PT值",$"PT1:{order.PT1},PT2:{order.PT2}");
                    break;
                case 4:
                    order.PT3 = lstPT[count - 2];
                    order.PT4 = lstPT[count - 1];
                    AddTextEvent("PT值", $"PT3:{order.PT3},PT4:{order.PT4}");
                    break;
                case 6:
                    order.PT5 = lstPT[count - 2];
                    order.PT6 = lstPT[count - 1];
                    AddTextEvent("PT值", $"PT5:{order.PT5},PT6:{order.PT6}");
                    break; 
            }

            List<double> lstValidValue = new List<double>();
            for (int i = 0; i < count; i++)
            {
                if (lstPT[i] > 0)
                    lstValidValue.Add(lstPT[i]);
            }
            double value = Math.Round(lstValidValue.Average(), 4);
            double valueMax = lstValidValue.Max();
            double valueMin = lstValidValue.Min();
            if (order != null) order.PTValue = value;
            Color color = (upperLimit + lowerLimit > 0 && (value > upperLimit || value < lowerLimit)) ? Color.Red : Color.White;
            Color colorMax = (upperLimit + lowerLimit > 0 && (valueMax > upperLimit || valueMax < lowerLimit)) ? Color.Red : Color.White;
            Color colorMin = (upperLimit + lowerLimit > 0 && (valueMin > upperLimit || valueMin < lowerLimit)) ? Color.Red : Color.White;
            this.Invoke(new System.Action(() =>
            {
                this.lblPT.Text = value.ToString();
                this.lblPT.ForeColor = color;

                this.lblPTMax.Text = Math.Round(valueMax, 4).ToString();
                this.lblPTMax.ForeColor = colorMax;

                this.lblPTMin.Text = Math.Round(valueMin, 4).ToString();
                this.lblPTMin.ForeColor = colorMin;
            }));
            //不合格
            if (color == Color.Red) order.Qualified = false;
        }
        private void updatePTValueTest(double upperLimit, double lowerLimit)
        {
            //每次加2个：PT1,PT2 
            int count = lstPT.Count();
            if (count < 1) return;
            switch (count)
            {
                case 1:
                    order.PT1 = lstPT[count - 1];
                    AddTextEvent("PT1", $"PT1:{order.PT1}");
                    break;
                case 2:
                    order.PT2 = lstPT[count - 1];
                    AddTextEvent("PT2", $"PT2:{order.PT2}");
                    break;
                case 3:
                    order.PT3 = lstPT[count - 1];
                    order.PT4 = 0;
                    AddTextEvent("PT3", $"PT3:{order.PT3}");
                    break;
                case 4:
                    order.PT5 = lstPT[count - 1];
                    AddTextEvent("PT4", $"PT4:{order.PT5}");
                    break;
                case 5:
                    order.PT6 = lstPT[count - 1];
                    AddTextEvent("PT5", $"PT5:{order.PT6}");
                    break;
            }

            List<double> lstValidValue = new List<double>();
            for (int i = 0; i < count; i++)
            {
                if (lstPT[i] > 0)
                    lstValidValue.Add(lstPT[i]);
            }
            double value = Math.Round(lstValidValue.Average(), 4);
            double valueMax = lstValidValue.Max();
            double valueMin = lstValidValue.Min();
            if (order != null) order.PTValue = value;
            Color color = (upperLimit + lowerLimit > 0 && (value > upperLimit || value < lowerLimit)) ? Color.Red : Color.White;
            Color colorMax = (upperLimit + lowerLimit > 0 && (valueMax > upperLimit || valueMax < lowerLimit)) ? Color.Red : Color.White;
            Color colorMin = (upperLimit + lowerLimit > 0 && (valueMin > upperLimit || valueMin < lowerLimit)) ? Color.Red : Color.White;
            this.Invoke(new System.Action(() =>
            {
                this.lblPT.Text = value.ToString();
                this.lblPT.ForeColor = color;

                this.lblPTMax.Text = Math.Round(valueMax, 4).ToString();
                this.lblPTMax.ForeColor = colorMax;

                this.lblPTMin.Text = Math.Round(valueMin, 4).ToString();
                this.lblPTMin.ForeColor = colorMin;
            }));
            //不合格
            if (color == Color.Red) order.Qualified = false;
        }
        private void updateCompareResult(bool compareResult)
        {
            this.Invoke(new System.Action(() =>
            { 
                this.lblCompareResult.Text = (order.CompareResult==1 ? "通过" : "未通过");
                if (!compareResult)
                {
                    this.lblCompareResultCount.Text = (Convert.ToInt16(lblCompareResultCount.Text) + 1).ToString();
                    this.lblCompareResult.ForeColor = lblCompareResultCount.ForeColor=Color.Red;
                    //不合格
                    order.Qualified = false;
                }
            }));
        }
        private void resetUIValue()
        {
            this.Invoke(new System.Action(() =>
            {
                //保持产品名称和料号
                //txtProductName.Text = txtProductCode.Text = txtProductSN.Text = "";
                txtProductSN.Text = "";

                lstHeight.Clear();
                lstLineWidth.Clear();
                lstTension.Clear();
                lstPT.Clear();
                this.lblTension.Text = this.lblHeight.Text = this.lblLineWidth.Text = "0.00";
                this.lblPT.Text = this.lblPTMax.Text= this.lblPTMin.Text="0.0000";
                this.lblCompareResult.Text =this.lblDefectResult.Text = "无";

                this.lblTensionMax.Text = this.lblHeightMax.Text = this.lblLineWidthMax.Text
                    = this.lblTensionMin.Text = this.lblHeightMin.Text = this.lblLineWidthMin.Text = "0.00";

                this.lblCompareResultCount.Text = "0";
                this.lblDefectResultCount.Text = "无"; 
                this.toolTip1.SetToolTip(lblDefectResultCount, lblDefectResultCount.Text);
                //
                this.lblTension.ForeColor = this.lblHeight.ForeColor = this.lblLineWidth.ForeColor = this.lblPT.ForeColor = this.lblCompareResult.ForeColor= this.lblDefectResult.ForeColor=Color.White; 
                this.lblTensionMax.ForeColor = this.lblHeightMax.ForeColor = this.lblLineWidthMax.ForeColor = this.lblPTMax.ForeColor 
                    = this.lblTensionMin.ForeColor = this.lblHeightMin.ForeColor = this.lblLineWidthMin.ForeColor = this.lblPTMin.ForeColor = Color.White;
                this.lblCompareResultCount.ForeColor = this.lblDefectResultCount.ForeColor=Color.White;

                this.dgvProcess.DataSource = null;
                this.lstLog.Items.Clear();
                this.clearDefectBmps();

                this.picScanner1.Image = this.picScanner2.Image = null;
                picScanner1.Refresh();
                picScanner2.Refresh();

                this.btnMakeTags.Enabled = false;
            }));
        }
        //info:  [{"1":[["92.7542","80.85799","99.54083","86.05363","dk","0.52"]]},
        private void showDefectSmallBmps(Bitmap[] bmps, Mat[] bmps_cut, double Xmm, double Ymm, List<Dictionary<int, List<string>[]>> info)
        {
            this.Invoke(new System.Action(() =>
            {
                int imgWidth = this.pnlBmpList.ClientSize.Width - 50;
                int imgHeight = (int)((bmps[0].Height * 1.0f / bmps[0].Width) * imgWidth + 0.5);
                int splitWidth = 20;
                int pnlWidth = this.pnlBmpList.ClientSize.Width;
                int index = (int)this.gboxDefectList.Tag; 
                for (int x = 0; x < info.Count(); x++)
                {
                    foreach (var item in info[x]) //单个info[x] = {"1":[["92.7542","80.85799","99.54083","86.05363","dk","0.52"]]}
                    {
                        //统计缺陷类型
                        countDefectClass(item.Value);//[["92.7542","80.85799","99.54083","86.05363","dk","0.52"]]

                        //
                        PictureBox picbox = new PictureBox();
                        picbox.Width =   imgWidth;
                        picbox.Height =  imgHeight;
                        CheckBox checkBox = new CheckBox();
                        picbox.Image = (Bitmap)bmps[x].Clone();
                        picbox.Name = "imgDefect_" + index;
                        picbox.Tag = item.Key + "," + Xmm + "," + Ymm;

                        checkBox.Tag = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(bmps_cut[item.Key]); //Mat转Bitmap ;//原始小图
                        checkBox.Name = "chkDefect_" + index;
                        checkBox.Text = $"第{index + 1}张";
                        checkBox.CheckedChanged += new System.EventHandler(this.CheckBox_CheckedChanged);
                        if (Config.MakeTag)//自动打标
                        {
                            checkBox.Checked = true;
                            checkBox.Enabled = false;
                        }
                        picbox.Click += new EventHandler(defectBmpBox_Click); 
                        picbox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
                        picbox.BorderStyle = BorderStyle.FixedSingle;
                        picbox.Margin = new Padding((pnlWidth - picbox.Width) / 2, splitWidth,0, 0);
                        checkBox.Margin = new Padding((pnlWidth - checkBox.Width) / 2 + splitWidth,
                            0, 0, 0);
                         
                        //
                        //if (this.pnlBmpList.Controls.Count > 0)
                        //{
                        //    if ((this.pnlBmpList.Controls[this.pnlBmpList.Controls.Count - 1].Right + imgWidth + splitWidth) > this.pnlBmpList.Width)
                        //        picbox.Location = new System.Drawing.Point(splitWidth, this.pnlBmpList.Controls[this.pnlBmpList.Controls.Count - 1].Bottom + splitWidth);
                        //    else
                        //        picbox.Location = new System.Drawing.Point(this.pnlBmpList.Controls[this.pnlBmpList.Controls.Count - 1].Right + splitWidth, this.pnlBmpList.Controls[this.pnlBmpList.Controls.Count - 1].Location.Y);
                        //}
                        //else
                        //{
                        //    picbox.Location = new System.Drawing.Point(splitWidth, 0);
                        //}
                        //checkBox.Location = new System.Drawing.Point(picbox.Location.X + 5, picbox.Location.Y + 5);
                        //checkBox.ForeColor = Color.Black; 
                        this.pnlBmpList.Controls.Add(picbox);
                        this.pnlBmpList.Controls.Add(checkBox);
                        break;
                    }
                     
                    //
                    index++;
                };
                this.gboxDefectList.Tag = index;
                this.gboxDefectList.Text = $"缺陷图像:{index} 张";
                if (Config.MakeTag) {
                    lblDefectAddTag.Text = $"待打标数:{index}";
                    lblDefectAddTag.Tag = index;
                }
                pnlBmpList.VerticalScroll.Value = pnlBmpList.VerticalScroll.Maximum;
            }));
        }
        //统计缺陷类型  [["92.7542","80.85799","99.54083","86.05363","dk","0.52"]]
        private void countDefectClass(List<string>[] list)
        {
            string className;
            for(int i=0; i<list.Length; i++)
            {
                className = list[i][4];
                switch (className)
                {
                    case "dk":
                        order.DKCount++;
                        break;
                    case "zw":
                        order.ZWCount++;
                        break;
                    case "gsyc":
                        order.GSYCCount++;
                        break;
                    case "xws":
                        order.XWSCount++;
                        break;
                    case "qk":
                        order.QKCount++;
                        break;
                    case "zk":
                        order.ZKCount++;
                        break;
                    case "pp":
                        order.PPCount++;
                        break;
                    case "hs":
                        order.HSCount++;
                        break;
                    case "yx":
                        order.YXCount++;
                        break;
                    case "xb":
                        order.XBCount++;
                        break;
                    case "sx":
                        order.SXCount++;
                        break;
                }
            }
        }
        private int getDefectCountFromCode(Order order, DefectCodeEnum defectCodeEnum)
        {
            switch (defectCodeEnum)
            {
                case DefectCodeEnum.dk:
                    return order.DKCount;
                case DefectCodeEnum.zw:
                    return order.ZWCount;
                case DefectCodeEnum.gsyc:
                    return order.GSYCCount;
                case DefectCodeEnum.xws:
                    return order.XWSCount;
                case DefectCodeEnum.qk:
                    return order.QKCount;
                case DefectCodeEnum.zk:
                    return order.ZKCount;
                case DefectCodeEnum.pp:
                    return order.PPCount;
                case DefectCodeEnum.hs:
                    return order.HSCount;
                case DefectCodeEnum.yx:
                    return order.YXCount; 
                case DefectCodeEnum.xb:
                    return order.XBCount;
                case DefectCodeEnum.sx:
                    return order.SXCount;
                default:
                    return 0;
            }
        }
        private void CheckBox_CheckedChanged(object sender, EventArgs e)
        {
            CheckBox ck = (CheckBox)sender;
            int cout = Convert.ToInt32(this.lblDefectAddTag.Tag);
            if (ck.Checked)
                cout++;
            else
                cout--;

            lblDefectAddTag.Text = $"待打标数:{cout}";
            lblDefectAddTag.Tag = cout;
        }

        private void defectBmpBox_Click(object sender, EventArgs e)
        {
            // if(autoMakeTagRuning) //自动打标中不可打开人工达标窗 
            PictureBox picbox = sender as PictureBox; 
            if (!devContainer.state || defectBmpNum != defectBmpNumResult || autoMakeTagRuning || currentState != CurrentStateEnum.自动流程结束)//检测中
            {
                //只查看
                FrmPhoto frm = new FrmPhoto(this.pnlBmpList.Controls, Convert.ToInt32(picbox.Name.Split(new char[] { '_' })[1]));
                frm.ShowDialog(this);
            }
            else//完成
            {
                FrmPhotoTag frm = new FrmPhotoTag(this.pnlBmpList.Controls, Convert.ToInt32(picbox.Name.Split(new char[] { '_' })[1])
                    ,(defectBmpNum == defectBmpNumResult ? this.devContainer : null));
                frm.ShowDialog(this);

                //
                if (devContainer.state && !Config.SkipScannerCC)
                {
                    devContainer.devScannerCC.setMode(false);
                    devContainer.devScannerCC.setPreviewWin(this.picScanner1.Handle);
                }
            }
            
        }
        private void clearDefectBmps()
        {
            this.pnlBmpList.Controls.Clear();
            this.gboxDefectList.Text = $"缺陷图像";
            this.gboxDefectList.Tag = 0;

            this.lblDefectAddTag.Text = "";
            this.lblDefectAddTag.Tag = 0;
        } 
        public delegate void AddTextDelegate(string tag, string msg, WarningEnum level);
        /// <summary>
        /// 异步输出日志
        /// </summary>
        /// <param name="tag">模板标识</param>
        /// <param name="msg">内容</param>
        /// <param name="type"></param>
        private void AddTextEvent(string tag, string msg, WarningEnum level = WarningEnum.Normal)
        {
            try
            {
                if (InvokeRequired)
                {
                    Invoke(new AddTextDelegate(AddTextEvent), new object[]
                    {
                        tag,
                        msg,
                        level
                    });
                }
                else
                {
                    if (tag != null && tag != "")
                        tag = $" - [{tag}]";

                    var now = DateTime.Now;
                    msg = now.ToString("HH:mm:ss fff") + tag + " - " + msg;
                    //cont = MyHelper.subString(cont, 300);

                    //写日志，warn和error日志直接写
                    writeLog(now, level, msg);

                    //
                    //if (type > 0)
                    //    cont = $"<color=\"{(type == 1 ? "Yellow" : "Red")}\">{cont}</color>";

                    msg = (level == WarningEnum.Normal ? "B" : level == WarningEnum.Low ? "Y" : "R") + msg;
                    this.Invoke(new System.Action(() =>
                    {
                        if (this.lstLog.Items.Count > 1000)
                            this.lstLog.Items.Clear();
                        lstLog.Items.Insert(0, msg);
                    }));
                    //日志滚动 
                    //lstLog.SelectedIndex = lstLog.Items.Count - 1; 
                }
            }
            catch (Exception ex)
            {
                //MessageBox.Show("AddTextEvent ex=(" + ex.Message + ")", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, 0);
            }
        }
        private void lstLog_DrawItem(object sender, DrawItemEventArgs e)
        {
            e.DrawBackground();
            if (e.Index < 0) return;

            try
            {
                string str = lstLog.Items[e.Index].ToString();
                e.Graphics.DrawString(str.Substring(1), e.Font,
                    new SolidBrush(str[0] == 'R' ? Color.Red : (str[0] == 'Y' ? Color.Orange : Color.Black)),
                    e.Bounds);
            }
            catch 
            {
                ;
            }
        }
        private void writeLog(DateTime now, WarningEnum level, string text)
        {
            string directory = Config.LogPath + "\\" + DateTime.Now.ToString("yyyyMM") + "\\";
            //if (type == 0) directory = Application.StartupPath + "\\Log\\Info\\";
            //else if (type == 1) directory = Application.StartupPath + "\\Log\\Warn\\";
            //else directory = Application.StartupPath + "\\Log\\Error\\";

            if (!System.IO.Directory.Exists(directory))
                System.IO.Directory.CreateDirectory(directory);

            File.AppendAllText(directory + now.ToString("yyyyMMdd") + ".log", text + "\r\n");
        }

        private void FrmMian_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (devContainer.state)
            {
                MessageBox.Show("请先停止生产后才能关闭程序！", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                e.Cancel = true;
                return;
            }

            if (!bExitApp && MessageBox.Show($"确认退出？", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
            {
                e.Cancel = true;
                return;
            }
        }
        private void FrmMian_FormClosed(object sender, FormClosedEventArgs e)
        {
            webService.stop();
            Application.Exit();
            System.GC.Collect();
            System.Environment.Exit(0);
        }
        private void tsbtnOpenDev_Click(object sender, EventArgs e)
        {
            //FrmGetPosByPic frr = new FrmGetPosByPic(new SizeLibProp());
            //frr.ShowDialog();

            Config.LoadAllConfig();
            //设置程序最小/大线程池
            // Get the current settings. 
            int minWorker, minIOC;
            ThreadPool.GetMinThreads(out minWorker, out minIOC);
            ThreadPool.SetMinThreads(25, minIOC);
            //ThreadPool.SetMaxThreads(256, 256);

            this.tsbtnProductRevise.Enabled = false;
            this.tsbtnOpenDev.Enabled = false; 
            //scannerCBmpIndex = 0;

            isProductRevise = false;
            this.resetUIValue();  
            currProcessIndex = -1;
            currentState = CurrentStateEnum.等待复位;
            warningLevel = WarningEnum.Normal;
            currentPT = CurrentPTEnum.InitPT;
            this.tsbtnWarning.Enabled=this.tsbtnGoDownPT.Enabled=this.tsbtnStart.Enabled=this.tsbtnStopNow.Enabled=this.tsbtnPause.Enabled=this.tsbtnReset.Enabled=false;
            //后台线程回调事件
            devContainer.StateChange = (state, msg) =>
            { 
                if (state)
                {
                    //全局中断
                    devContainer.devIOCard.INEvent = globalBreakEvent;
                    devContainer.OutDebugEvent = (tag, debugInfo) =>
                    {
                        AddTextEvent(tag, debugInfo);
                    };
                    //
                    AddTextEvent("设备启动", "请先进行复位操作！");
                    this.Invoke(new System.Action(() =>
                    {
                        this.tsbtnReset.Enabled = true;
                        tsbtnWarning.Enabled = this.tsbtnStart.Enabled = this.tsbtnGoDownPT.Enabled
                            = this.tsbtnPause.Enabled = this.tsbtnStopNow.Enabled = false;

                        this.tsbtnOpenDev.Enabled = true;
                        this.tsbtnOpenDev.Visible = false;
                        this.tsbtnCloseDev.Visible = true;
                    }));

                    timer.Elapsed += Timer_Elapsed;
                    timer.Interval = 500;
                    timer.Start();
                }
                else
                {
                    AddTextEvent("设备启动", $"启动失败，{msg}", WarningEnum.High);
                    this.Invoke(new System.Action(() =>
                    {
                        this.tsbtnOpenDev.Enabled = true;
                        this.tsbtnOpenDev.Visible = true;
                        this.tsbtnCloseDev.Visible = false;
                    }));
                }

            };
            devContainer.WarningEvent = (level, msg) =>
            {
                AddTextEvent("设备事件", msg, level);
                if (level == WarningEnum.High)
                    warning(level,true);
            }; 
            devContainer.start(this.picScanner1.Handle, this.picScanner2.Handle);
        }

        private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            this.Invoke(new System.Action(() =>
            {
                this.tsAxisState.Text = $"[命令位:{devContainer.devAxis.CmdPos[0]} 反馈位:{devContainer.devAxis.ActualPos[0]}] | " +
                    $"[命令位:{devContainer.devAxis.CmdPos[1]} 反馈位:{devContainer.devAxis.ActualPos[1]}] | " +
                    $"[命令位:{devContainer.devAxis.CmdPos[2]} 反馈位:{devContainer.devAxis.ActualPos[2]}] | " +
                    $"[命令位:{devContainer.devAxis.CmdPos[3]} 反馈位:{devContainer.devAxis.ActualPos[3]}]";
            }));
        }

        private void tsbtnCloseDev_Click(object sender, EventArgs e)
        {
            AddTextEvent("设备停止", $"设备停止..."); 
            this.tsbtnProductRevise.Enabled = false;
            this.tsbtnOpenDev.Visible = true;
            this.tsbtnCloseDev.Visible = false;
            this.btnMakeTags.Enabled = false;
            this.lblTimeLen.Visible = false;

            this.tsbtnReset.Enabled = tsbtnWarning.Enabled = this.tsbtnStart.Enabled = this.tsbtnGoDownPT.Enabled
                = this.tsbtnPause.Enabled = this.tsbtnStopNow.Enabled = false;

            this.tsbtnSizeTag.Enabled = tsbtnSizeImage.Enabled = tsbtnDefectImage.Enabled = false;
            if (devContainer.state)
            {
                devContainer.devIOCard.reset();
                devContainer.io_output(CMDName.IO默认输出);
            }
            timer.Stop();
            devContainer.stop();
        }
        /// <summary>
        /// 全局中断
        /// </summary>
        private void globalBreakEvent(int portIndex, byte data)
        {
            if (compareIOInput(CMDName.启动按钮) && this.tsbtnStart.Enabled)
                startCommand();
            else if (compareIOInput(CMDName.暂停按钮) && this.tsbtnPause.Enabled)
                warning(WarningEnum.Low, false);
            else if (compareIOInput(CMDName.复位按钮) && this.tsbtnReset.Enabled) 
                resetCommand(); 
            else if (compareIOInput(CMDName.张力读取) && currentPT == CurrentPTEnum.Moving)
                readTension();
            else if (!this.disableBuzzer && compareIOInput(CMDName.门磁告警输入))
                warning(WarningEnum.Low, true);
            else if (!this.disableBuzzer && compareIOInput(CMDName.喷墨告警输入))
                warning(WarningEnum.Low, true);
        }
        private bool compareIOInput(CMDName key)
        {
            JObject joJson = Config.CMDProcess[key];
            IODirectionEnum direction = (IODirectionEnum)joJson.Value<int>("Direction");
            if (direction == IODirectionEnum.输入 || direction == IODirectionEnum.输入输出)
            { 
                return Util.compareIOInput(
                    joJson.Value<JArray>("IN_OP_SHOW").ToObject<List<string>>().ToArray(), 
                    devContainer.devIOCard.DIData); 
            }
            return false;
        }
        private void readTension()
        {
            if (Config.SkipTension)
            {
                AddTextEvent("张力读取", $"张力设备禁用，忽略此步骤!");
                return;
            }
            devContainer.io_output(CMDName.张力读取);
            double tensionValue = devContainer.devTension.getValue() + Config.Tension_Offset;//加入张力偏差
            tensionValue=Math.Round(tensionValue, 2);//保留2位小数
            AddTextEvent("张力读取", $"张力值:{tensionValue}"); 
            devContainer.io_output(CMDName.张力读取,false,true,500); 
            lstTension.Add(tensionValue); 
            updateTensionValue(currProductModel.TensionBaseValue + currProductModel.TensionUpFloatValue, currProductModel.TensionBaseValue - currProductModel.TensionDownFloatValue); 
        }
        /// <summary>
        /// 启动
        /// </summary>
        private void startCommand()
        {
            this.setButtonEnabled(this.tsbtnProductRevise, false);
            //2023-10-23 运行前清除过期图片文件
            Config.DelectPictureFile();
            if (!devContainer.state || warningLevel != WarningEnum.Normal || currentState != CurrentStateEnum.等待启动)
                return;
            if (currentPT == CurrentPTEnum.MakeTag)
            {
                AddTextEvent("启动", "打标未结束，非可启动状态！", warningLevel);
                return;
            }

            if (!devContainer.devAxis.isReady())
            {
                AddTextEvent("启动", "轴状态异常，不可启动！", warningLevel);
                return;
            }
            if (!devContainer.devAxis.IsReset)
            {
                AddTextEvent("启动", "轴需先进行复位操作！", WarningEnum.High);
                return;
            }

            devContainer.io_output(CMDName.启动按钮);
            devContainer.io_output(CMDName.绿灯输出);
            devContainer.io_output(CMDName.暂停按钮, false, true, 0);
            devContainer.io_output(CMDName.红灯输出, false, true, 0);
            devContainer.io_output(CMDName.黄灯输出, false, true, 0);

            //暂停-》继续
            if (currProcessIndex >= 0 && currentPT == CurrentPTEnum.Moving && currentState == CurrentStateEnum.等待启动)
            {
                AddTextEvent("启动", $"暂停 -> 继续 当前工序索引：{currProcessIndex + 1}");
                threadProcess = new System.Threading.Thread(() =>
                {
                    int nextStepId = currProcessIndex;
                    do
                    {
                        currentState = CurrentStateEnum.运行中;
                        nextStepId = nextProcess(currProductModel, nextStepId);
                    } while (nextStepId >= 0 && !isBreakProcessRun());
                    //nextProcess(currProductModel, currProcessIndex);
                });
                threadProcess.IsBackground = true;
                threadProcess.Start();
                this.setButtonEnabled(this.tsbtnPause, true);
                this.setButtonEnabled(this.tsbtnStopNow, true);
            }
            else//开始/重新开始
            {
                //校正从复位-》运行，不会新启动
                isProductRevise = false;
                resetUIValue();
                AddTextEvent("启动", "移动至上料位...");
                gotoUpPT();

                this.Invoke(new System.Action(() =>
                {
                    //新开始
                    lblTimeLen.Visible = false;
                    runStep();
                }));
                this.setButtonEnabled(this.tsbtnPause, false);
                this.setButtonEnabled(this.tsbtnStopNow, false);
            }

            this.setButtonEnabled(this.tsbtnStart, false);
            this.setButtonEnabled(this.tsbtnReset, false);
            this.setButtonEnabled(this.tsbtnGoDownPT, false);
        }
        /// <summary>
        /// 暂停
        /// </summary>
        /// <param name="buzzer">是否响蜂鸣</param>
        private void pauseCommand(bool buzzer = false)
        { 
            devContainer.io_output(CMDName.暂停按钮);
            if (buzzer)
            {
                devContainer.io_output(CMDName.红灯输出);
                devContainer.io_output(CMDName.蜂鸣器输出);
            }
            else 
                devContainer.io_output(CMDName.黄灯输出);
            devContainer.io_output(CMDName.启动按钮, false, true, 0);
            devContainer.io_output(CMDName.绿灯输出, false, true, 0);

            this.setButtonEnabled(this.tsbtnWarning, true);
            this.setButtonEnabled(this.tsbtnGoDownPT, false);
            this.setButtonEnabled(this.tsbtnStart, false);
            this.setButtonEnabled(this.tsbtnPause, false);
            this.setButtonEnabled(this.tsbtnStopNow, false);
            this.setButtonEnabled(this.tsbtnReset, false); 
            AddTextEvent("暂停", $"当前工序索引：{currProcessIndex + 1}", WarningEnum.Low); 
        }
        /// <summary>
        /// 急停
        /// </summary> 
        private void stopNowCommand()
        {
            if (!devContainer.state)
            {
                this.setButtonEnabled(this.tsbtnStopNow, false);
                return;
            }

            devContainer.io_output(CMDName.Y轴复位输出, false, true, 0);
            devContainer.io_output(CMDName.红灯输出);
            if (!this.disableBuzzer) devContainer.io_output(CMDName.蜂鸣器输出);
            devContainer.io_output(CMDName.暂停按钮, false, true, 0);
            devContainer.io_output(CMDName.黄灯输出, false, true, 0);
            devContainer.io_output(CMDName.启动按钮, false, true, 0);
            devContainer.io_output(CMDName.绿灯输出, false, true, 0);

            this.setButtonEnabled(this.tsbtnWarning, true);
            this.setButtonEnabled(this.tsbtnGoDownPT, false);
            this.setButtonEnabled(this.tsbtnStart, false);
            this.setButtonEnabled(this.tsbtnPause, false);
            this.setButtonEnabled(this.tsbtnStopNow, false);
            this.setButtonEnabled(this.tsbtnReset, false);
            AddTextEvent("急停", $"当前工序索引：{currProcessIndex + 1}", WarningEnum.High);
        }
        private bool reseting = false;
        private void resetCommand()
        {
            currentState = CurrentStateEnum.等待复位;
            warningLevel = WarningEnum.Normal;
            currentPT = CurrentPTEnum.InitPT;
            this.Invoke(new System.Action(() =>
            {
                lblTimeLen.Visible = false;
                this.tsbtnWarning.Enabled = this.tsbtnGoDownPT.Enabled = this.tsbtnStart.Enabled = this.tsbtnStopNow.Enabled = this.tsbtnPause.Enabled = this.tsbtnReset.Enabled = false;
            }));

            try
            { 
                if (devContainer.state && !reseting)
                { 
                    reseting = true;
                    isProductRevise = false;
                    resetUIValue();
                    AddTextEvent("复位", $"设备复位中...");
                    currProcessIndex = -1;

                    this.setButtonEnabled(this.tsbtnReset, false);

                    // I/O reset后，输出默认状态 
                    AddTextEvent("复位", $"I/O复位中...");
                    if (!devContainer.devIOCard.reset())
                    {
                        AddTextEvent("复位", "I/O板卡复位失败！", WarningEnum.High);
                        return;
                    }
                    if (!devContainer.io_output(CMDName.IO默认输出))
                    {
                        //AddTextEvent("复位", "I/O板卡复位默认值失败！", WarningEnum.High);
                        //return;
                    }

                    //板卡复位输出灯 
                    devContainer.io_output(CMDName.复位按钮);
                    devContainer.io_output(CMDName.黄灯输出, true);

                    //4.5X
                    if(!Config.SkipSmallAxis)
                        devContainer.devSmallAxis.gotoPos((int)SmallAxCmdPos.倍率4_5X, false);

                    //移动Axis前等待厚度传感器收回
                    AddTextEvent("复位", $"检测厚度传感器安全值...");
                    if (!Config.SkipHeight)
                    {
                        while (devContainer.devHeight.getHeight() < (double)Math.Abs(Config.HeightDev_SafeValue))
                        {
                            if (!devContainer.state || currentState != CurrentStateEnum.等待复位)
                                return;
                            Thread.Sleep(100);
                            Application.DoEvents();
                        }
                    }

                    //goto ORG
                    AddTextEvent("复位", $"轴正在回原点...");
                    devContainer.devAxis.closeJogMode();
                    devContainer.devAxis.resetAxisState(-1);//reset state 
                    //AddTextEvent("复位", $"重置轴状态...");
                    for (int i = 0; i < Config.Axis_HomeMode.Length; i++)
                    {
                        //到起始位速度使用回HOME速度
                        devContainer.devAxis.setAxisVelParam((double)Config.Axis_HomeVelLow[i], (double)Config.Axis_HomeVelHigh[i],
                                 (double)Config.Axis_HomeAcc[i], (double)Config.Axis_HomeDec[i], i);
                        if (i != 2)
                        { 
                            //devContainer.devAxis.setAxisVelParam(40000,200000,5000000, 5000000,i,true);
                            devContainer.devAxis.home(i, (uint)Config.Axis_HomeMode[i], (uint)Config.Axis_HomeDir[i]);
                        }
                        else//Y轴 I/O控制回HOME
                        {
                            devContainer.io_output(CMDName.Y轴复位输出);
                        }
                    }
                    AddTextEvent("复位", $"等待轴状态完成...");
                    while (!devContainer.devAxis.isReady())
                    {
                        if (!devContainer.state || currentState != CurrentStateEnum.等待复位)
                        {
                            AddTextEvent("复位", $"当前状态："+ ((CurrentStateEnum)currentState).ToString());
                            return;
                        }
                        //AddTextEvent("复位", $"轴0状态:"+ ((AxisState)devContainer.devAxis.AxState[0]).ToString());
                        //AddTextEvent("复位", $"轴1状态:" + ((AxisState)devContainer.devAxis.AxState[1]).ToString());
                        //AddTextEvent("复位", $"轴2状态:" + ((AxisState)devContainer.devAxis.AxState[2]).ToString());
                        //AddTextEvent("复位", $"轴3状态:" + ((AxisState)devContainer.devAxis.AxState[3]).ToString());

                        Thread.Sleep(1000);
                        Application.DoEvents();
                    }
                    //判断Y轴回原点完成I/O信号
                    AddTextEvent("复位", $"等待Y轴回HOME完成...");
                    while (!this.compareIOInput(CMDName.Y轴复位完成输入))
                    {
                        Thread.Sleep(100);
                        Application.DoEvents();
                    } 
                    AddTextEvent("复位", $"Y轴回HOME已完成.");
                    devContainer.io_output(CMDName.Y轴复位输出, false, true, 0);
                    AddTextEvent("复位", $"重置Y轴反馈位置与命令位置.");
                    devContainer.devAxis.resetCmdPosition(2);
                    devContainer.devAxis.resetActualPosition(2);

                    //
                    if (devContainer.devAxis.isError())
                        throw new Exception("轴回原点失败！");

                    if (Config.OpenAxisXCalibration)
                    {
                        //2023-11-29 加入X1X2回原之后启动补偿
                        devContainer.io_output(CMDName.X1轴复位输出, false, true, 300);
                        devContainer.io_output(CMDName.X2轴复位输出, false, true, 300);
                    }

                    // 
                    //goto InitPT  
                    //AddTextEvent("复位", $"轴回原点完成，回到初始位...");
                    JArray arrPT = Config.joPTSetting.Value<JArray>("initPT");
                    for (int i = 0; i < arrPT.Count; i++)
                    {
                        if (!devContainer.state || currentState != CurrentStateEnum.等待复位)
                            return;

                        AddTextEvent($"复位", $"轴{i}准备运动至初始位:{(double)arrPT[i]}(当前轴状态:{((AxisState)devContainer.devAxis.AxState[i]).ToString()})...");
                        devContainer.devAxis.move_ptp(i, (double)arrPT[i], AxMoveMode.绝对位置);
                    } 
                    while (!devContainer.devAxis.isReady())
                    {
                        if (!devContainer.state || warningLevel != WarningEnum.Normal)
                            return;
                        Thread.Sleep(100);
                    }
                    if (devContainer.devAxis.isError())
                        throw new Exception("轴移动至初始位失败！");
                     
                    //
                    this.setButtonEnabled(this.tsbtnReset, true);
                    this.setButtonEnabled(this.tsbtnStart, true);
                    this.setButtonEnabled(this.tsbtnStopNow, true);
                    this.setButtonEnabled(this.tsbtnPause, false);
                    this.setButtonEnabled(this.tsbtnGoDownPT, false);
                    this.setButtonEnabled(this.tsbtnWarning, false);
                     
                    devContainer.io_output(CMDName.复位按钮, false, true, 0);//熄灭
                    devContainer.io_output(CMDName.黄灯输出);//长亮
                    currentPT = CurrentPTEnum.InitPT;
                    currentState = CurrentStateEnum.等待启动;
                    AddTextEvent("复位", $"复位完成。");
                    this.setButtonEnabled(this.tsbtnProductRevise, true);
                }
                else
                {
                    AddTextEvent("复位", "非可复位状态，请先停止后再复位！", WarningEnum.High);
                }
            }
            catch (Exception ex)
            {
                AddTextEvent("复位", "复位失败 Err:" + ex.Message, WarningEnum.High);
                warning(WarningEnum.High, true);
            }
            finally
            {
                reseting = false;
            }
        }
        /// <summary>
        /// 上料
        /// </summary>
        private void gotoUpPT()
        {
            try
            {
                JArray arrPT = Config.joPTSetting.Value<JArray>("upPT");
                for (int i = 0; i < arrPT.Count; i++)
                {
                    if (!devContainer.state || warningLevel != WarningEnum.Normal)
                        return;

                    AddTextEvent($"上料", $"轴{i}准备运动至上料位:{(double)arrPT[i]}(当前轴状态:{((AxisState)devContainer.devAxis.AxState[i]).ToString()})...");
                    devContainer.devAxis.move_ptp(i, (double)arrPT[i], AxMoveMode.绝对位置);
                } 
                while (!devContainer.devAxis.isReady())
                {
                    if (!devContainer.state || warningLevel != WarningEnum.Normal)
                        return;
                    Thread.Sleep(100);
                }
                if (devContainer.devAxis.isError())
                    throw new Exception("轴移动至上料位失败！");

                //
                currentPT = CurrentPTEnum.UpPT;
                currentState = CurrentStateEnum.等待扫码;
                AddTextEvent($"上料", "到达上料位！");
            }
            catch (Exception ex)
            {
                AddTextEvent("上料", "上料初始失败 Err:" + ex.Message, WarningEnum.High);
                warning(WarningEnum.High);
            }
        } 
        /// <summary>
        /// 下料
        /// </summary>
        private void gotoDownPT()
        {
            try
            {
                if (currentState != CurrentStateEnum.打标中 && currentState != CurrentStateEnum.等待启动 && currentState != CurrentStateEnum.自动流程结束)
                {
                    AddTextEvent($"下料", $"非可下料状态:{currentState.ToString()}");
                    return;
                }

                currentState = CurrentStateEnum.下料中;
                isProductRevise = false;

                JArray arrPT = Config.joPTSetting.Value<JArray>("downPT");
                for (int i = 0; i < arrPT.Count; i++)
                {
                    if (!devContainer.state || warningLevel != WarningEnum.Normal || currentState != CurrentStateEnum.下料中)
                        return;

                    AddTextEvent($"下料", $"轴{i}准备运动至下料位:{(double)arrPT[i]}(当前轴状态:{((AxisState)devContainer.devAxis.AxState[i]).ToString()})...");
                    devContainer.devAxis.move_ptp(i, (double)arrPT[i], AxMoveMode.绝对位置);
                }

                while (!devContainer.devAxis.isReady())
                {
                    if (!devContainer.state || warningLevel != WarningEnum.Normal || currentState != CurrentStateEnum.下料中)
                        return;
                    Thread.Sleep(100);
                }
                if (devContainer.devAxis.isError())
                    throw new Exception("轴移动至下料位失败！");

                //
                currentPT = CurrentPTEnum.DownPT;
                currentState = CurrentStateEnum.等待启动;
                devContainer.io_output(CMDName.下料完成输出);
                setButtonEnabled(tsbtnStart, true);
                AddTextEvent($"下料", "下料完成！");
            }
            catch (Exception ex)
            {
                AddTextEvent("下料", "下料失败 Err:" + ex.Message, WarningEnum.High);
                warning(WarningEnum.High);
            }
        }
        private void setButtonEnabled(ToolStripButton button, bool Enabled)
        {
            this.Invoke(new System.Action(() =>
            {
                button.Enabled = Enabled;
            }));
        }
        private void setButtonEnabled(ToolStripMenuItem button, bool Enabled)
        {
            this.Invoke(new System.Action(() =>
            {
                button.Enabled = Enabled;
            }));
        }
        private void setButtonEnabled(Control button, bool Enabled)
        {
            this.Invoke(new System.Action(() =>
            {
                button.Enabled = Enabled;
            }));
        }

        private void FrmMian_Shown(object sender, EventArgs e)
        {
        }

        private void FrmMian_Resize(object sender, EventArgs e)
        {

        }

        private void 流程管理ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (devContainer.state)
            {
                MessageBox.Show("请先停止生产，再进行流程修改！", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            FrmStepList frm = new FrmStepList();
            frm.ShowDialog();
        }

        private void 产品管理ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (devContainer.state)
            {
                MessageBox.Show("请先停止生产，再进行产品修改！", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            FrmProductList frm = new FrmProductList();
            frm.ShowDialog();
            loadProductCodeList();
        }
        private void tsbtnProductRevise_Click(object sender, EventArgs e)
        {
            if (!devContainer.state)
            { 
                MessageBox.Show("请先启动设备！", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            } 
            if (currentPT != CurrentPTEnum.InitPT || currentState != CurrentStateEnum.等待启动)
            {
                MessageBox.Show("请先复位设备！", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
             
            FrmProductList frm = new FrmProductList(true);
            if (frm.ShowDialog() == DialogResult.Yes && frm.Product != null)//执行校正
            {
                AddTextEvent("校正", "开始执行产品base厚度校正...");

                isProductRevise = true;
                currProductModel = frm.Product;
                currProductModel.HeightBaseDec = "";
                this.txtProductCode.Text = currProductModel.Code;
                this.txtProductName.Text = currProductModel.Name;
                this.txtProductSN.Text = "";
                this.dgvProcess.DataSource = new BindingSource(currProductModel.ReviseStepInfo.ProcessList, null);
                devContainer.libFor.clear();
                devContainer.libIF.clear();

                //    
                this.setButtonEnabled(this.tsbtnStart, false); 
                this.setButtonEnabled(this.tsbtnPause, true);
                this.setButtonEnabled(this.tsbtnStopNow, true);
                threadProcess = new System.Threading.Thread(() =>
                {
                    int nextStepId = 0;
                    do
                    {
                        currentState = CurrentStateEnum.运行中;
                        nextStepId = nextProcess(currProductModel, nextStepId);
                    } while (nextStepId >= 0 && !isBreakProcessRun());
                });
                threadProcess.IsBackground = true;
                threadProcess.Start();
            }
        }
        private void 设备调试ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (devContainer.state)
            {
                MessageBox.Show("请先停止生产，再进行设备调试！", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            FrmDebug3 frm = new FrmDebug3();
            frm.ShowDialog();
        }

        private void 角色管理ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            FrmRoleRight frm = new FrmRoleRight();
            frm.ShowDialog(this);
        }

        private void 用户管理ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            FrmUserList frm = new FrmUserList();
            frm.ShowDialog(this);
        }

        private void 退出ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
            System.GC.Collect();
            System.Environment.Exit(0);
        }

        private void 注销ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (devContainer.state)
            {
                MessageBox.Show("请先停止生产再退出！", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            if (MessageBox.Show($"确认退出？", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
            {
                this.bExitApp = true;
                Process.Start(Application.StartupPath + "\\ProductionControl.exe");
                this.Close();
            }
        }

        private void splitContainer1_Panel1_Resize(object sender, EventArgs e)
        { 
            int allWidth = this.splitContainer1.Panel1.Width;
            this.gpbProductInfo.Width = this.flpnlResultData.Width = this.gpbProcessList.Width = this.gpbLog.Width = allWidth;

            //
            this.txtProductName.Width = this.txtProductCode.Width = this.gpbProductInfo.Width - this.txtProductName.Left - 8;
            this.txtProductSN.Width = this.txtBatchId.Width = (this.txtProductName.Width- lblBatchId.Width)/2;
            this.lblBatchId.Left = this.txtProductSN.Right ;
            this.txtBatchId.Left = this.lblBatchId.Right;


            //
            int spliceWidth = 5;
            kanban1.Width = kanban2.Width = kanban3.Width = kanban4.Width = kanban5.Width = kanban6.Width = (flpnlResultData.Width-20) / 6;

            //2023-10-17 更新log和步骤列表显示
            this.gpbProcessList.Height = this.splitContainer1.Panel1.Height - this.gpbProcessList.Top - this.gpbLog.Height;
            this.gpbLog.Top = this.splitContainer1.Panel1.Height - this.gpbLog.Height;
        }


        private void 关于ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            AboutBox1 aboutBox1 = new AboutBox1();
            aboutBox1.Show();
        }


        private void 系统设置ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            FrmSysSetting frmSetting = new FrmSysSetting();
            frmSetting.ShowDialog();
        }



        private void dgvProcess_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
        {
            //string processCode;
            //for (int i = 0; i < this.dgvProcess.Rows.Count; i++)
            //{
            //processCode = dgvProcess.Rows[i].Cells[0].Value.ToString();
            //dgvProcess.Rows[i].Cells[1].Value = Config.dicDevType[processCode];
            //}
        }

        private void tsbtnWarning_Click(object sender, EventArgs e)
        {
            //if (warningLevel == WarningEnum.Normal)
            //{
            //    tsbtnWarning.Enabled = false;
            //    return;
            //}

            //重置AXIS状态
            devContainer.devAxis.resetAxisState();
            if (!devContainer.devAxis.isReady())
            {
                AddTextEvent("解除报警", "轴状态未恢复，解除报警失败！", warningLevel);
                return;
            }
            if (devContainer.devAxis.isError())
            {
                AddTextEvent("解除报警", "轴IO状态异常，解除报警失败！", warningLevel);
                return;
            }
            if (!this.disableDoorSensor && compareIOInput(CMDName.门磁告警输入))
            {
                AddTextEvent("解除报警", "门磁告警未清除 ，解除报警失败！", warningLevel);
                return;
            }
            if (!this.disableDoorSensor && compareIOInput(CMDName.喷墨告警输入))
            {
                AddTextEvent("解除报警", "喷墨告警输入未清除 ，解除报警失败！", warningLevel);
                return;
            }
            //关闭蜂鸣器
            devContainer.io_output(CMDName.蜂鸣器输出, false, true, 0); 
            devContainer.io_output(CMDName.红灯输出, false, true, 0);
            devContainer.io_output(CMDName.复位按钮, false, true, 0);
            if (warningLevel == WarningEnum.High)
            {
                AddTextEvent("解除报警", "急停告警已解除，但必需进行复位操作！", warningLevel);
                currentState = CurrentStateEnum.等待复位;
                this.tsbtnReset.Enabled = true;
                tsbtnWarning.Enabled = this.tsbtnStart.Enabled = this.tsbtnGoDownPT.Enabled
                    = this.tsbtnPause.Enabled = this.tsbtnStopNow.Enabled = this.btnMakeTags.Enabled= false;
            }
            else
            { 
                devContainer.io_output(CMDName.黄灯输出);
                AddTextEvent("解除报警", "告警已解除，请继续选择下一步操作！", warningLevel);
                if (currentPT == CurrentPTEnum.MakeTag)
                    currentState = CurrentStateEnum.打标中;
                else
                    currentState = CurrentStateEnum.等待启动;

                tsbtnWarning.Enabled = false;
                this.tsbtnStart.Enabled = this.tsbtnGoDownPT.Enabled = true;
                this.tsbtnReset.Enabled = this.tsbtnPause.Enabled = this.tsbtnStopNow.Enabled = true;
            }
            warningLevel = WarningEnum.Normal; 
        }
        private void tsbtnGoDownPT_Click(object sender, EventArgs e)
        {
            if (!devContainer.state || warningLevel!= WarningEnum.Normal)
                return; 
            if (!devContainer.devAxis.isReady())
            {
                AddTextEvent("下料失败", "轴状态异常！", WarningEnum.Low);
                return;
            }

            gotoDownPT();
        }
        //急停
        private void tsbtnStopNow_Click(object sender, EventArgs e)
        { 
            this.warning(WarningEnum.High);
        }
        private Thread threadProcess;

        private void tsbtnStart_Click(object sender, EventArgs e)
        {
            this.startCommand();
        }

        private void tsbtnPause_Click(object sender, EventArgs e)
        {
            warning(WarningEnum.Low,false); 
        }
        //复位
        private void tsbtnReset_Click(object sender, EventArgs e)
        { 
            this.resetCommand();
        }

        private void tsbtnGButSetting_Click(object sender, EventArgs e)
        {
            if (devContainer.state)
            {
                MessageBox.Show("请先停止生产，再进行设置！", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            FrmCMDProcess from = new FrmCMDProcess();
            from.ShowDialog();
        }

        private void tsbtnPTSetting_Click(object sender, EventArgs e)
        {
            if (devContainer.state)
            {
                MessageBox.Show("请先停止生产，再进行设置！", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            FrmPTSetting from = new FrmPTSetting();
            from.ShowDialog();
        }

        private void tsMenuModifyPW_Click(object sender, EventArgs e)
        {
            FrmModifyPW frm = new FrmModifyPW();
            frm.ShowDialog();
        }

        //分拆面板平均分配
        private void splitContainer1_ClientSizeChanged(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Minimized)
                return;

            SplitContainer sc = (SplitContainer)sender;
            sc.SplitterDistance = sc.Width / 7*3;
        }
        private void splitContainer3_ClientSizeChanged(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Minimized)
                return;

            SplitContainer sc = (SplitContainer)sender;
            sc.SplitterDistance = sc.Height / 2;
        }
        private void Menu查询统计_Click(object sender, EventArgs e)
        { 
            FrmWeb frm = new FrmWeb("查询统计", $"reportRecordSN?CustomerVer={Config.CustomerVer}");
            frm.Show();
        }

        private void tsMenuOrder_Click(object sender, EventArgs e)
        {
            //FrmOrderList frm = new FrmOrderList();
            string subject = (sender as ToolStripMenuItem).Text;
            FrmWeb frm = new FrmWeb(subject, $"reportRecordSN?CustomerVer={Config.CustomerVer}");
            frm.Show();
        }
        private void tsMenuOrderProduct_Click(object sender, EventArgs e)
        {
            string subject = (sender as ToolStripMenuItem).Text;
            FrmWeb frm = new FrmWeb(subject, $"reportRecordProduct?CustomerVer={Config.CustomerVer}");
            frm.Show();
        }
        private void tsMenuStatistics_Click(object sender, EventArgs e)
        {
            string subject = (sender as ToolStripMenuItem).Text;
            FrmWeb frm = new FrmWeb(subject, $"bigscreen/index.html", "http://127.0.0.1:18082/static/");
            frm.Show();
        }
        private void toolStripButton1_Click(object sender, EventArgs e)
        {
            //Bitmap[] bmps = new Bitmap[2];
            //bmps[0] = new Bitmap(@"f:\1.bmp");
            //bmps[1] = new Bitmap(@"f:\111.bmp");
            //List<Dictionary<int, List<string>[]>> list = new List<Dictionary<int, List<string>[]>>();
            //Dictionary<int, List<string>[]> dir = new Dictionary<int, List<string>[]>();
            //List<string>[] vs = new List<string>[1];
            //vs[0] = new List<string>();
            //dir.Add(0, vs);
            //list.Add(dir);
            //showDefectSmallBmps(bmps, list);
            var bmp = Bitmap.FromFile("f:\\1.jpg");
            int pnlWidth = this.pnlBmpList.ClientSize.Width;
            for (int i = 0; i < 5; i++)
            {
                int x = i;
                this.Invoke(new System.Action(() =>
                {
                    int imgWidth = 224;
                    int imgHeight = 200;
                    int splitWidth = 10;
                    int index = Convert.ToInt32(this.gboxDefectList.Tag); 

                    PictureBox picbox = new PictureBox();
                    picbox.Width = imgWidth;
                    picbox.Height = imgHeight;
                    CheckBox checkBox = new CheckBox();
                    picbox.Image = bmp;
                    picbox.Name = "imgDefect_" + index;
                    picbox.Tag ="1,2,3";
                    checkBox.Name = "chkDefect_" + index;
                    checkBox.Text = $"第{index + 1}张";
                    checkBox.CheckedChanged += new System.EventHandler(this.CheckBox_CheckedChanged);

                    picbox.Click += new EventHandler(defectBmpBox_Click); 
                    picbox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
                    picbox.BorderStyle = BorderStyle.FixedSingle;
                    picbox.Margin = new Padding((pnlWidth - picbox.Width) / 2, splitWidth, 0, 0);
                    checkBox.Margin = new Padding((pnlWidth - checkBox.Width) / 2+ splitWidth, 0, 0, 0);
                    //
                    //if (this.pnlBmpList.Controls.Count > 0)
                    //    {
                    //        if ((this.pnlBmpList.Controls[this.pnlBmpList.Controls.Count - 1].Right + imgWidth + splitWidth) > this.pnlBmpList.Width)
                    //            picbox.Location = new System.Drawing.Point(splitWidth, this.pnlBmpList.Controls[this.pnlBmpList.Controls.Count - 1].Bottom + splitWidth);
                    //        else
                    //            picbox.Location = new System.Drawing.Point(this.pnlBmpList.Controls[this.pnlBmpList.Controls.Count - 1].Right + splitWidth, this.pnlBmpList.Controls[this.pnlBmpList.Controls.Count - 1].Location.Y);
                    //    }
                    //    else
                    //    {
                    //        picbox.Location = new System.Drawing.Point(splitWidth, 0);
                    //    }
                    //    checkBox.Location = new System.Drawing.Point(picbox.Location.X + 5, picbox.Location.Y + 5);
                    //    checkBox.ForeColor = Color.Black; 
                    //}
                    this.pnlBmpList.Controls.Add(picbox);
                    this.pnlBmpList.Controls.Add(checkBox);

                    //
                    index++; 
                    this.gboxDefectList.Tag = index;
                    this.gboxDefectList.Text = $"缺陷图像:{index} 张";
                    pnlBmpList.VerticalScroll.Value = pnlBmpList.VerticalScroll.Maximum;
                }));
            }
        }
 

        private void chkBuzzer_CheckedChanged(object sender, EventArgs e)
        {
            this.disableBuzzer = chkBuzzer.Checked;
            if (devContainer.state && this.disableBuzzer)
                devContainer.io_output(CMDName.蜂鸣器输出, false, true, 0);
        }

        private void chkDoorSensor_CheckedChanged(object sender, EventArgs e)
        {
            this.disableDoorSensor = chkDoorSensor.Checked;
        }

        private void tsbtnLight_Click(object sender, EventArgs e)
        {
            if (devContainer.state)
            {
                if(tsbtnLight.Text.Trim()== "开灯")
                {
                    if(devContainer.io_output(CMDName.日光灯输出))
                        tsbtnLight.Text = "  关灯  ";
                }
                else
                {
                    if (devContainer.io_output(CMDName.日光灯输出, false, true, 0)) 
                        tsbtnLight.Text = "  开灯  ";
                }
            }
        }

        private void btnMakeTag_Click(object sender, EventArgs e)
        {
            if(currentPT != CurrentPTEnum.MakeTag)
            {
                AddTextEvent("打标失败", "非可打标状态！");
                return;
            }
            btnMakeTags.Enabled = false;
            autoMakeTagRuning = true;
            Thread threadtest = new System.Threading.Thread(() =>
            {
                try
                {
                    if (!devContainer.state)
                    {
                        AddTextEvent("打标", "设备已停机！");
                        return;
                    }
                    if ((int)this.lblDefectAddTag.Tag < 1)
                    {
                        AddTextEvent("打标", "打标队列为空！");
                        return;
                    }

                    int tagIndex;
                    double Xmm, Ymm;
                    Bitmap currBmp;
                    string[] tags;
                    for (int i = 0; i < this.pnlBmpList.Controls.Count; i += 2)
                    {
                        CheckBox cb = this.pnlBmpList.Controls[i + 1] as CheckBox;
                        if (cb.Checked)
                        {
                            PictureBox picBox = this.pnlBmpList.Controls[i] as PictureBox;
                            tags = picBox.Tag.ToString().Split(',');
                            tagIndex = Convert.ToInt32(tags[0]);
                            Xmm = Convert.ToDouble(tags[1]);
                            Ymm = Convert.ToDouble(tags[2]);
                            currBmp = (Bitmap)picBox.Image;

                            //打标
                            AddTextEvent("打标", $"第{i / 2 + 1}/{this.pnlBmpList.Controls.Count / 2}张 [索引:{tagIndex}, X:{Xmm}mm, Y:{Ymm}mm]");
                            var list = devContainer.libDefect.makeTag(tagIndex, Xmm, Ymm, currBmp.Width, currBmp.Height);
                            devContainer.devAxis.move_ptp(1, list[0], AxMoveMode.绝对位置);
                            devContainer.devAxis.move_ptp(2, list[1], AxMoveMode.绝对位置);
                            while (!devContainer.devAxis.isReady(1) || !devContainer.devAxis.isReady(2))
                            {
                                Application.DoEvents();
                                Thread.Sleep(100);
                                if (!devContainer.state)
                                    throw new Exception("设备停止，打标中止！");
                            }
                            //
                            if (!devContainer.io_output(CMDName.喷墨输出, false, true, 500))
                                throw new Exception("请确认[喷墨输出]指令设置是否正确！");

                            this.Invoke(new System.Action(() =>
                            {
                                cb.Checked = false;
                            })); 
                        }
                    }

                    //自动下料
                    AddTextEvent("打标完成", $"批量打标完成，进行下料...");
                    currentState = CurrentStateEnum.自动流程结束;
                    gotoDownPT();
                }
                catch (Exception ex)
                {
                    AddTextEvent("打标失败", ex.Message, WarningEnum.High);
                    warning(WarningEnum.Low, true);
                }
                finally { autoMakeTagRuning=false; }
            });
            threadtest.IsBackground = true;
            threadtest.Start();
        }

        private void btnInputCode_Click(object sender, EventArgs e)
        {
            txtProductCode_DoubleClick(null, null);
        }
        private void txtProductName_DoubleClick(object sender, EventArgs e)
        {
            txtProductCode_DoubleClick(null, null);
        }
        private void txtProductCode_DoubleClick(object sender, EventArgs e)
        {
            //不需要等待扫码才能选择
            //if (currentState != CurrentStateEnum.等待扫码) return;

            FrmInput frm = new FrmInput(productCodeList, "请选择产品料号:");
            if (frm.ShowDialog() != DialogResult.OK && string.IsNullOrWhiteSpace(frm.inputData))
                return;
            //显示料号
            //this.txtProductCode.Text = frm.inputData;
            var model = svcProduct.GetModelNav(frm.inputData);
            if (model != null && model.StepInfo.ProcessList.Count > 0)
            {
                this.txtProductCode.Text = model.Code;
                this.txtProductName.Text = model.Name;
            }
            /*//屏蔽扫码枪事件
            Task.Factory.StartNew(() =>
            {
                devContainer.devCodeScanner.ScanerEvent?.Invoke(frm.inputData);
            });
            */

            //string code = Interaction.InputBox("请输入产品编码", "输入框", "", -1, -1).Trim();
            //if(code  != "")
            //{
            //    Task.Factory.StartNew(() =>
            //    {
            //        devContainer.devCodeScanner.ScanerEvent?.Invoke(frm.inputData);
            //    });
            //}
        }

        private void txtProductSN_DoubleClick(object sender, EventArgs e)
        {
            //if (currentState != CurrentStateEnum.等待扫码 || this.txtProductCode.Text.Trim()=="") return;

            //string sn = Interaction.InputBox("请输入产品SN码", "输入框", "", -1, -1).Trim();
            //if (sn != "")
            //{
            //    this.txtProductSN.Text = sn;
            //    string code = this.txtProductCode.Text.Trim();
            //    Task.Factory.StartNew(() =>
            //    {
            //        devContainer.devCodeScanner.ScanerEvent?.Invoke(code);
            //    });
            //}
        } 
        private void 测试ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            tsbtnDefectImage.Enabled = tsbtnSizeImage.Enabled=true;
            return;
            Form1 frm = new Form1();
            frm.Show();
            //tsbtnDefectImage_Click(null, null);
            return;
            try
            {
                //string ls = "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,182.257722637742,182.257727282667,0,0";
                //List<string> list = new List<string>(ls.Split(','));
                //lstPT = list.Select<string, double>(q => Convert.ToDouble(q)).ToList();  
                //updatePTValue(1,2);
                //lstPT.Add(10);
                //lstPT.Add(0);
                //updatePTValue(1, 2);
                //{
                //    lstLineWidth.Add(res.Shanxian > 0 ? res.Shanxian : 0);
                //    updateLineWidthValue(model.LineWidthBaseValue + model.LineWidthUpFloatValue, model.LineWidthBaseValue - model.LineWidthDownFloatValue);
                //}
                //return;

                //loadProductCodeList();
                //FrmInput frm = new FrmInput(null, "请选择产品编码:");
                //if (frm.ShowDialog() != DialogResult.OK)
                //    return;
                //var model = svcProduct.GetModelNav(frm.inputData);
                //if (model == null)
                //    throw new Exception("没有此产品！");
                //order.ProductId = model.Id;
                //order.BatchId = model.BatchId;
                //order.StepId = model.StepId==null? 0:(int)model.StepId;
                //frm = new FrmInput(null, "请输入SN:");
                //if (frm.ShowDialog() != DialogResult.OK)
                //    return;
                //order.SN = frm.inputData;

                //frm = new FrmInput(null, "请输入主机台Mark点数据:", "[0,0,0,0,0,0,0,0]");
                //if (frm.ShowDialog() != DialogResult.OK)
                //    return;
                //order.MarkData= frm.inputData;
                //order.Tension1 = order.Tension2 = order.Tension3 = order.Tension4 = order.Tension5 = order.Tension5 + 3;
                //order.PT1 = order.PT4 = order.PT5 = order.PT6 = order.PT6 + 1.33;
                //order.LineWidth2 = order.LineWidth3 = order.LineWidth5 = order.LineWidth6 = +order.LineWidth6 + 1.22;

                //order.HeightValue = order.Height1 = order.Height3 = order.Height4 = 55;
                //order.DefectCount = order.DefectCount + 22;
                //order.YXCount = order.PPCount = 33;
                //order.TimeLen =  1000;
                //order.Succeed = true;
                //order.ModifyUserCode = order.CreateUserCode = Config.loginUser.Code;

                //if (!svcOrder.Insert(order))
                //    AddTextEvent("保存失败", $"保存生产记录失败！");
                //MessageBox.Show("成功！");

                //---------------
                this.lstCompareFailZoomImage.Add((Bitmap)Bitmap.FromFile(@"f:\1.bmp"));
                this.lstCompareFailZoomImage.Add((Bitmap)Bitmap.FromFile(@"f:\2.bmp"));
                this.lstCompareFailZoomImage.Add((Bitmap)Bitmap.FromFile(@"f:\3.bmp"));
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void toolStripSeparator4_Click(object sender, EventArgs e)
        {

        }

        private void chkHttpLog_CheckedChanged(object sender, EventArgs e)
        {
            if (this.chkHttpLog.Checked)
            {
                webService.LogEvent = (warning, msg) =>
                {
                    AddTextEvent("HTTP服务", msg, warning);
                };
            }
            else
                webService.LogEvent = null;
        }
        //打开比对失败的图片
        private void lblCompareResult_Click(object sender, EventArgs e)
        {
            if (lstCompareFailZoomImage.Count < 1) return;
            FrmPhotoShow frm = new FrmPhotoShow(lstCompareFailZoomImage);
            frm.ShowDialog();
        }

        //过程变量查看
        private void tsbtnSizeTag_Click(object sender, EventArgs e)
        {
            if (order == null || order.SizeTagDataList == null || order.SizeTagDataList.Count<1)
                return;

            FrmShowSizeTag frm = new FrmShowSizeTag(order);
            frm.Show();
        }

        //缺陷+比对与图纸查看
        private void tsbtnDefectImage_Click(object sender, EventArgs e)
        {
            //currProductModel = svcProduct.GetModelNav(53);
            //order = svcOrder.GetModelNav("20230814002"); 
            AddTextEvent("打开窗口", "1");
            if (currProductModel ==null || order == null || order.DefectInfoList == null || order.DefectInfoList.Count < 1)
                return;
            AddTextEvent("打开窗口", "2");
            //图纸
            try
            {
                string gbxBmpPath = "";
                var attachmentFile = currProductModel.AttachmentList.FirstOrDefault(x => x.Type == 0);
                if (attachmentFile != null)
                {
                    gbxBmpPath = Application.StartupPath + $"\\Attachment\\product\\{attachmentFile.NameTimestamp}";
                    if (!File.Exists(gbxBmpPath + ".bmp")) gbxBmpPath = "";
                }
                AddTextEvent("打开窗口", "3");
                if (string.IsNullOrEmpty(gbxBmpPath))
                {
                    MessageBox.Show("图纸文件不存在！", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    return;
                }
                AddTextEvent("打开窗口", "4");
                if (!File.Exists(gbxBmpPath + ".jpg"))//转背景后的图纸文件
                {
                    AddTextEvent("打开窗口", "5");
                    //换背景JPG
                    Mat mat = Cv2.ImRead(gbxBmpPath + ".bmp");
                    Cv2.CvtColor(mat, mat, ColorConversionCodes.RGB2GRAY);//转灰度图
                    for (int i = 0; i < mat.Height; i++)
                    {
                        for (int j = 0; j < mat.Width; j++)
                        {
                            if (mat.At<byte>(i, j) == 255)//白色 
                                mat.Set<byte>(i, j, 0);
                            else
                                mat.Set<byte>(i, j, 255);
                        }
                    }
                    AddTextEvent("打开窗口", "6");
                    //灰转彩
                    //Cv2.CvtColor(mat, mat, ColorConversionCodes.GRAY2RGB);
                    //for (int i = 0; i < mat.Height; i++)
                    //{
                    //    for (int j = 0; j < mat.Width; j++)
                    //    {
                    //        if (mat.At<byte>(i, j) == 255)//白色 
                    //            mat.Set<byte>(i, j, 0); //黄色
                    //    }
                    //}  
                    OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat).Save(gbxBmpPath + ".jpg", ImageFormat.Jpeg);
                }
                AddTextEvent("打开窗口", "7");
                //
                ToolStripButton btn = sender as ToolStripButton;
                AddTextEvent("打开窗口", btn.Tag.ToString());
                FrmShowDefectImage frm = new FrmShowDefectImage(gbxBmpPath + ".jpg", order, string.IsNullOrEmpty(btn.Tag.ToString()) ? 0 : Convert.ToInt32(btn.Tag.ToString()));
                frm.Show();
                AddTextEvent("打开窗口", "9");
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "异常", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, 0);
            }
        }

        private void tsbtnLog_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(Config.LogPath)) return;
            string lsPath = (Config.LogPath + "\\" + DateTime.Now.ToString("yyyyMM") + "\\").Replace("\\\\", "\\");
            if(!Directory.Exists(lsPath))
                Directory.CreateDirectory(lsPath);
            System.Diagnostics.Process.Start("explorer.exe", lsPath);
        }
    }
}
