using FLY.FeedbackRenZiJia.IService; using FLY.Thick.Base.UI; using FLY.Thick.Blowing.IService; using GalaSoft.MvvmLight.Command; using LiveCharts; using LiveCharts.Configurations; using Misc; using OfficeOpenXml.FormulaParsing.Excel.Functions.Logical; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using IBulkDbService = FLY.FeedbackRenZiJia.IService.IBulkDbService; namespace FLY.FeedbackRenZiJia.UI.Client { public class PgHeatAnalyseVm : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; #region 图表控制 public Func<double, string> YFormatter { get; private set; } public int XMax { get; private set; } #endregion /// <summary> /// 厚度% /// </summary> public ChartValues<double> ThickPercentDiffs { get; } = new ChartValues<double>(); public ChartValues<double> HeatDiffs { get; } = new ChartValues<double>(); public ChartValues<double> HeatDiffEffects { get; } = new ChartValues<double>(); public object MapperHeats { get; private set; } [PropertyChanged.DependsOn(nameof(Kp))] public object MapperThickPercents { get; private set; } public BlowingType BType { get; private set; } #region 参数 /// <summary> /// 比例因子 /// </summary> public double Kp { get; set; } = 3; public bool IsMoveByTakeEffect { get; set; } = true; public bool IsMoveByDiffCTime { get; set; } = true; /// <summary> /// 复位区号 /// </summary> public int OrgBoltNo { get; set; } = 1; /// <summary> /// 旋转角度 /// </summary> public double RAngle { get; set; } = 350; /// <summary> /// 反向 /// </summary> public bool IsReversed { get; set; } = false; #endregion #region 状态 /// <summary> /// 当前厚度%变化极差 单位% /// </summary> public double CurrMaxMin { get; set; } = -1; /// <summary> /// 当前加热与厚度相关性 /// </summary> public double CurrHeatThickR { get; set; } = -1; /// <summary> /// 当前加热与厚度 比例 /// </summary> public double CurrKp { get; set; } = -1; /// <summary> /// 数据异常,不能比较 /// </summary> public bool IsError { get; set; } = false; #endregion #region 分区表 public ObservableCollection<BoltMapCell> BoltMap { get; } = new ObservableCollection<BoltMapCell>(); /// <summary> /// 加热生效曲线,允许为空,则不生效, 必须是 2,3,5,3,2 这样对称的数组 /// </summary> public int[] HeatEffectCurve { get; set; } public bool IsUsedMap { get; set; } /// <summary> /// 参数修改,不会触发 偏差数据更新 /// </summary> public bool IsSkipUpdateD { get; set; } /// <summary> /// 自动计算 过程消息 /// </summary> public string AutoMsg { get; private set; } #endregion public RelayCommand KpCollapseCmd { get; } public RelayCommand KpExpandCmd { get; } public RelayCommand LeftCmd { get; } public RelayCommand RightCmd { get; } public RelayCommand CollapseCmd { get; } public RelayCommand ExpandCmd { get; } public RelayCommand AutoCmd { get; } public RelayCommand ApplyCmd { get; } public RelayCommand TestMapCmd { get; } public RelayCommand DelMapCmd { get; } public UcThickHeatVm[] ucThickHeatVms; IFeedbackHeatService feedback; IHeatBufService heatBuf; IHeatCellService heatCell; FLY.Thick.Blowing.IService.IBlowingService blowingService; FLY.Thick.Blowing.IService.IBlowingDetectService blowingDetectService; public PgHeatAnalyseVm() { YFormatter = (y) => { string text = y + "%"; return $"{text,6}"; }; MapperHeats = Mappers.Xy<double>() .X((value, index) => { return index + 1; }) .Y((value, index) => { return value; }); MapperThickPercents = Mappers.Xy<double>() .X((value, index) => { return index + 1; }) .Y((value) => { return value * Kp; }); KpCollapseCmd = new RelayCommand(KpCollapse); KpExpandCmd = new RelayCommand(KpExpand); LeftCmd = new RelayCommand(Left); RightCmd = new RelayCommand(Right); CollapseCmd = new RelayCommand(Collapse); ExpandCmd = new RelayCommand(Expand); AutoCmd = new RelayCommand(Auto); ApplyCmd = new RelayCommand(Apply); TestMapCmd = new RelayCommand(TestMap); DelMapCmd = new RelayCommand(DelMap); } public void Init( IFeedbackHeatService feedback, IHeatBufService heatBuf, IHeatCellService heatCell, IBulkDbService bulkDbService, FLY.Thick.Blowing.IService.IBlowingService blowingService, FLY.Thick.Blowing.IService.IBlowingDetectService blowingDetectService ) { this.feedback = feedback; this.heatBuf = heatBuf; this.heatCell = heatCell; this.blowingService = blowingService; this.blowingDetectService = blowingDetectService; Misc.BindingOperations.SetBinding( blowingService, nameof(blowingService.BType), this, nameof(BType)); Misc.BindingOperations.SetBinding( this.heatCell, nameof(this.heatCell.Kp), this, nameof(Kp)); Misc.BindingOperations.SetBinding( this.heatBuf, nameof(this.heatBuf.HeatEffectCurve), this, nameof(HeatEffectCurve)); Misc.BindingOperations.SetBinding( blowingService, nameof(blowingService.OrgBoltNo), this, nameof(OrgBoltNo)); Misc.BindingOperations.SetBinding( blowingDetectService, nameof(blowingDetectService.RAngle), this, nameof(RAngle)); Misc.BindingOperations.SetBinding( blowingService, nameof(blowingService.Map), () => { BoltMap.Clear(); if (blowingService.Map != null) { for (int i = 0; i < blowingService.Map.Count(); i++) { BoltMap.Add(blowingService.Map[i].Clone()); } } }); Misc.BindingOperations.SetBinding( blowingService, nameof(blowingService.IsUsedMap), this, nameof(IsUsedMap)); ucThickHeatVms = new UcThickHeatVm[2]; for (int i = 0; i < 2; i++) { ucThickHeatVms[i] = new UcThickHeatVm(); ucThickHeatVms[i].Init($"记录{i+1}", bulkDbService, 0-i, Kp); Misc.BindingOperations.SetBinding(this, nameof(Kp), ucThickHeatVms[i], nameof(UcThickHeatVm.Kp)); Misc.BindingOperations.SetBinding(this, nameof(IsMoveByTakeEffect), ucThickHeatVms[i], nameof(UcThickHeatVm.IsMoveByTakeEffect)); Misc.BindingOperations.SetBinding(this, nameof(IsMoveByDiffCTime), ucThickHeatVms[i], nameof(UcThickHeatVm.IsMoveByDiffCTime)); ucThickHeatVms[i].FrameUpdated += PgHeatAnalyseVm_FrameUpdated; } Misc.BindingOperations.SetBinding(this.feedback, nameof(this.feedback.ChannelCnt), () => { XMax = this.feedback.ChannelCnt + 1; }); UpdateD(); this.PropertyChanged += PgHeatAnalyseVm_PropertyChanged; } private void PgHeatAnalyseVm_FrameUpdated() { UpdateD(); } string[] propertyNames_updateD = new string[] { nameof(RAngle), nameof(OrgBoltNo), nameof(Kp), nameof(HeatEffectCurve), nameof(IsUsedMap), nameof(IsReversed) }; private void PgHeatAnalyseVm_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (propertyNames_updateD.Contains(e.PropertyName)) { if (IsSkipUpdateD) return; UpdateD(); } } bool IsValid() { if (!ucThickHeatVms.All(thvm => thvm.IsVaild)) return false; if (ucThickHeatVms[0].frame.Thicks.Count() != ucThickHeatVms[1].frame.Thicks.Count()) return false; if (ucThickHeatVms[0].frame.Heats.Count() != ucThickHeatVms[1].frame.Heats.Count()) return false; if (feedback.ChannelCnt != ucThickHeatVms[0].frame.Heats.Count()) return false; if (feedback.NBolts != ucThickHeatVms[0].frame.Thicks.Count()) return false; //if (Math.Abs(ucThickHeatVms[0].frame.FilmLength - ucThickHeatVms[1].frame.FilmLength) > 1) //{ // //偏差大于1m // return false;//膜距离不同不能比较 //} //if (Math.Abs(ucThickHeatVms[0].frame.RAngle - ucThickHeatVms[1].frame.RAngle) > 20) //{ // //偏差大于20° // return false;//膜距离不同不能比较 //} return true; } class CallDiffResult { public double[] thkPercentDiffs; public double[] heatDiffs; public double[] heatEffects; public double maxmin; public double r; public double kp; } CallDiffResult CalDiff(int orgBoltNo, double rAngle, bool isReversed, IEnumerable<BoltMapCell> boltmap, IEnumerable<double> heatEffectCurve) { if (!IsValid()) { //失败,加热棒数据不一致 return null; } CallDiffResult result = new CallDiffResult(); double[] thickpercents0 = ucThickHeatVms[0].GetThickPercents(orgBoltNo, rAngle, boltmap, isReversed); int[] heats0 = ucThickHeatVms[0].GetHeats(); double[] thickpercents1 = ucThickHeatVms[1].GetThickPercents(orgBoltNo, rAngle, boltmap, isReversed); int[] heats1 = ucThickHeatVms[1].GetHeats(); int channelCnt = feedback.ChannelCnt; result.thkPercentDiffs = new double[channelCnt]; result.heatDiffs = new double[channelCnt]; for (int i = 0; i < channelCnt; i++) { result.thkPercentDiffs[i] = thickpercents0[i] - thickpercents1[i]; result.heatDiffs[i] = heats1[i] - heats0[i]; } double heatDiffAvg = result.heatDiffs.Average(); for (int i = 0; i < channelCnt; i++) { result.heatDiffs[i] -= heatDiffAvg; } result.heatEffects = FLY.FeedbackRenZiJia.Common.MyMath.Filter(result.heatDiffs, heatEffectCurve); //----------------------------------------------------------------------------------------------------------------------------------------- //厚度%差 极差,判断可信度用 double min = result.heatDiffs.Min(); double max = result.heatDiffs.Max(); result.maxmin = max - min; //----------------------------------------------------------------------------------------------------------------------------------------- //当前数据的加热与厚度相关性 result.r = Misc.MyMath.Correl(result.thkPercentDiffs, result.heatEffects); //----------------------------------------------------------------------------------------------------------------------------------------- //当前数据的加热与厚度 比例 double b; double a; Misc.MyMath.Linest(result.heatDiffs, result.thkPercentDiffs, out a, out b); result.kp = a; return result; } void UpdateD() { ThickPercentDiffs.Clear(); HeatDiffs.Clear(); HeatDiffEffects.Clear(); var result = CalDiff(OrgBoltNo, RAngle, IsReversed, IsUsedMap?BoltMap.ToArray():null, HeatEffectCurve==null?null: HeatEffectCurve.Select(d=>(double)d)); if (result!=null) { ThickPercentDiffs.AddRange(result.thkPercentDiffs); HeatDiffs.AddRange(result.heatDiffs); HeatDiffEffects.AddRange(result.heatEffects); CurrMaxMin = result.maxmin; CurrHeatThickR = result.r; CurrKp = result.kp; IsError = false; } else { IsError = true; CurrMaxMin = -1; CurrHeatThickR = -1; CurrKp = -1; } } private void DelMap() { BoltMap.Clear(); } private void TestMap() { if (!checkValid_map()) { FLY.ControlLibrary.Window_WarningTip.Show("异常", "分区表格式出错"); return; } UpdateD(); } bool IsMapSame() { int cnt = 0; if (blowingService.Map != null) cnt = blowingService.Map.Count(); if (BoltMap.Count() != cnt) return false; for (int i = 0; i < cnt; i++) { BoltMapCell modify = BoltMap[i]; BoltMapCell used = blowingService.Map[i]; if (modify.NewNo != used.NewNo) return false; if (modify.OldNo != used.OldNo) return false; } return true; } bool checkValid_map() { bool map_err = false; bool g_o = false; bool g_n = false; //Map 检测 //OldNo,NewNo不能 <1 >NBolts //Map OldNo 必须从小到大环形排列 //Map NewNo 必须从小到大环形排列 for (int i = 0; i < BoltMap.Count(); i++) { if (BoltMap[i].NewNo < 1 || BoltMap[i].NewNo > feedback.NBolts) { map_err = true; break; } if (BoltMap[i].OldNo < 1 || BoltMap[i].OldNo > feedback.NBolts) { map_err = true; break; } int i_next = i + 1; if (i_next >= BoltMap.Count()) i_next = 0; if (BoltMap[i].OldNo >= BoltMap[i_next].OldNo) { if (!g_o) g_o = true; else { map_err = true; break; } } if (BoltMap[i].NewNo >= BoltMap[i_next].NewNo) { if (!g_n) g_n = true; else { map_err = true; break; } } } return !map_err; } bool CheckValid_HeatEffectCurve() { int[] list = HeatEffectCurve; if ((list == null) || (list.Count() == 0)) { return false; } int cnt = list.Count(); if (cnt % 2 != 1) return false; for (int i = 0; i < list.Count() / 2; i++) { int idx0 = i; int idx_0 = list.Count() - 1 - idx0; if (list[idx0] != list[idx_0]) { return false; } int idx1 = idx0 + 1; if (list[idx0] > list[idx1]) { return false; } } return true; } private void Apply() { if (IsReversed) { FLY.ControlLibrary.Window_WarningTip.Show("应用失败", "当前图是反向,需要先去调换旋转架的正反向信号线", TimeSpan.FromSeconds(2)); return; } if (!WdPassword.Authorize("AirRing")) return; blowingService.IsUsedMap = IsUsedMap; blowingService.Map = BoltMap.ToArray(); blowingService.OrgBoltNo = OrgBoltNo; blowingService.Apply(); blowingDetectService.RAngle = RAngle; blowingDetectService.Apply(); if (CheckValid_HeatEffectCurve()) { heatBuf.HeatEffectCurve = HeatEffectCurve; } heatBuf.Apply(); heatCell.Kp = Kp; heatCell.Apply(); FLY.ControlLibrary.Window_Tip.Show("通知", "应用成功", TimeSpan.FromSeconds(2)); } private async void Auto() { if (!IsValid()) { return; } IsSkipUpdateD = true; AutoMsg = "0%"; double rAngle_min = 300; double rAngle_max = 358; int total_cal_cnt = feedback.NBolts + (int)(rAngle_max - rAngle_min + 1); int cal_cnt = 0; await Task.Factory.StartNew(() => { int nbolts = feedback.NBolts; double bestr = -1; int orgBoltNo = OrgBoltNo; int bestOrgBoltNo = orgBoltNo; var boltmap = IsUsedMap ? BoltMap.ToArray() : null; double bestRAngle = RAngle; bool isReversed = IsReversed; IEnumerable<double> heatEffectCurve = HeatEffectCurve == null ? null : HeatEffectCurve.Select(d => (double)d); for (int i = 0; i < nbolts; i++) { var result = CalDiff(orgBoltNo, bestRAngle, isReversed, boltmap, heatEffectCurve); if (result.r > bestr) { bestr = result.r; bestOrgBoltNo = orgBoltNo; } orgBoltNo++; if (orgBoltNo > nbolts) orgBoltNo = 1; cal_cnt++; AutoMsg = (1.0 * cal_cnt / total_cal_cnt).ToString("P0"); } if (BType == BlowingType.Fix) { for (double rAngle = rAngle_min; rAngle <= rAngle_max; rAngle++) { var result = CalDiff(bestOrgBoltNo, rAngle, isReversed, boltmap, heatEffectCurve); if (result.r > bestr) { bestr = result.r; bestRAngle = rAngle; } cal_cnt++; AutoMsg = (1.0 * cal_cnt / total_cal_cnt).ToString("P0"); } //锁住,不用更新画面 RAngle = bestRAngle; } //锁住,不用更新画面 OrgBoltNo = bestOrgBoltNo; }); UpdateD(); Kp = CurrKp; IsSkipUpdateD = false; AutoMsg = "100%"; } private void Expand() { if (RAngle < 358) RAngle += 1; } private void Collapse() { if (RAngle > 280) RAngle -= 1; } private void Right() { if (OrgBoltNo < feedback.NBolts) OrgBoltNo++; else OrgBoltNo = 1; } private void Left() { if (OrgBoltNo > 1) OrgBoltNo--; else OrgBoltNo = feedback.NBolts; } private void KpExpand() { if (Kp < 6) Kp += 0.1; } private void KpCollapse() { if (Kp > 0.2) Kp -= 0.1; } } }